Commit Graph

197 Commits

Author SHA1 Message Date
Deeman
00ad0f7e63 feat(logging): replace all print() with Python logging module
Zero print() calls remain in the app and worker (scripts use
basicConfig for clean CLI output). Setup_logging() in core.py
reads LOG_LEVEL env var and configures the root logger once.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

# Conflicts:
#	web/src/padelnomics/core.py
#	web/src/padelnomics/worker.py
2026-02-24 11:10:02 +01:00
Deeman
77ca817925 feat(logging): convert scripts and migrations from print() to logging
- migrations/migrate.py: module logger, basicConfig in __main__
- scripts/seed_dev_data.py: module logger, convert all 19 prints
- scripts/seed_content.py: module logger, convert all 13 prints
- scripts/refresh_from_daas.py: module logger, convert all 11 prints
- scripts/setup_paddle.py: module logger, convert all 20 prints

All scripts use basicConfig(level=INFO, format='%(levelname)-8s %(message)s')
in their __main__ blocks for clean CLI output without timestamps.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 11:00:07 +01:00
Deeman
ac4ad3179d feat(logging): convert planner/routes.py nurture warning to logging
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:52:16 +01:00
Deeman
39181ba452 feat(logging): convert worker.py print() to logging
- Add module logger (padelnomics.worker) and scheduler_logger
  (padelnomics.worker.scheduler)
- Call setup_logging() at start of run_worker() and run_scheduler()
- Convert all 26 print() calls — drop manual [WORKER]/[SCHEDULER] prefixes
- Magic link + quote verification debug prints → logger.debug() (only
  shown when LOG_LEVEL=DEBUG)
- Errors with exception context use logger.error() with %s formatting

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:51:37 +01:00
Deeman
b4b486e908 feat(logging): add setup_logging() to core.py, wire into app startup
- Add logging import and setup_logging() that reads LOG_LEVEL env var
  (defaults DEBUG in dev, INFO in prod), sets format with timestamp +
  level + logger name, silences hypercorn/asyncio noise
- Add module-level logger to core.py
- Convert 3 [EMAIL] print() calls to logger.info / logger.error
- Call setup_logging() from app.py at import time

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:50:10 +01:00
Deeman
f0c041a00c merge: fix datetime.utcnow() deprecation warnings across all files
Replaces 94 occurrences of deprecated datetime.utcnow() and
datetime.utcfromtimestamp() across 22 files with utcnow()/utcnow_iso()
helpers. Zero DeprecationWarnings remain. All 1201 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:36:26 +01:00
Deeman
d9de9e4cda fix(planner): replace alert() error popups with inline error banner
Show API errors and network failures in a red inline div below the
export form instead of browser alert() dialogs. Error div is hidden
on each new submit attempt so stale messages don't linger.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:36:01 +01:00
Deeman
c5176d7d17 fix(admin): center confirm dialog with fixed position + transform
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:34:38 +01:00
Deeman
bd79617851 feat(admin): replace browser confirm() dialogs with native <dialog> modal
- Add styled <dialog id="confirm-dialog"> to base_admin.html — frosted
  backdrop, rounded card, Cancel / Confirm buttons
- Add confirmAction(message, form) JS helper — clones OK button to
  avoid listener accumulation, calls form.submit() on confirm
- Replace all 5 onclick="return confirm()" across templates with
  type="button" + confirmAction(..., this.closest('form'))
  · articles.html — Rebuild All
  · template_detail.html — Regenerate
  · generate_form.html — Generate Articles
  · scenario_results.html — Delete scenario
  · audience_contacts.html — Remove contact

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:33:38 +01:00
Deeman
e33b28025e fix: use SQLite-compatible space format in utcnow_iso(), fix credits ordering
utcnow_iso() now produces 'YYYY-MM-DD HH:MM:SS' (space separator) matching
SQLite's datetime('now') so lexicographic comparisons like
'published_at <= datetime(now)' work correctly.

