Skip to content

Commit 17ab03d

Browse files
Pigbibicodex
andcommitted
fix: close legacy quality and manifest compatibility gaps
Co-Authored-By: Codex <noreply@openai.com>
1 parent 1334892 commit 17ab03d

4 files changed

Lines changed: 45 additions & 10 deletions

File tree

docs/advisory_contract.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,9 @@ policy
270270
generated_at
271271
```
272272

273-
`source_artifacts` also records each existing input's SHA-256, point-in-time
274-
`as_of`/`generated_at`/`expires_at`, and schema/header metadata. Scheduled
273+
`source_artifacts_metadata` (schema version `1`) records each existing input's
274+
SHA-256, point-in-time `as_of`/`generated_at`/`expires_at`, and schema/header
275+
metadata while preserving the legacy `source_artifacts` string map. Scheduled
275276
workflows pass the checked-out commit SHA for each upstream repository in
276277
`upstream_repositories`; missing or stale context is excluded from scoring and
277278
reported in `summary.data_quality_warnings`.

src/quant_advisor_research/advisory_report.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,7 @@ def load_market_confirmation(path: str | Path | None, as_of: dt.date) -> dict[st
410410
current = confirmations.get(symbol)
411411
if current and current.as_of and row_as_of and row_as_of < current.as_of:
412412
continue
413+
quality_metadata_present = "confirmation_quality" in row and "warnings" in row
413414
confirmations[symbol] = MarketConfirmation(
414415
symbol=symbol,
415416
as_of=row_as_of,
@@ -424,7 +425,9 @@ def load_market_confirmation(path: str | Path | None, as_of: dt.date) -> dict[st
424425
market_score=as_float(row.get("market_score")) if str(row.get("market_score", "")).strip() else None,
425426
data_source=str(row.get("data_source", "")),
426427
price_observation_count=int(as_float(row.get("price_observation_count"))),
427-
confirmation_quality=str(row.get("confirmation_quality", "")),
428+
confirmation_quality=(str(row.get("confirmation_quality", "")).strip() or "missing_quality_metadata")
429+
if quality_metadata_present
430+
else "missing_quality_metadata",
428431
warnings=str(row.get("warnings", "")),
429432
)
430433
return confirmations
@@ -1021,7 +1024,7 @@ def market_confirmation_score(market: MarketConfirmation | None) -> float | None
10211024
return None
10221025
if market.data_source == "theme_momentum_fallback":
10231026
return None
1024-
if market.confirmation_quality == "anomalous" or "extreme_return_63d" in market.warnings or "stale_price" in market.warnings:
1027+
if market.confirmation_quality != "price_observed":
10251028
return None
10261029
if market.market_score is not None:
10271030
return round(clamp(market.market_score, 0, 1), 3)

src/quant_advisor_research/artifacts.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def write_report_manifest(
6262
if run_attempt:
6363
version_parts.append(f"attempt-{run_attempt}")
6464

65-
source_artifacts = {}
65+
source_artifact_metadata = {}
6666
for name, raw_path in (input_paths or {}).items():
6767
if raw_path is None:
6868
continue
@@ -90,7 +90,7 @@ def write_report_manifest(
9090
)
9191
except (OSError, json.JSONDecodeError):
9292
metadata["schema"] = "invalid_json"
93-
source_artifacts[name] = metadata
93+
source_artifact_metadata[name] = metadata
9494

9595
payload = {
9696
"manifest_type": "model_recommendation_report",
@@ -109,7 +109,11 @@ def write_report_manifest(
109109
"github_run_id": run_id or "",
110110
"github_run_attempt": run_attempt or "",
111111
},
112-
"source_artifacts": source_artifacts or dict(report.get("source_artifacts") or {}),
112+
"source_artifacts": dict(report.get("source_artifacts") or {}),
113+
"source_artifacts_metadata": {
114+
"schema_version": "1",
115+
"items": source_artifact_metadata,
116+
},
113117
"upstream_repositories": dict(upstream_repo_shas or {}),
114118
"summary": dict(report.get("summary") or {}),
115119
"artifacts": {

tests/test_advisory_report.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,37 @@ def test_expired_ai_signal_is_excluded_from_long_context_and_warns(tmp_path: Pat
5252
assert report["summary"]["long_context_available"] is False
5353

5454

55+
def test_legacy_market_confirmation_without_quality_metadata_cannot_score(tmp_path: Path) -> None:
56+
market_path = tmp_path / "legacy_market.csv"
57+
market_path.write_text(
58+
"symbol,as_of,return_5d,return_20d,return_63d,relative_return_20d,relative_return_63d,volume_zscore,drawdown_63d,volatility_21d,market_score,data_source,price_observation_count\n"
59+
"MU,2026-05-30,0.03,0.12,0.28,0.07,0.13,1.2,-0.06,0.33,0.99,yahoo_chart,120\n",
60+
encoding="utf-8",
61+
)
62+
report = build_advisory_report(
63+
as_of="2026-05-30",
64+
cadence="weekly",
65+
political_events_path=ROOT / "examples/political_events.example.csv",
66+
political_watchlist_path=ROOT / "examples/political_watchlist.example.csv",
67+
market_confirmation_path=market_path,
68+
)
69+
70+
assert report["final_decisions"]["recommendations"] == []
71+
72+
5573
def test_manifest_records_input_hash_and_upstream_metadata(tmp_path: Path) -> None:
5674
report_path = tmp_path / "report.json"
5775
markdown_path = tmp_path / "report.md"
5876
report_path.write_text("{}\n", encoding="utf-8")
5977
markdown_path.write_text("# report\n", encoding="utf-8")
6078
input_path = ROOT / "examples/political_events.example.csv"
61-
report = {"as_of": "2026-05-30", "cadence": "weekly", "schema_version": "5", "mode": "model_recommendations"}
79+
report = {
80+
"as_of": "2026-05-30",
81+
"cadence": "weekly",
82+
"schema_version": "5",
83+
"mode": "model_recommendations",
84+
"source_artifacts": {"political_events": str(input_path)},
85+
}
6286

6387
manifest_path = write_report_manifest(
6488
report=report,
@@ -71,8 +95,11 @@ def test_manifest_records_input_hash_and_upstream_metadata(tmp_path: Path) -> No
7195

7296
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
7397
assert manifest["upstream_repositories"]["QuantStrategyLab/PoliticalEventTrackingResearch"] == "abc123"
74-
assert manifest["source_artifacts"]["political_events"]["sha256"]
75-
assert manifest["source_artifacts"]["political_events"]["schema"].startswith("event_id,")
98+
assert manifest["source_artifacts"]["political_events"] == str(input_path)
99+
metadata = manifest["source_artifacts_metadata"]
100+
assert metadata["schema_version"] == "1"
101+
assert metadata["items"]["political_events"]["sha256"]
102+
assert metadata["items"]["political_events"]["schema"].startswith("event_id,")
76103

77104

78105
def test_low_confidence_events_remain_verify_source_until_verified() -> None:

0 commit comments

Comments
 (0)