Skip to content

Commit 94786eb

Browse files
Pigbibicodex
andcommitted
Align feed status digest with emitted rows
Co-Authored-By: Codex <noreply@openai.com>
1 parent 771f7d1 commit 94786eb

4 files changed

Lines changed: 16 additions & 24 deletions

File tree

src/political_event_tracking_research/feed_primitives.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ def _is_digest(value: object) -> bool:
7474
return type(value) is str and len(value) == 64 and all(char in "0123456789abcdef" for char in value)
7575

7676

77+
def _row_sort_key(row: PrimitiveRow) -> tuple[str, str]:
78+
return row.published_at, row.item_id
79+
80+
7781
@dataclass(frozen=True, slots=True)
7882
class PrimitiveRow:
7983
item_id: str
@@ -156,7 +160,7 @@ def _snapshot_feed(value: object) -> dict[str, Any]:
156160

157161

158162
def _feed_wire(feed: Mapping[str, Any]) -> dict[str, object]:
159-
rows = [row.to_mapping() for row in feed["rows"]]
163+
rows = [row.to_mapping() for row in sorted(feed["rows"], key=_row_sort_key)]
160164
return {
161165
"feed_id": feed["feed_id"],
162166
"feed_url": feed["feed_url"],
@@ -189,7 +193,8 @@ def build_status(feed_records: Iterable[Mapping[str, object]]) -> dict[str, obje
189193
accepted = sum(item["state"] == "accepted" for item in records)
190194
failed = sum(item["state"] == "failed" for item in records)
191195
quarantined = sum(item["state"] == "quarantined" for item in records)
192-
rows = [row.to_mapping() for item in records if item["state"] == "accepted" for row in item["rows"]]
196+
accepted_rows = [row for item in records if item["state"] == "accepted" for row in item["rows"]]
197+
rows = [row.to_mapping() for row in sorted(accepted_rows, key=_row_sort_key)]
193198
complete = accepted == len(records) and failed == 0 and quarantined == 0 and bool(rows)
194199
return {
195200
"status_version": STATUS_VERSION,

src/political_event_tracking_research/rss_source_fetch.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ def parse_feed_items(
126126
raise FeedXmlError("feed_xml_invalid") from None
127127
rows: list[dict[str, str]] = []
128128

129-
rss_items = root.findall("./channel/item")
130-
if rss_items:
129+
if root.tag == "rss":
130+
rss_items = root.findall("./channel/item")
131131
for item in rss_items[:max_items]:
132132
title = child_text(item, ("title",))
133133
link = rss_item_link(item)

tests/test_feed_primitives.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ def test_failed_rows_are_excluded_and_mixed_status_is_not_complete() -> None:
7070
) == status
7171

7272

73+
def test_digest_matches_emitted_row_order() -> None:
74+
late = {**ROW, "item_id": "feed-a-2", "published_at": "2026-05-02T12:30:00Z"}
75+
status = build_status([feed("a", rows=[late, ROW])])
76+
assert status["aggregate_row_digest"] == build_status([feed("a", rows=[ROW, late])])["aggregate_row_digest"]
77+
78+
7379
def test_canonical_roundtrip_and_order_are_strict() -> None:
7480
status = build_status([feed("b"), feed("a")])
7581
wire = serialize_status(status)

tests/test_rss_source_fetch.py

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -198,26 +198,6 @@ def fake_fetch(_url: str) -> bytes:
198198
)
199199

200200

201-
def test_all_zero_entry_feeds_write_quarantine_status_without_hard_failure(tmp_path: Path) -> None:
202-
feeds_path = tmp_path / "feeds.csv"
203-
feeds_path.write_text(
204-
"feed_id,feed_url,source_type,author\n"
205-
"empty,https://example.invalid/empty.xml,official_remarks,Example\n",
206-
encoding="utf-8",
207-
)
208-
feed_xml = b"<rss version='2.0'><channel/></rss>"
209-
210-
output = tmp_path / "source_items.csv"
211-
status = tmp_path / "status.json"
212-
rows = fetch_rss_sources(feeds_path, output, status_output=status, fetcher=lambda _url: feed_xml)
213-
214-
assert rows == []
215-
assert output.read_text(encoding="utf-8") == "item_id,published_at,source_type,source_url,author,text\n"
216-
payload = json.loads(status.read_text(encoding="utf-8"))
217-
assert payload["quarantined_feed_count"] == 1
218-
assert payload["accepted_row_count"] == 0
219-
assert payload["publication_complete"] is False
220-
assert payload["eligible_for_live_publication"] is False
221201

222202

223203
def test_all_zero_entry_feeds_write_quarantine_status_without_hard_failure(tmp_path: Path) -> None:
@@ -236,6 +216,7 @@ def test_all_zero_entry_feeds_write_quarantine_status_without_hard_failure(tmp_p
236216
assert rows == []
237217
assert output.read_text(encoding="utf-8") == "item_id,published_at,source_type,source_url,author,text\n"
238218
payload = json.loads(status.read_text(encoding="utf-8"))
219+
assert payload["feeds"][0]["kind"] == "rss"
239220
assert payload["quarantined_feed_count"] == 1
240221
assert payload["accepted_row_count"] == 0
241222
assert payload["publication_complete"] is False

0 commit comments

Comments
 (0)