Addressable Market 20→15, Economic Power 15→10, Supply Deficit 40→50.
Update scaling description (LN/500K → SQRT/1M) and add existence
dampener explanation to supply deficit description.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add 12 map_* keys to EN and DE locale files
- Inject window.__MAP_T from templates that load map scripts
- article-maps.js already used __MAP_T with fallbacks (no change needed)
- markets.html and opportunity_map.html inline scripts now use T.*
- Admin preview templates get EN fallback __MAP_T
- Fix mkt_legend_color: "Market Score" → "Padelnomics Score"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rename /market-score → /padelnomics-score with 301 redirect
- Rewrite methodology page as single Padelnomics Score (pnscore_* keys)
- Replace all mscore_* i18n keys with pnscore_* in both EN and DE
- Business plan: query opportunity_score from location_profiles
- Footer link updated to padelnomics-score route
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase B: Remove dual-ring marker design (ring/core), replace with
single-color markers colored by opportunity_score ("Padelnomics Score").
Simplify CSS, update tooltips to show one score line on all maps.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace flat circle markers with nested dual-ring structure encoding
two scores per marker (core = primary, ring = secondary). Upgrade
from 3-tier to 5-tier colorblind-safe scale. Add pulse animation for
high-opportunity markers (>=75). Extract shared PNMarkers module from
3 duplicated implementations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pre-select user's country on opportunity map dropdown (CF-IPCountry),
auto-load the map on page load. Highlight user's city on country
overview maps with a blue ring (CF-IPCity best-effort match). Unify
opportunity score color scale to red/amber/green (was using blue for
low scores). Inject window.__GEO global for client-side geo access.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All three Cloudflare geo headers now available:
- g.user_country (CF-IPCountry) — used by geo-sorted article listing
- g.user_region (CF-RegionCode) — available for within-country sorting
- g.user_city (CF-IPCity) — available for city-level proximity
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Read Cloudflare CF-IPCountry header into g.user_country (before_request)
- _filter_articles() sorts user's country first, nearby countries second,
then rest — falls back to published_at DESC when header is absent
- map_countries sorted so user's country bubble renders on top (Leaflet z-order)
- Nearby-country mapping covers DACH, Iberia, Nordics, Benelux, UK/IE, Americas
Prerequisite: Nginx must forward CF-IPCountry header to Quart (same as Umami).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add legend below map: bubble size = venue count, color = Market Score
- Unify opportunity score color to use same green/orange/red scale
(was using blue for low scores, inconsistent with market score)
- Add mkt_legend_size / mkt_legend_color i18n keys (EN + DE)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- CHANGELOG.md: document Market Score v4 and Opportunity Score v5 changes
- pipeline_routes.py: add dim_countries to location_profiles dependency list
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Delete opportunity() JSON endpoint from api.py (dead after this refactor)
- Add GET /opportunity-map/data route returning HTML partial with two JSON
data islands (opp_points + ref_points from serving.location_profiles)
- Create partials/opportunity_map_data.html (2-line data island partial)
- Rewrite opportunity_map.html: HTMX attrs on <select>, invisible #map-data
swap target, htmx:afterSwap listener replaces fetch()-based loadCountry()
city_venues endpoint stays public (article-maps.js calls it on public pages).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
opportunity_map.html (public page) still fetches these. Only countries.json
and city_venues.json are no longer called from any public page, so those two
keep @login_required.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Defines REPO_ROOT = Path(__file__).parents[3] once in core.py.
Replaces Path(__file__).parent.parent...parent chains and Path("data/...")
CWD-relative references in admin/routes.py, content/__init__.py,
content/routes.py, and worker.py (4x local repo_root variables).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three cases: single delete, bulk by IDs, bulk apply_to_all.
Also extends _create_article() helper with article_type param.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Migration 0029: article_type column (cornerstone/editorial/generated)
- Tab bar on /admin/articles with per-type counts
- Template filter only on Generated tab; delete guard uses article_type
- Type dropdown in article_new/edit form
- Fix: affiliate program and product Delete buttons had missing text/tag
- Bulk delete (both explicit-IDs and apply_to_all paths) now only unlinks
source .md files for generated articles (template_slug IS NOT NULL).
Manual cornerstone articles keep their .md source on disk.
- _sync_static_articles() now also renders markdown → HTML and writes to
BUILD_DIR/<lang>/<slug>.html after upserting the DB row, so cornerstones
are immediately servable after a sync without a separate rebuild step.
- scenario_pdf(): replace d = json.loads(scenario["calc_json"]) with
d = calc(state) so all current calc fields (moic, dscr, cashOnCash, …)
are present and the PDF route no longer 500s on stale stored JSON.
- Restored data/content/articles/ cornerstone .md files via git checkout.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract _build_article_where() helper, eliminating duplicated WHERE
logic from _get_article_list() and _get_article_list_grouped()
- Add template_slug='__manual__' sentinel → filters template_slug IS NULL
(cornerstone / hand-written articles without a pSEO template)
- Add GET /articles/matching-count endpoint returning count of articles
matching current filter params (for the Gmail-style select-all banner)
- Extend POST /articles/bulk with apply_to_all=true mode: builds WHERE
from filter params instead of explicit IDs; rebuild capped at 2,000,
delete at 5,000
- Add "Manual" option to Template filter dropdown
- Add Gmail-style "select all matching" banner: appears when select-all
checkbox is checked, fetches total count, lets user switch to
apply_to_all mode with confirmation dialog
- Sync filter hidden inputs into bulk form on filter change; changing
filters resets apply-to-all state and clears selection
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
`sqlmesh run` only re-evaluates intervals for already-planned models —
it does not detect new, modified, or deleted models. Switching to
`plan prod --auto-apply` ensures schema changes (like the new
location_profiles model) are picked up automatically.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Update api.py (3 endpoints), public/routes.py, analytics.py docstring,
pipeline_routes.py DAG, pipeline_query.html placeholder, and
test_pipeline.py fixtures to use the new unified model.
Subtask 3/5: web app references.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
"Score X/100" → "Padelnomics Market Score: X/100" on country map (markets
hub), city map (country overview). Opportunity map uses "Padelnomics
Opportunity Score: X/100". Consistent branding across all three map views.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Non-article cities were fully gray (#9CA3AF), stripping informational value.
Now all cities show score-based colors (green/amber/red). Non-article cities
are differentiated via lower opacity, dashed border, desaturation, and
default cursor (no click handler). Tooltips show scores for all cities —
article cities get "Click to explore →", non-article cities get "Coming soon".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cities without published articles appear in muted gray and are not
clickable. The cities.json API endpoint now queries SQLite for
published articles and adds a has_article boolean to each city row.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Custom error templates extending base.html with centered layout.
404 is context-aware: detects /markets/{country}/{city} paths and
shows city-specific message with link back to country overview.
Both pages support EN/DE translations.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add /admin/articles/stats HTMX partial endpoint that was referenced
by article_stats.html but never created (caused 500 during generation)
- Add @app.errorhandler(500) to log exceptions with traceback
- Switch dev_run.sh from Granian to Quart debug mode for browser
tracebacks and auto-reload
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
L.divIcon() was called at IIFE top level before the dynamic Leaflet
script loaded, throwing ReferenceError and preventing all maps from
rendering. Move icon creation into script.onload callback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The .card wrapper has overflow:hidden which clips Leaflet's
absolutely-positioned tile layers. Override to overflow:visible
on the rendered-article card. Add .catch() to map fetch calls.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Put Leaflet init scripts inside admin_content block instead of relying
on the scripts block inheritance chain through base_admin → base.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The /admin/templates/<slug>/preview/<key> page renders article HTML
directly but never loaded Leaflet CSS/JS, so country-map and city-map
divs appeared empty.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The admin article preview iframe was missing Leaflet CSS/JS and had
scripts blocked by the sandbox policy, so map shortcodes rendered as
empty divs.
- Extract inline map script to static/js/article-maps.js (shared
between article_detail.html and admin preview)
- Replace f-string preview doc with a proper Jinja template that
includes Leaflet assets
- Add allow-scripts to iframe sandbox on both initial load and HTMX
preview updates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
`plan --auto-apply` only detects SQL model changes and won't re-run
for new data. `run prod` evaluates missing cron intervals and picks
up newly extracted data — matching the fix already applied to the
supervisor.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Grid children default to min-width:auto, letting the Chart.js canvas
push the container wider than its grid track. Adding min-width:0 and
overflow:hidden constrains charts to their column width.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add scripts/check_pipeline.py: read-only diagnostic for pricing pipeline
row counts, date range analysis, HAVING filter impact, join coverage
- Add description field to all 12 workflows in workflows.toml
- Parse and display descriptions on extraction status cards
- Show spinner + "Running" state with blue-tinted card border
- Display start time with "running..." text for active extractions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
In prod the package is installed in a venv, so __file__.parents[4] doesn't
reach the repo root. Use CWD (repo root in both dev and prod via systemd
WorkingDirectory) with REPO_ROOT env var override.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
_extract_line_items() was returning [] for all checkout sessions, which
meant _handle_transaction_completed never processed credit packs, sticky
boosts, or business plan PDF purchases. Now fetches line items from the
Stripe API using the session ID, with a fallback to embedded line_items.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- dev_run.sh: also remove app.db-shm and app.db-wal on reset to fix
SQLite disk I/O error from stale WAL/SHM files
- articles bulk: add checkboxes to grouped rows (data-ids holds all
variant IDs); checking a group selects EN+DE together
- restore select-all checkbox in grouped <th>
- add toggleArticleGroupSelect() JS function
- fix htmx:afterSwap to re-check group checkboxes correctly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Step 4 (Project Phase) required location_status server-side but had no
visual "*" indicator and no error message when submitting without a
selection. All other steps already had both.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>