Also add `id DESC` tiebreaker to get_ledger() ORDER BY to preserve
insertion order when multiple credits are added within the same second.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:30:18 +01:00
Deeman
6bd92c69ce fix(admin): use task_name column (not task_type) in _is_generating query
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:24:53 +01:00
Deeman
a05c230ce3 fix(tests): replace datetime.utcnow() with utcnow_iso() in test files
Also fixes test_supplier_webhooks.py fromisoformat() comparisons:
expires (naive, from DB) now compared against datetime.now(UTC).replace(tzinfo=None)
to avoid mixing naive/aware datetimes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:24:16 +01:00
Deeman
ff170d94ff merge: add padelnomics Market Score methodology page 2026-02-24 10:23:03 +01:00
Deeman
5644a1ebf8 fix: replace datetime.utcnow() with utcnow()/utcnow_iso() across all source files
Migrates 15 source files from the deprecated datetime.utcnow() API.
Uses utcnow() for in-memory math and utcnow_iso() (strftime format)
for SQLite TEXT column writes to preserve lexicographic sort order.
Also fixes datetime.utcfromtimestamp() in seo/_bing.py.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:22:42 +01:00
Deeman
b7485902e6 test: add Market Score methodology page tests
Subtask 6/6: 8 tests covering EN/DE 200 status, legacy 301 redirect,
JSON-LD schema types, FAQ sections, OG tags, footer link.
Add footer_market_score to i18n parity allowlist (branded term).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 10:15:36 +01:00
Deeman
4033e13e05 feat(admin): live-poll Articles and Scenarios tabs during generation
- Add _is_generating() helper — queries tasks table for pending generate_articles tasks
- Pass is_generating to article_results partial (both full page and HTMX route)
- article_results.html: render invisible hx-trigger="every 3s" div when generating;
  polling stops naturally once generation completes and div is absent
- Add /admin/scenarios/results HTMX partial route with same is_generating logic
- Extract scenario table into admin/partials/scenario_results.html partial
- scenarios.html: wrap table in #scenario-results div, include partial

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:14:21 +01:00
Deeman
815edf3cef feat(seo): link first Market Score mention to methodology page
Subtask 5/6: Wrap first "padelnomics Market Score" per language
section in anchor to /{language}/market-score. Updated templates:
- city-cost-de.md.jinja (DE intro + EN intro)
- city-pricing.md.jinja (DE comparison + EN comparison)
- country-overview.md.jinja (DE intro + EN intro)

Creates hub-and-spoke internal linking from hundreds of city
articles to the methodology page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 10:13:40 +01:00
Deeman
f76d2889e5 fix(core): add utcnow()/utcnow_iso() helpers, migrate core.py usages
Replace deprecated datetime.utcnow() with datetime.now(UTC).
- utcnow() -> datetime: for in-memory datetime math
- utcnow_iso() -> str: strftime format preserving existing SQLite TEXT format

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 10:12:50 +01:00
Deeman
2a038e48be feat(seo): add Market Score to sitemap and footer
Subtask 4/6: Add /market-score to STATIC_PATHS for sitemap
generation (both lang variants + hreflang). Add footer link
in Product column between Markets and For Suppliers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 10:12:29 +01:00
Deeman
39fe025e5b feat(template): create Market Score methodology page
Subtask 3/6: Standalone informational page extending base.html.
Sections: hero, what it measures (4-card grid), score bands,
data sources, limitations, CTAs, FAQ with details/summary.
JSON-LD: WebPage + BreadcrumbList + FAQPage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 10:12:03 +01:00
Deeman
33aa705ef9 feat(routes): add /market-score route + legacy redirect
Subtask 2/6: Route handler in public blueprint, 301 redirect
from /market-score → /en/market-score for bookmarks without
lang prefix.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 10:11:07 +01:00
Deeman
c2bf82917a feat(i18n): add Market Score methodology page keys (EN + DE)
Subtask 1/6: ~40 mscore_* keys per locale covering page title, meta,
section headings, category descriptions, score band interpretations,
data sources, limitations, CTAs, and 5 FAQ Q&A pairs.

