fix(content): slugify transliteration + article links + country overview ranking
- Add @slugify SQLMesh macro (STRIP_ACCENTS + ß→ss) replacing broken inline REGEXP_REPLACE that dropped non-ASCII chars (Düsseldorf → d-sseldorf) - Apply @slugify to dim_venues, dim_cities, dim_locations - Fix Python slugify() to pre-replace ß→ss before NFKD normalization - Add language prefix to B2B article market links (/markets/germany → /de/markets/germany) - Change country overview top-5 ranking: venue count (not raw market_score) for top cities, population for top opportunity cities Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -33,8 +33,7 @@ venue_cities AS (
|
||||
SELECT
|
||||
country_code,
|
||||
city AS city_name,
|
||||
-- Lowercase before regex so uppercase letters aren't stripped to '-'
|
||||
LOWER(REGEXP_REPLACE(LOWER(city), '[^a-z0-9]+', '-')) AS city_slug,
|
||||
@slugify(city) AS city_slug,
|
||||
COUNT(*) AS padel_venue_count,
|
||||
AVG(lat) AS centroid_lat,
|
||||
AVG(lon) AS centroid_lon
|
||||
|
||||
@@ -38,7 +38,7 @@ locations AS (
|
||||
geoname_id,
|
||||
city_name AS location_name,
|
||||
-- URL-safe location slug
|
||||
LOWER(REGEXP_REPLACE(LOWER(city_name), '[^a-z0-9]+', '-')) AS location_slug,
|
||||
@slugify(city_name) AS location_slug,
|
||||
country_code,
|
||||
lat,
|
||||
lon,
|
||||
|
||||
@@ -99,7 +99,7 @@ SELECT
|
||||
indoor_court_count,
|
||||
outdoor_court_count,
|
||||
-- Conformed city key: enables deterministic joins to dim_cities / venue_pricing_benchmarks
|
||||
LOWER(REGEXP_REPLACE(LOWER(COALESCE(city, '')), '[^a-z0-9]+', '-')) AS city_slug,
|
||||
@slugify(COALESCE(city, '')) AS city_slug,
|
||||
extracted_date
|
||||
FROM ranked
|
||||
QUALIFY ROW_NUMBER() OVER (
|
||||
|
||||
@@ -20,15 +20,15 @@ SELECT
|
||||
SUM(padel_venue_count) AS total_venues,
|
||||
ROUND(AVG(market_score), 1) AS avg_market_score,
|
||||
MAX(market_score) AS top_city_market_score,
|
||||
-- Top 5 cities by market score for internal linking (DuckDB list slice syntax)
|
||||
LIST(city_slug ORDER BY market_score DESC NULLS LAST)[1:5] AS top_city_slugs,
|
||||
LIST(city_name ORDER BY market_score DESC NULLS LAST)[1:5] AS top_city_names,
|
||||
-- Top 5 cities by venue count (prominence), then score for internal linking
|
||||
LIST(city_slug ORDER BY padel_venue_count DESC, market_score DESC NULLS LAST)[1:5] AS top_city_slugs,
|
||||
LIST(city_name ORDER BY padel_venue_count DESC, market_score DESC NULLS LAST)[1:5] AS top_city_names,
|
||||
-- Opportunity score aggregates (NULL-safe: cities without geoname_id match excluded from AVG)
|
||||
ROUND(AVG(opportunity_score), 1) AS avg_opportunity_score,
|
||||
MAX(opportunity_score) AS top_opportunity_score,
|
||||
-- Top 5 cities by opportunity score (may differ from top market score cities)
|
||||
LIST(city_slug ORDER BY opportunity_score DESC NULLS LAST)[1:5] AS top_opportunity_slugs,
|
||||
LIST(city_name ORDER BY opportunity_score DESC NULLS LAST)[1:5] AS top_opportunity_names,
|
||||
-- Top 5 opportunity cities by population (prominence), then opportunity score
|
||||
LIST(city_slug ORDER BY population DESC, opportunity_score DESC NULLS LAST)[1:5] AS top_opportunity_slugs,
|
||||
LIST(city_name ORDER BY population DESC, opportunity_score DESC NULLS LAST)[1:5] AS top_opportunity_names,
|
||||
-- Pricing medians across cities (NULL when no Playtomic coverage in country)
|
||||
ROUND(MEDIAN(median_hourly_rate), 0) AS median_hourly_rate,
|
||||
ROUND(MEDIAN(median_peak_rate), 0) AS median_peak_rate,
|
||||
|
||||
Reference in New Issue
Block a user