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
-
feat: rich results dashboard with inline sparklines and overview
KSvend +1: Claude Opus 4.6 (1M context)Previously the results page showed only a map and tiny cards with a single headline, so users had to download the PDF to see any actual findings. Every ProductResult already contained chart_data, z-scores, confidence factors, summary text, and limitations — they were just being thrown away by the frontend. Backend: - new GET /api/jobs/{id}/overview serves overview_score.json + compound_signals.json (both already written at job-complete time) Frontend: - new getJobOverview() helper in api.js - results.js rewritten to render: - Overview header: composite score ring, plain-language headline, clickable per-indicator chip strip, compound signals banner - Rich indicator cards with: - name + question, status badge, trend glyph, confidence dot - inline SVG sparkline (baseline envelope, mean line, current line, anomaly markers) — no external chart library - z-score / anomaly months / hotspot % metrics row - full summary paragraph (no truncation) - 4-factor …57ba197 -
fix: anchor all maps on AOI bbox to stop squashed hotspot images
KSvend +1: Claude Opus 4.6 (1M context)The hotspot map was using the product raster's bounds for set_xlim/ylim, while render_raster_map was using the true-color raster's bounds. When CDSE returned a product raster with tile-boundary-cropped bounds (e.g. a 0.03° × 0.08° strip), the hotspot came out several times flatter than the top product map. Fix: - new _figsize_for_extent() sizes the matplotlib figure to match the data aspect (cos-lat corrected) so tight_layout + bbox_inches never produce a stretched PNG - render_raster_map, render_hotspot_map, and render_overview_map all now anchor xlim/ylim on the AOI bbox instead of whichever raster happened to be opened first - imshow() calls no longer pass the aspect kwarg — set_aspect() on the axes is the single source of truth Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>ccd0638 -
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: data integrity — built-up persistence, status gating, SAR drift detection, direction-aware narrative
KSvend +1: Claude Opus 4.6 (1M context)Second-pass fixes based on the Nyala South (Sudan) test report showing AMBER flags driven by statistical artifacts rather than real signals. 1. Built-up areas: persistence-based classification - Replace monthly NDBI>0 classification with a persistence mask: pixel is built-up only if NDBI>0 AND NDVI<0.15 in ≥60% of months across the analysis period. Removes the seasonal cycling artifact where bare soil got classified as built-up in dry seasons only. - Tighten NDVI threshold from 0.2 → 0.15 (sparse-veg regions). - Replace monthly z-score with direct percentage change between current and baseline persistent masks. Change thresholds: |change|≥15% → RED, ≥5% → AMBER, else GREEN. - Chart now shows baseline vs current as two bars instead of an oscillating monthly time series that misrepresents reality. - Shared _analyze_persistence helper used by both batch and non-batch harvest paths (eliminates duplication). - Smoke-tested: synthetic sea…ffb57c8 -
fix: report contradictions, NaN propagation, NDBI threshold, blue tint
KSvend +1: Claude Opus 4.6 (1M context)Five fixes for the v2 analytical report based on the test PDF review: 1. SAR NaN propagation - Add safe_float helper that converts NaN/inf to 0.0 - Apply at every np.nanmean/nanstd source (sar, ndvi, water, buildup) - Prevents 'nan dB' / 'z=+nan' strings in headlines and chart data 2. Headline/status alignment - Add BaseProduct._generate_headline as single source of truth - Headlines for RED/AMBER never say 'within normal range'; they describe whichever signal (z-score, hotspot %, anomaly months) drove the non-GREEN status - All four products use the unified generator - Fix _build_seasonal_chart_data to use a year-aware date helper (was reusing time_range.end.year for every month) - Buildup chart had unit mismatch (current ha vs raw NDBI ha); now uses paired baseline buildup fractions for envelope 3. Settlement NDBI threshold for arid terrain - openEO graph now emits paired NDBI/NDVI bands per timestep - _compute_stats applies combin…e968fb9
-
fix: sanitize NaN/inf floats to prevent JSON serialization crashes
KSvend +1: Claude Opus 4.6 (1M context)SAR (and potentially other EO products) produce NaN/inf from nanmean on empty pixel slices. These values crash json.dumps() in both the database write path and the API response path. Adds sanitize_for_json() utility that recursively replaces NaN/inf with 0.0, applied at three levels: - ProductResult model_validator (catches on construction) - Database save_job_result (catches on storage) - API get_job endpoint (catches on response) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>2850a7e -
fix: accept legacy indicator_id/indicator_ids from old database records
KSvend +1: Claude Opus 4.6 (1M context)The rename to product_id/product_ids broke deserialization of existing jobs stored in SQLite. Add model_validators to accept both old and new field names. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>09fc02d -
fix: merge multiple TIF files from CDSE batch downloads
KSvend +1: Claude Opus 4.6 (1M context)CDSE may return temporal bands as separate TIF files instead of a single multi-band GeoTIFF. The _find_tif method now detects this and merges them. Also adds diagnostic logging for band count, CRS, and file listing to help debug the 1-composite issue. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>7e20cf0 -
fix: always reproject to EPSG:4326 in openEO graph builders
KSvend +1: Claude Opus 4.6 (1M context)CDSE returns data in native UTM projection when resample_spatial is skipped. This breaks aggregate_temporal_period (returns 1 band instead of 12) and causes maps to show projected coordinates instead of lat/lon. Fix: always call resample_spatial with EPSG:4326 at the requested resolution, even at native 10m/20m. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>42ff8a1
-
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 -
chore: remove stale RESOLUTION_M constant
KSvend +2: Claude, HappyNo longer used — all indicators now have per-indicator native resolution constants (NDVI_RESOLUTION_M, etc.). 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>f954d8c -
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 -
feat: upgrade Water, SAR, and Settlement indicators with seasonal analysis
KSvend +2: Claude, HappyApply the same analytical upgrade pattern from NDVI to all three remaining batch indicators: - Seasonal baselines via compute_seasonal_stats_aoi - Per-month z-score anomaly detection - Pixel-level hotspot detection with compute_zscore_raster - Four-factor confidence scoring - Native resolution (Water 20m, SAR 10m, Settlement 20m) - Seasonal envelope chart data with anomaly flags SAR uses custom seasonal stats from VV means (interleaved VV/VH bands can't go through compute_seasonal_stats_aoi directly). 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>3d48a34 -
chore: remove NDVI harvest diagnostic print statements
KSvend +2: Claude, HappyPipeline confirmed stable — remove debug prints added during batch job troubleshooting. 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>9d401d9 -
feat: add cross-indicator compound signal detection
KSvend +1: Claude Opus 4.6 (1M context)Implements compound signal module with 4 detection patterns: - land_conversion: NDVI decline + settlement growth - flood_event: SAR backscatter decrease + water increase - drought_stress: NDVI decline + water decline + SAR increase - displacement_pressure: settlement growth adjacent to vegetation loss Includes overlap calculation, confidence tagging, and comprehensive tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>ae4c60c -
feat: add four-factor confidence scoring model
KSvendcbe084f -
feat: add pixel-level change detection with z-scores and hotspot clustering
KSvend +1: Claude Opus 4.6 (1M context)Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>aa09e0b -
feat: add seasonal baseline computation module
KSvend +1: Claude Opus 4.6 (1M context)Implements core seasonal baseline functionality for EO anomaly detection: - group_bands_by_calendar_month: Maps multi-year band indices to calendar months - compute_seasonal_stats_aoi: Computes per-month AOI-level stats from baseline raster - compute_seasonal_stats_pixel: Computes per-pixel statistics across bands - compute_zscore: Computes z-scores with std-floor to avoid division-by-zero All 6 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>03db879 -
feat: update openEO graph builders to native resolution (10-20m)
KSvend515a08f -
feat: add per-indicator native resolution and z-score threshold config
KSvendbfe3c94 -
feat: add anomaly, hotspot, confidence fields to IndicatorResult; add CompoundSignal model
KSvend87be95e -
fix: use MAX_AOI_KM2 from config instead of hardcoded 10,000 km² in models
KSvend +2: Claude, HappyBackend now enforces the same 500 km² limit shown in config.py. Previously models.py had its own MAX_AREA_KM2 = 10,000 constant that was never connected to the configurable value. 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>31cdc2e -
fix: address code review — race condition, listener leak, HTML nesting, client caching
KSvend +2: Claude, Happy- Add _advisorRequestId guard to prevent stale advisor responses from overwriting newer ones on rapid clicks - Move all event listeners (size toggles, geocoder, season hints) inside the _aoiMapInit guard to prevent accumulation on re-navigation - Fix advisor card HTML nesting — sibling of form-group, not child - Cache Anthropic client at module level for connection pooling - Fix latitude label to correctly show °S for southern hemisphere - Update landing page copy from "Draw" to "Select" 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>b8c5bd8 -
feat: add /api/aoi-advice endpoint and startup logging
KSvend4f52b3e -
feat: add advisor module — Claude-powered region insight
KSvend9f90242 -
feat: add ANTHROPIC_API_KEY config and AoiAdviceRequest model
KSvend7534962 -
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: resolve Python deprecation warnings in database and openeo_client
KSvend +2: Claude, HappyReplace datetime.utcnow() with datetime.now(UTC) and bitwise ~bool with == 0 comparison to silence warnings ahead of Python 3.16. 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>0dfd092
-
fix: load job data when viewing previous analyses from history
KSvend +2: Claude, HappyHistory card clicks navigated to results without fetching job data, causing "No results data" for all previously completed analyses. Now fetches full job data (including AOI bbox for map) before showing results. 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>d75edf9
-
feat: rewrite PDF report with narrative structure (The Place, Assessment, Deep Dives, Annex)
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>dfdabfe -
feat: add cross-indicator narrative engine with interpretation templates
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>31dea57 -
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 -
fix: use DataCube.log10() instead of ProcessBuilder.log10() in SAR graph
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>f79ac92 -
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: use print for startup diagnostics so they appear in HF logs
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>7946db4 -
fix: fail fast when CDSE credentials missing on HF Spaces
KSvend +2: Claude, HappyInstead of silently falling through to browser-based OIDC auth (which always fails non-interactively), raise a clear error when running on HF Spaces without OPENEO_CLIENT_ID/SECRET configured. Also logs credential status at startup. 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>ea03df5 -
refactor: extract _find_tif to base, fix valid_months and trend symmetry
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>4dd4ef6 -
feat: add batch support to water indicator
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>14b5fe5 -
feat: add batch support to buildup indicator
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>762f7b2 -
feat: add batch support to SAR indicator
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>311d610 -
fix: NDVI harvest baseline_path never assigned after download
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>cb89291