DE content written as native German (Du-form), not translated from EN.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 10:10:39 +01:00
Deeman
3d99b8c375 fix(cms): change articles unique constraint to (url_path, language)
url_path UNIQUE prevented multilingual generation — the second language
(e.g. EN after DE) always failed with UNIQUE constraint, leaving tasks in
a retry loop and only the first 1-2 articles visible.

Migration 0020 recreates the articles table with UNIQUE(url_path, language)
and adds a composite index. Adds idx_articles_url_lang for the new lookup
pattern used by article_page and generate_articles upsert.

Also adds search/country/venue_type filters to the admin Scenarios tab
and clarifies what "Published Scenarios" means in the subtitle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 09:53:58 +01:00
Deeman
a35036807e merge: lazy-import duckdb to fix CI hang + mount analytics.duckdb in prod 2026-02-24 04:27:06 +01:00
Deeman
d5e99eead1 fix(ci): lazy-import duckdb to prevent pytest hang
DuckDB spawns non-daemon background threads on import. Since
analytics.py was imported at module level (transitively by the
test suite), these threads kept the pytest process alive after
all tests completed. Moving the import into open_analytics_db()
means duckdb is only loaded when actually needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 04:26:22 +01:00
Deeman
0e2c1e19e6 merge: ci gate deploys on passing tags + fix markets feature flag test 2026-02-24 03:52:20 +01:00
Deeman
6c1dc90a8d fix(ci): gate deploys on passing tags + fix markets feature flag test
- CI now creates v<pipeline_iid> tag after tests pass on master
- Supervisor fetches tags and only deploys when a newer tag is available;
  skips if already on latest or no tags exist
- Fix test_seeds_markets_enabled: markets is seeded disabled (enabled=0),
  test was asserting the wrong value

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 03:36:59 +01:00
Deeman
ebc944a6d3 fix(cms): open analytics DB in worker + fix per-language article upsert
Three bugs causing 0 articles after generation:

1. Worker never called open_analytics_db() — fetch_analytics always
   returned [] since _conn was None. Generation completed silently with
   0 rows from DuckDB.

2. generate_articles upserted by url_path alone — after the URL prefix
   fix, all languages share the same url_path (e.g. /markets/de/berlin).
   The EN article was overwriting the DE article on every generation.
   Fixed: deduplicate on (url_path, language).

3. article_page and _filter_articles didn't filter by language — with
   shared url_paths a DE request could serve an EN article, and the
   markets hub would show duplicate entries per city. Fixed: both now
   filter by g.lang from the /<lang> blueprint prefix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 03:04:01 +01:00
Deeman
7fb6dd9b6f fix(pseo): escape double quotes in YAML meta_description_pattern
Unescaped HTML attribute quotes inside YAML double-quoted scalars broke
YAML parsing, causing discover_templates() to silently skip both
templates and generate nothing.

Fixed by replacing \" with \" in the span style attributes within the
YAML frontmatter. The wordmark span is preserved; YAML now parses
correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 02:32:19 +01:00
Deeman
140b4f2c70 feat(pseo): rebrand market score to "padelnomics Market Score" with wordmark styling
- Updated city-cost-de and country-overview templates
- All user-facing "Market Score" mentions now use full branded name
- "padelnomics" rendered in Bricolage Grotesque 800 wordmark style
- Stat strip labels kept short (space-constrained)
- SQL column names unchanged (market_score stays as-is)
- Also seeds markets feature flag as disabled (0) by default

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 02:28:42 +01:00
Deeman
71b003cb49 feat(pseo): add German language + expanded content + FAQPage schema to country-overview
Subtask 5: Bilingual country-overview template with schema_type fix.

