"""Frozen-fixture suite for analyze.py classifier and aggregator. Each test parses a real-ish Clerk/Senate XML fixture via parse.py, then asserts classify_vote / aggregate behavior. Fixtures live in tests/fixtures/ and are trimmed/edited copies of real cached XMLs (see header comment in each). """ import os import pytest import analyze import parse FIX = os.path.join(os.path.dirname(__file__), "fixtures") # ---------- Unit tests on pure helpers ---------- @pytest.mark.parametrize("raw,expected", [ ("Yea", "Yea"), ("Aye", "Yea"), ("Nay", "Nay"), ("No", "Nay"), ("Present", "Present"), ("Not Voting", "Not Voting"), (None, None), ("", ""), ]) def test_norm_vote(raw, expected): assert analyze._norm_vote(raw) == expected @pytest.mark.parametrize("totals,expected", [ ({"yea": 100, "nay": 50}, "Yea"), ({"yea": 50, "nay": 100}, "Nay"), ({"yea": 100, "nay": 100}, "Split"), ({"yea": 0, "nay": 0}, "Split"), ]) def test_majority_position(totals, expected): assert analyze._majority_position(totals) == expected # ---------- Fixture loaders ---------- def _house(name, year=2025, roll=900): rec, _ = parse.parse_house_vote(os.path.join(FIX, name), year, roll) assert rec is not None, f"parse_house_vote returned None for {name}" return rec def _senate(name, session=1, vnum=900): rec, _ = parse.parse_senate_vote(os.path.join(FIX, name), session, vnum) assert rec is not None, f"parse_senate_vote returned None for {name}" return rec # ---------- Classification tests ---------- def test_partisan_house_helps_republicans(): rec = _house("partisan_house.xml") # Jordan (R) Yea on R-Yea/D-Nay partisan vote cls = analyze.classify_vote(rec, "J000289") assert cls["member_vote"] == "Yea" assert cls["member_vote_norm"] == "Yea" assert cls["r_pos"] == "Yea" assert cls["d_pos"] == "Nay" assert cls["alignment"] == "Helped Republicans" assert cls["blocked"] is None def test_partisan_house_helps_democrats(): rec = _house("partisan_house.xml") # Khanna (D) Nay on R-Yea/D-Nay partisan vote cls = analyze.classify_vote(rec, "K000389") assert cls["alignment"] == "Helped Democrats" def test_bipartisan_aye_no_normalization(): rec = _house("bipartisan_house.xml") # Both parties Yea; Jordan voted "Aye" (procedural) cls = analyze.classify_vote(rec, "J000289") assert cls["member_vote"] == "Aye" # raw preserved assert cls["member_vote_norm"] == "Yea" # normalized assert cls["alignment"] == "Helped Both" # Khanna also Aye → Helped Both assert analyze.classify_vote(rec, "K000389")["alignment"] == "Helped Both" # Donalds "No" → normalized Nay, helped neither majority donalds = analyze.classify_vote(rec, "D000032") assert donalds["member_vote"] == "No" assert donalds["member_vote_norm"] == "Nay" assert donalds["alignment"] == "Helped Neither" def test_member_absent(): rec = _house("partisan_house.xml") cls = analyze.classify_vote(rec, "Z999999") # not in record assert cls["alignment"].startswith("N/A: absent") assert cls["member_vote"] is None assert cls["member_vote_norm"] is None assert cls["blocked"] is None def test_split_party_no_false_helped(): rec = _house("split_party_house.xml") # D is tied (100/100) → Split. Khanna (D) voted Yea; R majority is Yea. # Should classify as Helped Republicans only — NOT Helped Democrats. cls = analyze.classify_vote(rec, "K000389") assert cls["d_pos"] == "Split" assert cls["r_pos"] == "Yea" assert cls["alignment"] == "Helped Republicans" # And aggregate must not credit Khanna a "with-dem" or "against-dem" # because d_pos is Split (no party position to compare against). kpi = analyze.aggregate([rec], "K000389", "D", "house") assert kpi["voted_with_dem"] == 0 assert kpi["voted_against_dem"] == 0 assert kpi["voted_with_gop"] == 1 # ---------- Failed-blocking tests ---------- def test_failed_blocking_democrat_senate(): rec = _senate("failed_blocking_senate.xml", session=1, vnum=910) # D-Yea / R-Nay / Failed. Manchin (D) voted Nay → blocked="Democrat". cls = analyze.classify_vote(rec, "S307") assert cls["d_pos"] == "Yea" assert cls["r_pos"] == "Nay" assert cls["blocked"] == "Democrat" # Cruz (R) Nay also gets blocked="Democrat" (he helped block Dem-backed bill) assert analyze.classify_vote(rec, "S288")["blocked"] == "Democrat" # Schumer (D) Yea → no block (he supported it) assert analyze.classify_vote(rec, "S168")["blocked"] is None def test_failed_blocking_republican_senate(): rec = _senate("failed_blocking_rep_senate.xml", session=1, vnum=911) # R-Yea / D-Nay / Rejected. Collins (R) Nay → blocked="Republican". assert analyze.classify_vote(rec, "S289")["blocked"] == "Republican" # Warren (D) Nay → blocked="Republican" (Dem helped block GOP-backed bill) assert analyze.classify_vote(rec, "S366")["blocked"] == "Republican" # Cruz (R) Yea → no block assert analyze.classify_vote(rec, "S288")["blocked"] is None def test_failed_blocking_house_analog(): rec = _house("failed_blocking_house.xml", year=2025, roll=920) # House R-Yea / D-Nay / Failed. Confirms classifier is chamber-agnostic. assert analyze.classify_vote(rec, "D000032")["blocked"] == "Republican" assert analyze.classify_vote(rec, "K000389")["blocked"] == "Republican" # ---------- Aggregate end-to-end ---------- def test_aggregate_end_to_end_house(): recs = [ _house("partisan_house.xml", year=2025, roll=900), _house("bipartisan_house.xml", year=2025, roll=901), _house("failed_blocking_house.xml", year=2025, roll=920), _house("split_party_house.xml", year=2025, roll=930), ] # Khanna (D) — votes: Nay (partisan), Aye(=Yea) (bipartisan), # Nay (failed-blocking-house), Yea (split). All 4 are voting (no Present/NV). kpi = analyze.aggregate(recs, "K000389", "D", "house") assert kpi["chamber"] == "house" assert kpi["total"] == 4 assert kpi["voting"] == 4 assert kpi["yeas"] == 2 # bipartisan Aye→Yea, split Yea assert kpi["nays"] == 2 # partisan Nay, failed-blocking-house Nay assert kpi["present"] == 0 assert kpi["nv"] == 0 # voted_with_gop: vote matched R majority position (excluding R-Split votes). # partisan: R=Yea, Khanna=Nay → against. bipartisan: R=Yea, Khanna=Yea → with. # failed-blocking-house: R=Yea, Khanna=Nay → against. split: R=Yea, Khanna=Yea → with. assert kpi["voted_with_gop"] == 2 assert kpi["voted_against_gop"] == 2 # blocked counter: failed_blocking_house gives blocked="Republican" for Khanna assert kpi["blocked_rep_count"] == 1 assert kpi["blocked_dem_count"] == 0 assert kpi["lone_wolf_threshold"] == 5 # alignment counts sum to total assert sum(kpi["alignment"].values()) == 4 def test_lone_wolf_threshold_chamber_dependent(): # partisan_house: R yea=210, R nay=4. Donalds (R) voted Nay → defector. # defectors == 4. House threshold 5 → lone wolf. Senate threshold 3 → NOT. rec = _house("partisan_house.xml") house_kpi = analyze.aggregate([rec], "D000032", "R", "house") assert house_kpi["lone_wolf"] == 1 assert house_kpi["lone_wolf_threshold"] == 5 senate_kpi = analyze.aggregate([rec], "D000032", "R", "senate") assert senate_kpi["lone_wolf"] == 0 assert senate_kpi["lone_wolf_threshold"] == 3