Adds more bill status visibility to the dashboard

This commit is contained in:
kfj001 committed 2026-09-06 14:29:40 -07:00
1 parent a104685daf
commit 1919d43bf0
6 files changed
+360 -10

No files matched your search

+19 -1
View File
@@ -141,11 +141,29 @@ def test_bills_personal_returns_relevance_and_floats(services):
def test_bills_personal_requires_auth(services):
status, body = api.run(_req("GET", "/api/v1/bills", services,
query={"personal": "1"}))
query={"personal": "1"}))
assert status == 401, body
assert body["error"]["code"] == "not_authenticated"
# ---- §5 "upcoming" item 2: each item carries status + reason -------------
def test_bills_items_carry_status_reason_and_possibly_closed(services):
# Every item must expose the "why is this here?" triad so a closed/past
# bill doesn't read as a vague "pending" (API_CONTRACT §5 item 2).
status, body = api.run(_req("GET", "/api/v1/bills", services,
query={"upcoming": "1"}))
assert status == 200, body
items = body["items"]
assert items, "expected at least one item"
for it in items:
for key in ("status", "reason", "possibly_closed"):
assert key in it, key
assert it["status"] in ("open", "closed", "unknown")
assert isinstance(it["reason"], str) and it["reason"]
assert it["possibly_closed"] in (True, False, None)
# ---- six empty states -> 200 items:[] (never 4xx/5xx) ---------------------
def test_empty_no_next_hearing(services):
+110
View File
@@ -0,0 +1,110 @@
"""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"}