Schema fix:
- Changed schema_type from [Article] to [Article, FAQPage] — enables
  FAQ rich results for existing FAQ content.

German variant:
- Full German prose: "Padel in {{ country_name_en }} — Marktüberblick"
- German stats-strip labels (Anlagen gesamt, Erfasste Städte, Ø Market Score,
  Median Spitzenpreis)
- German market landscape analysis, pricing overview, FAQ (5 questions)
- German CTAs: "Zum Finanzplaner" throughout
- Peak/off-peak spread commentary for German readers

English expansion (~500 → ~1300 words):
- New "Market Landscape" section with market maturity analysis
  (conditional on avg_market_score ranges)
- Expanded "Top Cities" with ranking methodology context
- Expanded "Pricing Overview" with peak/off-peak spread commentary
  and utilization management implications
- Expanded "Build Your Business Plan" with more persuasive copy
- 2 new FAQ questions: "How fast is padel growing?" and "Which cities
  have the best pricing data?"
- Planner links in FAQ answers + second CTA at bottom
- Language-prefixed internal links: /{{ language }}/markets/...

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 02:13:06 +01:00
Deeman
02fcfae0ae feat(pseo): add German language + expanded content to city-pricing template
Subtask 4: Bilingual city-pricing template with language conditionals.

German variant:
- Full German prose: "Padel-Court-Preise in {{ city_name }}"
- German stats-strip labels (Hauptzeit, Nebenzeit, Preisspanne, Auslastung)
- German pricing tables, comparison section, pricing drivers
- 5 German FAQ questions with planner links
- German CTAs throughout

English expansion (~400 → ~1500 words):
- New "Pricing Trends" section with time-of-day analysis
- Expanded city comparison with venue density benchmarks
- Expanded pricing drivers (facility quality, market maturity)
- Investment Outlook section with scenario cross-reference
  [scenario:city-cost-de-{{ city_key }}:operating]
- 2 new FAQ questions (city comparison + price trends)
- Cross-links to city-cost-de and country-overview articles
- Second CTA at bottom + planner links in FAQ answers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 02:08:22 +01:00
Deeman
fb18d5a387 feat(pseo): add German language + expanded content to city-cost-de template
- Full German prose variant (natural Du/Dein, not literal translation)
- German stats-strip labels, table headers, FAQ, CTAs
- Expanded English content (~1500 words): analytical commentary after
  scenario markers, Market Context section, venue density analysis
- Cross-link to city-pricing page in both languages
- Planner links in FAQ answers (both languages)
- Second CTA at bottom of each language block
- Language-conditional title_pattern and meta_description_pattern

Subtask 2+3 of pSEO template improvements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 02:03:25 +01:00
Deeman
ffe4b111eb fix: remove double language prefix from article URLs
generate_articles() was storing url_path with lang prefix (/en/markets/...)
but the content blueprint is registered at /<lang>, producing double-prefix
URLs like /en/en/markets/italy. Fix: store url_path without prefix, build
full_url with prefix for SEO tags (canonical, OG, hreflang, breadcrumbs).

Also removes /markets from RESERVED_PREFIXES since article sub-paths under
/markets/ are valid pSEO content URLs, not blueprint routes.

Subtask 1 of pSEO template improvements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 02:01:33 +01:00
Deeman
48e9169a4c Merge branch 'worktree-cms-admin-improvement' 2026-02-24 01:22:55 +01:00
Deeman
5808ba6490 fix: scenario preview rendering and double-encoded ampersands
- bake_scenario_cards() accepts scenario_overrides dict for preview mode
  (bypasses DB lookup when no published_scenario exists)
- preview_article() builds in-memory scenario dict and passes it through
- Fix double-encoded &amp; in locale strings (was &amp;amp; in rendered HTML)
- Fix ruff import sort in _datetimeformat
- Fix migration 0019 minor issue

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 01:22:25 +01:00
Deeman
a59c670e43 feat: HTMX-ify article publish/delete actions for inline updates
- Publish/Unpublish returns updated <tr> partial via HTMX
- Delete returns empty string to remove row without page reload
- Extract article_row.html partial (used by both results table and
  individual HTMX responses)
