diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bc92d0..366c173 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Fixed - **Admin template preview maps** — Leaflet maps rendered blank because `article-maps.js` called `L.divIcon()` at the IIFE top level before Leaflet was dynamically loaded, crashing the script. Moved `VENUE_ICON` creation into the `script.onload` callback so it runs after Leaflet is available. Previous commit's `.card` `overflow: visible` fix remains (clips tile layers otherwise). +- **Admin articles page 500** — `/admin/articles` crashed with `BuildError` when an article generation task was running because `article_stats.html` partial referenced `url_for('admin.article_stats')` but the route didn't exist. Added the missing HTMX partial endpoint. +- **Silent 500 errors in dev** — `dev_run.sh` used Granian which swallowed Quart's debug error pages, showing generic "Internal Server Error" with no traceback. Switched to `uv run python -m padelnomics.app` for proper debug mode with browser tracebacks. Added `@app.errorhandler(500)` to log exceptions even when running under Granian in production. - **Pipeline diagnostic script** (`scripts/check_pipeline.py`) — handle DuckDB catalog naming quirk where `lakehouse.duckdb` uses catalog `lakehouse` instead of `local`, causing SQLMesh logical views to break. Script now auto-detects the catalog via `USE`, and falls back to querying physical tables (`sqlmesh__.__`) when views fail. - **Eurostat gas prices extractor** — `nrg_pc_203` filter missing `unit` dimension (API returns both KWH and GJ_GCV); now filters to `KWH`. - **Eurostat labour costs extractor** — `lc_lci_lev` used non-existent `currency` filter dimension; corrected to `unit: EUR`. diff --git a/web/scripts/dev_run.sh b/web/scripts/dev_run.sh index 5a1ec1d..ed275a8 100755 --- a/web/scripts/dev_run.sh +++ b/web/scripts/dev_run.sh @@ -165,7 +165,7 @@ echo "" echo "Press Ctrl-C to stop all processes." echo "" -run_with_label "$COLOR_APP" "app " uv run granian --interface asgi --host 127.0.0.1 --port 5000 --reload --reload-paths web/src padelnomics.app:app +run_with_label "$COLOR_APP" "app " uv run python -m padelnomics.app run_with_label "$COLOR_WORKER" "worker" uv run python -u -m padelnomics.worker run_with_label "$COLOR_CSS" "css " make css-watch diff --git a/web/src/padelnomics/admin/routes.py b/web/src/padelnomics/admin/routes.py index cb37dca..426922f 100644 --- a/web/src/padelnomics/admin/routes.py +++ b/web/src/padelnomics/admin/routes.py @@ -2465,6 +2465,18 @@ async def articles(): ) +@bp.route("/articles/stats") +@role_required("admin") +async def article_stats(): + """HTMX partial: article stats bar (polled while generating).""" + stats = await _get_article_stats() + return await render_template( + "admin/partials/article_stats.html", + stats=stats, + is_generating=await _is_generating(), + ) + + @bp.route("/articles/results") @role_required("admin") async def article_results(): diff --git a/web/src/padelnomics/app.py b/web/src/padelnomics/app.py index 98d3e43..1085bf3 100644 --- a/web/src/padelnomics/app.py +++ b/web/src/padelnomics/app.py @@ -270,6 +270,13 @@ def create_app() -> Quart: from .sitemap import sitemap_response return await sitemap_response(config.BASE_URL) + # Ensure unhandled exceptions are always logged (Granian doesn't show + # Quart's debug error page, so without this 500s are silent). + @app.errorhandler(500) + async def handle_500(error): + app.logger.exception("Unhandled 500 error: %s", error) + return "Internal Server Error", 500 + # Health check @app.route("/health") async def health():