Skip to content

Commit 758b7e6

Browse files
Pigbibicodex
andcommitted
Migrate AI tests to manifest v2 contract
Co-Authored-By: Codex <noreply@openai.com>
1 parent 278bcfb commit 758b7e6

3 files changed

Lines changed: 65 additions & 19 deletions

File tree

tests/test_advisory_report.py

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from __future__ import annotations
22

33
import datetime as dt
4+
import hashlib
45
import json
6+
import subprocess
57
from pathlib import Path
68

79
import pytest
@@ -25,6 +27,55 @@
2527
ROOT = Path(__file__).resolve().parents[1]
2628

2729

30+
def write_trusted_example_ai_signal(tmp_path: Path) -> Path:
31+
repo = tmp_path / "ResearchSignalContextPipelines"
32+
signal = repo / "data/output/latest_signal.json"
33+
signal.parent.mkdir(parents=True)
34+
signal.write_bytes((ROOT / "examples/research_signal_context.example.json").read_bytes())
35+
payload = json.loads(signal.read_text(encoding="utf-8"))
36+
manifest = {
37+
"manifest_type": "research_signal_context",
38+
"schema_version": 2,
39+
"artifact": {
40+
"path": "data/output/latest_signal.json",
41+
"sha256": hashlib.sha256(signal.read_bytes()).hexdigest(),
42+
},
43+
"as_of": payload["as_of"],
44+
"generated_at": payload["generated_at"],
45+
"expires_at": payload["expires_at"],
46+
"mode": payload["mode"],
47+
"producer": {
48+
"repository": "QuantStrategyLab/ResearchSignalContextPipelines",
49+
"commit_sha": "a" * 40,
50+
},
51+
"input_digest": f"sha256:{'b' * 64}",
52+
"policy": {"execution_allowed": False},
53+
}
54+
signal.with_name("latest_signal.manifest.json").write_text(json.dumps(manifest) + "\n", encoding="utf-8")
55+
subprocess.run(["git", "init", "-q", repo], check=True)
56+
subprocess.run(
57+
["git", "-C", repo, "remote", "add", "origin", "https://github.com/QuantStrategyLab/ResearchSignalContextPipelines.git"],
58+
check=True,
59+
)
60+
subprocess.run(["git", "-C", repo, "add", "data/output"], check=True)
61+
subprocess.run(
62+
[
63+
"git",
64+
"-C",
65+
repo,
66+
"-c",
67+
"user.name=Test",
68+
"-c",
69+
"user.email=test@example.invalid",
70+
"commit",
71+
"-qm",
72+
"trusted fixture",
73+
],
74+
check=True,
75+
)
76+
return signal
77+
78+
2879
def test_build_advisory_report_blocks_execution_and_allocation() -> None:
2980
report = build_advisory_report(
3081
as_of="2026-05-30",
@@ -61,13 +112,13 @@ def test_low_confidence_events_remain_verify_source_until_verified() -> None:
61112
assert by_symbol["EVT1"]["evidence_score"] > by_symbol["EVT4"]["evidence_score"]
62113

63114

64-
def test_ai_avoid_bias_defers_research_item() -> None:
115+
def test_ai_avoid_bias_defers_research_item(tmp_path: Path) -> None:
65116
report = build_advisory_report(
66117
as_of="2026-05-30",
67118
cadence="monthly",
68119
political_events_path=ROOT / "examples/political_events.example.csv",
69120
political_watchlist_path=ROOT / "examples/political_watchlist.example.csv",
70-
ai_signal_path=ROOT / "examples/research_signal_context.example.json",
121+
ai_signal_path=write_trusted_example_ai_signal(tmp_path),
71122
)
72123

73124
by_symbol = {item["symbol"]: item for item in report["recommendations"]}
@@ -138,13 +189,13 @@ def test_mixed_confidence_recommendation_is_not_tier_one(tmp_path: Path) -> None
138189
assert rec["recommendation_tier"] == "tier_2"
139190

140191

141-
def test_long_horizon_window_is_measured_in_years() -> None:
192+
def test_long_horizon_window_is_measured_in_years(tmp_path: Path) -> None:
142193
report = build_advisory_report(
143194
as_of="2026-05-30",
144195
cadence="weekly",
145196
political_events_path=ROOT / "examples/political_events.example.csv",
146197
political_watchlist_path=ROOT / "examples/political_watchlist.example.csv",
147-
ai_signal_path=ROOT / "examples/research_signal_context.example.json",
198+
ai_signal_path=write_trusted_example_ai_signal(tmp_path),
148199
)
149200

150201
by_symbol = {item["symbol"]: item for item in report["recommendations"]}
@@ -280,7 +331,7 @@ def test_report_manifest_records_contract_version_and_hashes(tmp_path: Path) ->
280331
assert manifest["artifacts"]["markdown"]["sha256"]
281332

282333

283-
def test_theme_bias_can_lift_static_watchlist_item_without_direct_symbol_bias(tmp_path: Path) -> None:
334+
def test_legacy_v1_theme_bias_is_untrusted_no_op(tmp_path: Path) -> None:
284335
events_path = tmp_path / "events.csv"
285336
events_path.write_text(
286337
"event_id,event_date,symbol,event_type,direction,confidence,source_url,notes\n",
@@ -339,11 +390,12 @@ def test_theme_bias_can_lift_static_watchlist_item_without_direct_symbol_bias(tm
339390

340391
rec = report["recommendations"][0]
341392
assert rec["symbol"] == "MU"
342-
assert rec["rating"] == "watch"
343-
assert rec["evidence_score"] > 4
344-
assert any("主题=hbm_memory" in reason for reason in rec["reasons"])
393+
assert rec["rating"] == "monitor"
394+
assert rec["evidence_score"] == 4
395+
assert rec["ai_context"]["source"] == ""
396+
assert report["summary"]["data_quality_warnings"] == ["ai_signal_provenance_untrusted"]
345397
assert report["summary"]["long_context_available"] is False
346-
assert report["summary"]["long_context_missing_reason"] == "current_candidates_do_not_meet_long_context_gate"
398+
assert report["summary"]["long_context_missing_reason"] == "ai_signal_not_available"
347399
assert "MU" not in report["summary"]["long_context_symbols"]
348400
assert report["final_decisions"]["horizon_action_buckets"]["long"]["watch"] == []
349401

tests/test_m0_research_hypothesis.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,7 @@ def test_adapter_supports_existing_v6_public_report_contract() -> None:
108108
"expires_at": (reference_time + dt.timedelta(days=7)).isoformat().replace("+00:00", "Z"),
109109
"input_digest": "a" * 64,
110110
"freshness": {
111-
"ai_signal": {
112-
"present": True,
113-
"valid": True,
114-
"reason": "fresh",
115-
"as_of": "2026-05-30",
116-
"generated_at": "2026-05-30T00:00:00Z",
117-
"expires_at": "2026-06-30",
118-
},
111+
"ai_signal": {"present": False, "valid": False, "reason": "not_provided"},
119112
"theme_momentum": {"present": False, "valid": False, "reason": "not_provided"},
120113
},
121114
}

tests/test_publisher.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def test_index_limits_recent_history_and_archive_keeps_all_reports() -> None:
183183
assert '"json": "advisory_report_2026-05-01.json"' in report_index
184184

185185

186-
def test_render_report_html_does_not_show_fixture_warning_for_live_paths(tmp_path: Path) -> None:
186+
def test_render_report_html_treats_manifestless_operator_ai_as_no_op(tmp_path: Path) -> None:
187187
live_dir = tmp_path / "live"
188188
live_dir.mkdir()
189189
political_events = live_dir / "political_events.csv"
@@ -206,7 +206,8 @@ def test_render_report_html_does_not_show_fixture_warning_for_live_paths(tmp_pat
206206
html = render_report_html(report)
207207

208208
assert report["summary"]["source_mode"] == "operator_supplied"
209-
assert report["summary"]["data_quality_warnings"] == []
209+
assert report["summary"]["data_quality_warnings"] == ["ai_signal_provenance_untrusted"]
210+
assert report["source_artifacts"]["ai_signal"] == ""
210211
assert "来源模式" not in html
211212
assert "Input artifacts include example fixture paths" not in html
212213

0 commit comments

Comments
 (0)