Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.13.0"
__version__ = "0.14.0"
3 changes: 2 additions & 1 deletion app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
)
1 change: 1 addition & 0 deletions app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions app/postal_patterns.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
5 changes: 4 additions & 1 deletion app/postal_patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
6 changes: 6 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Loading