- article_results.html now includes article_row.html via loop

Subtask 7 of CMS admin improvement.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 01:20:02 +01:00
Deeman
8ae5aa1935 feat: write markdown to disk during generation for admin editing
- generate_articles() now writes body_md alongside body_html
  to BUILD_DIR/{lang}/md/{slug}.md
- article_edit GET checks both manual and generated markdown paths
- Fix pre-existing ruff import sort in _datetimeformat

Subtask 5 of CMS admin improvement.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 01:18:48 +01:00
Deeman
f0f6e7542f feat: move article generation to background worker queue
- Add generate_articles task handler to worker.py
- template_generate and template_regenerate now enqueue tasks instead
  of running inline (was blocking HTTP request for seconds with 1k articles)
- rebuild_all enqueues per-template + inline rebuilds manual articles
- Update tests to check task enqueue instead of immediate article creation

Subtask 4 of CMS admin improvement.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 01:17:59 +01:00
Deeman
81b361859d feat: invalidate sitemap cache on article publish/delete/create
Add invalidate_sitemap_cache() to sitemap.py and call it from
article_publish, article_delete, and article_new admin routes.

Subtask 6 of CMS admin improvement.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 01:15:44 +01:00
Deeman
0c69d9e1a0 feat: HTMX articles list with filter, search, pagination, and view links
- Add _get_article_list() with filters: status, template, language, search
- Add _get_article_stats() for header stats strip (total/live/scheduled/draft)
- Add /articles/results HTMX partial endpoint
- Add filter bar: search input + status/template/language dropdowns
- Paginate at 50 articles per page
- Add "View" link to live articles (opens public URL in new tab)
- Remove URL column (redundant), add Language column

Subtasks 2+3 of CMS admin improvement.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 01:14:40 +01:00
Deeman
566c578770 fix: correct broken status display and source column in articles list
- Compute display_status (live/scheduled/draft) in SQL instead of broken
  Jinja string comparison against undefined `now` variable
- Replace template_data_id (dropped in migration 0018) with template_slug

Subtask 1/8 of CMS admin improvement.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 01:13:22 +01:00
Deeman
bd1d29468a fix(content): handle None values in template pattern rendering
DuckDB rows can have NULL columns (e.g. market_score, median_peak_rate).
Replace None with 0 in render context so numeric Jinja2 filters like
round() and int don't crash with "NoneType doesn't define __round__".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 00:21:00 +01:00
Deeman
2be54b1c88 fix(content): add missing datetimeformat filter; move preview column first
- Add _datetimeformat Jinja2 filter to _render_pattern() — templates
  use {{ 'now' | datetimeformat('%Y') }} but the filter was never
  registered, causing "No filter named 'datetimeformat'" on preview.
- Move Preview button to first column in template detail data table
  so it's visible without scrolling on wide tables.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 00:13:05 +01:00
Deeman
194100c577 fix(calculator): 10 financial model fixes — IRR, NPV, OPEX growth, value bridge
Part B: Calculator improvements

Fix 1 — annualRevGrowth was dead code. Now applied as compound multiplier
from Year 2 onwards across all revenue streams (court, ancillary, membership,
F&B, coaching, retail).

Fix 2 — IRR initial outflow bug (HIGH). Was using -capex but NCFs are
post-debt-service (levered). Using capex as denominator while using levered
cash flows understates equity returns by the leverage ratio. Fix: use -equity
as outflow for equity IRR, add separate projectIrr (unlevered, uses -capex
with EBITDA flows).

Fix 3 — NPV at hurdle rate. Discounts equity NCFs and exit proceeds at
hurdleRate (default 12%). Reports npv and npvPositive. NPV > 0 iff equity
IRR > hurdle rate. Added hurdleRate slider (5–35%) to Exit settings.

