SPB Git forge

spb/countryatlas

Public
20commits 1branches 0releases
268.3 MBsize
maindefault branch
12 days agolast push
TypeScript 57% Python 38.6% JavaScript 3.6% CSS 0.6%
6.5 KB · 127 lines python
Raw Blame History
1"""Eurostat JSON-stat connector — recorded fixtures (une_rt_a with EL + EU27_2020; rd_e_gerdtot with `p` flags)."""2from __future__ import annotations34import json5from datetime import date67import pytest89from countryatlas.connectors._util import ConnectorError10from countryatlas.connectors.eurostat import EurostatConnector, decode_jsonstat11from countryatlas.models import IndicatorSourceSpec, RawPayload121314def _raw(fixtures_dir, name: str, dataset: str, code: str, retrieved_at) -> RawPayload:15    body = (fixtures_dir / "eurostat" / name).read_bytes()16    doc = json.loads(body)17    return RawPayload(connector="eurostat", dataset=dataset, code=code, url="fixture://eurostat", retrieved_at=retrieved_at,18                      status_code=200, content_type="application/json", body=body, meta={"updated": doc.get("updated")})192021@pytest.fixture22def es() -> EurostatConnector:23    return EurostatConnector()242526def test_decoder_sparse_row_major():27    doc = {28        "id": ["freq", "geo", "time"], "size": [1, 2, 3],29        "dimension": {30            "freq": {"category": {"index": {"A": 0}}},31            "geo": {"category": {"index": {"DE": 0, "EL": 1}}},32            "time": {"category": {"index": {"2022": 0, "2023": 1, "2024": 2}}},33        },34        "value": {"0": 1.0, "2": 3.0, "4": 5.0},35        "status": {"4": "p"},36    }37    cells = decode_jsonstat(doc)38    assert [(c["dims"]["geo"], c["dims"]["time"], c["value"], c["status"]) for c in cells] == [39        ("DE", "2022", 1.0, None), ("DE", "2024", 3.0, None), ("EL", "2023", 5.0, "p"),40    ]414243def test_unemployment_maps_el_and_drops_eu_aggregate(es, fixtures_dir, retrieved_at):44    spec = IndicatorSourceSpec(indicator_id="unemployment-rate", connector="eurostat", dataset="une_rt_a", code="Y15-74.PC_ACT.T",45                               params={"age": "Y15-74", "unit": "PC_ACT", "sex": "T"}, priority=3)46    rows = es.normalize(_raw(fixtures_dir, "une_rt_a_sample.json", "une_rt_a", spec.code, retrieved_at), spec)47    assert {r.country_id for r in rows} == {"DEU", "GRC"}, "EL → GRC, EU27_2020 dropped"48    grc = sorted((r for r in rows if r.country_id == "GRC"), key=lambda r: r.year)49    assert [r.year for r in grc] == [2022, 2023, 2024, 2025]50    assert grc[0].period == date(2022, 1, 1) and grc[0].frequency == "A"51    assert grc[0].value == pytest.approx(12.5)52    assert grc[0].unit == "% of labour force"53    assert grc[0].source_updated_at is not None and grc[0].source_updated_at.year == 202654    assert grc[0].metadata["filters"] == {"age": "Y15-74", "unit": "PC_ACT", "sex": "T"}55    assert es.validate(rows).errors == 0565758def test_status_flags_mark_estimates(es, fixtures_dir, retrieved_at):59    spec = IndicatorSourceSpec(indicator_id="rd-expenditure-pct-gdp", connector="eurostat", dataset="rd_e_gerdtot",60                               code="TOTAL.PC_GDP", params={"sectperf": "TOTAL", "unit": "PC_GDP"}, priority=3)61    rows = es.normalize(_raw(fixtures_dir, "rd_e_gerdtot_sample.json", "rd_e_gerdtot", spec.code, retrieved_at), spec)62    assert {r.country_id for r in rows} == {"DEU", "FRA"}63    flagged = [r for r in rows if r.is_estimate]64    assert len(flagged) == 2 and all(r.year == 2024 for r in flagged) and all(r.metadata["flags"] == "p" for r in flagged)65    assert not any(r.is_forecast for r in rows)66    deu = {r.year: r.value for r in rows if r.country_id == "DEU"}67    assert deu == {2021: pytest.approx(3.07), 2022: pytest.approx(3.04), 2023: pytest.approx(3.13), 2024: pytest.approx(3.13)}686970def test_forecast_flag_and_quarterly_time(es, retrieved_at):71    doc = {72        "id": ["freq", "unit", "geo", "time"], "size": [1, 1, 2, 2], "updated": "2026-07-02T11:00:00+0200",73        "dimension": {74            "freq": {"category": {"index": {"Q": 0}}},75            "unit": {"category": {"index": {"PC": 0}}},76            "geo": {"category": {"index": {"UK": 0, "FR": 1}}},77            "time": {"category": {"index": {"2025-Q4": 0, "2026-Q1": 1}}},78        },79        "value": {"0": 1.5, "1": 1.7, "2": 2.0, "3": 2.2},80        "status": {"3": "f", "1": "bp"},81    }82    raw = RawPayload(connector="eurostat", dataset="x_q", code="PC", url="fixture://", retrieved_at=retrieved_at,83                     status_code=200, body=json.dumps(doc).encode())84    spec = IndicatorSourceSpec(indicator_id="inflation", connector="eurostat", dataset="x_q", code="PC", params={"unit": "PC"})85    rows = es.normalize(raw, spec)86    by = {(r.country_id, r.period): r for r in rows}87    assert set(by) == {("GBR", date(2025, 10, 1)), ("GBR", date(2026, 1, 1)), ("FRA", date(2025, 10, 1)), ("FRA", date(2026, 1, 1))}88    assert all(r.frequency == "Q" for r in rows)89    assert by[("FRA", date(2026, 1, 1))].is_forecast is True90    assert by[("GBR", date(2026, 1, 1))].is_estimate is True and by[("GBR", date(2026, 1, 1))].metadata["flags"] == "bp"919293def test_ambiguous_dimension_raises(es, fixtures_dir, retrieved_at):94    body = json.loads((fixtures_dir / "eurostat" / "une_rt_a_sample.json").read_text())95    # pretend the sex dimension was not filtered: 2 categories with the same cell count96    body["id"] = ["freq", "age", "unit", "sex", "geo", "time"]97    body["size"] = [1, 1, 1, 2, 3, len(body["dimension"]["time"]["category"]["index"])]98    body["dimension"]["sex"]["category"]["index"] = {"T": 0, "M": 1}99    raw = RawPayload(connector="eurostat", dataset="une_rt_a", code="x", url="fixture://", retrieved_at=retrieved_at,100                     status_code=200, body=json.dumps(body).encode())101    spec = IndicatorSourceSpec(indicator_id="unemployment-rate", connector="eurostat", dataset="une_rt_a", code="x")102    with pytest.raises(ConnectorError, match="sex="):103        es.normalize(raw, spec)104105106def test_registry_specs_for_eurostat():107    from countryatlas import registry108109    specs = registry.source_specs("eurostat")110    by_ind = {s.indicator_id: s for s in specs}111    assert {"employment-rate", "median-household-income", "at-risk-of-poverty-rate", "homeownership-rate",112            "housing-cost-overburden-rate", "inflation"} <= set(by_ind)113    for s in specs:114        assert s.dataset and s.params, s.indicator_id115        assert "geo" not in s.params and "time" not in s.params116117118@pytest.mark.live119def test_live_small_query():120    es = EurostatConnector()121    spec = IndicatorSourceSpec(indicator_id="unemployment-rate", connector="eurostat", dataset="une_rt_a", code="Y15-74.PC_ACT.T",122                               params={"age": "Y15-74", "unit": "PC_ACT", "sex": "T", "geo": ["DE", "EL"], "sinceTimePeriod": 2023})123    raw = es.fetch(spec)124    rows = es.normalize(raw, spec)125    assert {r.country_id for r in rows} == {"DEU", "GRC"}126    es.close()127