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