feat(analysis): fetch real MP metadata, fix anchor axis for party-level actors

- fetch_mp_metadata: use real OData URL with pagination (1200 records, 5 pages)
  uses Fractie.Afkorting not NaamNL for abbreviation matching
  skips Verwijderd=true records
- upsert_mp_metadata: keep most recent membership (prefer active over ended,
  then higher Van date) so current party affiliations are not overwritten by historical
- compute_anchor_axis: anchor directly on party-level SVD entities (GroenLinks-PvdA etc)
  before falling back to mp_metadata individual MP lookup
- test_fetch_mp_metadata: fix mock for timeout kwarg + pagination + Afkorting field
- Generated anchor axis HTML for 2025-Q2 through 2026-Q1 in outputs/
This commit is contained in:
2026-03-21 23:33:47 +01:00
parent 5ad83ef1be
commit aa2f66ac9f
8 changed files with 152 additions and 63 deletions
+15 -13
View File
@@ -87,22 +87,24 @@ def compute_anchor_axis(
if not mp_vecs:
return {}
# Load party affiliation for this window from mp_metadata
left_set = set(left_parties)
right_set = set(right_parties)
# 1. Party-level actors whose entity_id IS a party name (e.g. "GroenLinks-PvdA")
left_vecs = [mp_vecs[p] for p in left_set if p in mp_vecs]
right_vecs = [mp_vecs[p] for p in right_set if p in mp_vecs]
# 2. Individual MPs via mp_metadata party affiliation
conn = duckdb.connect(db_path)
rows = conn.execute("SELECT mp_name, party FROM mp_metadata").fetchall()
conn.close()
party_of = {mp: party for mp, party in rows}
left_vecs = [
mp_vecs[mp]
for mp, party in party_of.items()
if party in left_parties and mp in mp_vecs
]
right_vecs = [
mp_vecs[mp]
for mp, party in party_of.items()
if party in right_parties and mp in mp_vecs
]
for mp_name, party in rows:
if mp_name not in mp_vecs:
continue
if party in left_set and mp_name not in left_set:
left_vecs.append(mp_vecs[mp_name])
elif party in right_set and mp_name not in right_set:
right_vecs.append(mp_vecs[mp_name])
if not left_vecs or not right_vecs:
_logger.warning(