feat(11-02): implement NUTS-1 to Bundesland derivation
bundeslandFromRegion() derives one of the 16 Bundesland names from a region's NUTS-1 (3-char) prefix; nutsPrefixFor() reverses a Bundesland name back to its prefix for the query builder. Null-safe throughout — 7/7 tests green, validated against real region samples from the live DB. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* NUTS-1 -> Bundesland derivation (FILTER-02, D-02, Pitfall 1).
|
||||||
|
*
|
||||||
|
* `Tender.bundesland` is 100% NULL in the live DB (0/1671 rows, verified)
|
||||||
|
* because the Phase-10 normalizer explicitly deferred NUTS->Bundesland
|
||||||
|
* mapping to this phase (`tender-normalizer.service.ts` previously set
|
||||||
|
* `bundesland = null` unconditionally). Without this derivation the
|
||||||
|
* Bundesland filter would return a constant zero result and look like a
|
||||||
|
* bug rather than an empty column.
|
||||||
|
*
|
||||||
|
* `region` holds a EU NUTS code (e.g. `DE212`); the first 3 characters
|
||||||
|
* (`DE` + one Bundesland-level digit/letter) form the stable NUTS-1
|
||||||
|
* region — the standardized, deterministic EU classification scheme,
|
||||||
|
* unlike a PLZ-range heuristic (PLZ zones cross Bundesland borders).
|
||||||
|
*
|
||||||
|
* Validated against real region samples pulled from the live DB
|
||||||
|
* (`docker exec tessera-ctl-db-1 psql`, Assumption A1):
|
||||||
|
* DE212 (60 rows) -> Bayern (Oberbayern)
|
||||||
|
* DE300 (56 rows) -> Berlin
|
||||||
|
* DE600 (38 rows) -> Hamburg
|
||||||
|
* DE712 (34 rows) -> Hessen (Darmstadt)
|
||||||
|
* DEA22 (24 rows) -> Nordrhein-Westfalen (Köln)
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const NUTS1_BUNDESLAND: Record<string, string> = {
|
||||||
|
DE1: 'Baden-Württemberg',
|
||||||
|
DE2: 'Bayern',
|
||||||
|
DE3: 'Berlin',
|
||||||
|
DE4: 'Brandenburg',
|
||||||
|
DE5: 'Bremen',
|
||||||
|
DE6: 'Hamburg',
|
||||||
|
DE7: 'Hessen',
|
||||||
|
DE8: 'Mecklenburg-Vorpommern',
|
||||||
|
DE9: 'Niedersachsen',
|
||||||
|
DEA: 'Nordrhein-Westfalen',
|
||||||
|
DEB: 'Rheinland-Pfalz',
|
||||||
|
DEC: 'Saarland',
|
||||||
|
DED: 'Sachsen',
|
||||||
|
DEE: 'Sachsen-Anhalt',
|
||||||
|
DEF: 'Schleswig-Holstein',
|
||||||
|
DEG: 'Thüringen',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derive the Bundesland name from a NUTS region code's NUTS-1 (3-char)
|
||||||
|
* prefix. Null-safe: null/undefined/empty region (17%+ of live rows) and
|
||||||
|
* unrecognized prefixes return `null` instead of throwing — never crash
|
||||||
|
* on the common missing-region case.
|
||||||
|
*/
|
||||||
|
export function bundeslandFromRegion(region?: string | null): string | null {
|
||||||
|
if (!region) return null;
|
||||||
|
const prefix = region.slice(0, 3).toUpperCase();
|
||||||
|
return NUTS1_BUNDESLAND[prefix] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse lookup: Bundesland display name -> NUTS-1 prefix, used by the
|
||||||
|
* query builder for `region: { startsWith: nutsPrefixFor(bundesland) }`
|
||||||
|
* as a fallback path (and available even independent of the backfilled
|
||||||
|
* `bundesland` column).
|
||||||
|
*/
|
||||||
|
export function nutsPrefixFor(bundesland: string): string | null {
|
||||||
|
for (const [prefix, name] of Object.entries(NUTS1_BUNDESLAND)) {
|
||||||
|
if (name === bundesland) return prefix;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user