Fix sidebar username, Signals filter clarity and layout
- JWT now carries a username claim; sidebar shows "Signed in as <name>" instead of the bare user id (sub). Re-login required for the new claim. - Signals: Min R:R / Min Confidence inputs reflect the effective filter — auto-filled from the activation gate when "Qualified only" is on, reset to 0 when off (no more misleading 0 while the gate is active). - Signals layout: Run Scanner moved to its own action row (it's a job trigger, not a filter); qualified toggle grouped with the refinement filters under one Filters panel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ from dataclasses import dataclass
|
||||
from app.services.recommendation_service import (
|
||||
_build_reasoning,
|
||||
_choose_recommended_action,
|
||||
_select_primary_target,
|
||||
direction_analyzer,
|
||||
probability_estimator,
|
||||
signal_conflict_detector,
|
||||
@@ -102,6 +103,31 @@ def test_reasoning_explains_missing_setup():
|
||||
assert "no high-conviction long setup" in reasoning.lower()
|
||||
|
||||
|
||||
def test_primary_target_is_most_likely_worthwhile_not_lottery():
|
||||
targets = [
|
||||
{"price": 110.0, "rr_ratio": 2.0, "probability": 65.0}, # worthwhile, most likely ← primary
|
||||
{"price": 120.0, "rr_ratio": 3.5, "probability": 50.0},
|
||||
{"price": 140.0, "rr_ratio": 6.0, "probability": 15.0}, # far lottery — not chosen
|
||||
]
|
||||
primary = _select_primary_target(targets)
|
||||
assert primary is not None
|
||||
assert primary["price"] == 110.0
|
||||
|
||||
|
||||
def test_primary_target_skips_sub_threshold_rr():
|
||||
targets = [
|
||||
{"price": 102.0, "rr_ratio": 1.0, "probability": 95.0}, # high prob but trivial R:R — skipped
|
||||
{"price": 115.0, "rr_ratio": 2.5, "probability": 60.0}, # most likely above the R:R floor ← primary
|
||||
]
|
||||
primary = _select_primary_target(targets)
|
||||
assert primary is not None
|
||||
assert primary["price"] == 115.0
|
||||
|
||||
|
||||
def test_primary_target_none_when_empty():
|
||||
assert _select_primary_target([]) is None
|
||||
|
||||
|
||||
def test_detects_sentiment_technical_conflict():
|
||||
conflicts = signal_conflict_detector.detect_conflicts(
|
||||
dimension_scores={"technical": 72.0, "momentum": 55.0, "fundamental": 50.0},
|
||||
|
||||
Reference in New Issue
Block a user