"""Tests for ``scraper.dashboard.bill_status`` — the "why is this bill here?" classifier (API_CONTRACT §5 "upcoming" item 2: a bill should not be a vague "pending" when its floor history already shows a terminal / current state). Pure, no DB: ``derive_status(description)`` -> ``{status, reason, possibly_closed}``. The *latest* matching action in the chronological log wins; the contract's "default to including, flag possibly_closed" applies when the source can't disambiguate. """ from __future__ import annotations from scraper.dashboard import bill_status from scraper.dashboard.bill_status import derive_status, is_closed # --------------------------------------------------------------------------- # the motivating case: a passed bill sitting in "unfinished business" # --------------------------------------------------------------------------- def test_awaiting_concurrence_in_senate_amendments_is_open(): # AB1099: passed the Assembly, now awaiting Senate-amendment concurrence. r = derive_status( "Vote required: 41 2026 Aug. 30 - Read third time. Passed. " "Ordered to the Assembly. (Ayes 39. Noes 0.). Aug. 30 - In Assembly. " "Concurrence in Senate amendments pending. View Details", "AB 1099") assert r["status"] == "open" assert "concurrence" in r["reason"].lower() assert r["possibly_closed"] is False assert not is_closed(r) def test_passed_is_not_terminal_but_closed_terms_are(): # "Passed" (one chamber) is an in-progress step -> open, not dropped. passed = derive_status("Read third time. Passed. Ordered to the Assembly.", "AB 1") assert passed["status"] == "open" # "To inactive file" is a terminal/acted state -> closed, possibly_closed. inact = derive_status("2026 Aug. 25 - To inactive file on motion of " "Assembly Member Jackson. View Details", "AB 2") assert inact["status"] == "closed" assert inact["possibly_closed"] is True assert is_closed(inact) assert "inactive" in inact["reason"].lower() # --------------------------------------------------------------------------- # latest-action-wins + priority # --------------------------------------------------------------------------- def test_latest_action_in_log_wins(): # A bill that passed long ago but now has "Motion to reconsider # continued" surfaces the *current* reconsideration state, not the old pass. r = derive_status( "2025 Sep. 13 - Read third time. Passed. Ordered to the Assembly. " "Sep. 13 - In Assembly. Assembly refused to concur in Senate " "amendments. (Ayes 34. Noes 27.) Motion to reconsider on the next " "legislative day made. ... 2026 Aug. 30 - Motion to reconsider " "continued. View Details", "AB 1231") assert "reconsider" in r["reason"].lower() assert r["status"] == "open" def test_priority_breates_ties_on_same_span(): # "Motion to reconsider" must win over a bare "passed" even though both # match; the specific/current fact dominates. r = derive_status("Passed. Motion to reconsider made.", "AB 1") assert "reconsider" in r["reason"].lower() # --------------------------------------------------------------------------- # unknown / content-free -> default to including + flag (API_CONTRACT §5) # --------------------------------------------------------------------------- def test_content_free_defaults_to_unknown_and_flags(): # A bare dated line with no action text -> "On file", never a false pass. r = derive_status("Vote required: 41 2026 May 27 - View Details", "AB 3") assert r["status"] == "unknown" assert r["possibly_closed"] is None # can't confirm -> not asserted open assert "on file" in r["reason"].lower() def test_empty_description_is_unknown_not_crash(): for text in (None, "", " "): r = derive_status(text, "AB 4") assert r["status"] == "unknown" assert r["possibly_closed"] is None # --------------------------------------------------------------------------- # a few more real-world terminal / open signals # --------------------------------------------------------------------------- def test_engrossed_and_withdrawn_are_closed(): assert is_closed(derive_status("Engrossed. View Details", "AB 5")) assert is_closed(derive_status("Withdrawn from committee. View Details", "AB 6")) def test_in_committee_and_scheduled_hearing_are_open(): assert derive_status("Referred to Com. on Health. View Details", "AB 7")["status"] == "open" assert derive_status("June 17 - Set for first hearing. View Details", "AB 8")["status"] == "open" def test_return_shape_is_stable(): r = derive_status("Pending. View Details", "AB 9") assert set(r.keys()) == {"status", "reason", "possibly_closed"}