Skip to content

Commit 5e345a2

Browse files
agent-smith-ddpramonperezDDPclaude
authored
UT: fix votes not scraped for 2025+ sessions (API-rendered bills) (#5695)
* UT: fix votes not scraped for 2025+ sessions (API-rendered bills) Utah migrated to API-rendered bill pages around 2025. The new code path (`scrape_bill_details_from_api`) handled sponsors, versions, and actions but had a `# TODO vote processing` comment and never yielded any votes. Co-authored-by: Ramon Perez <ramon.perez@digitaldemocracyproject.org> Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
1 parent 1f8b635 commit 5e345a2

1 file changed

Lines changed: 25 additions & 3 deletions

File tree

scrapers/ut/bills.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,7 @@ def scrape_bill(self, chamber, session, url, session_slug):
130130
# starting 2025 seems UT is rendering bill data from an API/JSON
131131
# but prior years seem to have static-ish HTML
132132
# so we have two logic branches here
133-
# TODO vote processing - need to see what data looks like
134-
self.scrape_bill_details_from_api(bill, url, session_slug)
133+
yield from self.scrape_bill_details_from_api(bill, url, session_slug)
135134
else:
136135
yield from self.parse_bill_details_from_html(
137136
bill, bill_id, chamber, page, primary_info
@@ -377,6 +376,26 @@ def scrape_bill_details_from_api(self, bill: Bill, bill_url, session_slug: str):
377376
committee, entity_type="organization"
378377
)
379378

379+
# Recorded votes (voiceVote=="1" are voice votes with no individual records)
380+
if action_data.get("voteID") and action_data.get("voiceVote") == "0":
381+
vote_chamber = (
382+
"lower" if action_data["voteHouse"] == "H" else "upper"
383+
)
384+
vote_url = (
385+
f"https://le.utah.gov/DynaBill/svotes.jsp"
386+
f"?sessionid={session_slug}"
387+
f"&voteid={action_data['voteID']}"
388+
f"&house={action_data['voteHouse']}"
389+
)
390+
yield from self.parse_html_vote(
391+
bill,
392+
vote_chamber,
393+
date,
394+
action_data["description"],
395+
vote_url,
396+
f"{action_data['voteID']}-{action_data['voteHouse']}",
397+
)
398+
380399
def parse_status(self, bill, status_table, chamber):
381400
page = status_table
382401
uniqid = 0
@@ -531,7 +550,10 @@ def parse_html_vote(self, bill, actor, date, motion, url, uniqid):
531550
self.warning(descr)
532551
raise NotImplementedError("Can't see if we passed or failed")
533552

534-
headings = page.xpath("//b")[1:]
553+
# The "Yeas" heading uses <b><center><font ...> which lxml normalizes by
554+
# promoting <center> out of <b>, leaving the <b> empty. Select the size=5
555+
# Arial fonts directly — they appear in all three section headings.
556+
headings = page.xpath('//font[@face="Arial"][@size="5"]')
535557
votes = page.xpath("//table")
536558
sets = zip(headings, votes)
537559
vdict = {}

0 commit comments

Comments
 (0)