Commit Graph

12 Commits

Author SHA1 Message Date
Deeman
adf6f0c1ef fix(score): country_supply uses dim_cities.padel_venue_count (not city_padel_venue_count)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 10:09:30 +01:00
Deeman
ff6401254a feat(score): Opportunity Score v8 — better spread/discrimination
Reweight: addressable market 20→15, economic power 15→10, supply deficit 40→50.
Supply deficit existence dampener (country_venues/50, floor 0.1): zero-venue
countries drop from ~80 to ~17. Steeper addressable market curve (LN/500K →
SQRT/1M). NULL distance gap → 0.0 (was 0.5). Added country_percentile output
column (PERCENT_RANK within country, 0–100).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 22:14:30 +01:00
Deeman
e39dd4ec0b fix(score): Opportunity Score v7 — calibration fix for saturated markets
Two fixes:
1. dim_locations now sources venues from dim_venues (deduplicated OSM + Playtomic)
   instead of stg_padel_courts (OSM only). Playtomic-only venues are no longer
   invisible to spatial lookups.
2. Country-level supply saturation dampener on supply deficit component.
   Saturated countries (Spain 7.4/100k) get dampened supply deficit (x0.30 → 12 pts max).
   Emerging markets (Germany 0.24/100k) nearly unaffected (x0.98 → ~39 pts).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 18:03:14 +01:00
Deeman
8e0dd6af63 fix(data): filter non-Latin city names + score range clamp (Phase F)
- stg_population_geonames: reject CJK/Cyrillic/Arabic city names via regex
  (fixes "Seelow" showing Japanese characters on map)
- dim_locations: filter empty location names after trim
- location_profiles: defensive LEAST/GREATEST clamp on both scores (0-100)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:23:50 +01:00
Deeman
67fbfde53d feat(scoring): Opportunity Score v5 → v6 — calibrate for saturated markets
- Lower density ceiling 8→5/100k (Spain at 6-16/100k now hits zero-gap)
- Increase supply deficit weight 35→40 pts (primary differentiator)
- Reduce addressable market 25→20 pts (less weight on population alone)
- Invert market validation → market headroom (high country maturity = less opportunity)

Target: Spain avg opportunity drops from ~78 to ~50-60 range.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 20:23:08 +01:00
Deeman
118c2c0fc7 feat(scoring): Opportunity Score v4 → v5 — fix correlated components
- Merge supply gap (30pts) + catchment gap (15pts) → supply deficit (35pts, GREATEST)
  Eliminates ~80% correlated double-count on a single signal.
- Add sports culture signal (10pts): tennis court density as racquet-sport adoption proxy.
  Ceiling 50 courts/25km. Harmless when tennis data is zero (contributes 0).
- Add construction affordability (5pts): income relative to PLI construction costs.
  Joins dim_countries.pli_construction. High income + low build cost = high score.
- Reduce economic power from 20 → 15pts to make room.

New weights: addressable market 25, economic power 15, supply deficit 35,
sports culture 10, construction affordability 5, market validation 10.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:30:04 +01:00
Deeman
cd6d950233 feat(scoring): Market Score v3 → v4 — fix Spain underscoring
- Lower count gate threshold: 5 → 3 venues (3 establishes a market pattern)
- Lower density ceiling: LN(21) → LN(11) (10/100k is reachable for mature markets)
- Better demand fallback: 0.4 → 0.65 multiplier + 0.3 floor (venues = demand evidence)
- Fix economic context: income/200 → income/25000 (actual discrimination vs free 10 pts)

Expected: Spain avg market score rises from ~54 to ~65-75.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 15:22:48 +01:00
Deeman
f215ea8e3a fix: supply gap inflation + inline map data + guard API endpoints
A. location_profiles.sql: supply gap now uses GREATEST(catchment_padel_courts,
   COALESCE(city_padel_venue_count, 0)) so Playtomic venues prevent cities like
   Murcia/Cordoba/Gijon from receiving a full 30-pt supply gap bonus when their
   OSM catchment count is zero. Expected ~10-15 pt drop for affected ES cities.

B. pseo_country_overview.sql: add population-weighted lat/lon centroid columns
   so the markets map can use accurate country positions from this table.

C/D. content/routes.py + markets.html: query pseo_country_overview in the route
   and pass as map_countries to the template, replacing the fetch('/api/...') call
   with inline JSON. Map scores now match pseo_country_overview (pop-weighted),
   and the page loads without an extra round-trip.

E. api.py: add @login_required to all 4 endpoints. Unauthenticated callers get
   a 302 redirect to login instead of data.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 20:33:31 +01:00
Deeman
544891611f feat(transform): opportunity score v4 — market validation + population-weighted aggregation
All checks were successful
CI / test (push) Successful in 57s
CI / tag (push) Successful in 2s
Two targeted fixes for inflated country scores (ES 83, SE 77):

1. pseo_country_overview: replace AVG() with population-weighted averages
   for avg_opportunity_score and avg_market_score. Madrid/Barcelona now
   dominate Spain's average instead of hundreds of 30K-town white-space
   towns. Expected ES drop from ~83 to ~55-65.

2. location_profiles: replace dead sports culture component (10 pts,
   tennis data all zeros) with market validation signal.
   Split scored CTE into: market_scored → country_market → scored.
   country_market aggregates AVG(market_score) per country from cities
   with padel courts (market_score > 0), so zero-court locations don't
   dilute the signal. ES (~60/100) → ~6 pts. SE (~35/100) → ~3.5 pts.
   NULL → 0.5 neutral → 5 pts (untested market, not penalised).

Score budget unchanged: 25+20+30+15+10 = 100 pts.
No new models, no new data sources, no cycles.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 17:23:11 +01:00
Deeman
77ec3a289f feat(transform): H3 catchment index, res 5 k_ring(1) ~24km radius
All checks were successful
CI / test (push) Successful in 54s
CI / tag (push) Successful in 3s
Merges worktree-h3-catchment-index. dim_locations now computes h3_cell_res5
(res 5, ~8.5km edge). location_profiles and dim_locations updated;
old location_opportunity_profile.sql already removed on master.

Conflict: location_opportunity_profile.sql deleted on master, kept deletion
and applied h3_cell_res4→res5 rename to location_profiles instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 14:45:45 +01:00
Deeman
4d29ecf1d6 merge: unified location_profiles serving model + both scores on map tooltips
All checks were successful
CI / test (push) Successful in 55s
CI / tag (push) Successful in 3s
# Conflicts:
#	CHANGELOG.md
#	transform/sqlmesh_padelnomics/models/serving/location_opportunity_profile.sql
2026-03-06 14:03:55 +01:00
Deeman
cda94c9ee4 feat(serving): add unified location_profiles model
Combines city_market_profile and location_opportunity_profile into a
single serving model at (country_code, geoname_id) grain. Both Market
Score and Opportunity Score computed per location. City data enriched
via LEFT JOIN dim_cities on geoname_id.

Subtask 1/5: create new model (old models not yet removed).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 11:36:36 +01:00