diff --git a/backend/data_loader.py b/backend/data_loader.py index 7f039d8..e3aec2f 100644 --- a/backend/data_loader.py +++ b/backend/data_loader.py @@ -339,7 +339,11 @@ _GIAS_CODE_COLUMN_NAMES = ( "admissions_policy_code", ) -_MISSING_COLUMN_RE = re.compile(r'column "?(?:s\.)?(\w+)"? does not exist') +# Strip any single table alias prefix (s., l., p., foi., …) — Postgres reports +# an undefined *qualified* column unquoted as "column l.county does not exist", +# so matching only the s. alias would miss dim_location columns (county, +# parliamentary_constituency) and defeat the graceful-degradation fallback. +_MISSING_COLUMN_RE = re.compile(r'column "?(?:\w+\.)?(\w+)"? does not exist') def _missing_column_name(exc: Exception) -> Optional[str]: diff --git a/backend/tests/test_gias_translation.py b/backend/tests/test_gias_translation.py index 414b8e4..9b57a10 100644 --- a/backend/tests/test_gias_translation.py +++ b/backend/tests/test_gias_translation.py @@ -73,6 +73,24 @@ def test_missing_column_name_table_prefixed(): ) +def test_missing_column_name_location_alias_prefixed(): + # dim_location columns are selected via the `l.` alias; Postgres reports a + # missing qualified column unquoted (e.g. "column l.county does not exist"). + # The matcher must strip any alias, not just `s.`, or the county / + # parliamentary_constituency fallback never triggers and the whole data + # load degrades to an empty DataFrame (zero schools) instead of NULLs. + assert ( + _missing_column_name(_fake_exc("column l.county does not exist")) + == "county" + ) + assert ( + _missing_column_name( + _fake_exc("column l.parliamentary_constituency does not exist") + ) + == "parliamentary_constituency" + ) + + def test_missing_column_name_no_match_returns_none(): assert _missing_column_name(_fake_exc("relation \"marts.dim_school\" does not exist")) is None