|
5 | 5 |
|
6 | 6 | import pytest |
7 | 7 |
|
8 | | -from quant_advisor_research.advisory_report import build_advisory_report, primary_horizon_from_actions, render_markdown |
| 8 | +from quant_advisor_research.advisory_report import ( |
| 9 | + build_advisory_report, |
| 10 | + build_final_decisions, |
| 11 | + load_theme_momentum, |
| 12 | + normalize_source_score, |
| 13 | + primary_horizon_from_actions, |
| 14 | + render_markdown, |
| 15 | +) |
9 | 16 | from quant_advisor_research.artifacts import write_report_manifest |
10 | 17 | from quant_advisor_research.contracts import AdvisoryValidationError, validate_advisory_report |
11 | 18 |
|
@@ -307,10 +314,10 @@ def test_theme_bias_can_lift_static_watchlist_item_without_direct_symbol_bias(tm |
307 | 314 | assert rec["rating"] == "watch" |
308 | 315 | assert rec["evidence_score"] > 4 |
309 | 316 | assert any("主题=hbm_memory" in reason for reason in rec["reasons"]) |
310 | | - assert report["summary"]["long_context_available"] is True |
311 | | - assert report["summary"]["long_context_missing_reason"] == "" |
312 | | - assert "MU" in report["summary"]["long_context_symbols"] |
313 | | - assert report["final_decisions"]["horizon_action_buckets"]["long"]["watch"][:1] == ["MU"] |
| 317 | + assert report["summary"]["long_context_available"] is False |
| 318 | + assert report["summary"]["long_context_missing_reason"] == "current_candidates_do_not_meet_long_context_gate" |
| 319 | + assert "MU" not in report["summary"]["long_context_symbols"] |
| 320 | + assert report["final_decisions"]["horizon_action_buckets"]["long"]["watch"] == [] |
314 | 321 |
|
315 | 322 |
|
316 | 323 | def test_theme_momentum_snapshot_is_display_context_not_rating_input(tmp_path: Path) -> None: |
@@ -443,3 +450,53 @@ def test_contract_rejects_final_decision_account_action_fields() -> None: |
443 | 450 |
|
444 | 451 | with pytest.raises(AdvisoryValidationError): |
445 | 452 | validate_advisory_report(report) |
| 453 | + |
| 454 | + |
| 455 | +def test_source_score_does_not_use_recommendation_tier_prior() -> None: |
| 456 | + assert normalize_source_score( |
| 457 | + { |
| 458 | + "evidence_score": 5, |
| 459 | + "source_confidence": "no_event", |
| 460 | + "recommendation_tier": "watchlist", |
| 461 | + } |
| 462 | + ) == 0.0 |
| 463 | + |
| 464 | + |
| 465 | +def test_final_decision_sections_keep_action_semantics_and_overflow() -> None: |
| 466 | + report = build_advisory_report( |
| 467 | + as_of="2026-05-30", |
| 468 | + cadence="weekly", |
| 469 | + political_events_path=ROOT / "examples/political_events.example.csv", |
| 470 | + political_watchlist_path=ROOT / "examples/political_watchlist.example.csv", |
| 471 | + ai_signal_path=ROOT / "examples/research_signal_context.example.json", |
| 472 | + theme_momentum_path=ROOT / "examples/theme_momentum_snapshot.example.json", |
| 473 | + ) |
| 474 | + decisions = build_final_decisions( |
| 475 | + report["recommendations"], |
| 476 | + load_theme_momentum(ROOT / "examples/theme_momentum_snapshot.example.json"), |
| 477 | + max_recommendations=1, |
| 478 | + max_watchlist=1, |
| 479 | + ) |
| 480 | + |
| 481 | + assert all(item["action"] == "recommend" for item in decisions["recommendations"]) |
| 482 | + assert all(item["action"] == "watch" for item in decisions["watchlist"]) |
| 483 | + assert all(item["action"] == "recommend" for item in decisions["overflow_recommendations"]) |
| 484 | + assert decisions["overflow_recommendations"] |
| 485 | + assert report["summary"]["recommendation_count"] == report["summary"]["base_recommendation_count"] |
| 486 | + assert report["summary"]["final_recommendation_count"] == len(report["final_decisions"]["recommendations"]) |
| 487 | + assert report["summary"]["final_watchlist_count"] == len(report["final_decisions"]["watchlist"]) |
| 488 | + |
| 489 | + |
| 490 | +def test_contract_rejects_final_decision_section_action_mismatch() -> None: |
| 491 | + report = build_advisory_report( |
| 492 | + as_of="2026-05-30", |
| 493 | + cadence="weekly", |
| 494 | + political_events_path=ROOT / "examples/political_events.example.csv", |
| 495 | + political_watchlist_path=ROOT / "examples/political_watchlist.example.csv", |
| 496 | + ai_signal_path=ROOT / "examples/research_signal_context.example.json", |
| 497 | + theme_momentum_path=ROOT / "examples/theme_momentum_snapshot.example.json", |
| 498 | + ) |
| 499 | + report["final_decisions"]["watchlist"][0]["action"] = "recommend" |
| 500 | + |
| 501 | + with pytest.raises(AdvisoryValidationError, match="watchlist.*action must be watch"): |
| 502 | + validate_advisory_report(report) |
0 commit comments