diff --git a/transform/sqlmesh_padelnomics/models/serving/location_opportunity_profile.sql b/transform/sqlmesh_padelnomics/models/serving/location_opportunity_profile.sql index e848db0..1258c30 100644 --- a/transform/sqlmesh_padelnomics/models/serving/location_opportunity_profile.sql +++ b/transform/sqlmesh_padelnomics/models/serving/location_opportunity_profile.sql @@ -1,7 +1,7 @@ -- Per-location padel investment opportunity intelligence. -- Consumed by: Gemeinde-level pSEO pages, opportunity map, "top markets" lists. -- --- Padelnomics Marktpotenzial-Score (0–100): +-- Padelnomics Marktpotenzial-Score v2 (0–100): -- Answers "Where should I build a padel court?" -- Covers ALL GeoNames locations (pop ≥ 1K) — NOT filtered to existing padel markets. -- Zero-court locations score highest on supply gap component (white space = opportunity). @@ -9,9 +9,21 @@ -- 25 pts addressable market — log-scaled population, ceiling 500K -- (opportunity peaks in mid-size cities; megacities already served) -- 20 pts economic power — country income PPS, normalised to 200 --- 30 pts supply gap — INVERTED venue density; 0 courts/100K = full marks --- 15 pts catchment gap — distance to nearest padel court (>30km = full marks) --- 10 pts sports culture — tennis courts within 25km (≥10 = full marks) +-- NOTE: PPS values are country-level constants in the range +-- 18k-37k — ALL EU countries saturate this component (20/20). +-- Component is a flat uplift per country until city-level +-- income data becomes available. +-- 30 pts supply gap — INVERTED venue density; 0 courts/100K = full marks. +-- Ceiling raised to 8/100K (was 4) for a gentler gradient +-- and to account for ~87% data undercount vs FIP totals. +-- Linear: GREATEST(0, 1 - density/8) +-- 15 pts catchment gap — distance to nearest padel court. +-- DuckDB LEAST ignores NULLs: LEAST(1.0, NULL/30) = 1.0, +-- so NULL nearest_km = full marks (no court in bounding box +-- = high opportunity). COALESCE fallback is dead code. +-- 10 pts sports culture — tennis courts within 25km (≥10 = full marks). +-- NOTE: dim_locations tennis data is empty (all 0 rows). +-- Component contributes 0 pts everywhere until data lands. MODEL ( name serving.location_opportunity_profile, @@ -50,9 +62,11 @@ SELECT + 20.0 * LEAST(1.0, COALESCE(l.median_income_pps, 100) / 200.0) -- Supply gap (30 pts): INVERTED venue density. - -- 0 courts/100K = full 30 pts (white space); ≥4/100K = 0 pts (served market). + -- 0 courts/100K = full 30 pts (white space); ≥8/100K = 0 pts (served market). + -- Ceiling raised from 4→8/100K for a gentler gradient and to account for data + -- undercount (~87% of real courts not in our data). -- This is the key signal that separates Marktpotenzial from Marktreife. - + 30.0 * GREATEST(0.0, 1.0 - COALESCE(l.padel_venues_per_100k, 0) / 4.0) + + 30.0 * GREATEST(0.0, 1.0 - COALESCE(l.padel_venues_per_100k, 0) / 8.0) -- Catchment gap (15 pts): distance to nearest existing padel court. -- >30km = full 15 pts (underserved catchment area).