diff --git a/src/fedcourtsai/corpus.py b/src/fedcourtsai/corpus.py index 33cc11a6..e31b52b2 100644 --- a/src/fedcourtsai/corpus.py +++ b/src/fedcourtsai/corpus.py @@ -1001,7 +1001,7 @@ def _from_record(record: RecordRow) -> CorpusRow: disposition=record["disposition"], judges=json.loads(record["judges"]), panel=[PanelMember(**m) for m in json.loads(record["panel"])], - counsel=[CounselEntry(**c) for c in json.loads(record["counsel"])], + counsel=[CounselEntry(**c) for c in json.loads(_optional_str(record, "counsel") or "[]")], parties=json.loads(record["parties"]), attorneys=json.loads(record["attorneys"]), topic=record["topic"], diff --git a/tests/test_corpus.py b/tests/test_corpus.py index 0c906b0c..236f164e 100644 --- a/tests/test_corpus.py +++ b/tests/test_corpus.py @@ -1833,6 +1833,27 @@ def test_the_sibling_letter_forms_take_the_same_tolerances() -> None: assert corpus.is_disbarment_docket(_scotus("16D02977")) +def test_counsel_reads_empty_from_a_blob_that_predates_the_column(tmp_path: Path) -> None: + """The ranged and service backends read the published blob as-is — no + ``connect`` migration runs — so a column the blob predates must read as its + default through ``_from_record``, the same contract every ``_optional_*`` + column honors.""" + db = tmp_path / "corpus.db" + with corpus.connect(db) as conn: + corpus.upsert_rows(conn, [_row("scotus/886")]) + + raw = sqlite3.connect(db) + try: + raw.execute("ALTER TABLE cases DROP COLUMN counsel") + raw.commit() + raw.row_factory = sqlite3.Row + record = raw.execute("SELECT * FROM cases WHERE case_id = 'scotus/886'").fetchone() + row = corpus._from_record(record) + finally: + raw.close() + assert row.counsel == [] + + def test_counsel_round_trips_with_its_side(tmp_path: Path) -> None: """The side is the reason the column exists, so it is the thing that must survive storage — a round trip that kept only the names would be the flat