docs: CHANGELOG + PROJECT.md for score recalibration (market_score v3 + opportunity_score v2)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Deeman
2026-02-27 06:58:48 +01:00
parent 10266c3a24
commit 721b2a37df
2 changed files with 12 additions and 1 deletions

View File

@@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased]
### Changed
- **Market Score v3 (Marktreife-Score recalibration)** — fixes ranking inversion where early-stage markets (Germany 1/100k) outscored mature markets (Spain 36/100k):
- **Formula rewrite** (`city_market_profile.sql`): supply development now 40 pts (log-scaled density LN(d+1)/LN(21) × count gate min(1,count/5)); demand evidence 25 pts (occupancy or 40% density proxy); population reduced to 15 pts (context); income to 10 pts (context); data quality to 10 pts; saturation discount removed
- **Count gate** eliminates small-town inflation: a single venue in a 5k-resident town can no longer outscore Berlin (was 92.7 → now 43.9 for Bernau bei Berlin)
- **LN ceiling at 20/100k** (was linear 4/100k) gives meaningful differentiation from 0 to 20: Málaga 70.1, Barcelona 67.4, Madrid 66.9, Amsterdam 58.4, Berlin 42.2, London 44.1
- **Template thresholds updated** across all 3 pSEO templates (city-cost-de, country-overview, city-pricing): color coding green ≥55 (was ≥65) / amber ≥35 (was ≥40); intro/FAQ tiers strong ≥55 (was ≥70) / mid ≥35 (was ≥45); white-space signal interplay market_score < 40 (was < 50)
- **Opportunity Score supply gap ceiling raised 4→8/100k** (`location_opportunity_profile.sql`) — gentler gradient for partially-served markets; accounts for ~87% data undercount vs FIP real-world totals. Documents discovered formula behaviour: DuckDB `LEAST(1.0, NULL)=1.0` means NULL catchment already yields full 15 pts; income PPS saturates for all EU countries; tennis courts data currently empty (formula correct, data pending)
### Added
- **Opportunity Score integration** — second scoring dimension (`Marktpotenzial`) now visible in city and country articles:
- **SQL chain**: `dim_cities` now carries `geoname_id` (from the existing GeoNames LEFT JOIN); threaded through `city_market_profile``pseo_city_costs_de` which LEFT JOINs `location_opportunity_profile` on `(country_code, geoname_id)`; `pseo_country_overview` gains `avg_opportunity_score`, `top_opportunity_score`, `top_opportunity_slugs`, `top_opportunity_names`

View File

@@ -1,7 +1,7 @@
# Padelnomics — Project Tracker
> Move tasks across columns as you work. Add new tasks at the top of the relevant column.
> Last updated: 2026-02-25.
> Last updated: 2026-02-27.
---
@@ -87,6 +87,8 @@
- [x] Markets hub (`/<lang>/markets`) — article listing with FTS + country/region filters
- [x] DuckDB refresh script (`refresh_from_daas.py`)
- [x] **Opportunity Score integration**`opportunity_score` (Marktpotenzial) wired into city + country templates; `geoname_id` threaded through SQL chain (dim_cities → city_market_profile → pseo_city_costs_de); 71.4% city match rate; stats strip, intro paragraphs, market tables, and FAQ updated in both DE + EN
- [x] **Market Score v3 recalibration** — fixes ranking inversion (Germany 1/100k was outscoring Spain 36/100k); log-scaled density + count gate replaces linear formula; saturation discount removed; template thresholds updated across all 3 pSEO templates; verified: Málaga 70.1, Barcelona 67.4, Madrid 66.9, Amsterdam 58.4, Bernau 43.9 (was 92.7), Berlin 42.2, London 44.1
- [x] **Opportunity Score v2** — supply gap ceiling raised 4→8/100k (gentler gradient, accounts for 87% data undercount); formula documentation added (DuckDB LEAST NULL behaviour, income saturation, tennis data gap)
### Data Pipeline (DaaS)
- [x] Overpass API extractor (OSM padel courts)