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>
SPORTPLATZWELT_RESEARCH.md: full research report on Sportplatzwelt (Stadionwelt GmbH)
— business model, audience, padel coverage, pricing, event contacts, recommendation.
CUSTOMER_CHANNELS.md updates:
- Verified DTB padel contacts (Zamani Badawere, Fabienne Bretz, Toralf Bitzer)
- Add mypadel.de, Tennis Magazin, Sportplatzwelt as publications
- Correct Padel Magazine to FR-only (not EN/ES)
- All trade show dates verified: Padel World Summit May 2026, FSB 2027,
ISPO moved to Amsterdam Nov 2026, FIBO Apr 2026, Sportplatzwelt LIVE Jun 2026
- Elevate Padel World Summit to Tier 1 (dedicated padel B2B event)
- Update Top 10 Priority Actions with verified contacts and Padel World Summit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add language selector (en/de) to article create/edit form
- Store language and generated seo_head in articles table on CREATE and UPDATE
- Write HTML build to BUILD_DIR/{lang}/{slug}.html (consistent with pSEO)
- article_detail.html: render article.seo_head when present (canonical,
hreflang, OG, JSON-LD Article) — falls back to inline for legacy articles
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a new "Pipeline" section to the admin panel at /admin/pipeline with
full visibility into the data extraction → transform → serve pipeline.
Tabs:
- Overview: per-extractor status grid, serving table freshness, landing
zone file stats
- Extractions: filterable run history, mark-stale action for stuck runs,
trigger-all button
- Catalog: serving schema browser with lazy-loaded column types and 10-row
samples
- Query: dark-themed SQL editor with schema sidebar, keyword blocklist,
1k-row cap, 10s timeout, HTMX result swapping
Also:
- Adds execute_user_query() to analytics.py for the query editor
- Registers pipeline_bp in app.py
- Adds run_extraction background task to worker.py
- 29 tests (all passing), CHANGELOG + PROJECT.md updated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- partials/pipeline_catalog.html: accordion list of serving tables with
row count badges, column count, click-to-expand lazy-loaded detail
- partials/pipeline_table_detail.html: column schema grid + sticky-header
sample data table (10 rows, truncated values with title attribute)
- JS: toggleCatalogTable() + htmx.trigger(content, 'revealed') for
lazy-loading detail only on first open
Subtask 4 of 6
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Filterable extraction run history table (extractor + status dropdowns,
HTMX live filter via 'change' trigger)
- Status badges with stale row highlighting (amber background)
- 'Mark Failed' button for stuck 'running' rows (with confirm dialog)
- 'Run All Extractors' trigger button
- Pagination via hx-vals
Subtask 3 of 6
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- pipeline.html: 4 stat cards (total runs, success rate, serving tables,
last export) + stale-run warning banner + tab bar (Overview/Extractions/
Catalog/Query) + tab container (lazy-loaded via HTMX on page load)
- partials/pipeline_overview.html: extraction status grid (one card per
workflow with status dot, schedule, last run timestamp, error preview),
serving freshness table (row counts per table), landing zone file stats
Subtask 2 of 6
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Creates minimal .jsonl.gz and .json.gz seed files so all SQLMesh staging
models can compile and run before real extraction data arrives.
Each seed has a single null record filtered by the staging model's WHERE
clause (tenant_id IS NOT NULL, geoname_id IS NOT NULL, type IS NOT NULL, etc).
Covers both formats (JSONL + blob) for the UNION ALL transition CTEs:
playtomic/1970/01/: tenants.{jsonl,json}.gz, availability seeds (morning + recheck)
geonames/1970/01/: cities_global.{jsonl,json}.gz
overpass_tennis/1970/01/: courts.{jsonl,json}.gz
overpass/1970/01/: courts.json.gz (padel, unchanged format)
eurostat/1970/01/: urb_cpop1.json.gz, ilc_di03.json.gz
eurostat_city_labels/1970/01/: cities_codelist.json.gz
ons_uk/1970/01/: lad_population.json.gz
census_usa/1970/01/: acs5_places.json.gz
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All 10 email handlers now use render_email_template(). The two legacy
inline-HTML helpers are no longer needed and have been removed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace single global Overpass query (150K+ elements, times out) with
10 regional bbox queries (~10-40K elements each, 150s server / 180s client).
- REGIONS: 10 bboxes covering all continents
- Crash recovery: working.jsonl accumulates per-region results;
already_seen_ids deduplication skips re-written elements on restart
- Overlapping bbox elements deduped by OSM id across regions
- Retry per region: up to 2 retries with 30s cooldown
- Polite 5s inter-region delay
- Skip if courts.jsonl.gz or courts.json.gz already exists for the month
stg_tennis_courts: UNION ALL transition (jsonl_elements + blob_elements)
- jsonl_elements: JSONL, explicit columns, COALESCE lat/lon with center coords
(supports both node direct lat/lon and way/relation Overpass out center)
- blob_elements: existing UNNEST(elements) pattern, unchanged
- Removed osm_type='node' filter — ways/relations now usable via center coords
- Dedup on (osm_id, extracted_date DESC) unchanged
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- availability_{date}.jsonl.gz replaces .json.gz for morning snapshots
- Each JSONL line = one venue object with date + captured_at_utc injected
- Eliminates in-memory consolidation: working.jsonl IS the final file
(compress_jsonl_atomic at end instead of write_gzip_atomic blob)
- Crash recovery unchanged: working.jsonl accumulates via flush_partial_batch
- _load_morning_availability tries .jsonl.gz first, falls back to .json.gz
- Skip check covers both formats during transition
- Recheck files stay blob format (small, infrequent)
stg_playtomic_availability: UNION ALL transition (morning_jsonl + morning_blob + recheck_blob)
- morning_jsonl: read_json JSONL, tenant_id direct column, no outer UNNEST
- morning_blob / recheck_blob: subquery + LATERAL UNNEST (unchanged semantics)
- All three produce (snapshot_date, captured_at_utc, snapshot_type, recheck_hour, tenant_id, slots_json)
- Downstream raw_resources / raw_slots CTEs unchanged
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>