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
+25 -10
View File
@@ -28,44 +28,58 @@ class MockResponse:
class MockSession:
def __init__(self, response):
self._response = response
"""Session mock that returns a data page on first call and empty page on second."""
def get(self, url):
return self._response
def __init__(self, data_page):
self._pages = [data_page, {"value": []}]
self._call = 0
def get(self, url, **kwargs):
resp = MockResponse(self._pages[min(self._call, len(self._pages) - 1)])
self._call += 1
return resp
def test_fetch_mp_metadata_idempotent(tmp_path, monkeypatch):
# Prepare canned OData response with two FractieZetelPersoon records
# Prepare canned OData response with two FractieZetelPersoon records.
# Use Afkorting (not NaamNL) because fetch_mp_metadata prefers Afkorting.
data = {
"value": [
{
"Verwijderd": False,
"Persoon": {
"Achternaam": "Yesilgöz-Zegerius",
"Initialen": "D.",
"Tussenvoegsel": None,
"Id": "guid-1",
},
"FractieZetel": {"Fractie": {"NaamNL": "VVD"}},
"FractieZetel": {
"Fractie": {
"Afkorting": "VVD",
"NaamNL": "Volkspartij voor Vrijheid en Democratie",
}
},
"Van": "2023-01-01",
"TotEnMet": None,
},
{
"Verwijderd": False,
"Persoon": {
"Achternaam": "Plas",
"Initialen": "C.",
"Tussenvoegsel": "van der",
"Id": "guid-2",
},
"FractieZetel": {"Fractie": {"NaamNL": "BBB"}},
"FractieZetel": {
"Fractie": {"Afkorting": "BBB", "NaamNL": "BoerBurgerBeweging"}
},
"Van": "2023-06-01",
"TotEnMet": "2024-01-01",
},
]
}
mock_resp = MockResponse(data)
mock_session = MockSession(mock_resp)
mock_session = MockSession(data)
# Patch requests.Session to return our mock session
monkeypatch.setattr(requests, "Session", lambda: mock_session)
@@ -98,6 +112,7 @@ def test_fetch_mp_metadata_idempotent(tmp_path, monkeypatch):
assert rows[1][3] == None
assert rows[1][4] == "guid-1"
# Run again to assert idempotence (no exception and same count processed)
# Run again to assert idempotence: same records processed, DB unchanged
monkeypatch.setattr(requests, "Session", lambda: MockSession(data))
count2 = fetch_mp_metadata(db_path=db_path, odata_url="http://example/odata")
assert count2 == 2