feat(members): add full member bio, contact info, and service history

Lazy-enriches member profiles on first view via Congress.gov detail API.
Adds office address, phone, official website, congress.gov link, birth
year, terms history, leadership roles, and sponsored/cosponsored counts.
Includes DB migration 0007 for new member columns.

Co-Authored-By: Jack Levy
This commit is contained in:
Jack Levy
2026-03-01 00:14:16 -05:00
parent 37339d6950
commit e21eb21acf
7 changed files with 345 additions and 27 deletions

View File

@@ -103,7 +103,7 @@ def parse_bill_from_api(data: dict, congress: int) -> dict:
def parse_member_from_api(data: dict) -> dict:
"""Normalize raw API member data into our model fields."""
"""Normalize raw API member list data into our model fields."""
terms = data.get("terms", {}).get("item", [])
current_term = terms[-1] if terms else {}
return {
@@ -118,3 +118,50 @@ def parse_member_from_api(data: dict) -> dict:
"photo_url": data.get("depiction", {}).get("imageUrl"),
"official_url": data.get("officialWebsiteUrl"),
}
def parse_member_detail_from_api(data: dict) -> dict:
"""Normalize Congress.gov member detail response into enrichment fields."""
member = data.get("member", data)
addr = member.get("addressInformation") or {}
terms_raw = member.get("terms", [])
if isinstance(terms_raw, dict):
terms_raw = terms_raw.get("item", [])
leadership_raw = member.get("leadership") or []
if isinstance(leadership_raw, dict):
leadership_raw = leadership_raw.get("item", [])
first = member.get("firstName", "")
last = member.get("lastName", "")
bioguide_id = member.get("bioguideId", "")
slug = f"{first}-{last}".lower().replace(" ", "-").replace("'", "")
return {
"birth_year": str(member["birthYear"]) if member.get("birthYear") else None,
"address": addr.get("officeAddress"),
"phone": addr.get("phoneNumber"),
"official_url": member.get("officialWebsiteUrl"),
"photo_url": (member.get("depiction") or {}).get("imageUrl"),
"congress_url": f"https://www.congress.gov/member/{slug}/{bioguide_id}" if bioguide_id else None,
"terms_json": [
{
"congress": t.get("congress"),
"chamber": t.get("chamber"),
"partyName": t.get("partyName"),
"stateCode": t.get("stateCode"),
"stateName": t.get("stateName"),
"startYear": t.get("startYear"),
"endYear": t.get("endYear"),
"district": t.get("district"),
}
for t in terms_raw
],
"leadership_json": [
{
"type": l.get("type"),
"congress": l.get("congress"),
"current": l.get("current"),
}
for l in leadership_raw
],
"sponsored_count": (member.get("sponsoredLegislation") or {}).get("count"),
"cosponsored_count": (member.get("cosponsoredLegislation") or {}).get("count"),
}