Both DE + EN language variants. All additions wrapped in {% if avg_opportunity_score %}
guards for graceful degradation.
Changes per language:
- Stats strip: avg Opportunity Score as 5th item (with auto-fit CSS now supporting this)
- Market Landscape section: paragraph on opportunity interplay (high opp + low market =
first-mover signal; high both = proven demand + open sites)
- New section: "Top Locations by Investment Potential" — table of top_opportunity_names
(distinct from top Market Score cities)
- New FAQ: explains Market Score vs Opportunity Score difference (avg values used)
DE copy written with linguistic mediation — native investor register, Du-form.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both DE + EN language variants. All additions wrapped in {% if opportunity_score %}
guards so cities without a GeoNames match degrade gracefully (score hidden).
Changes per language:
- Stats strip: Opportunity Score item after Market Score (same green/orange/red thresholds)
- Intro paragraph: contextual sentence with supply-gap / white-space interpretation
- Market Overview table: Opportunity Score row
- New FAQ: explains the difference between Market Score (maturity) and Opportunity Score
(investment potential / supply gap)
DE copy written with linguistic mediation — native investor register, Du-form,
avoids calque from English.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Change from repeat(4, 1fr) to repeat(auto-fit, minmax(140px, 1fr)) so the
stats strip accommodates both 4-item (country overview) and 5-item (city
articles with opportunity score) layouts without breaking smaller widths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- dim_cities: add geoname_id to geonames_pop CTE and final SELECT
Creates FK between dim_cities (city-with-padel-venues) and dim_locations (all GeoNames),
enabling joins to location_opportunity_profile for the first time.
- city_market_profile: pass geoname_id through base CTE and final SELECT
- pseo_city_costs_de: LEFT JOIN location_opportunity_profile on (country_code, geoname_id),
add opportunity_score to output columns
- pseo_country_overview: add avg_opportunity_score, top_opportunity_score, top_opportunity_slugs,
top_opportunity_names aggregates
Cities with no GeoNames name match get opportunity_score = NULL; templates guard with
{% if opportunity_score %}.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Simpler, clearer two-level navigation:
- Sidebar: 9 flat section-level links (no toggling), active at section level
- Horizontal subnav: compact tab strip renders above content for sections
with multiple pages (Marketplace, Content, Email, System)
- Single-page sections (Dashboard, Suppliers, Billing, Analytics, Pipeline)
get no subnav — one click, you're there
- Sidebar active state uses active_section not admin_page, so any sub-page
correctly highlights its parent section
- Zero JS beyond the existing confirm dialog
- Unread badge remains on Email sidebar item + Inbox subnav tab
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Admin articles list:
- Group EN/DE language variants into a single row (grouped by url_path)
- Language chips (● EN/● DE) coloured by status: green=live, amber=scheduled, blue=draft
- Inline View ↗ (live only) and Edit buttons per variant — one-click access
- Filter by language switches back to flat single-row view
- Live HTMX polling of article counts while generation runs (every 3s, self-terminates)
- Table overflow fix: card gets overflow:hidden, table wrapped in overflow-x:auto scroll div
Bug fixes:
- X-Forwarded-Proto: pass $http_x_forwarded_proto through Nginx so Quart sees https
- pipeline_routes.py: fix relative import for analytics module (from .analytics → from ..analytics)
- Scheduled articles: redirect to parent path instead of 404 when not yet published
- city-cost-de: change priority_column from population to padel_venue_count
- Quote wizard step 4: make location_status required
- Article generation: use COUNT(*) instead of 501-sentinel hack for row counts
- Makefile: pin Tailwind v4.1.18, add dev/help targets, uv run python, .PHONY
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Read-only overview of all Paddle products with live metrics:
- Stats cards: active subscriptions, estimated MRR (yearly÷12),
active boosts, completed business plan exports
- Products grouped by category: Supplier Plans, Planner Plans,
Boosts (sub + one-time), Credit Packs, One-time Products
- Per-product: name, key, price, type badge, active count, Paddle IDs
- Empty-state message when paddle_products table is unpopulated
- PRODUCT_CATEGORIES constant in routes.py defines grouping + ordering
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces flat 20-link sidebar with collapsible section groups:
- Multi-item sections (Marketplace, Content, Email, System) are
collapsible with animated chevron; active section always expands
- Single-item sections (Dashboard, Suppliers, Billing, Analytics,
Pipeline) render as direct links — no toggle overhead
- pSEO merged into Content; Users moved into System; new Billing slot
- Unread badge surfaces on Email group header when collapsed
- localStorage persists per-section open/closed state (key: admin_sidebar_v1)
- Mobile: group headers hidden, all items shown in horizontal scroll
(preserves existing mobile behavior exactly)
- section_map Jinja dict derives active_section from existing admin_page
— no route changes needed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Articles now live at data/content/articles/{slug}.md — this is the path the
admin CMS reads from (admin/routes.py:1861) when rebuilding manual articles
via the publish pipeline.
Marketing assets moved to marketing/ at the project root.
All 14 article files (C2–C8 + C4 DE/EN) and 4 marketing files relocated from
scratch/ where they never belonged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
C4 articles:
- scratch/articles/state-of-padel-q1-2026-de.md — German State of Padel Q1 2026
report (~2,500w); DE version front-loads Germany section; Wirtschaftsjournalismus
register; FIP + Playtomic/PwC + Padelnomics pipeline data embedded
- scratch/articles/state-of-padel-q1-2026-en.md — English adaptation (~2,500w);
Germany as case study; international audience framing
Marketing assets:
- scratch/marketing/founding-member-deal.md — founding member deal structure
(20 slots, €990/yr locked 3 years, Professional tier at Basic price, +rationale)
- scratch/marketing/supplier-outreach-emails.md — 3 email templates × DE + EN
(cold intro, founding member pitch, day-7 follow-up); Sie-register throughout
- scratch/marketing/linkedin-posts-launch-week.md — 5 DE launch-week posts
(<300w each, max 5 hashtags, company-page appropriate)
- scratch/marketing/linkedin-approach.md — company page setup guide + engagement
strategy (no personal exposure, supplier tagging, SEO backlink approach)
Data sources used: FIP WPR 2024/2025, Playtomic/PwC Global Padel Report 2025,
Padelnomics DuckDB pipeline (12,441 venues / 80 countries / 5,492 cities).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
878 → 4212 cities. Broadens coverage to match the granularity of
Eurostat and GeoNames data for smaller metro markets.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
85K text extraction of the gated 39MB PDF (already in ~/Downloads).
Notes PDF location in README. Removes landing page placeholder.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves brief into research/state-of-padel-q1-2026/ and adds source files:
- FIP 2024 PDF (8 MB)
- Extracted text from both FIP 2024 + 2025 PDFs
- README with download link for 2025 PDF (44 MB, not committed)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FIP 2024 + 2025 report data: player population, courts, federations,
prize pools, broadcast stats, and data source scrapeability assessment.
Raw PDF text at /tmp/fip_2024_text.txt and /tmp/fip_2025_text.txt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Shift+Enter shortcut to execute query (alongside Cmd/Ctrl+Enter)
- Add ▶ preview button to schema sidebar tables: populates editor with
SELECT * FROM serving.<table> LIMIT 100 and auto-submits
- Update hint text to show "Shift+Enter to run"
- Overview tab: fall back to information_schema when _serving_meta.json
is absent instead of showing error message; row counts show "—"
- Dashboard stat cards: same fallback — query DuckDB for table count
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace single hardcoded Chrome 131 UA with:
- BOT_UA: honest padelnomics-bot UA for Overpass, Eurostat, GeoNames etc.
- _UA_POOL + ua_for_proxy(): deterministic browser UA per proxy URL so each
IP presents a consistent, distinct fingerprint across runs.
Public-API extractors (shared session, no proxy) now send BOT_UA.
Playtomic extractors (proxy-backed) each get a stable pool UA keyed on
their proxy URL hash.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds OVERPASS_MIRRORS list (overpass-api.de, kumi.systems, openstreetmap.ru)
and a post_overpass() helper in _shared.py that tries mirrors in order,
logging a warning on each failure and re-raising the last RequestException
if all mirrors fail. Both overpass.py and overpass_tennis.py now call
post_overpass() instead of hard-coding the primary URL.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- outreach_import(): contact_email was extracted + used for dedup but
missing from the INSERT — added it to the column list and values tuple
- test_import_creates_prospects: strengthen to assert contact_email is
actually persisted (regression test for the above bug)
- dev_run.sh: after server ready, open incognito/private browser window
at dev-login URL; tries google-chrome → chromium → firefox in order
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Route writes to BUILD_DIR/<language>/<slug>.html but test was checking
BUILD_DIR/<slug>.html (missing language subdirectory). Default language
is "en" so correct path is BUILD_DIR/en/manual-art.html.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
`-m padelnomics.export_serving` doesn't resolve because src/ is not
installed as a package in the workspace. Use the direct script path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
From worktree-bp-and-articles:
Content (12 articles, Batch 1):
C2 Cost Bible (DE+EN), C3 Business Plan for Banks (DE+EN),
C5 Location Guide (DE+EN), C6 Financing Guide (DE+EN),
C7 Risk Register (DE+EN), C8 Build Guide (DE+EN)
All written natively (linguistic-mediation for DE), frontmatter complete.
CMS fix:
Article form now includes language selector; seo_head generated +
stored for manually created articles; build path is lang-prefixed.
Business Plan PDF overhaul (KfW Gründerkredit-ready):
- compute_sensitivity() extracted as reusable function
- matplotlib SVG charts (P&L + 12-month cash flow)
- Opening balance sheet, use-of-funds, sensitivity analysis
- Market analysis auto-populated from DuckDB city data
- Pre-export details form (/planner/export/details)
- Migration 0020: bp_details_json on scenarios table
- Complete PDF redesign: Precision Finance aesthetic
(navy/gold, Georgia headings, cover page, TOC, 15 sections)
- 28 new translation keys in en.json + de.json
Docs:
SPORTPLATZWELT_RESEARCH.md + CUSTOMER_CHANNELS.md updated
with verified contacts and trade show dates.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# Conflicts:
# web/src/padelnomics/admin/routes.py
# web/src/padelnomics/locales/de.json
# web/src/padelnomics/locales/en.json
Part B of the bp-and-articles plan. Full overhaul of the PDF generation
engine and template to produce bank-ready documents.
New sections added (all required by KfW application):
- Founder/Management Profile (Gründerprofil)
- Business Description (Vorhabensbeschreibung)
- Market Analysis (auto-populated from DuckDB city data when available)
- Use of Funds (Mittelverwendungsplan)
- Opening Balance Sheet (Eröffnungsbilanz)
- Sensitivity Analysis (utilization + price tables)
- Risk Analysis (templated 6-risk register)
Data & computation:
- Extract compute_sensitivity() from augment_d() → reusable function
shared by web planner and PDF generator
- Add matplotlib chart generation: _generate_pnl_chart_svg() +
_generate_cashflow_chart_svg() — SVG embedded in WeasyPrint HTML
- Add _compute_opening_balance() + _compute_use_of_funds() helpers
- Add _fetch_market_data() — queries DuckDB serving.city_market_overview
Business plan details form:
- New /planner/export/details route (GET/POST)
- New export_details.html template — 5 narrative sections with placeholders
- Migration 0020: bp_details_json TEXT column on scenarios table
- generate_business_plan() loads bp_details_json + calls _fetch_market_data()
Design (plan.html + plan.css):
- Complete redesign: "Precision Finance" aesthetic
- Navy (#0F2651) + warm gold (#C9922C) + Georgia serif headings
- Full-bleed branded cover page with sidebar
- Table of contents page
- Gold-accented section headers (h2 with left border)
- Professional @page running headers/footers
- Executive summary 3-column card grid
- 2-column opening balance sheet
- Sensitivity tables with highlight-row for target/base values
- All tables: navy thead, alternating rows, professional total rows
Translations: 28 new bp_ keys in en.json and de.json for all new
section headings and table labels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both stg_playtomic_resources and stg_playtomic_opening_hours lacked QUALIFY ROW_NUMBER()
dedup despite declaring a grain. When both tenants.json.gz (old) and tenants.jsonl.gz (new)
exist for the same month, the UNION ALL produced exactly 2× rows.
Fixes:
- stg_playtomic_resources: QUALIFY ROW_NUMBER() OVER (PARTITION BY tenant_id, resource_id)
- stg_playtomic_opening_hours: QUALIFY ROW_NUMBER() OVER (PARTITION BY tenant_id, day_of_week)
- playtomic_tenants.py: skip if old blob OR new JSONL already exists for the month,
preventing same-month dual-format writes that trigger the duplicate
Row counts after fix: ~43.8K resources, ~93.4K opening_hours (was 87.6K, 186.8K).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Six topics × 2 languages (DE + EN) = 12 markdown files in scratch/articles/:
C2 Cost Bible: padel-halle-kosten-de, padel-hall-cost-guide-en
C3 Business Plan for Banks: padel-business-plan-bank-de, padel-business-plan-bank-requirements-en
C5 Location Guide: padel-standort-analyse-de, padel-hall-location-guide-en
C6 Financing Guide: padel-halle-finanzierung-de, padel-hall-financing-germany-en
C7 Risk Register: padel-halle-risiken-de, padel-hall-investment-risks-en
C8 Build Guide: padel-halle-bauen-de, padel-hall-build-guide-en
All articles written natively (linguistic-mediation skill for DE), include
[scenario:padel-halle-6-courts:full] markers where relevant, frontmatter with
slug/language/url_path/meta_description/cornerstone fields.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>