| """Pure helper tests for the PM Flash decision screen.""" |
| from __future__ import annotations |
|
|
| from datetime import date |
|
|
| from dashboard.provenance import filing_staleness |
| from dashboard.verdict import ( |
| _collect_source_types, |
| _evidence_coverage_label, |
| _normalize_watch_items, |
| _select_swing_factor, |
| ) |
| from dashboard.signals_view import _sentiment_display_allowed |
| from dashboard.i18n import STRINGS, t |
| from dashboard.nav import NAV_ITEMS, VALID_KEYS |
|
|
|
|
| def test_collect_source_types_is_recursive_deduplicated_and_ordered(): |
| brief = { |
| "bull_points": [{"source": "transcript, 10-Q"}], |
| "risks_categorized": [{"evidence": {"source": "10-K"}}], |
| "management_commentary": [{"source": "news"}, {"source": "10-Q"}], |
| "ignored": {"source": "yfinance"}, |
| } |
|
|
| assert _collect_source_types(brief) == ["10-K", "10-Q", "Transcript", "News"] |
|
|
|
|
| def test_evidence_coverage_uses_verified_counts_with_legacy_source_fallback(): |
| brief = { |
| "evidence_coverage": { |
| "status": "INCOMPLETE", |
| "verified": 7, |
| "unverified": 2, |
| "failed": 1, |
| "total": 10, |
| }, |
| "bull_points": [{"source": "10-Q"}], |
| } |
|
|
| assert _evidence_coverage_label(brief) == "Incomplete 路 7/10 verified 路 10-Q" |
| assert _evidence_coverage_label( |
| {"bull_points": [{"source": "transcript"}]} |
| ) == "Legacy / unverified 路 Transcript" |
| assert _evidence_coverage_label({ |
| "evidence_coverage": {"status": "VERIFIED", "verified": 2, "total": 3} |
| }).startswith("Incomplete") |
| assert _evidence_coverage_label({ |
| "evidence_coverage": {"status": "VERIFIED", "verified": 3, "total": 3} |
| }) == "Verified 路 3/3 verified" |
|
|
|
|
| def test_normalize_watch_items_supports_legacy_and_structured_values(): |
| result = _normalize_watch_items([ |
| "Q2 gross margin", |
| { |
| "label": "Data-center growth", |
| "metric": "Revenue growth", |
| "trigger": "below 30%", |
| "event_date": "Q2 FY27", |
| "source": "10-Q", |
| "status": "open", |
| }, |
| {}, |
| None, |
| ]) |
|
|
| assert result[0] == { |
| "text": "Q2 gross margin", |
| "metric": "", |
| "threshold": "", |
| "period": "", |
| "source": "", |
| "status": "", |
| } |
| assert result[1]["text"] == "Data-center growth" |
| assert result[1]["threshold"] == "below 30%" |
| assert result[1]["period"] == "Q2 FY27" |
| assert len(result) == 2 |
|
|
|
|
| def test_select_swing_factor_prefers_material_tension_stably(): |
| brief = { |
| "analytical_tensions": [ |
| {"headline": "Watch first", "weight": "watch"}, |
| {"headline": "Material first", "weight": "material"}, |
| {"headline": "Material second", "weight": "material"}, |
| ] |
| } |
|
|
| factor = _select_swing_factor(brief) |
|
|
| assert factor is not None |
| assert factor["kind"] == "tension" |
| assert factor["headline"] == "Material first" |
|
|
|
|
| def test_select_swing_factor_falls_back_to_top_tell(): |
| brief = { |
| "between_the_lines": [{ |
| "observation": "Management stopped quantifying backlog", |
| "reading": "Visibility may be weakening", |
| "implication": "Watch conversion next quarter", |
| "signal_type": "omission", |
| "evidence": { |
| "source": "transcript", |
| "reliability": "MEDIUM", |
| "impact": "HIGH", |
| "evidence_snippet": "Backlog was not discussed.", |
| }, |
| }] |
| } |
|
|
| factor = _select_swing_factor(brief) |
|
|
| assert factor is not None |
| assert factor["kind"] == "tell" |
| assert factor["headline"] == "Management stopped quantifying backlog" |
| assert factor["implication"] == "Watch conversion next quarter" |
|
|
|
|
| def test_select_swing_factor_returns_none_without_interpretive_data(): |
| assert _select_swing_factor({}) is None |
|
|
|
|
| def test_sentiment_display_policy_fails_closed(): |
| assert _sentiment_display_allowed({}) is False |
| assert _sentiment_display_allowed({"display_policy": {"sentiment_calibrated": "true"}}) is False |
| assert _sentiment_display_allowed({"display_policy": {"sentiment_calibrated": True}}) is True |
|
|
|
|
| def test_nav_order_is_analyst_flow(): |
| assert VALID_KEYS == {"company", "verdict", "signals", "financials", "chat"} |
| assert [item["key"] for item in NAV_ITEMS] == [ |
| "company", "verdict", "signals", "financials", "chat" |
| ] |
| assert not any(item.get("secondary") for item in NAV_ITEMS) |
|
|
|
|
| def test_nav_items_have_no_hardcoded_display_label(): |
| assert not any("display_label" in item for item in NAV_ITEMS) |
|
|
|
|
| def test_nav_i18n_labels_complete(): |
| for item in NAV_ITEMS: |
| labels = STRINGS[item["i18n_key"]] |
| assert set(labels) == {"en", "fr", "es", "de"} |
| assert t("nav_company") == "Company Overview" |
|
|
|
|
| def test_filing_staleness_flags_old_filings(): |
| assert filing_staleness( |
| "2026-07-29", today=date(2026, 8, 2) |
| ) == {"days_old": 4, "likely_newer_filing": False} |
| assert filing_staleness( |
| "2026-07-29", today=date(2026, 12, 15) |
| ) == {"days_old": 139, "likely_newer_filing": True} |
| assert filing_staleness("", today=date(2026, 8, 2)) == { |
| "days_old": None, |
| "likely_newer_filing": False, |
| } |
| assert filing_staleness("garbage", today=date(2026, 8, 2)) == { |
| "days_old": None, |
| "likely_newer_filing": False, |
| } |
|
|
|
|
| def test_partial_notice_strings_exist_in_all_languages(): |
| for key in ( |
| "freshness_stale_note", |
| "partial_brief_notice_zero", |
| "data_through_label", |
| ): |
| assert set(STRINGS[key]) == {"en", "fr", "es", "de"} |
|
|