From bb0c9e1bd2677d5acb6df8aedb5bb0785468e7ae Mon Sep 17 00:00:00 2001 From: bk86a <41694587+bk86a@users.noreply.github.com> Date: Tue, 3 Mar 2026 14:05:22 +0100 Subject: [PATCH] feat: add version metadata to postal_patterns.json (#34) Add _meta block (version, date) to postal_patterns.json and surface patterns_version in /health endpoint. Bump to v0.14.0. --- app/__init__.py | 2 +- app/main.py | 3 ++- app/models.py | 1 + app/postal_patterns.json | 1 + app/postal_patterns.py | 5 ++++- tests/test_api.py | 6 ++++++ 6 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index f23a6b3..9e78220 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1 +1 @@ -__version__ = "0.13.0" +__version__ = "0.14.0" diff --git a/app/main.py b/app/main.py index 828d650..2d7489f 100644 --- a/app/main.py +++ b/app/main.py @@ -32,7 +32,7 @@ normalize_country, ) from app.models import ErrorResponse, HealthResponse, NUTSResult, PatternResponse -from app.postal_patterns import POSTAL_PATTERNS +from app.postal_patterns import PATTERNS_META, POSTAL_PATTERNS logging.basicConfig( level=logging.INFO, @@ -266,6 +266,7 @@ def health(response: Response): total_nuts_names=len(get_nuts_names()), nuts_version=settings.nuts_version, extra_sources=get_extra_source_count(), + patterns_version=PATTERNS_META.get("version", "unknown"), data_stale=stale, last_updated=get_data_loaded_at(), ) diff --git a/app/models.py b/app/models.py index b4335e4..897cbef 100644 --- a/app/models.py +++ b/app/models.py @@ -37,6 +37,7 @@ class HealthResponse(BaseModel): nuts_version: str total_nuts_names: int = Field(default=0, description="Number of NUTS region names loaded") extra_sources: int = Field(default=0, description="Number of extra ZIP source URLs configured") + patterns_version: str = Field(description="Version of the postal_patterns.json file") data_stale: bool = Field(description="True if serving expired cache after a failed TERCET refresh") last_updated: str = Field( description="ISO 8601 timestamp of when TERCET data was last successfully loaded" diff --git a/app/postal_patterns.json b/app/postal_patterns.json index eaec6fd..4c8bc89 100644 --- a/app/postal_patterns.json +++ b/app/postal_patterns.json @@ -1,4 +1,5 @@ { + "_meta": { "version": "1.0", "date": "2026-03-03" }, "AT": { "regex": "^(?:A[\\s\\-\u2013\u2014.]*|AT[\\s\\-\u2013\u2014.]*)?([0-9]{4})$", "example": "1010, A-1010, AT-1010, A 1010, AT 1010", diff --git a/app/postal_patterns.py b/app/postal_patterns.py index cd7a003..31e5833 100644 --- a/app/postal_patterns.py +++ b/app/postal_patterns.py @@ -30,10 +30,13 @@ # Regexes are applied after .strip().upper() and are case-insensitive. _patterns_path = Path(__file__).parent / "postal_patterns.json" try: - POSTAL_PATTERNS: dict[str, dict] = json.loads(_patterns_path.read_text()) + _raw: dict[str, dict] = json.loads(_patterns_path.read_text()) except (json.JSONDecodeError, OSError) as _exc: raise SystemExit(f"Fatal: failed to load {_patterns_path}: {_exc}") from _exc +PATTERNS_META: dict[str, str] = _raw.pop("_meta", {}) +POSTAL_PATTERNS: dict[str, dict] = _raw + # Pre-compile all patterns for performance _COMPILED: dict[str, re.Pattern] = { cc: re.compile(pat["regex"], re.IGNORECASE) for cc, pat in POSTAL_PATTERNS.items() diff --git a/tests/test_api.py b/tests/test_api.py index 0c1ae53..b10ade4 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -97,6 +97,12 @@ def test_includes_estimates(self, client): assert "total_estimates" in data assert data["total_estimates"] >= 0 + def test_includes_patterns_version(self, client): + resp = client.get("/health") + data = resp.json() + assert "patterns_version" in data + assert data["patterns_version"] == "1.0" + def test_includes_nuts_names(self, client): resp = client.get("/health") data = resp.json()