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
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## Unreleased

### Fixed

- **An impossible calendar date discarded a whole house.** `2025-02-30T00:00:00Z`
has the shape of an ISO timestamp and names a day that does not exist.
`strptime` raised a bare `ValueError`, `_record()` catches `UnreadableDate`
alone, so the error reached the house handler, wrote one `fetch_error`, and
abandoned every later bill in that house. It now raises `UnreadableDate`, the
bad field goes to null, and the bill keeps everything else. This is the defect
0.15.0 fixed for other date shapes and missed for this one.


## 0.15.0 — 2026-08-18

**Two of these change published numbers. Re-run the bills probe, and read the
Expand Down
11 changes: 10 additions & 1 deletion commoner_probe/bill_catalog_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,16 @@ def _date(value: object, field: str = "date") -> str | None:
# read differently on two machines walking the same source.
stamped = _ISO_STAMP.match(text)
if stamped and _in_range(stamped):
return datetime.strptime(stamped.group(1), "%Y-%m-%d").strftime("%Y-%m-%d")
try:
return datetime.strptime(stamped.group(1), "%Y-%m-%d").strftime("%Y-%m-%d")
except ValueError as exc:
# `2025-02-30` has the shape of a date and is not one. A bare
# ValueError here escaped `_record()`, which catches UnreadableDate
# alone, reached the house handler, and abandoned every later bill
# in that house over one field.
raise UnreadableDate(
f"{field}: {text!r} has the shape of an ISO timestamp and names a day "
f"that does not exist ({exc}).") from exc
raise UnreadableDate(
f"{field}: {text!r} matches no date format this endpoint is known to serve "
f"({', '.join(_DATE_FORMATS)}, or ISO 8601). It is not being truncated into "
Expand Down
27 changes: 27 additions & 0 deletions tests/test_bill_dates.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,3 +374,30 @@ def test_another_adapter_s_history_survives(self, tmp_path):
tracked = [r["bill_status"] for r in rows if r.get("kind") == "prs_bill_track"]
assert tracked == ["Pending", "Passed"], tracked
assert [r["assent_date"] for r in rows if r.get("kind") == "bill_record"] == ["2025-12-20"]


class TestAnImpossibleCalendarDateStaysOneField:
"""A P1 from Codex that merged unfixed on #142. The dispatcher read one
comment of a two-comment review."""

def test_an_impossible_day_raises_unreadable_not_value_error(self):
with pytest.raises(UnreadableDate):
_date("2025-02-30T00:00:00Z", "assent_date")

def test_the_house_survives_an_impossible_day(self, tmp_path):
"""`_record()` catches `UnreadableDate` alone, so a bare `ValueError`
reached the house handler, wrote one `fetch_error`, and abandoned every
later bill in that house."""
from commoner_probe.bill_catalog_api import BillsProbe

good = {"billNumber": "1", "billYear": "2025", "billName": "Good Bill",
"billIntroducedDate": "2025-01-02 00:00:00.0"}
bad = {"billNumber": "2", "billYear": "2025", "billName": "Bad Bill",
"billIntroducedDate": "2025-02-30T00:00:00Z"}
probe = BillsProbe(tmp_path / "out", sleep=0, houses=["ls"])
probe.bills_all = lambda house: iter([bad, good])

rows = probe.probe()
assert [r["fetch_status"] for r in rows] == ["parse_error", "ok"], rows
assert rows[0]["bill_name"] == "Bad Bill"
assert rows[0]["introduced_date"] is None
Loading