Commit History
-
fix: graceful fallback when indicator has no grid overlay
KSvend +1: Claude Opus 4.6 (1M context)The overlay was disappearing completely when clicking between indicators if any of them hit the raster→grid fallback path — the frontend fell through to _renderStatusOverlay() which (after the AOI rectangle was removed) did nothing visible, leaving a blank map with no feedback. Backend: - raster_to_grid_dict() now handles all-NaN rasters by emitting an empty grid so the legend still renders instead of returning None - _save_spatial_json() logs the raster path + band + existence when serializing, and writes a richer raster-unavailable payload (with label, colormap, vmin, vmax) so the frontend can still show a meaningful legend Frontend: - renderSpatialOverlay() no longer falls through to the old status overlay — unknown/fallback payloads render the legend in its new "overlay not available" mode with the product name + color hint - new _activeProductId tracking drops stale spatial responses when the user clicks through indicators rapidly - addSource/addLayer calls are wra…ef89b38 -
feat: show real indicator overlay on dashboard map + fix downloads
KSvend +1: Claude Opus 4.6 (1M context)The results dashboard map was only tinting the AOI box because the frontend had no handler for map_type="raster" — all four indicators use raster spatial data, so nothing ever rendered. Map overlay: - new app/outputs/spatial_web.raster_to_grid_dict() downsamples the product raster (using NaN-aware block mean) to ~96x96, flips rows to match the frontend's ascending-lat grid convention, and emits explicit matplotlib-colormap hex stops so the browser does not need to know RdYlGn, Blues, PiYG, etc. - _save_spatial_json() converts raster SpatialData through the helper before writing, so indicators return a grid-format payload the existing _renderGrid() already understands - _renderGrid() now prefers backend-provided color stops and fits the map view to the grid extent so the overlay is always visible Download fixes: - authDownload(): anchor is appended to the DOM before .click() and objectURL revocation is deferred — the old synchronous teardown broke in Safari/Firefox a…d2a9a16
-
fix: aspect ratio, confidence factors, GREEN trend alignment, compound signal gating
KSvend +1: Claude Opus 4.6 (1M context)- maps: geographic aspect (cos-lat) replaces aspect="auto" that caused skewed rendering - report: PIL-based _fit_image preserves PNG aspect ratio in PDF layout - report: coordinate-derived AOI display name when name is missing - report: drop headline truncation in summary table - report: 4-factor confidence breakdown incl. anomaly consistency - narrative: drift/gating-aware interpretations via get_interpretation_for_result - confidence: continuous scoring, new anomaly_consistency factor, expected_months - base: status-aware trend (GREEN -> STABLE), evidence-gated classification - worker: skip GREEN and baseline-drift indicators before compound signal detection Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>b0128ec
-
fix: fix remaining indicator_id/indicator_ids references after rename
KSvend +2: Claude, HappySeveral references to old field names survived the rename: - job.request.indicator_ids → product_ids (jobs.py, worker.py) - result.indicator_id → product_id (worker.py, 15 occurrences) - indicator_name kwarg → product_name (worker.py) - indicator_status param → status (database.py) - advisor prompt text cleanup These caused AttributeError at runtime on HF Spaces. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>749b346 -
refactor: rename "indicators" to "EO products" throughout
KSvend +2: Claude, HappyFull rename across codebase: - app/indicators/ → app/eo_products/ - IndicatorResult → ProductResult, IndicatorMeta → ProductMeta - IndicatorRegistry → ProductRegistry, BaseIndicator → BaseProduct - NdviIndicator → NdviProduct (and Water, SAR, Buildup) - indicator_id → product_id in models, APIs, and frontend - /api/indicators → /api/eo-products - All user-facing text updated - Frontend page/element IDs updated - 27 tests passing Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>df6bf75 -
fix: address code review — memory, dedup, confidence, zoom, overview
KSvend +2: Claude, HappyCritical fixes: - Memory: compute_seasonal_stats_pixel uses Welford's online algorithm instead of stacking all bands in RAM (prevents OOM at native res) - Memory: worker releases z-score rasters from indicators after compound signal detection via release_rasters() Important fixes: - Extract _compute_spatial_completeness, _classify_zscore, _compute_trend_zscore to BaseIndicator (was copy-pasted 4x) - Remove fake mean_obs_per_composite=5.0 — confidence model is now 3-factor (temporal, baseline depth, spatial completeness) - overview.py factors anomaly_months into composite score penalty - Worker zoom: order=1 (bilinear) and upsample to finest resolution instead of downsampling to coarsest - Water chart: compute baseline from water fractions directly instead of misleadingly scaling raw MNDWI values - Buildup _compute_stats docstring consistency Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthrop…7daf007 -
feat: add output pipeline for seasonal analysis and compound signals
KSvend +2: Claude, Happy- Charts: anomaly markers (red rings) on months exceeding z-score threshold; default y-axis labels per indicator - Maps: new render_hotspot_map() for z-score change visualization over true-color base layer - Narrative: generate_compound_signals_text() for cross-indicator compound signal descriptions - Report: anomaly column in summary table, compound signals section, hotspot maps in indicator blocks, confidence breakdown table in technical annex - Worker: generates hotspot maps, detects compound signals via spatial z-score overlap, passes both to PDF report Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>1e8f4b0 -
refactor: strip to 4 real EO indicators, remove all tests and mock fallbacks
KSvend +2: Claude, HappyKeep only NDVI, Water (MNDWI), SAR, and Built-up — the four indicators with full openEO batch pipelines. Remove 8 unused indicators, all test files, and every placeholder/fallback path. Failures now propagate as errors instead of silently returning synthetic data. - Delete: cropland, fires, food_security, lst, nightlights, no2, rainfall, vegetation - Delete: entire tests/ directory - Remove _fallback() from all indicators — process() raises on failure - Remove harvest() fallback-to-placeholder on current data failure - Worker no longer falls back to process() when batch submit fails - Remove placeholder caveat from narrative engine and PDF report - Re-weight overview scores for 4 indicators (ndvi 0.30, sar 0.25, water 0.25, buildup 0.20) - Remove build_lst_graph from openeo_client Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>c42be1f
-
fix: exit poll loop 10 min after first batch job finishes
KSvend +2: Claude, HappyInstead of waiting 90 min for all 3 jobs, exit once any job finishes + 10 min grace period for the others. Harvest with whatever completed — _SkippedJob wraps the rest. Typical flow: current finishes at ~10 min, grace gives baseline/ true-color until ~20 min, then harvest with partial results. Much better than blocking 90 min. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>6ccfecb -
fix: wrap non-finished batch jobs to fail fast on download
KSvend +2: Claude, HappyWhen poll times out with 2/3 jobs finished, calling download_results() on a still-running job blocks for 30 min (HTTP read timeout). Now non-finished jobs are wrapped with _SkippedJob that raises immediately — the harvest methods' existing try/except handles this as graceful degradation. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>5bf6898 -
debug: add harvest diagnostics to trace NDVI download-to-result pipeline
KSvend +2: Claude, HappyLogs: download_results return value, directory listing, file paths, sizes, compute_stats output, and saved result data_source. Needed to diagnose why jobs complete but frontend shows no data. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>c90dd1c -
fix: harvest partial batch results on timeout instead of discarding
KSvend +2: Claude, HappyWhen 2 of 3 batch jobs finish but 1 times out, harvest with what we have. The harvest methods already handle per-job download failures with try/except and degrade gracefully (e.g., no baseline = lower confidence, not a total failure). Previously, timeout meant all finished results were discarded and the sync fallback was tried, which also timed out. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>067d6c2
-
fix: increase batch timeout to 90 min for 5-year baseline jobs
KSvend +2: Claude, Happy5-year baseline jobs need ~65 min on CDSE. Previous 40-min timeout was killing them before completion. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>a6638ce -
fix: sequential batch indicator processing with 40-min per-indicator timeout
KSvend +2: Claude, HappyGenerated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>ad28ab3 -
debug: log batch job IDs and poll statuses to diagnose CDSE timeouts
KSvend +2: Claude, HappyGenerated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>cb6d0e1
-
fix: create download directories before batch job download_results()
KSvend +2: Claude, HappyRoot cause: download_results() requires the target directory to exist. We were passing paths like /tmp/aperture_ndvi_batch_xxx/current without creating them first, causing NotADirectoryError. Also removes temporary debug logging from worker and harvest. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>4c579fd -
chore: add batch job debug logging to worker
KSvend +2: Claude, HappyTemporary: logs batch job errors and surfaces harvest failures in result limitations field for remote debugging. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>a20be0b -
feat: three-phase batch job worker (submit → poll → harvest)
KSvend +2: Claude, HappyGenerated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>f491e48
-
feat: integrate visual overview into worker pipeline
KSvend +1: Claude Sonnet 4.6Compute composite score and write overview_score.json after the summary map step. Reuse the first available true-color raster path to render an overview_map.png, then pass both into generate_pdf_report. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>8202626 -
refactor: generalize raster map vmin/vmax via SpatialData fields
KSvend +2: Claude, HappyGenerated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>4939573 -
feat: register NDVI indicator and add raster map support to worker
KSvend +2: Claude, HappyGenerated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>1ee8d52 -
feat: wire season_months through worker to all indicators
KSvend +1: Claude Sonnet 4.6Add season_months: list[int] | None = None to the abstract process() signature in BaseIndicator, pass it from the worker via job.request.season_months(), and update all 9 indicator implementations plus the worker test mock to accept the parameter (ignored for now). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>55881c1
-
fix: use display names for chart titles (NO2/LST casing)
KSvend +2: Claude, HappyWorker was passing raw indicator_id to chart renderer, producing titles like "No2" and "Lst". Now uses _indicator_label() for correct casing. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>5c28888 -
feat: PDF report includes summary map and per-indicator maps
KSvend +2: Claude, HappyGenerated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>1da14bf -
feat: worker generates maps for all indicators, serves spatial JSON
KSvend +2: Claude, Happy- Switch worker to new render_indicator_map/render_status_map signatures - Generate map PNG for every indicator (not just ones with SpatialData) - Serialize spatial data to JSON after each indicator for frontend use - Add GET /api/jobs/{job_id}/spatial/{indicator_id} endpoint in main.py Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>bc642b5
-
feat: wire up indicator map layers on results dashboard
KSvend +2: Claude, HappySpatial indicators (cropland, vegetation, water) now cache their 2D raster data after processing. The worker generates styled PNG maps via render_indicator_map(), served through a new API endpoint. The frontend overlays these on the results map when clicking indicator cards. - Add SpatialData dataclass and get_spatial_data() to BaseIndicator - Cache NDVI/MNDWI grids in cropland, vegetation, water indicators - Worker generates map PNGs alongside charts during job processing - New GET /api/jobs/{job_id}/maps/{indicator_id} endpoint - Frontend: MapLibre image overlay with graceful fallback for non-spatial indicators - Fix setuptools package discovery in pyproject.toml Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>85f7c19 -
Initial commit: Aperture platform (extracted from SR4S)
KSvendae74af5