From 00d2e3793462a5113012b5018ea91d200553d2bb Mon Sep 17 00:00:00 2001 From: Deeman Date: Mon, 9 Mar 2026 14:11:28 +0100 Subject: [PATCH] chore: tests, changelog, project docs (Phase G) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename test_market_score.py → test_padelnomics_score.py - Test 301 redirects from old /market-score URL - Update i18n parity allowlist (remove mscore_*, add pnscore brand terms) - Update CHANGELOG.md with single-score simplification - Update PROJECT.md: mark single-score done, fix location_profiles refs Co-Authored-By: Claude Opus 4.6 --- CHANGELOG.md | 8 ++++ PROJECT.md | 7 ++-- web/tests/test_i18n_parity.py | 8 ++-- ...ket_score.py => test_padelnomics_score.py} | 41 +++++++++++-------- 4 files changed, 39 insertions(+), 25 deletions(-) rename web/tests/{test_market_score.py => test_padelnomics_score.py} (54%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4355491..9ce7204 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +### Changed +- **Single-score simplification** — consolidated two public-facing scores (Market Score + Opportunity Score) into one **Padelnomics Score** (internally: `opportunity_score`). All maps, tooltips, article templates, and the methodology page now show a single score. Dual-ring markers reverted to single-color markers. `/market-score` route renamed to `/padelnomics-score` (old URL 301-redirects). All `mscore_*` i18n keys replaced with `pnscore_*`. Business plan queries `opportunity_score` from `location_profiles` (replaces legacy `city_market_overview` view). Map tooltip strings now i18n'd via `window.__MAP_T` (12 keys, EN + DE). + +### Fixed +- **Non-Latin city names on map** — GeoNames entries with CJK/Cyrillic/Arabic characters (e.g. "Seelow" showing Japanese) now filtered in `stg_population_geonames` via Latin-only regex. +- **Score range safety** — `location_profiles` clamps both scores to 0-100 via `LEAST/GREATEST`. +- **Pipeline cast fix** — `venue_pricing_benchmarks.sql` defensively casts `snapshot_date` VARCHAR to DATE. + ### Changed - **Dual-ring map markers** — map markers now encode two scores visually: inner core = primary score, outer ring = secondary score. Markets hub and country overview show Market Score (core) + Opportunity Score (ring). Opportunity map shows Opportunity Score (core) + Market Score (ring). City venue maps unchanged (navy dots). Color scale upgraded from 3-tier (green/amber/red) to 5-tier (deep green ≥80, teal ≥60, amber ≥40, orange-red ≥20, red <20) with distinct luminance at each tier for colorblind safety. Markers < 18px fall back to single-layer (no ring). Muted markers (cities without articles) show dashed ring outline. Highlighted markers (user's geo city) get blue outer glow. Opportunity map markers with score ≥75 pulse gently to highlight top investment targets. Tooltip lines now have inline color dots matching marker layers. All map scripts share a single `map-markers.js` module (`PNMarkers.scoreColor` + `PNMarkers.makeIcon`), replacing 3 duplicated implementations. diff --git a/PROJECT.md b/PROJECT.md index 7f11e10..62109c6 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -159,6 +159,7 @@ - [x] Feedback widget (HTMX POST, rate-limited) - [x] Interactive ROI calculator widget on landing page (JS sliders, no server call) - [x] **CRO overhaul — homepage + supplier landing pages** — JTBD-driven copy rewrite (feature → outcome framing), proof strip, struggling-moments sections, "Why Padelnomics" comparison, rewritten FAQ, conditional supplier stats, data-backed proof points, tier-specific CTAs (EN + DE) +- [x] **Single-score simplification** — consolidated Market Score + Opportunity Score into one public "Padelnomics Score" (`opportunity_score`). Single-color map markers, unified methodology page at `/padelnomics-score`, i18n'd map tooltips, updated pSEO templates + business plan. Non-Latin city name filter in pipeline. --- @@ -180,12 +181,12 @@ | Submit sitemap to Google Search Console | Set up Google Search Console + Bing Webmaster Tools (SEO hub ready — just add env vars) | | Verify Litestream R2 backup running on prod | | -### Gemeinde-level pSEO (follow-up from dual score work) +### Gemeinde-level pSEO (follow-up from single-score simplification) | 🛠 Tech | |--------| -| Gemeinde-level pSEO article template — consumes `location_opportunity_profile` data, targets "Padel in [Ort]" + "Padel bauen in [Ort]" queries (zero SERP competition confirmed) | -| "Top 50 underserved locations" ranking page — high-value SEO content, fully programmatic from `location_opportunity_profile` ORDER BY opportunity_score DESC | +| Gemeinde-level pSEO article template — consumes `location_profiles` data, targets "Padel in [Ort]" + "Padel bauen in [Ort]" queries (zero SERP competition confirmed) | +| "Top 50 underserved locations" ranking page — high-value SEO content, fully programmatic from `location_profiles` ORDER BY opportunity_score DESC | ### Week 1–2 — First Revenue diff --git a/web/tests/test_i18n_parity.py b/web/tests/test_i18n_parity.py index ba844cf..9b866cf 100644 --- a/web/tests/test_i18n_parity.py +++ b/web/tests/test_i18n_parity.py @@ -57,10 +57,10 @@ _IDENTICAL_VALUE_ALLOWLIST = { # Business plan — Indoor/Outdoor same in DE, financial abbreviations "bp_indoor", "bp_outdoor", "bp_lbl_ebitda", "bp_lbl_irr", "bp_lbl_moic", "bp_lbl_opex", - # Market Score — branded term kept in English in DE - "footer_market_score", - # Market Score chip labels — branded product names, same in DE - "mscore_reife_chip", "mscore_potenzial_chip", + # Padelnomics Score — branded term kept in English in DE + "footer_padelnomics_score", "bp_lbl_padelnomics_score", + # Map tooltip keys — some are identical in both languages + "map_score_label", "map_indoor", "map_outdoor", # Brand name "Padelnomics" — same in DE "landing_vs_col_us", } diff --git a/web/tests/test_market_score.py b/web/tests/test_padelnomics_score.py similarity index 54% rename from web/tests/test_market_score.py rename to web/tests/test_padelnomics_score.py index 2adf335..3642323 100644 --- a/web/tests/test_market_score.py +++ b/web/tests/test_padelnomics_score.py @@ -1,30 +1,36 @@ -"""Tests for the Market Score methodology page.""" +"""Tests for the Padelnomics Score methodology page.""" async def test_en_returns_200(client): - resp = await client.get("/en/market-score") + resp = await client.get("/en/padelnomics-score") assert resp.status_code == 200 text = await resp.get_data(as_text=True) - assert "Market Score" in text + assert "Padelnomics Score" in text assert "padelnomics" in text async def test_de_returns_200(client): - resp = await client.get("/de/market-score") + resp = await client.get("/de/padelnomics-score") assert resp.status_code == 200 text = await resp.get_data(as_text=True) - assert "Market Score" in text + assert "Padelnomics Score" in text assert "padelnomics" in text -async def test_legacy_redirect(client): - resp = await client.get("/market-score") +async def test_old_market_score_redirects(client): + resp = await client.get("/en/market-score") assert resp.status_code == 301 - assert resp.headers["Location"].endswith("/en/market-score") + assert "/padelnomics-score" in resp.headers["Location"] + + +async def test_de_old_market_score_redirects(client): + resp = await client.get("/de/market-score") + assert resp.status_code == 301 + assert "/padelnomics-score" in resp.headers["Location"] async def test_contains_jsonld(client): - resp = await client.get("/en/market-score") + resp = await client.get("/en/padelnomics-score") text = await resp.get_data(as_text=True) assert '"@type": "WebPage"' in text assert '"@type": "FAQPage"' in text @@ -32,28 +38,27 @@ async def test_contains_jsonld(client): async def test_contains_faq_section(client): - resp = await client.get("/en/market-score") + resp = await client.get("/en/padelnomics-score") text = await resp.get_data(as_text=True) assert "Frequently Asked Questions" in text assert "