Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
from __future__ import annotations

import json

from forecasting_tools.agents_and_tools.source_archive.config import ArchiveConfig
from forecasting_tools.agents_and_tools.source_archive.models import (
StoredCapture,
url_hash,
)
from forecasting_tools.agents_and_tools.source_archive.pipeline import (
CaptureOutcome,
PipelineSummary,
Expand All @@ -12,6 +18,12 @@
from forecasting_tools.agents_and_tools.source_archive.storage import LocalBlobStore


def _stored(url: str, fetcher: str) -> StoredCapture:
return StoredCapture(
url=url, url_hash=url_hash(url), content_hash="c1", fetcher=fetcher
)


def test_run_report_roundtrip_canonicalizes(tmp_path):
store = LocalBlobStore(tmp_path)
config = ArchiveConfig(s3_prefix="t")
Expand All @@ -29,6 +41,32 @@ def test_run_report_roundtrip_canonicalizes(tmp_path):
assert out["https://b.test/q"] == "error"


def test_run_report_records_backend_per_url(tmp_path):
store = LocalBlobStore(tmp_path)
config = ArchiveConfig(s3_prefix="t")
summary = PipelineSummary(
outcomes=[
CaptureOutcome(
url="https://a.test/p",
status="stored",
stored=_stored("https://a.test/p", "firecrawl"),
),
CaptureOutcome(
url="https://c.test/r",
status="cache_hit",
stored=_stored("https://c.test/r", "playwright"),
),
CaptureOutcome(url="https://b.test/q", status="error", reason="cloudflare"),
]
)
key = write_run_report(store, "r1", summary, config)

rows = {r["url"]: r for r in json.loads(store.get(key).decode("utf-8"))}
assert rows["https://a.test/p"]["backend"] == "firecrawl"
assert rows["https://c.test/r"]["backend"] == "playwright"
assert rows["https://b.test/q"]["backend"] == "" # nothing fetched


def test_captured_status_wins_over_failure(tmp_path):
store = LocalBlobStore(tmp_path)
config = ArchiveConfig(s3_prefix="t")
Expand Down
14 changes: 12 additions & 2 deletions forecasting_tools/agents_and_tools/source_archive/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,19 @@ def report_key(run_id: str, config: ArchiveConfig) -> str:
def write_run_report(
store: BlobStore, run_id: str, summary, config: ArchiveConfig
) -> str:
"""Persist a run's per-URL outcomes; ``summary`` is a ``PipelineSummary``."""
"""Persist a run's per-URL outcomes; ``summary`` is a ``PipelineSummary``.

``backend`` is the fetcher that produced the capture (from the stored
capture, so it is set for stored/deduped/cache_hit outcomes and ``""``
when unknown, e.g. errors) — it enables per-domain cost attribution.
"""
rows = [
{"url": o.url, "status": o.status, "reason": getattr(o, "reason", "")}
{
"url": o.url,
"status": o.status,
"reason": getattr(o, "reason", ""),
"backend": o.stored.fetcher if o.stored is not None else "",
}
for o in summary.outcomes
]
key = report_key(run_id, config)
Expand Down
Loading