Initial commit
This commit is contained in:
commit
5f2db08b43
100 files changed
+16985
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user