111 lines
4.8 KiB
Python
111 lines
4.8 KiB
Python
"""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"}
|