docs: update CHANGELOG, CLAUDE.md, and comments for location_profiles
Update transform CLAUDE.md source integration map and conformed dimensions table. Update CHANGELOG with unified model + tooltip changes. Fix stale comments in dim_cities.sql and serving README. Subtask 5/5: documentation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **Unified `location_profiles` serving model** — merged `city_market_profile` and `location_opportunity_profile` into a single `serving.location_profiles` table at `(country_code, geoname_id)` grain. Both Marktreife-Score (Market Score) and Marktpotenzial-Score (Opportunity Score) are now computed per location. City data enriched via LEFT JOIN `dim_cities` on `geoname_id`. Downstream models (`planner_defaults`, `pseo_city_costs_de`, `pseo_city_pricing`) updated to query `location_profiles` directly. `city_padel_venue_count` (exact from dim_cities) distinguished from `padel_venue_count` (spatial 5km from dim_locations).
|
||||||
|
- **Both scores on all map tooltips** — country map shows avg Market Score + avg Opportunity Score; city map shows Market Score + Opportunity Score per city; opportunity map shows Opportunity Score + Market Score per location. All score labels use the trademarked "Padelnomics Market Score" / "Padelnomics Opportunity Score" names.
|
||||||
|
- **API endpoints** — `/api/markets/countries.json` adds `avg_opportunity_score`; `/api/markets/<country>/cities.json` adds `opportunity_score`; `/api/opportunity/<country>.json` adds `market_score`.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- **Custom 404/500 error pages** — styled error pages extending `base.html` with i18n support (EN/DE). The 404 page is context-aware: when the URL matches `/markets/{country}/{city}`, it shows a city-specific message with a link back to the country overview instead of a generic "page not found".
|
- **Custom 404/500 error pages** — styled error pages extending `base.html` with i18n support (EN/DE). The 404 page is context-aware: when the URL matches `/markets/{country}/{city}`, it shows a city-specific message with a link back to the country overview instead of a generic "page not found".
|
||||||
- **Map: city article indicators** — country overview map bubbles now differentiate cities with/without published articles. All cities retain score-based colors (green/amber/red); non-article cities are visually receded with lower opacity, dashed borders, desaturated color, and default cursor (no click). Tooltips show scores for all cities — article cities get "Click to explore →", non-article cities get "Coming soon". The `/api/markets/<country>/cities.json` endpoint includes a `has_article` boolean per city.
|
- **Map: city article indicators** — country overview map bubbles now differentiate cities with/without published articles. All cities retain score-based colors (green/amber/red); non-article cities are visually receded with lower opacity, dashed borders, desaturated color, and default cursor (no click). Tooltips show scores for all cities — article cities get "Click to explore →", non-article cities get "Coming soon". The `/api/markets/<country>/cities.json` endpoint includes a `has_article` boolean per city.
|
||||||
|
|||||||
@@ -56,27 +56,27 @@ Grain must match reality — use `QUALIFY ROW_NUMBER()` to enforce it.
|
|||||||
|-----------|-------|---------|
|
|-----------|-------|---------|
|
||||||
| `foundation.dim_countries` | `country_code` | `dim_cities`, `dim_locations`, `pseo_city_costs_de`, `planner_defaults` — single source for country names, income, PLI/cost overrides |
|
| `foundation.dim_countries` | `country_code` | `dim_cities`, `dim_locations`, `pseo_city_costs_de`, `planner_defaults` — single source for country names, income, PLI/cost overrides |
|
||||||
| `foundation.dim_venues` | `venue_id` | `dim_cities`, `dim_venue_capacity`, `fct_daily_availability` (via capacity join) |
|
| `foundation.dim_venues` | `venue_id` | `dim_cities`, `dim_venue_capacity`, `fct_daily_availability` (via capacity join) |
|
||||||
| `foundation.dim_cities` | `(country_code, city_slug)` | `serving.city_market_profile` → all pSEO serving models |
|
| `foundation.dim_cities` | `(country_code, city_slug)` | `serving.location_profiles` (city_slug + city_padel_venue_count) → all pSEO serving models |
|
||||||
| `foundation.dim_locations` | `(country_code, geoname_id)` | `serving.location_opportunity_profile` — all GeoNames locations (pop ≥1K), incl. zero-court locations |
|
| `foundation.dim_locations` | `(country_code, geoname_id)` | `serving.location_profiles` — all GeoNames locations (pop ≥1K), incl. zero-court locations |
|
||||||
| `foundation.dim_venue_capacity` | `tenant_id` | `foundation.fct_daily_availability` |
|
| `foundation.dim_venue_capacity` | `tenant_id` | `foundation.fct_daily_availability` |
|
||||||
|
|
||||||
## Source integration map
|
## Source integration map
|
||||||
|
|
||||||
```
|
```
|
||||||
stg_playtomic_venues ─┐
|
stg_playtomic_venues ─┐
|
||||||
stg_playtomic_resources─┤→ dim_venues ─┬→ dim_cities ──────────────→ city_market_profile
|
stg_playtomic_resources─┤→ dim_venues ─┬→ dim_cities ──┐
|
||||||
stg_padel_courts ─┘ └→ dim_venue_capacity (Marktreife-Score)
|
stg_padel_courts ─┘ └→ dim_venue_capacity
|
||||||
↓
|
│
|
||||||
stg_playtomic_availability ──→ fct_availability_slot ──→ fct_daily_availability
|
stg_playtomic_availability ──→ fct_availability_slot ──→ fct_daily_availability
|
||||||
↓
|
↓
|
||||||
venue_pricing_benchmarks
|
venue_pricing_benchmarks
|
||||||
↓
|
↓
|
||||||
stg_population ──→ dim_cities ─────────────────────────────┘
|
stg_population ──→ dim_cities ─────────────────────────────┘
|
||||||
stg_income ──→ dim_cities
|
stg_income ──→ dim_cities │
|
||||||
|
↓
|
||||||
stg_population_geonames ─┐
|
stg_population_geonames ─┐ location_profiles
|
||||||
stg_padel_courts ─┤→ dim_locations ──→ location_opportunity_profile
|
stg_padel_courts ─┤→ dim_locations ────────→ (both scores:
|
||||||
stg_tennis_courts ─┤ (Marktpotenzial-Score)
|
stg_tennis_courts ─┤ Marktreife + Marktpotenzial)
|
||||||
stg_income ─┘
|
stg_income ─┘
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
-- Built from venue locations (dim_venues) as the primary source — padelnomics
|
-- Built from venue locations (dim_venues) as the primary source — padelnomics
|
||||||
-- tracks cities where padel venues actually exist, not an administrative city list.
|
-- tracks cities where padel venues actually exist, not an administrative city list.
|
||||||
--
|
--
|
||||||
-- Conformed dimension: used by city_market_profile and all pSEO serving models.
|
-- Conformed dimension: used by location_profiles and all pSEO serving models.
|
||||||
-- Integrates four sources:
|
-- Integrates four sources:
|
||||||
-- dim_venues → city list, venue count, coordinates (Playtomic + OSM)
|
-- dim_venues → city list, venue count, coordinates (Playtomic + OSM)
|
||||||
-- foundation.dim_countries → country_name_en, country_slug, median_income_pps
|
-- foundation.dim_countries → country_name_en, country_slug, median_income_pps
|
||||||
@@ -128,7 +128,7 @@ SELECT
|
|||||||
vc.padel_venue_count,
|
vc.padel_venue_count,
|
||||||
c.median_income_pps,
|
c.median_income_pps,
|
||||||
c.income_year,
|
c.income_year,
|
||||||
-- GeoNames ID: FK to dim_locations / location_opportunity_profile.
|
-- GeoNames ID: FK to dim_locations / location_profiles.
|
||||||
-- String match preferred; spatial fallback used when name doesn't match (Milano→Milan, etc.)
|
-- String match preferred; spatial fallback used when name doesn't match (Milano→Milan, etc.)
|
||||||
COALESCE(gn.geoname_id, gs.spatial_geoname_id) AS geoname_id
|
COALESCE(gn.geoname_id, gs.spatial_geoname_id) AS geoname_id
|
||||||
FROM venue_cities vc
|
FROM venue_cities vc
|
||||||
|
|||||||
@@ -3,4 +3,4 @@
|
|||||||
Analytics-ready views consumed by the web app and programmatic SEO.
|
Analytics-ready views consumed by the web app and programmatic SEO.
|
||||||
Query these from `analytics.py` via DuckDB read-only connection.
|
Query these from `analytics.py` via DuckDB read-only connection.
|
||||||
|
|
||||||
Naming convention: `serving.<purpose>` (e.g. `serving.city_market_profile`)
|
Naming convention: `serving.<purpose>` (e.g. `serving.location_profiles`)
|
||||||
|
|||||||
Reference in New Issue
Block a user