Files
OpenBB/openbb_platform/providers/ecb/tests/test_table_builder.py
Danglewood 840658d01f [V5] openbb-ecb: Refactor for V5 and Expand Coverage (#7560)
* refactor openbb-ecb for v5 and expand coverage

* some updates

* tests

* workflow

* remove 3.15 from test matrix

* some more table schema updates

* remove some html chrome on embedded articles

---------

Co-authored-by: deeleeramone <>
2026-09-23 11:57:59 -07:00

913 lines
27 KiB
Python

import asyncio
from openbb_core.app.model.abstract.error import OpenBBError
from openbb_ecb.utils import query_builder
from openbb_ecb.utils.table_builder import (
_area_index,
_batch_keys,
_blank,
_jdf_units,
_or_key,
_row_unit,
_scale,
_segments,
_short_unit,
build_presentation_table,
publication_frequencies,
)
_NB = chr(0xA0)
def test_scale():
assert _scale(9.0, "6") == 9_000_000.0
assert _scale(5.0, "0") == 5.0
assert _scale(5.0, None) == 5.0
assert _scale(5.0, "") == 5.0
assert _scale(6.0, "bad") == 6.0
assert _scale(None, "6") is None
def test_or_key():
assert _or_key(["M.A.1", "M.B.1", "Q.A.1"]) == "M+Q.A+B.1"
assert _or_key(["X.Y"]) == "X.Y"
assert _or_key(["Q.W1.X"], 1) == "Q..X"
def test_blank():
assert _blank("A.B.C", 1) == "A..C"
assert _blank("A.B", 5) == "A.B"
def test_batch_keys():
homogeneous = [f"M.U2.A{i}.EUR" for i in range(10)]
assert _batch_keys(homogeneous) == [homogeneous]
many = [f"M.U2.A{i}.EUR" for i in range(30)]
assert [len(b) for b in _batch_keys(many)] == [25, 5]
diverse = [f"F{i}.R{i}.I{i}.U{i}" for i in range(4)]
assert [len(b) for b in _batch_keys(diverse, cap=8)] == [1, 1, 1, 1]
assert _batch_keys([]) == []
def test_segments():
assert _segments("Monetary aggregate M3, Stocks") == [
"Monetary aggregate M3",
"Stocks",
]
assert _segments("HICP - Total - Annual rate") == ["HICP", "Total", "Annual rate"]
assert _segments("") == []
def test_short_unit():
assert _short_unit("EUR", "Euro") == "Euro"
assert _short_unit("XG", "Domestic currency; ratio to gross domestic product") == (
"% of GDP"
)
assert _short_unit("XDC", "Domestic currency (incl. conversion)") == (
"Domestic currency"
)
assert _short_unit("LONG", "A very long unit label that exceeds the width cap") == (
"LONG"
)
assert _short_unit("", "") == ""
class _FakeMeta:
def __init__(self, table, dsd=None, dsd_error=False, codelists=None):
self._table = table
self._dsd = dsd
self._dsd_error = dsd_error
self._codelists = codelists or {}
def get_table(self, table_id):
return self._table
def get_codelist(self, codelist_id):
return self._codelists.get(codelist_id, {"EUR": "Euro"})
def get_dsd_for_dataflow(self, flow):
if self._dsd_error:
raise OpenBBError("no dsd")
return self._dsd or {"dimensions": []}
def test_area_index():
dsd = {"dimensions": [{"id": "FREQ"}, {"id": "COUNTERPART_AREA"}]}
assert _area_index(_FakeMeta({}, dsd=dsd), "X") == 1
assert _area_index(_FakeMeta({}, dsd={"dimensions": [{"id": "FREQ"}]}), "X") is None
assert _area_index(_FakeMeta({}, dsd_error=True), "X") is None
_DB = {
"AAA": [
{
"series_key": "a.1",
"TITLE": "Monetary aggregate M1, Stocks",
"TITLE_COMPL": "M1 stocks full definition",
"UNIT": "EUR",
"UNIT_MULT": "6",
"OBS_VALUE": 9.0,
"date": "2024-03-01",
},
{
"series_key": "a.1",
"UNIT": "EUR",
"UNIT_MULT": "6",
"OBS_VALUE": 8.0,
"date": "2024-02-01",
},
{
"series_key": "a.1",
"OBS_VALUE": None,
"date": None,
},
{
"series_key": "a.2",
"TITLE": "Monetary aggregate M1, Transactions",
"UNIT": "EUR",
"UNIT_MULT": "6",
"OBS_VALUE": 2.0,
"date": "2024-03-01",
},
{
"series_key": "a.3",
"TITLE": "Monetary aggregate M2, Stocks",
"UNIT": "EUR",
"OBS_VALUE": 5.0,
"date": "2024-03-01",
},
{
"series_key": "a.5",
"TITLE": "Reserve assets",
"UNIT": "EUR",
"UNIT_MULT": "0",
"OBS_VALUE": 3.0,
"date": "2024-03-01",
},
{
"series_key": "a.5",
"OBS_VALUE": 2.5,
"date": "2024-01-01",
},
{
"series_key": "a.6",
"TITLE": "",
"UNIT": "EUR",
"OBS_VALUE": 7.0,
"date": "2024-03-01",
},
{
"series_key": "a.9",
"TITLE": "Not wanted",
"OBS_VALUE": 1.0,
"date": "2024-03-01",
},
{
"OBS_VALUE": 1.0,
"date": "2024-03-01",
},
],
"GEO": [
{
"series_key": "Q.US.X",
"TITLE": "Direct investment with the United States, assets, stocks",
"UNIT": "EUR",
"UNIT_MULT": "0",
"OBS_VALUE": 11.0,
"date": "2024-03-01",
},
{
"series_key": "Q.GB.X",
"TITLE": "Direct investment with the United Kingdom, assets, stocks",
"UNIT": "EUR",
"UNIT_MULT": "0",
"OBS_VALUE": 12.0,
"date": "2024-03-01",
},
{
"series_key": "Q.W1.X",
"TITLE": "Direct investment, assets, stocks",
"UNIT": "EUR",
"UNIT_MULT": "0",
"OBS_VALUE": 13.0,
"date": "2024-03-01",
},
{
"series_key": "Q.US.Y",
"TITLE": "Other, series",
"OBS_VALUE": 99.0,
"date": "2024-03-01",
},
],
"GEO2": [
{
"series_key": "Q.US.X",
"TITLE": "Total, stocks",
"UNIT": "EUR",
"OBS_VALUE": 21.0,
"date": "2024-03-01",
},
{
"series_key": "Q.GB.X",
"TITLE": "Total with Britain, stocks",
"UNIT": "EUR",
"OBS_VALUE": 22.0,
"date": "2024-03-01",
},
],
"FRQ": [
{
"series_key": "M.R1",
"TITLE": "Euribor 3-month, Historical close, Monthly",
"UNIT": "PCPA",
"OBS_VALUE": 2.34,
"date": "2024-03-01",
},
{
"series_key": "M.R2",
"TITLE": "Euribor 6-month, Historical close, Monthly",
"UNIT": "PCPA",
"OBS_VALUE": 2.6,
"date": "2024-03-01",
},
{
"series_key": "A.R1",
"TITLE": "Euribor 3-month, Historical close, Annual",
"UNIT": "PCPA",
"OBS_VALUE": 2.2,
"date": "2024-01-01",
},
{
"series_key": "A.R2",
"TITLE": "Euribor 6-month, Historical close, Annual",
"UNIT": "PCPA",
"OBS_VALUE": 2.21,
"date": "2024-01-01",
},
],
"LMX": [
{
"series_key": "Q.AT.U",
"TITLE": "",
"FREQ": "Q",
"REF_AREA": "AT",
"MEASURE": "U",
"UNIT": "PS",
"OBS_VALUE": 10.0,
"date": "2024-03-01",
},
{
"series_key": "Q.BE.U",
"TITLE": "",
"FREQ": "Q",
"REF_AREA": "BE",
"MEASURE": "U",
"UNIT": "PS",
"OBS_VALUE": 20.0,
"date": "2024-03-01",
},
{
"series_key": "Q.AT.R",
"TITLE": "",
"FREQ": "Q",
"REF_AREA": "AT",
"MEASURE": "R",
"UNIT": "PC",
"OBS_VALUE": 3.1,
"date": "2024-03-01",
},
{
"series_key": "Q.BE.R",
"TITLE": "",
"FREQ": "Q",
"REF_AREA": "BE",
"MEASURE": "R",
"UNIT": "PC",
"OBS_VALUE": 4.2,
"date": "2024-03-01",
},
],
"MIX": [
{
"series_key": "Q.AT.U",
"TITLE": "Employment, Known",
"FREQ": "Q",
"REF_AREA": "AT",
"MEASURE": "U",
"UNIT": "PS",
"OBS_VALUE": 5.0,
"date": "2024-03-01",
},
{
"series_key": "Q.BE.R",
"TITLE": "",
"FREQ": "Q",
"REF_AREA": "BE",
"MEASURE": "R",
"UNIT": "PC",
"OBS_VALUE": 4.2,
"date": "2024-03-01",
},
],
"SC": [
{
"series_key": "s.1",
"TITLE": "Alpha, Shared",
"UNIT": "EUR",
"OBS_VALUE": 1.0,
"date": "2024-03-01",
},
{
"series_key": "s.2",
"TITLE": "Shared",
"UNIT": "EUR",
"OBS_VALUE": 2.0,
"date": "2024-03-01",
},
],
"ENT": [
{
"series_key": "M.AT.D",
"TITLE": "Government debt, percent of GDP",
"FREQ": "M",
"REF_AREA": "AT",
"ITEM": "D",
"UNIT": "XG",
"OBS_VALUE": 0.4,
"date": "2024-03-01",
},
{
"series_key": "M.BE.D",
"TITLE": "Government debt, percent of GDP",
"FREQ": "M",
"REF_AREA": "BE",
"ITEM": "D",
"UNIT": "XG",
"OBS_VALUE": 0.5,
"date": "2024-03-01",
},
],
}
def _make_fetch(calls):
async def _fetch(flow_ref, key, **kwargs):
calls.append((flow_ref, key, kwargs.get("last_n")))
return list(_DB.get(flow_ref, []))
return _fetch
def _table(rows, title="Monetary aggregates"):
return {"title": title, "subcategory": "", "rows": rows}
def test_build_presentation_table(monkeypatch):
calls: list = []
monkeypatch.setattr(query_builder, "fetch_sdmx_data_csv", _make_fetch(calls))
rows = [
{"flow": "AAA", "key": "a.1"},
{"flow": "AAA", "key": "a.1"},
{"flow": "AAA", "key": "a.2"},
{"flow": "AAA", "key": "a.3"},
{"flow": "AAA", "key": "a.4"},
{"flow": "AAA", "key": "a.5"},
{"flow": "AAA", "key": "a.6"},
]
result = asyncio.run(
build_presentation_table(_FakeMeta(_table(rows)), "T", use_cache=False, limit=2)
)
assert all(last_n == 2 for _, _, last_n in calls)
date_cols = [k for k in result[0] if k not in ("title", "description", "unit")]
assert date_cols == ["2024-03-01", "2024-02-01"]
titles = [r["title"] for r in result]
assert "Monetary aggregate M1" in titles
assert "Monetary aggregate M2, Stocks" in titles
assert _NB * 4 + "Stocks" in titles
assert _NB * 4 + "Transactions" in titles
assert "Reserve assets" in titles
by_title = {r["title"]: r for r in result}
assert by_title["Monetary aggregate M1"]["2024-03-01"] is None
assert by_title[_NB * 4 + "Stocks"]["2024-03-01"] == 9_000_000.0
assert by_title["Monetary aggregate M2, Stocks"]["2024-03-01"] == 5.0
assert by_title[_NB * 4 + "Stocks"]["unit"] == "Euro"
assert by_title[_NB * 4 + "Transactions"]["description"] == (
"Monetary aggregate M1, Transactions"
)
assert "M1 stocks full definition" in {r["description"] for r in result}
assert "a.6" in titles
assert by_title["a.6"]["description"] == "a.6"
assert "Not wanted" not in titles
assert by_title["Reserve assets"]["2024-03-01"] == 3.0
assert by_title["Reserve assets"]["2024-02-01"] == 2.5
assert by_title["a.6"]["2024-02-01"] is None
def test_build_presentation_table_order(monkeypatch):
monkeypatch.setattr(query_builder, "fetch_sdmx_data_csv", _make_fetch([]))
rows = [{"flow": "AAA", "key": "a.3"}, {"flow": "AAA", "key": "a.1"}]
result = asyncio.run(
build_presentation_table(_FakeMeta(_table(rows)), "T", use_cache=False, limit=2)
)
assert [r["title"] for r in result] == [
"Monetary aggregate M2",
"Monetary aggregate M1",
]
def test_build_presentation_table_geographical(monkeypatch):
calls: list = []
monkeypatch.setattr(query_builder, "fetch_sdmx_data_csv", _make_fetch(calls))
dsd = {
"dimensions": [
{"id": "FREQ"},
{"id": "COUNTERPART_AREA", "codelist_id": "CL_AREA"},
{"id": "REF_SECTOR"},
]
}
codelists = {"CL_AREA": {"US": "United States", "GB": "United Kingdom"}}
table = _table(
[{"flow": "GEO", "key": "Q.W1.X"}],
title="Financial account (Geographical breakdown)",
)
result = asyncio.run(
build_presentation_table(
_FakeMeta(table, dsd=dsd, codelists=codelists), "G", use_cache=False
)
)
assert calls[0][1] == "Q..X"
assert [r["title"] for r in result] == [
"Direct investment",
_NB * 4 + "assets",
_NB * 8 + "stocks",
_NB * 12 + "United Kingdom",
_NB * 12 + "United States",
]
assert result[2]["2024-03-01"] == 13.0
assert result[3]["2024-03-01"] == 12.0
assert result[4]["2024-03-01"] == 11.0
def test_build_presentation_table_geographical_no_aggregate(monkeypatch):
monkeypatch.setattr(query_builder, "fetch_sdmx_data_csv", _make_fetch([]))
dsd = {
"dimensions": [
{"id": "FREQ"},
{"id": "COUNTERPART_AREA"},
{"id": "REF_SECTOR"},
]
}
table = _table(
[{"flow": "GEO2", "key": "Q.W1.X"}],
title="Geographical breakdown",
)
result = asyncio.run(
build_presentation_table(_FakeMeta(table, dsd=dsd), "G2", use_cache=False)
)
assert [r["title"] for r in result] == [
"Total",
_NB * 4 + "stocks",
_NB * 8 + "GB",
]
assert result[1]["2024-03-01"] == 21.0
assert result[2]["2024-03-01"] == 22.0
def test_build_presentation_table_entities(monkeypatch):
monkeypatch.setattr(query_builder, "fetch_sdmx_data_csv", _make_fetch([]))
dsd = {
"dimensions": [
{"id": "FREQ"},
{"id": "REF_AREA", "codelist_id": "CL_AREA"},
{"id": "ITEM"},
{},
]
}
codelists = {
"CL_AREA": {"AT": "Austria", "BE": "Belgium"},
"CL_UNIT": {"XG": "Domestic currency; ratio to gross domestic product"},
}
rows = [{"flow": "ENT", "key": "M.AT.D"}, {"flow": "ENT", "key": "M.BE.D"}]
result = asyncio.run(
build_presentation_table(
_FakeMeta(_table(rows), dsd=dsd, codelists=codelists),
"E",
use_cache=False,
)
)
assert [r["title"] for r in result] == [
"Government debt",
_NB * 4 + "percent of GDP",
_NB * 8 + "Austria",
_NB * 8 + "Belgium",
]
assert result[2]["2024-03-01"] == 0.4
assert result[3]["2024-03-01"] == 0.5
assert result[2]["unit"] == "% of GDP"
def test_build_presentation_table_entities_key_fallback(monkeypatch):
monkeypatch.setattr(query_builder, "fetch_sdmx_data_csv", _make_fetch([]))
rows = [{"flow": "ENT", "key": "M.AT.D"}, {"flow": "ENT", "key": "M.BE.D"}]
result = asyncio.run(
build_presentation_table(
_FakeMeta(_table(rows), dsd_error=True), "E", use_cache=False
)
)
assert [r["title"].strip() for r in result] == [
"Government debt",
"percent of GDP",
"AT",
"BE",
]
_FRQ_DSD = {"dimensions": [{"id": "FREQ"}, {"id": "ITEM"}]}
_FRQ_ROWS = [
{"flow": "FRQ", "key": "M.R1"},
{"flow": "FRQ", "key": "M.R2"},
{"flow": "FRQ", "key": "A.R1"},
{"flow": "FRQ", "key": "A.R2"},
]
def test_build_presentation_table_frequency(monkeypatch):
monkeypatch.setattr(query_builder, "fetch_sdmx_data_csv", _make_fetch([]))
meta = _FakeMeta(_table(_FRQ_ROWS), dsd=_FRQ_DSD)
result = asyncio.run(build_presentation_table(meta, "T", use_cache=False))
assert [r["title"] for r in result] == ["Euribor 3-month", "Euribor 6-month"]
assert result[0]["2024-03-01"] == 2.34
assert result[1]["2024-03-01"] == 2.60
annual = asyncio.run(
build_presentation_table(meta, "T", use_cache=False, context={"FREQ": "A"})
)
assert [r["title"] for r in annual] == ["Euribor 3-month", "Euribor 6-month"]
assert annual[0]["2024-01-01"] == 2.2
assert annual[1]["2024-01-01"] == 2.21
unknown = asyncio.run(
build_presentation_table(meta, "T", use_cache=False, context={"FREQ": "X"})
)
assert unknown[0]["2024-03-01"] == 2.34
def test_publication_frequencies():
meta = _FakeMeta(_table(_FRQ_ROWS), dsd=_FRQ_DSD)
assert publication_frequencies(meta, "T") == ["M", "A"]
assert publication_frequencies(_FakeMeta(_table(_FRQ_ROWS)), "T") == []
late = {"dimensions": [{"id": "ITEM"}, {"id": "FREQ"}]}
rows = [{"flow": "FRQ", "key": "R1.M"}]
assert publication_frequencies(_FakeMeta(_table(rows), dsd=late), "T") == ["M"]
def test_build_presentation_table_entity_hierarchy(monkeypatch):
monkeypatch.setattr(query_builder, "fetch_sdmx_data_csv", _make_fetch([]))
dsd = {
"dimensions": [
{"id": "FREQ"},
{"id": "REF_AREA", "codelist_id": "CL_AREA"},
{"id": "MEASURE", "codelist_id": "CL_MEAS"},
]
}
codelists = {
"CL_AREA": {"AT": "Austria", "BE": "Belgium"},
"CL_MEAS": {"U": "Unemployment", "R": "Unemployment rate"},
}
rows = [
{"flow": "LMX", "key": "Q.AT.U"},
{"flow": "LMX", "key": "Q.BE.U"},
{"flow": "LMX", "key": "Q.AT.R"},
{"flow": "LMX", "key": "Q.BE.R"},
]
result = asyncio.run(
build_presentation_table(
_FakeMeta(_table(rows), dsd=dsd, codelists=codelists),
"L",
use_cache=False,
)
)
assert [r["title"] for r in result] == [
"Unemployment",
_NB * 4 + "Austria",
_NB * 4 + "Belgium",
"Unemployment rate",
_NB * 4 + "Austria",
_NB * 4 + "Belgium",
]
assert result[1]["2024-03-01"] == 10.0
assert result[2]["2024-03-01"] == 20.0
assert result[4]["2024-03-01"] == 3.1
assert result[5]["2024-03-01"] == 4.2
assert result[1]["description"] == "Unemployment, Austria"
def test_build_presentation_table_untitled_single(monkeypatch):
monkeypatch.setattr(query_builder, "fetch_sdmx_data_csv", _make_fetch([]))
dsd = {
"dimensions": [
{"id": "FREQ"},
{"id": "REF_AREA", "codelist_id": "CL_AREA"},
{"id": "MEASURE", "codelist_id": "CL_MEAS"},
]
}
codelists = {
"CL_AREA": {"AT": "Austria", "BE": "Belgium"},
"CL_MEAS": {"U": "Employment", "R": "Unemployment rate"},
}
rows = [{"flow": "MIX", "key": "Q.AT.U"}, {"flow": "MIX", "key": "Q.BE.R"}]
result = asyncio.run(
build_presentation_table(
_FakeMeta(_table(rows), dsd=dsd, codelists=codelists),
"M",
use_cache=False,
)
)
titles = [r["title"] for r in result]
assert "Employment, Known" in titles
assert "Unemployment rate, Belgium" in titles
assert not any("Q.BE.R" in t for t in titles)
solo = asyncio.run(
build_presentation_table(
_FakeMeta(_table([{"flow": "MIX", "key": "Q.BE.R"}]), dsd=dsd),
"M",
use_cache=False,
)
)
assert [r["title"] for r in solo] == ["Q.BE.R"]
def test_build_presentation_table_strip_fallback(monkeypatch):
monkeypatch.setattr(query_builder, "fetch_sdmx_data_csv", _make_fetch([]))
rows = [{"flow": "SC", "key": "s.1"}, {"flow": "SC", "key": "s.2"}]
result = asyncio.run(
build_presentation_table(_FakeMeta(_table(rows)), "T", use_cache=False)
)
assert [r["title"] for r in result] == ["Alpha", "Shared"]
def test_build_presentation_table_batches(monkeypatch):
calls: list = []
monkeypatch.setattr(query_builder, "fetch_sdmx_data_csv", _make_fetch(calls))
rows = [{"flow": "AAA", "key": f"a.{i}"} for i in range(30)]
asyncio.run(build_presentation_table(_FakeMeta(_table(rows)), "T", use_cache=False))
assert len(calls) == 2
def test_build_presentation_table_empty():
assert asyncio.run(build_presentation_table(_FakeMeta(_table([])), "T")) == []
class _UnitMeta:
def __init__(self, dsd, error=False):
self._dsd = dsd
self._error = error
def get_dsd_for_dataflow(self, dataflow_id):
if self._error:
raise OpenBBError("no dsd")
return self._dsd
def get_codelist(self, codelist_id):
return {"EUR": "Euro", "G1": "Growth rate, period on period"}
def test_jdf_units():
assert _jdf_units(_UnitMeta(None, error=True), "J", ["FREQ"]) == {}
dsd = {
"dimensions": [
{"id": "FREQ"},
{"id": "UNIT_MEASURE", "codelist_id": "CL_UNIT"},
{"id": "TRANSFORMATION"},
]
}
units = _jdf_units(_UnitMeta(dsd), "J", ["FREQ", "UNIT_MEASURE", "TRANSFORMATION"])
assert units["UNIT_MEASURE"][0] == 1
assert units["TRANSFORMATION"][0] == 2
assert _jdf_units(_UnitMeta(dsd), "J", ["FREQ"]) == {}
def test_row_unit():
units = {
"TRANSFORMATION": (2, {"G1": "Growth rate, period on period"}),
"UNIT_MEASURE": (1, {"EUR": "Euro"}),
}
assert _row_unit(None, units) == ""
assert _row_unit("M.EUR.G1", units) == "Growth rate, period on period"
assert _row_unit("M.EUR.N", units) == "Euro"
assert _row_unit("M.EUR._Z", units) == "Euro"
assert _row_unit("M.XX.N", {"UNIT": (1, {"XX": "Index"})}) == "Index"
assert _row_unit("M..N", {"UNIT_MEASURE": (1, {})}) == ""
assert _row_unit("M", units) == ""
class _FakeJdfMeta:
def __init__(self, structure, table, dims, unit_dsd=None):
self._structure = structure
self._table = table
self._dims = dims
self._unit_dsd = unit_dsd
def get_table(self, table_id):
return self._table
def get_table_structure(self, table_id):
return self._structure
def get_table_default_context(self, table_id):
return dict(self._table.get("default_context") or {})
def get_dataflow_dimensions(self, dataflow_id):
return self._dims
def get_codelist(self, codelist_id):
return {"EUR": "Euro", "G1": "Growth rate, period on period"}
def get_dsd_for_dataflow(self, dataflow_id):
if self._unit_dsd is None:
raise OpenBBError("no dsd")
return self._unit_dsd
def _make_sdmx(records, calls=None):
async def _fetch(flow, key, **kwargs):
if calls is not None:
calls.append(key)
return list(records)
return _fetch
_JDF_STRUCTURE = [
{
"label": "New business",
"code": "1",
"codelist_id": "JDF_ROW_LABELS",
"dimension_id": None,
"children": [
{
"label": "Loans",
"code": "A20",
"codelist_id": "CL_BS_ITEM",
"dimension_id": "BS_ITEM",
"children": [],
},
{
"label": "Deposits",
"code": "A21",
"codelist_id": "CL_BS_ITEM",
"dimension_id": "BS_ITEM",
"children": [],
},
],
}
]
_JDF_DIMS = [
{"id": "FREQ"},
{"id": "BS_ITEM"},
{"id": "REF_AREA"},
{"id": "UNIT_MEASURE"},
{"id": "TRANSFORMATION"},
]
def test_build_jdf_table(monkeypatch):
records = [
{
"FREQ": "M",
"BS_ITEM": "A20",
"REF_AREA": "DE",
"UNIT_MEASURE": "EUR",
"TRANSFORMATION": "N",
"TITLE_COMPL": "Loans to euro area residents reported by MFIs",
"OBS_VALUE": 5.0,
"date": "2024-02-01",
},
{
"FREQ": "M",
"BS_ITEM": "A20",
"REF_AREA": "DE",
"UNIT_MEASURE": "EUR",
"TRANSFORMATION": "N",
"OBS_VALUE": 6.0,
"date": "2024-01-01",
},
{
"FREQ": "M",
"BS_ITEM": "A20",
"REF_AREA": "DE",
"UNIT_MEASURE": "EUR",
"TRANSFORMATION": "N",
"OBS_VALUE": None,
"date": "2024-03-01",
},
{
"FREQ": "M",
"BS_ITEM": "A21",
"REF_AREA": "DE",
"UNIT_MEASURE": "EUR",
"TRANSFORMATION": "G1",
"TITLE": "Deposit growth",
"UNIT_MULT": "3",
"OBS_VALUE": 1.5,
"date": "2024-02-01",
},
{
"FREQ": "M",
"BS_ITEM": "ZZZ",
"REF_AREA": "DE",
"UNIT_MEASURE": "EUR",
"TRANSFORMATION": "N",
"OBS_VALUE": 9.0,
"date": "2024-02-01",
},
]
calls: list = []
monkeypatch.setattr(
query_builder, "fetch_sdmx_data_csv", _make_sdmx(records, calls)
)
table = {
"source": "jdf",
"dataflow_id": "J",
"default_context": {"FREQ": "M", "REF_AREA": "U2"},
"leaf_keys": [None, "M.A20.U2.EUR.N", "M.A21.U2.EUR.G1"],
}
unit_dsd = {
"dimensions": [
{"id": "UNIT_MEASURE", "codelist_id": "CL_UNIT"},
{"id": "TRANSFORMATION", "codelist_id": "CL_TRANS"},
]
}
meta = _FakeJdfMeta(_JDF_STRUCTURE, table, _JDF_DIMS, unit_dsd)
rows = asyncio.run(
build_presentation_table(
meta, "J", use_cache=False, limit=4, context={"FREQ": "M", "REF_AREA": "DE"}
)
)
assert calls == ["M.A20+A21.DE.EUR.G1+N"]
by = {r["title"].strip(): r for r in rows}
assert by["New business"]["2024-02-01"] is None
assert by["New business"]["unit"] == ""
assert by["New business"]["description"] == "New business"
assert by["Loans"]["2024-02-01"] == 5.0
assert by["Loans"]["2024-01-01"] == 6.0
assert by["Loans"]["unit"] == "Euro"
assert by["Loans"]["title"].startswith(_NB * 4)
assert by["Loans"]["description"] == "Loans to euro area residents reported by MFIs"
assert by["Deposits"]["2024-02-01"] == 1500.0
assert by["Deposits"]["2024-01-01"] is None
assert by["Deposits"]["unit"] == "Growth rate, period on period"
assert by["Deposits"]["description"] == "Deposit growth"
def test_build_jdf_table_unit_attribute(monkeypatch):
records = [
{
"FREQ": "M",
"BS_ITEM": "A20",
"UNIT": "EUR",
"OBS_VALUE": 7.0,
"date": "2024-02-01",
}
]
monkeypatch.setattr(query_builder, "fetch_sdmx_data_csv", _make_sdmx(records))
structure = [
{
"label": "Loans",
"code": "A20",
"codelist_id": "CL_BS_ITEM",
"dimension_id": "BS_ITEM",
"children": [],
}
]
table = {
"source": "jdf",
"dataflow_id": "J",
"default_context": {"FREQ": "M"},
"leaf_keys": ["M.A20"],
}
dims = [{"id": "FREQ"}, {"id": "BS_ITEM"}]
meta = _FakeJdfMeta(structure, table, dims, unit_dsd={"dimensions": []})
rows = asyncio.run(build_presentation_table(meta, "J", use_cache=False))
assert rows[0]["unit"] == "Euro"
assert rows[0]["2024-02-01"] == 7.0
def test_build_jdf_table_unresolved(monkeypatch):
monkeypatch.setattr(query_builder, "fetch_sdmx_data_csv", _make_sdmx([]))
table = {"source": "jdf", "dataflow_id": "J", "default_context": {"FREQ": "M"}}
meta = _FakeJdfMeta(_JDF_STRUCTURE, table, _JDF_DIMS)
rows = asyncio.run(build_presentation_table(meta, "J", use_cache=False))
assert len(rows) == 3
assert all(
r.get(key) is None
for r in rows
for key in r
if key not in ("title", "description", "unit")
)