Initial commit

This commit is contained in:
kfj001 committed 2026-09-06 10:18:26 -07:00
commit 5f2db08b43
100 files changed
+16985

No files matched your search

+75
View File
@@ -0,0 +1,75 @@
"""bill_id codec + session/chamber/number helpers, per spec §4."""
from __future__ import annotations
import re
from typing import Optional, Tuple
# Universal bill_id pattern across all daily-file links (spec §4).
# 20252026 0 AB 28 -> 202520260AB28
BILL_ID_RE = re.compile(
r"^(?P<session>\d{8})0?(?P<chamber>AB|SB|ACR|SCR|HR|SJR|ABR)"
r"(?P<number>\d+)$"
)
# Chamber prefix -> origin house (spec §8 #7: origin, not publishing house).
ASSEMBLY_PREFIXES = {"AB", "ACR", "HR", "ABR"}
SENATE_PREFIXES = {"SB", "SCR", "SJR"}
_SESSION_RE = re.compile(r"^(?P<a>\d{4})(?P<b>\d{4})$")
_BILL_DISPLAY_RE = re.compile(r"^(?P<prefix>AB|SB|ACR|SCR|HR|SJR|ABR)\s*(?P<num>\d+)$")
_SUPPLEMENT_RE = re.compile(r"Supplemental\s+(\d+)", re.IGNORECASE)
def origin_house(chamber: str) -> str:
if chamber in ASSEMBLY_PREFIXES:
return "assembly"
if chamber in SENATE_PREFIXES:
return "senate"
return "unknown"
def normalise_session(raw: str) -> str:
"""'20252026' -> '2025-2026'."""
m = _SESSION_RE.match(raw or "")
if m:
return f"{m['a']}-{m['b']}"
return raw
def parse_bill_id(bill_id: str) -> Optional[dict]:
"""Decode a canonical bill_id. Returns dict or None if it does not match.
Keys: session (YYYY-YYYY), session_raw, chamber (prefix upper), number
(int, unpadded), display (e.g. 'AB 28'), house (origin), raw.
"""
if not bill_id:
return None
m = BILL_ID_RE.match(bill_id.strip())
if not m:
return None
session_raw = m["session"]
chamber = m["chamber"].upper()
number = int(m["number"])
return {
"session": normalise_session(session_raw),
"session_raw": session_raw,
"chamber": chamber,
"number": number,
"display": f"{chamber} {number}",
"house": origin_house(chamber),
"raw": bill_id,
}
def parse_display(display: str) -> Optional[Tuple[str, int]]:
"""'AB 00028' or 'AB 28' -> ('AB', 28)."""
m = _BILL_DISPLAY_RE.match((display or "").strip())
if not m:
return None
return m["prefix"].upper(), int(m["num"])
def parse_supplement(text: str) -> Optional[int]:
m = _SUPPLEMENT_RE.search(text or "")
return int(m.group(1)) if m else None