Fix 4 — Remaining loan: replaces heuristic with correct amortization math
(PV of remaining payments: monthlyPayment × (1 - (1+r)^-(n-k)) / r).

Fix 5 — Exit EBITDA: uses terminal year EBITDA (holdYears - 1) instead of
hardcoded Year 3. exitValue now reflects actual exit year, not always Y3.

Fix 6 — MOIC: moic is now equity MOIC (total equity CFs / equity invested).
projectMoic is the project-level multiple. Waterfall updated to show both.

Fix 7 — Return decomposition / value bridge. Standard PE attribution:
EBITDA growth value (operational alpha) + debt paydown (financial leverage).
Displayed in tab_returns.html as an attribution table.

Fix 8 — OPEX growth rate. annualOpexGrowth (default 2%) inflates utilities,
staff, insurance from Year 2 onwards. Without this Y4-Y5 EBITDA was
systematically overstated. Added annualOpexGrowth slider to Exit settings.

Fix 9 — LTV and DSCR warnings. ltvWarning (>75%) and dscrWarning (<1.25x)
with inline warnings in tab_metrics.html.

Fix 10 — Interest-only period. interestOnlyMonths (0–24) reduces early NCF
drag. Added slider to Financing section.

Updated test: test_stab_ebitda_is_year3 → test_stab_ebitda_is_exit_year.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 00:11:52 +01:00
Deeman
e76b6b4715 fix: grant admin role in seed_dev_data so dev-login works immediately
Previously the admin role was only granted during dev-login via
ensure_admin_role(), but dev_run.sh resets the DB on each start,
so the role was never present when first visiting /admin.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 23:49:51 +01:00
Deeman
c795ccfa48 fix(makefile): extract inline Python to script to fix parse error
The multi-line python3 -c heredoc in the Makefile caused
"missing separator" errors since Make runs each recipe line
in a separate shell. Moved to web/scripts/init_landing_seeds.py.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 23:40:58 +01:00
Deeman
13adbb06ef chore: update test screenshots
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-23 22:26:42 +01:00
Deeman
ebba46f700 refactor: align transform layer with template methodology
Three deviations from the quart_saas_boilerplate methodology corrected:

1. Fix dim_cities LIKE join (data quality bug)
   - Old: FROM eurostat_cities LEFT JOIN venue_counts LIKE '%country_code%'
     → cartesian product (2.6M rows vs ~5500 expected)
   - New: FROM venue_cities (dim_venues) as primary table, Eurostat for
     enrichment only. grain (country_code, city_slug).
   - Also fixes REGEXP_REPLACE to LOWER() before regex so uppercase city
     names aren't stripped to '-'

2. Rename fct_venue_capacity → dim_venue_capacity
   - Static venue attributes with no time key are a dimension, not a fact
   - No SQL logic changes; update fct_daily_availability reference

3. Add fct_availability_slot at event grain
   - New: grain (snapshot_date, tenant_id, resource_id, slot_start_time)
   - Recheck dedup logic moves here from fct_daily_availability
   - fct_daily_availability now reads fct_availability_slot (cleaner DAG)

Downstream fixes:
- city_market_profile, planner_defaults grain → (country_code, city_slug)
- pseo_city_costs_de, pseo_city_pricing add city_key composite natural key
  (country_slug || '-' || city_slug) to avoid URL collisions across countries
- planner_defaults join in pseo_city_costs_de uses both country_code + city_slug
- Templates updated: natural_key city_slug → city_key

Added transform/sqlmesh_padelnomics/CLAUDE.md documenting data modeling rules,
conformed dimension map, and source integration architecture.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-23 21:17:04 +01:00
Deeman
042fe35777 Merge branch 'worktree-visual-test-overhaul' — add J-N tests, fix WAITLIST_MODE 2026-02-23 21:08:59 +01:00