diff --git a/code_tests/unit_tests/test_agents_and_tools/test_source_archive/test_reports.py b/code_tests/unit_tests/test_agents_and_tools/test_source_archive/test_reports.py index 52c9d841..7a637a64 100644 --- a/code_tests/unit_tests/test_agents_and_tools/test_source_archive/test_reports.py +++ b/code_tests/unit_tests/test_agents_and_tools/test_source_archive/test_reports.py @@ -3,6 +3,10 @@ import json from forecasting_tools.agents_and_tools.source_archive.config import ArchiveConfig +from forecasting_tools.agents_and_tools.source_archive.cost import ( + RunCost, + write_cost_report, +) from forecasting_tools.agents_and_tools.source_archive.models import ( StoredCapture, url_hash, @@ -87,3 +91,22 @@ def test_captured_status_wins_over_failure(tmp_path): config, ) assert read_outcomes(store, config)["https://a.test"] == "stored" + + +def test_read_outcomes_ignores_cost_reports(tmp_path): + """Cost reports live under reports/ too (``_cost.json``, a JSON + dict, not a list of rows) — read_outcomes must skip them, not crash.""" + store = LocalBlobStore(tmp_path) + config = ArchiveConfig(s3_prefix="t") + write_run_report( + store, + "r1", + PipelineSummary( + outcomes=[CaptureOutcome(url="https://a.test", status="stored")] + ), + config, + ) + write_cost_report(store, "r1", RunCost(run_id="r1", archived=1), config) + + out = read_outcomes(store, config) + assert out == {"https://a.test": "stored"} diff --git a/forecasting_tools/agents_and_tools/source_archive/reports.py b/forecasting_tools/agents_and_tools/source_archive/reports.py index 9ac515e1..8ad95945 100644 --- a/forecasting_tools/agents_and_tools/source_archive/reports.py +++ b/forecasting_tools/agents_and_tools/source_archive/reports.py @@ -73,12 +73,16 @@ def read_outcomes(store: BlobStore, config: ArchiveConfig) -> dict[str, str]: prefix = config.s3_prefix.rstrip("/") out: dict[str, str] = {} for key in store.list_keys(f"{prefix}/reports/"): - if not key.endswith(".json"): + # reports/ also holds per-run cost breakdowns (``_cost.json``, + # a dict) — only run reports (a list of outcome rows) belong here. + if not key.endswith(".json") or key.endswith("_cost.json"): continue try: rows = json.loads(store.get(key).decode("utf-8")) except (UnicodeDecodeError, ValueError): continue + if not isinstance(rows, list): + continue for r in rows: url = canonicalize_url(r.get("url", "")) status = r.get("status", "")