Skip to content

Phase 3+ Vision — Draft for Discussion

Status: Working ideation document. Not a phase plan. Owner: gk + Claude Created: 2026-04-22 Purpose: Capture, assess, and refine end-state ideas before converting them into per-phase plans and GitHub issues. We will iterate on this file recursively until it's plan-ready.


How to read this document

  1. The first section anchors against the existing end goal in plan.md and the placeholder scope in phase-3-hosted.md.
  2. Section "User-proposed ideas" assesses each of the 20 ideas the user raised, with a uniform schema:
  3. What it means (concrete interpretation)
  4. Why (alignment with end goal / pilot customer needs / OSS positioning)
  5. Risks (cost, scope creep, infra debt, UX traps)
  6. Refinement (a sharper, scoped version)
  7. Cross-stack scope (engine / backbone / UI / data)
  8. Phase placement (P3 = must-have for first revenue, P4 = expansion, Later = P5+ or never)
  9. Section "Additional ideas (Claude's)" raises gaps I see between current state and the end goal that none of the 20 cover.
  10. Section "Strawman P3 scope" is my opinion on what makes the cut for Phase 3 if we ship in ~6 months. Push back freely.
  11. Section "Open questions" lists decisions the user needs to make before this becomes plannable.

A note on phase placement: phase-3-hosted.md is currently scoped narrowly around "open source the backbone, stand up hosting, land first pilots." Many of the user's ideas would expand P3 considerably. Where I say "P3" below, that's a recommendation — the user can choose to slot the work into a renamed/widened P3 or into a P4 expansion.


Anchor: end-state recap

From plan.md:

  • Target personas (for feature prioritization, not sales): government agencies, NGOs, development banks, smaller insurers, researchers. Common need across all: transparency, reproducibility, auditability.
  • Product surface: REST API + lean engine + browser UI with map-based risk viz, portfolio views, scenarios, time horizons (2030/2050/2100), regulatory report export (TCFD, CSRD, ISSB S2).
  • Hazards: river flood, tropical cyclone, wildfire, storm Europe.
  • Scenarios: SSP1-2.6 → SSP5-8.5.
  • Distribution: self-hostable (Docker Compose, no external calls) AND, eventually, managed hosting. Multi-tenancy + RBAC already landed in Phase 2.
  • Licensing: engine + backbone Apache 2.0; UI license TBD. The UI may or may not be open-sourced ("most likely, but not 100%").

Framing for this document: Climate-Lama is currently a private side project. This draft plans the vision and priority ordering without timelines or resourcing assumptions. Per-phase deliverables will be scoped later as capacity allows. "Must-have" / "should-have" / "defer" below means priority rank, not "by Q2." Every deferred or longshot idea should still be filed as a GitHub issue for open discussion.


User-proposed ideas, assessed

1. Customizable / white-label UI

What it means: branding (logo, name), theming (light/dark/high-contrast), translations, accessibility modes — all swappable per deploy without code changes.

Why: aligned with the UI's potential role as a PoC surface for interested parties (drop a logo, ship a branded variant) and with the open-source-UI-maybe path (forks self-customize). Riskwise-v2 already proved most of this works with MUI v7 design tokens + i18next.

Risks: - White-labeling becomes a treadmill if every customer needs a custom CSS pull request. Need a sharp boundary between "configurable via JSON" and "fork the repo". - Accessibility "modes" can balloon. The honest version is WCAG 2.1 AA compliance + light/dark/high-contrast — anything more is YAGNI until a customer asks.

Refinement: - Define a BrandConfig JSON schema loaded at app boot: {logo_url, favicon_url, app_name, primary_color, secondary_color, default_locale, theme_mode, support_url}. Source: env var → mounted file → API endpoint (in that fallback order). Three sources covers self-hosted, Docker, and managed-hosting personas without code change. - Themes: MUI palette overrides + CSS variables (riskwise-v2 D04 pattern). Ship light, dark, high-contrast. Not "12 themes." - i18n: i18next + locale JSON files. Start with EN; add as customers ask. - WCAG 2.1 AA as the audit target, not "all accessibility."

Cross-stack: UI only. Phase: P3 must-have (every demo needs it).


2. Settings page (application + user)

What it means: a settings surface bifurcated into org-level admin settings and per-user preferences.

Why: required by multi-tenancy and RBAC (already in Phase 2). Without a settings page the multi-tenancy is invisible to users.

Risks: settings pages are scope-creep magnets. Every "where should this go?" answer becomes "let's add a setting for it." Need a strict gate: a setting only exists if at least 2 user types want different values.

Refinement (start small): - User settings: theme, language, default time horizon, default scenario, units (metric/imperial), email notifications. - Org/admin settings: branding overrides (extends BrandConfig from #1), default impact-function selection per hazard, data-source allow-list (which catalog buckets are visible), default discount rate. - Storage: user_preferences and org_settings tables, scoped via RBAC. JSON columns acceptable for forward compat.

Cross-stack: backbone needs /v1/users/{id}/preferences and /v1/orgs/{id}/settings endpoints + RBAC; UI needs the page + state management. Phase: P3 (org settings front-loaded for pilot customers; user settings can grow later).


3. Modern UI redesign

What it means: re-think the whole layout (sidebar, navbar, map, right panel) to look modern and differentiate.

Why: the user is right that current state is "wizard + map + right panel" which is the baseline of every climate risk tool. Differentiation matters for sales narrative.

Risks: - "Modern" is unmeasurable. Without a target persona's workflow, redesign churns. - Redesigning from scratch loses the working flows already built. Iterate, don't rewrite.

Refinement: - Pick 2-3 inspirations, not 10. Candidates worth comparing: Mapbox Atlas (vector tiles + clean UX), Kepler.gl (data-first map UX), Probable Futures (storytelling-first), ClimSight (hazard browser), Climate.gov (gov-credible). Map their patterns to our personas. - Decide layout commitment first, before any code: map-as-canvas (UI floats over a fullscreen map) vs. document-mode (map is a panel inside a document). Riskwise-v2 is document-mode. For a "modern climate tool" feel, map-as-canvas tends to win. - Produce 2-3 wireframes before implementing. This is the single highest-ROI step. - Iterate on existing flows, don't rewrite. The Wizard, ConfigPanel, ResultMap, AssetDetailPanel pattern is sound — re-skin it before re-architecting it.

Cross-stack: UI. Phase: design work in P3 entry (decision needed before settings/customization land), implementation rolls through P3 + P4.


4. Map handling (multi-modal hazard rendering)

What it means: hazards rendered with the visualization that fits their nature — raster tiles for river flood, polygons for admin-aggregated impact, polylines for TC tracks, heatmaps for density, points for exposures/centroids.

Why: the current "dots on a map" approach undersells the data. River flood as a continuous tile layer looks fundamentally different from circles.

Risks: - Leaflet starts to creak with raster tiles + 100k vector points + heatmaps in the same view. WebGL-based MapLibre is a better foundation for what's described — but it's a meaningful migration. - Each visualization mode adds backend cost (raster tiling, polygon aggregation, etc).

Refinement: - Define a LayerSpec model: {type: raster|vector_polygon|vector_point|heatmap|line, source_url, style, interactivity_config}. Backend exposes /v1/hazards/{id}/layers listing available specs; frontend renders each spec uniformly. - Migrate to MapLibre GL as part of this work. Vector tiles, WebGL, better perf, modern feel — also unblocks #5. - Start with raster tiles for river flood + improved point rendering for exposures. Add heatmap, polygon, line as later increments.

Cross-stack: engine/backbone produce raster + vector tiles (martin or pg_tileserv); UI swaps Leaflet → MapLibre. Phase: P3 (MapLibre swap + raster flood layer), P4 (full LayerSpec model + heatmaps + polylines).


5. Aggregation/disaggregation by zoom

What it means: at zoom 0-3 see country totals, zoom 4-7 admin-1 aggregates, zoom 8-12 admin-2, zoom 13+ raw points. Server-side aggregation, vector tile streaming.

Why: this is the single most important UX decision for the platform. Without it, hazards with millions of points are unusable.

Risks: - Significant infrastructure investment: PostGIS spatial joins, admin boundary tables (Natural Earth, GADM), tile server (martin or pg_tileserv), client-side vector tile rendering. - Admin boundaries are politically sensitive (e.g., Crimea, Kashmir). Use Natural Earth or GADM and document the choice. - Pre-computed aggregates need invalidation when underlying data changes — adds operational complexity.

Refinement: - Add admin_boundaries table (admin0/1/2 from GADM) loaded once. - Compute aggregated impact per (scenario_id, admin_unit_id) as a Celery task triggered on impact result completion. Store as result_aggregates table. - Serve via vector tiles using martin (Rust, PostGIS-native, fast). - Client renders MVT tiles via MapLibre with a zoom-aware style. - For Phase 3 lite: skip pre-computation — query PostGIS on the fly per zoom level. Add caching later.

Cross-stack: backbone (PostGIS spatial joins + tile serving), engine (no change), UI (MapLibre + zoom-aware layers). Bound to #4. Phase: P3 lite version (on-the-fly admin aggregation, country + admin-1 only), P4 full (martin tile server + admin-2 + pre-compute).

Round 4 re-prioritization: this is now the concrete vehicle for #27b large-dataset read-side performance — the single highest-impact Tier 2 performance item. Treat the "P3 lite + P4 full" split as a delivery sequence within Tier 2, not as a demotion to later. The martin tile server + COG raster path should land in Phase 3, not Phase 4.


6. Loading exposures (richer UX)

What it means: a modal or slide-over panel offering: browse catalog of available exposures, upload (CSV / XLSX entity files / HDF5 / GeoTIFF), view metadata, preview, auto-detect type from metadata.

Why: the current dropdown won't scale. Riskwise-v2's ExposureEconomic / ExposureNonEconomic selectors point at the same problem. A "Datasets" surface is also the natural anchor for the future data marketplace (#19).

Risks: - Multi-format ingest (HDF5, GeoTIFF, XLSX) is real backend work — current backbone only does CSV. - "Auto-detect type from metadata" needs a published spec. Without it, every customer's CSV is ambiguous.

Refinement: - UI: a Datasets page (or modal) with two tabs: Catalog (org-shared + global) and Upload. Each row shows name, source, license, date, hazard/exposure type, a "preview" action (sample rows or map thumbnail). - Auto-classification: define a sidecar JSON manifest schema (dataset.manifest.json shipped alongside the data file), not header-name heuristics. Manifest declares type, unit, license, crs, source. If absent, prompt the user. - Preview: backend returns first N rows + bbox + sample point for thumbnail. - Upload formats: P3 ships CSV + XLSX (CLIMADA entity). HDF5 + GeoTIFF in P4.

Cross-stack: backbone (richer metadata model on Exposure, parsers, preview endpoint), UI (modal/page). Phase: P3 (Datasets page, CSV + XLSX, manifest schema, preview), P4 (HDF5, GeoTIFF, marketplace-like browsing).


7. Loading hazards (richer UX)

What it means: same shape as #6 but for hazards. Catalog browse + upload + metadata + preview, instead of hardcoded selection.

Why: identical to #6. Worth designing #6 + #7 as one feature with two tabs ("My Datasets") to avoid duplication.

Refinement: - Reuse the Datasets surface from #6 with hazard-specific filters: hazard type, region, scenario, source, year range. - Hazard preview: thumbnail of intensity raster + return-period selector. - Hazard upload already exists (HazardUploadModal.tsx) — extend it with metadata fields and a manifest-driven flow.

Cross-stack: same as #6. Phase: P3 (hazard side of Datasets surface).


8. Impact functions (richer UX, editor, save-as)

What it means: every selected impact function shows a chart (intensity → MDR) + table view. User can edit (drag points or table values), save as new function scoped to user/org, run scenarios with it.

Why: the data model already supports this (source = "user" flag, see CLIMATE_LAMA.md §5). The user-facing endpoint is currently slated for P4+ (#69) but the UX of seeing what curve you're using is much higher value than editing it — and is cheap.

Refinement: - P3: read-only — every impact-function selector renders the curve as a small chart (sparkline-style in the dropdown, full chart in a side panel). Show the source ("JRC Flood — Europe — Residential"), citation, original paper. This is a credibility win for academic/regulator audiences. - P4: editor + save-as. Bring forward issue #69 endpoint. UI: clone existing function, edit in form, save with a name. Fork pattern. - P4+: scenario-level overrides ("use this function for residential, that one for commercial").

Cross-stack: backbone activates POST /v1/impact-functions earlier (P3 instead of P4); UI adds curve viz + editor. Phase: P3 read-only viz, P4 editor.


9. Time horizon flexibility

What it means: replace hardcoded {present, 2030, 2050, 2100} with a slider/range that supports the years actually present in the dataset.

Why: some datasets (e.g., yearly CMIP6 outputs) have finer resolution than 30-year buckets. Hardcoding limits us to IPCC AR6 milestone years.

Risks: - A continuous slider falsely implies the model interpolates between dataset years. It doesn't (engine doesn't do temporal interpolation in 0.2.0). - Different hazard datasets have different supported years → slider needs to be dataset-aware.

Refinement: - Discrete slider locked to dataset-supported years. Read available years from hazard dataset metadata. Show ticks; user can only land on a tick. - Double-handle slider for ref_year + future_year (riskwise-v2 pattern). - When years change, refetch metadata: "this scenario at this year covers X% of the country, with N data points." - Engine roadmap question: is interpolation worth adding to engine 0.3.0+? Probably not — better to ingest more years.

Cross-stack: hazard dataset metadata exposes supported_years: [...]; UI consumes it. Phase: P3 (small change, high UX win).


10. Adaptation measures (richer UX, custom builder)

What it means: catalog browser of predefined measures + custom measure form (cost, hazard reduction curve, region, lifetime) + save sets per scenario.

Why: Phase 1 already shipped the data model + CRUD API (#54-#57) but the UI is still minimal (CostBenefitPanel.tsx). Riskwise-v2 had a mature catalog + custom-pack model.

Refinement: - P3 catalog: a Measures page listing all available measures (filterable by hazard, region, source). Detail view shows cost, effectiveness curve, source, lifetime. - P3 selection: scenario-level "select measures" UX — multi-select chips, riskwise-v2 pattern. - P4 custom builder: form for "create your own measure" — name, cost, applicable hazard, hazard reduction curve (intensity → reduction %), lifetime. - P4 sets: save measure combinations as a "set" reusable across scenarios.

Cross-stack: backbone has the API; UI builds the surfaces. Phase: P3 catalog + selection, P4 builder + sets.


11. Macroeconomic / discounting (CLIMADA entity-style)

What it means: discount rates, GDP growth, population growth — projected to future years for cost-benefit credibility. CLIMADA entity files contain these.

Why: regulators (CSRD, ISSB) expect financial costs to be discounted. Current backbone may have hardcoded defaults (worth verifying); making these configurable + per-region is a step toward audit-grade reports.

Risks: - Easy to slide into "build a full DSGE economic model." Resist. - Defaults need to be defensible (cite the IPCC, not pick numbers).

Refinement: - P3 minimal: surface discount_rate, value_growth_rate as configurable inputs in the cost-benefit screen with sensible defaults (Stern 1.4%, Nordhaus 4.3% as presets + custom). Document the choice. - P4 macro context: per-country tables for GDP growth + population growth from SSP scenarios. Use these to project exposure value forward (replaces "annual_growth" single value with year-by-year). - Skip: full CRED-style timeseries visualization (riskwise-v2's macro tab) unless a customer specifically asks. That was an ERA-project artifact (see #13).

Cross-stack: engine already supports these inputs (verify); backbone exposes them in compute API; UI adds form fields. Phase: P3 configurable defaults, P4 per-country tables.


12. Other application standards (beyond CLIMADA)

What it means: support file formats from competitor / sibling tools (Hazus, OASIS LMF, GEM, MunichRe NatCat).

Why: differentiates from "CLIMADA-only" framing. Opens markets where customers already have Hazus / OASIS data.

Risks: - Most enterprise formats are proprietary or licensed (MunichRe, AIR, RMS). - Open formats (Hazus, GEM, OASIS LMF) are non-trivial to support — each needs its own ingest adapter.

Refinement: - P3 nothing. Stay CLIMADA-shaped. - P4 architecture: design IngestAdapter ABC in the ingest module, with a registry pattern. First non-CLIMADA adapter: GEM exposure CSV (open standard, covers global building stock). - P5+ open: OASIS LMF (insurance industry's open standard — strategically interesting if we go after insurers) and Hazus.

Cross-stack: backbone ingest module. Phase: P4 design + first alternative format, later increments.


13. Macroeconomic adaptation (RISK WISE concept)

What it means: per riskwise-v2 exploration, this was the CRED data feature — pre-computed economist-supplied tables of country/scenario/sector/variable/year/value with chart visualization. Used for ERA project (Egypt/Thailand) to show macro impacts.

Why: useful for development-bank stakeholders showing GDP impact, not core for local planners or insurers.

Honest take: this was a project-specific feature that became part of the codebase. It has narrow appeal outside the original ERA stakeholders.

Refinement (revised after Round 2 discussion): - Generalize the pattern as "context overlays" / "sidecar datasets" — supplementary data that doesn't drive the impact calc but provides narrative context (GDP projections, population growth, sea-level rise, any timeseries a stakeholder cares about). Surface as a dedicated tab ("Context") in the scenario view and optionally embed in reports. - The configurable discount rate in #11 covers the discounting use case; this is for visual context only. - Schema: a context_datasets table (id, name, source, license, scope: {country, scenario, sector, variable, year, value}) + a simple chart view. Users can upload their own (CRED-style, or anything else) and share within their org. - Data model designed in P3 (cheap), UI in P4 or whenever a user asks.

Phase: P3 data-model design, P4 UI (or on-demand).


14. Reports (saved scenarios + screenshots + downloads + templates)

What it means: this is at least four features bundled together: - (a) Save scenario (re-loadable bundle of inputs + results) - (b) Snapshot (frozen map state, image) - (c) Export bundle (download datasets used) - (d) Standardized report (PDF/Excel from template) - (e) Custom report templates (user-uploadable Jinja/Word templates)

Why: this is the biggest single feature area in the list. Reproducibility and auditability are the platform's North Star; without saveable scenarios and standardized report export, neither claim holds. The user flagged this as "almost a phase in itself" — agreed; see Round 2 for a possible "Phase R" carve-out.

Risks: - Custom templates (e) is a feature factory of its own. Defer. - "Save scenario" requires a Scenario entity (currently we have impact results but no top-level Scenario wrapper) — see additional idea A below. - PDF generation pipelines are notoriously fragile (headless Chrome, weasyprint, pandoc).

Refinement (decompose): - P3 (a) + (d) basic: introduce Scenario model. Save scenario = persist all inputs (exposure_id, hazard_id, impact_fn_id, year, scenario_label, measures, discount_rate) + reference to results. Standardized report = single PDF template (matplotlib charts → reportlab/weasyprint, or Quarto). One opinionated layout: cover, summary metrics, map, frequency curve, cost-benefit, methodology, sources. - P3 (b) light: snapshot = "save current map view as PNG" via leaflet-image or canvas screenshot. - P4 (c) + (b) full: export bundle = ZIP of inputs + results in CSV/GeoJSON/NPZ. Snapshot store as DB-backed (riskwise-v2 snapshots table pattern). - P4 (e): custom template upload — Quarto markdown with placeholders, validated server-side, rendered per scenario.

Cross-stack: backbone (Scenario model, report-rendering Celery task, template store), UI (save/load/report buttons, scenario list page). Phase: P3 (a) + (d) + (b) light, P4 (c) + (b) full, P4/P5 (e).


15. Multilingual support

What it means: i18n + l10n for the UI.

Why: target personas span multiple language regions (EU agencies, LATAM development banks, MENA government bodies, Asia-Pacific NGOs). Building i18n infrastructure later is expensive; doing it from the start is cheap.

Refinement: - Infrastructure first: i18next setup, all hardcoded strings extracted to keys, EN locale 100% populated. RTL CSS scaffolding (for Arabic) wired but not filled in. - Add languages as contributors volunteer or specific use emerges. Candidate order based on the persona map: EN → DE → EL → ES → FR → AR. - Backbone API stays in English. Translate at the UI layer only. Error codes (machine-readable) in API; messages translated in UI. This also dovetails with the "error taxonomy" item surfaced in Cross-cutting themes below. - Date/number formatting via Intl (not server).

Cross-stack: UI primarily; backbone needs to ensure error responses are code-driven (not human strings). Priority: P3 infrastructure (foundation cheap and hard to retrofit), ongoing population.


16. More interactive in-app guidance (tooltips, info, tutorials)

What it means: tooltip layer everywhere, info icons opening doc popovers, guided tours for first-time users.

Why: cheap, high-impact. Climate risk has a steep jargon curve (EAD vs AAI, return period vs exceedance frequency, MDR vs MDD vs PAA). Every "what does this mean?" moment is an opportunity to differentiate by being didactic instead of jargon-dense.

Refinement: - P3: every form field has a tooltip; every metric card has an info icon → opens a popover with definition + link to docs. Use MUI Tooltip + a custom <InfoIcon helpKey="ead" /> reading from a static JSON glossary. - P3+: guided tour for first-run experience using react-joyride. Riskwise-v2 has the pattern. - Glossary JSON doubles as a docs source.

Cross-stack: UI. Phase: P3 (tooltips + info icons), P3/P4 (guided tour).


17. Modern documentation (replace Sphinx)

What it means: replace Sphinx with a modern docs site for technical + user docs.

Why: Sphinx is fine but heavy and dated-looking. The "open-source backbone with credible docs" story benefits from a modern site.

Refinement: - Two flavors, one site: - Technical reference (API, deploy, architecture, ADRs) — auto-generated from OpenAPI + handwritten guides - User docs (how-to, conceptual, glossary) — handwritten, includes #16's glossary - Tooling choice: MkDocs Material (see Round 2 deep-dive for Sphinx vs MkDocs Material vs Docusaurus pros/cons). Reasons: Markdown-first, Python-native (aligns with the backbone), beautiful default theme, trivial migration from Sphinx for anyone fluent in rST, API docs via mkdocstrings, DocSearch for search when needed. - Host on GitHub Pages or Cloudflare Pages (free). - Auto-deploy on tag in each repo.

Cross-stack: separate climate-lama-docs repo (or docs/ subtree in backbone — both work). Phase: P3 (set up site + port existing markdown docs + first OpenAPI integration), ongoing.


18. Enhanced monitoring

What it means: beyond docker logs — metrics, dashboards, traces, log aggregation, alerts.

Why: required for managed hosting. Hard to run a SaaS without observability. Self-hosted users may not need it but should be able to opt in.

Refinement: - Stack: Prometheus + Grafana + Loki + Tempo (the Grafana stack). All open-source, all self-hostable, all free for self-hosters. Right alignment with our open-source positioning. - Phase 2 already added structlog → trivial to ship logs to Loki. - Backbone changes: - Prometheus /metrics endpoint via prometheus-fastapi-instrumentator (5 lines of code). - Celery task instrumentation for compute durations. - OpenTelemetry tracing for request → worker spans (skip for P3 if it slips). - Managed hosting: bundled Grafana stack in Terraform. - Self-hosters: opt-in monitoring Docker Compose profile.

Cross-stack: backbone (instrumentation), infra (Grafana stack), UI (status dashboard — likely later). Priority: High — essential before any managed hosting exists; optional but useful for self-hosters. Instrumentation itself is a few hours of work; the dashboard curation is the ongoing part.


19. Cloud-hosted dataset catalog (DO Spaces)

What it means: a public catalog of curated hazards/exposures/impact-funcs hosted in DO Spaces. Users browse + use freely; users with their own data point at their own filesystem/bucket.

Why: aligned directly with the "data marketplace" end goal in plan.md. DO Spaces is S3-compatible and cheap.

Risks: - Data licensing is the hard part, not infra. JRC flood is open. CMIP6 outputs vary by source. Many "free" datasets have non-commercial restrictions. Need a license matrix per dataset. - Egress costs at scale. Cloudflare R2 (zero egress) may be a better choice than DO Spaces if usage grows. - Hosting copyrighted data invites takedown notices.

Refinement: - Catalog format: a versioned JSON manifest in the bucket (catalog.v1.json) listing every dataset with {name, type, source, license, region, year, scenario, s3_url, sha256, manifest_url}. Backbone fetches catalog at boot or on demand, caches locally. - License gate: every dataset entry includes license; UI surfaces it (#1 + #6). - Self-hosters: env var points at their own bucket / their own catalog URL. Same code path. - Curation: start with JRC flood for all EU (already in the P3 scope) + CMIP6 TC tracks (open) + Worldpop population (open). 3 datasets is enough for launch. - Considering R2: if economics matter, evaluate now — switching later is annoying.

Cross-stack: backbone (catalog client + cache), data ops (curation + manifest authoring), UI (browse via #6/#7). Phase: P3 (initial 3-dataset catalog + JSON manifest + UI integration), P4 (broader curation, license matrix, possibly R2 swap).


20. Predefined results caching

What it means: run popular scenarios (e.g., "JRC Flood + Greek buildings + 2050 + RCP4.5") once, store results, serve instantly for matching requests.

Why: reduces latency + compute cost. Riskwise-v2 had this with deterministic SHA256 cache keys.

Risks: - Cache invalidation: when impact function or hazard data is updated, downstream cached results are stale. Need versioning in the cache key. - Adaptation measures applied post-cache complicates pre-warming if measures must be in the key.

Refinement: - Cache key: SHA256(exposure_dataset_id || exposure_dataset_sha256 || hazard_dataset_id || hazard_dataset_sha256 || impact_function_id || impact_function_version || year || scenario || discount_rate || growth_rate). Adaptation measures NOT in key — applied post-cache as a multiplier. - Schema: result_cache(cache_key TEXT PK, result_id UUID FK, created_at, hit_count). - Serve flow: /v1/compute/impact checks cache → if hit, returns 200 with cache_hit: true flag; if miss, dispatches Celery task as today. - Pre-warm: Celery beat job runs popular combos overnight (start with hand-curated list of 20 demo scenarios). - Versioning impact functions: add version column; bumping version invalidates dependent cache entries.

Cross-stack: backbone (cache table, lookup logic, pre-warm scheduler). Phase: P3 (cache table + lookup + manual seeding of popular scenarios), P4 (pre-warm scheduler, UI showing cache hits, version-aware invalidation).


Additional ideas (Claude's)

These are gaps I see between current state and the end goal that the 20 ideas don't directly cover. Numbered with letters to keep clear which originated where.

A. Scenario model (top-level wrapper)

What it means: a Scenario entity that groups exposure_id + hazard_id + impact_fn_id + year + scenario_label + measures + discount/growth_rate + name + tags + notes + status, with FK to impact_results.

Why: foundational for #14 (reports), #20 (cache), #B (compare), and any "save my analysis" UX. Currently the backbone has impact_results but no top-level user-facing concept. Riskwise-v2 has the right pattern (scenarios + scenario_results tables).

Phase: P3 must-have (precondition for #14 + #20).


B. Scenario comparison view

What it means: side-by-side or diff view of two saved scenarios. E.g., "with measure X" vs "without measure X" — compare EAD, frequency curves, maps.

Why: cost-benefit analysis is fundamentally comparative. Without a compare view, every CB analysis is "look at this number, then click back, then look at that number." Critical UX for the audience.

Refinement: - P3 lite: list view selectable + open both in two columns. - P4 full: difference map (where did damage change?), waterfall chart of contributing factors.

Cross-stack: UI primarily; backbone may need a compare endpoint for efficiency. Phase: P3 lite, P4 full.


C. Reproducibility metadata on every scenario

What it means: every saved scenario stamps engine_version, backbone_version, hazard_dataset_sha256, exposure_dataset_sha256, impact_function_id + version, random_seed. Riskwise-v2's provenance pattern.

Why: the single most important feature for the academic + regulator audience. "Reproducibility, transparency, auditability" is the marketing. Without this, that's a lie.

Phase: P3 must-have.


D. Sample dataset + onboarding flow

What it means: a "try the demo" path that runs a Greek flood scenario in 2 clicks with a pre-populated exposure + JRC hazard. New user lands → sees a populated map + result, then can swap inputs.

Why: time-to-value matters for adoption. A blank app with an empty wizard is a churn point.

Phase: P3 must-have for the marketing site / pilot demos.


E. Audit log

What it means: per-tenant log of "who did what, when, against what data" — runs, exports, settings changes, member adds.

Why: compliance (SOC2, ISO27001) and pilot customers in regulated environments will ask. Cheap given structlog is in place.

Refinement: audit_log(id, org_id, user_id, action, target_type, target_id, metadata, created_at). Append-only. Surfaced in admin settings page.

Cross-stack: backbone (table + write hooks in API), UI (admin view). Phase: P3 (write side), P4 (UI for browsing).


F. Marketing landing page (separate from app UI)

What it means: SSG site at climate-lama.com (or similar) with hero, features, pricing, docs link, sign-up CTA. Separate from the app UI repo.

Why: phase-3-hosted.md mentions "landing page." It's NOT the same surface as the app UI — different audience, different stack constraints (SEO matters).

Refinement: Astro (SSG, lightweight) or Next.js (broader feature set). Recommendation: Astro — minimal JS, fast, SEO-good, easy for a non-frontend-specialist to maintain.

Cross-stack: new repo climate-lama-marketing. Phase: P3 must-have.


G. Open data attribution surface

What it means: every hazard/exposure/impact-function dataset shows source, license, citation, original paper link in the UI — both in catalog and in scenario reports.

Why: credibility with academics + regulators. Aligned with Brand of transparency. Trivial to add if the data model has the fields (it should).

Phase: P3 (UI surface), data-model fields can be added incrementally.


H. Bulk scenario submission

What it means: power user uploads a CSV of N scenario specs (each row = one scenario) → backbone batch-submits → returns dashboard with N status rows.

Why: analysts running parameter sweeps shouldn't have to click "run" 50 times. Asked for in research / academic use.

Phase: P4 (not a launch blocker, but high ROI for the academic tier).


I. API client SDKs

What it means: published climate-lama-python and climate-lama-typescript packages, auto-generated from OpenAPI.

Why: when backbone goes open source, having SDKs is the difference between "yet another REST API" and "an actual platform." Auto-generation makes maintenance cheap.

Refinement: use openapi-generator or Stainless in CI; publish to PyPI + npm on backbone tag.

Cross-stack: new repos (or subdirs in backbone). CI pipeline. Phase: P3 (Python SDK for the academic/research audience), P4 (TypeScript SDK).


J. Webhook / async notification system

What it means: long jobs can post a webhook callback when done, instead of requiring poll. UI keeps polling; SDK users use webhooks.

Why: UI is fine with polling but SDK use cases (overnight pipelines, pre-warm scripts, large analyst workflows) want push.

Phase: P4 (not launch-critical).


K. Map drawing tools (ad hoc area)

What it means: user draws a polygon/rectangle on the map → "compute impact for this area only." Bypasses the need to upload an exposure for ad hoc analysis.

Why: high-impact for exploration UX — "what's the flood EAD for this neighborhood?"

Refinement: combined with a "default exposure layer" (e.g., LitPop or Worldpop) that's auto-clipped to the drawn area. Backbone exposes a "compute on bbox" variant.

Phase: P4 (genuine differentiator but non-trivial).


L. Frequency curve uncertainty bands

What it means: render mean curve + 5th/95th percentile bands on the frequency curve panel.

Why: engine 0.3.0 is slated to add uncertainty quantification per phase-3-hosted.md. UI should consume it. Without bands, the platform looks deterministic in a way climate isn't.

Phase: P3 if engine 0.3.0 is in P3, otherwise P4.


M. Cost model / metering for managed hosting

What it means: per-tenant counter of compute-seconds / runs / storage. Surfaced in admin settings + billable.

Why: phase-3-hosted.md mentions Stripe + tier enforcement but the actual metering needs to be designed. Without it, "10K calcs/mo" pricing is unenforceable.

Refinement: every compute job records compute_seconds, cpu_seconds, result_bytes. Aggregated in a usage table per tenant per billing period. Stripe metered billing API.

Cross-stack: backbone (instrumentation + table), UI (usage surface), infra (Stripe webhook handlers). Phase: P3 must-have for managed-hosting launch.


N. Engine plugin contract (for the future "pluggable engine" goal)

What it means: nail down how a third-party compute engine registers itself for selection at request time. Per plan.md's open cross-cutting points, this is unresolved.

Why: even if we don't ship a third-party engine in P3, designing the contract early prevents painting into a corner.

Refinement: write an ADR that defines: engine discovery (entry-point or env-var registry), engine capabilities (which hazards / impact-fn types), engine input/output dataclasses (already exists for climate-lama-engine), per-request engine selection (X-Engine header or request body field).

Phase: P3 ADR write-up, implementation in P4 / P5.


O. Privacy / data residency banner

What it means: for self-hosted users in regulated environments, surface "no external calls, no telemetry, your data stays here" prominently — both in UI and in marketing.

Why: it's already true (no external calls per plan.md). It just needs to be a marketed feature. Critical for govs and central banks.

Phase: P3 marketing site copy + UI footer.


P. Scenario versioning

What it means: when a saved scenario's underlying impact function or hazard data changes, generate v2 of the scenario; keep v1 immutable.

Why: reproducibility + audit + "I ran this report 3 months ago, what did I see?"

Phase: P4 (not launch critical but a credibility win for academic/regulatory audiences).


Priority ordering (not a timeline)

This section ranks the ideas by importance, not by when work should land. No timeline, no resourcing — only relative priority. The split into "Tier 1 / Tier 2 / Tier 3 / Tier 4" is about what's foundational vs. optional. The eventual phase plans will draw from here as capacity allows.

Tier 1 — foundational (everything downstream depends on these; missing them undermines the transparency/auditability claim):

  • #A Scenario model — top-level wrapper grouping inputs + results. Precondition for reports, cache, compare, versioning.
  • #C Reproducibility metadata — engine/data SHA stamps. The "auditability" claim is hollow without it.
  • #18 Monitoring (instrumentation) — Prometheus /metrics + structlog → Loki. Minimal code, huge ops leverage.
  • #17 Docs site (MkDocs Material) — OSS credibility + contributor onboarding.
  • #G Open data attribution surface — trivial if data model has the fields; massive credibility win.
  • #N Engine plugin ADR write-up — just the ADR, so we don't corner-paint.
  • #32 Adapter-by-design architectural commitment — the broader stance that drives #N and #24; codifies extension-point discipline across compute, ingest, reports, data sources.
  • MapLibre ADR (from Q4 R3) + dependency policy ADR (from A7). Frozen direction for the Tier 2 map work.

Tier 2 — core UX upgrades (what makes the UI feel like a platform, not a demo):

  • #3 UI redesign direction — wireframes + layout commitment before coding.
  • #4 + #5 Map foundation — MapLibre migration + zoom-aware aggregation (lite, on-the-fly). See Round 2 deep-dives for the tradeoffs.
  • #6 + #7 Datasets surface — unified browse/upload/preview for exposures and hazards (two tabs, one feature).
  • #8 Impact-function read-only visualization — chart + table view of the selected curve. The editor is Tier 3.
  • #9 Time horizon flexibility — discrete slider locked to dataset years.
  • #14 Reports (Tier 2 subset) — save scenario + standardized PDF template + map screenshot. The full report story is big enough to warrant its own phase (see Round 2 "Phase R").
  • #B Scenario compare lite — side-by-side view of two saved scenarios.
  • #16 Tooltips + info icons — powered by a glossary JSON that doubles as the docs-site glossary.
  • #D Sample dataset + onboarding — 2-click demo scenario.
  • #19 Initial dataset catalog (DO Spaces) — 3 curated datasets, JSON manifest.
  • #20 Predefined results caching — deterministic key + result_cache table.
  • #E Audit log (write side) — table + middleware writes; UI comes later.
  • #1 Brand customization + #15 i18n infrastructure — cheap up front, expensive to retrofit.
  • #2 Settings page — org and user settings; needed for multi-tenancy UX.
  • #21 Admin panel (new idea, Round 2) — admin surface where datasets, users, audit, jobs, and system health are curated.
  • #10 Adaptation measures catalog UX — the Phase 1 API has no proper UI yet.
  • #11 Configurable discount rate + growth — small backbone change, big credibility win.
  • #26 Map layering (new, R3) — hazard/exposure/impact as independent layers with legend + opacity + on/off. Foundational UX for trust and debugging; minimal incremental effort over #4.
  • #27 Data volume pipelines (new, R3) — admin-only downloads into DO Spaces + async/parallel ingest pipelines; critical for real datasets. Ties to #21 and #22.
  • #28 Playwright smoke suite (new, R3) — critical-path regression before any managed rollout.
  • #33 Translation XLSX round-trip (new, R3) — makes #15 i18n infrastructure actually pay off.
  • #Gap-7 Error taxonomy + error/warning/info UI (promoted from gap to Tier 2 in R3) — stable error codes + consistent UI presentation; unblocks i18n of errors and SDK quality.

Tier 3 — "nice-to-have" extensions (useful but non-critical; cut if scope pressure):

  • #8 Impact-function editor + save-as.
  • #10 Custom measure builder + sets — later-in-Tier-3 (user: "not immediate, not Tier 4").
  • #11 Per-country macroeconomic tables — later-in-Tier-3.
  • #12 Non-CLIMADA format adapters (Hazus, GEM, OASIS LMF) — promoted from Tier 4 in R3; later-in-Tier-3.
  • #13 Context overlays ("CRED-like tab") — data-model design cheap, UI on-demand. Later-in-Tier-3.
  • #14 Reports remaining subsets (c) + (b) full — export bundle + DB-backed snapshots. Later-in-Tier-3.
  • #L Frequency-curve uncertainty bands — gated on engine 0.3.0.
  • #K Map drawing tools — high UX value, non-trivial.
  • #P Scenario versioning.
  • #I Python SDK — auto-generated from OpenAPI.
  • #O Privacy banner — marketing copy, trivial to add once a landing site exists.
  • #F Marketing/landing site (Option A) — dedicated Astro site for mobile-shareable demos. Sequence: after the docs site (Option D) is live.
  • #22 External dataset polling (new idea, Round 2) — big platform value, but fully optional.
  • #23 Desktop-readiness affordances (new idea, Round 2) — architectural discipline now, real port later.
  • #M Observability slice (reframed in R2) — usage metering for admin dashboards; promoted back from Tier 4. Stripe wiring stays Tier 4.
  • #28 Storybook (new, R3) — component inventory and visual regression; introduce after #3 redesign.
  • #29 Deployment ADR + release pipeline (new, R3) — write the ADR and ship versioned image tags; K8s itself is Tier 4.
  • #30 Notifications system (new, R3) — minimum-viable in-app bell + table; gated on having async workflows that need surfacing (#27 downloads, #22 polling).
  • #32 First non-default adapter (CLIMADA engine adapter) (new, R3) — the reference implementation that proves #24 Level 2 and #32.
  • #33 Evaluate hosted translation tool (Weblate/Tolgee) (new, R3) — gated on XLSX workflow proving painful.
  • Gap-10/11/12 performance budgets + engine-call cost + log sensitivity — rolling Tier 3 artifacts, publish as we go.
  • Gap-14 Engine parity with CLIMADA benchmarks — Tier 3, not immediate; ties to A3 resolution.

Tier 4 — parking lot (file as issues, revisit when context changes; do not build against these):

  • #14 (e) Custom report templates — confirmed as an issue-for-later per user.
  • #H Bulk scenario submission — defer until a research user asks.
  • #J Webhooks — no pressure without SDK usage.
  • #24 User-provided engines (Level 3b) — remote webhook engine; security-sensitive.
  • #25 Building-level disaggregation + 3D streetview (new, Round 3) — explicitly flagged by the user as much-later work.
  • #29 Kubernetes + full managed-hosting infra (new, Round 3) — gated on managed hosting becoming real.
  • #31 In-app news / changelog landing page (new, Round 3) — "not a strong opinion" per user.
  • #M Stripe metering integration — billing wiring; observability slice stays in Tier 3.
  • Gap-5 GPU acceleration via JAX / CuPy — ADR only; implementation much later.
  • Gap-9 Security threat-model document — much later per user; revisit before any first public deployment.
  • Dataset marketplace / paid packages — latent end-goal item, not a near-term concern.

Items reclassified in Round 3 (user feedback: "not immediate but not Tier 4 either"): #10 custom measure builder, #11 per-country macro tables, #12 non-CLIMADA adapters, #13 context overlays, #14 (c) export bundles, and Gap-14 engine parity with CLIMADA benchmarks. All stay in Tier 3 but flagged as "later-in-Tier-3" — not the first Tier 3 things to pull off the shelf, but worth leaving in-scope rather than parking. The "not-immediate" signal should inform Phase planning, not trigger further demotion.

Pattern: file every deferred or Tier 4 item as a GitHub issue regardless. Per user direction, "even longshot ideas to be opened as issues and discussed in comments." This keeps the doc short and the discussion distributed.


Open questions — status

Updated after Round 3 discussion. All original Q1-Q8 now settled; Round 2 deep-dives remain below for reference.

# Question Status Resolution
1 UI license (open vs proprietary vs BSL) Settled (R3) Target Apache 2.0 eventually; stay in a private repo for now. Decision to open-source deferred until ready. The license target matches the backbone/engine story; the "private for now" lets us delay irreversible choices.
2 Which personas to optimize for? Settled (R3) Researcher + agency as primary. Revisit if a specific use case appears.
3 ~~Resource model / timeline~~ Dropped (R2) Private side project; no timeline or resourcing in plans.
4 MapLibre vs Leaflet Settled (R3) Plan MapLibre from the start. User preference: "don't leave more open work for later on" and MapLibre "feels more modern." Migrate the existing map-using components as part of Tier 2 rather than waiting for a forcing feature. Constraint: use only well-maintained, actively adopted libraries.
5 Managed-hosting-first vs self-hosted-first Settled (R3) Self-hosted-first; keep a managed-hosting path planned for the future in case it becomes relevant.
6 Docs tooling Settled (R3) MkDocs Material.
7 Cloud storage (DO Spaces vs R2) Settled (R2) DO Spaces (already provisioned). Revisit if egress becomes painful.
8 Branding name Held Keep "Climate-Lama" for now. Revisit later; user likes one-word names like "CLIMADA."

All eight original questions resolved or explicitly held. Round 2 deep-dives (below) capture the rationale for each. Any new open questions from Round 3 appear in the Round 3 section.


Round 2 — new ideas, deep dives, settled framing

This section captures the second-pass discussion: four new ideas from the user, feedback on Claude's Round 1 additions, a landing-site placement question, and deep dives on the open questions that were still ambiguous.

New ideas from the user

21. Extensive admin panel

What it means: a dedicated admin surface for users who hold the admin role. Home for user management, org/tenant management, dataset curation, job monitoring, audit log browsing, cache management, feature flags, system health, impact-function library curation, measure library curation.

Why: the Phase 2 multi-tenancy + RBAC landed the plumbing but there is no admin-facing UX for it. Without an admin panel, tenant admins can't do things like add members, toggle datasets, or see what's happening. Also, #E (audit log), #M (metering), #19 (dataset curation), #22 (polling review), and #21 all naturally surface here — avoid sprinkling admin UX throughout the normal UI.

Risks: - Admin panels become feature factories. Every "where should this go?" is answered with "the admin panel." Gate on role requires admin + there are at least two user-types that differ before adding a setting. - Admin UI touches everything → wide security surface. Keep it behind a separate route tree (/admin/*) with hard RBAC at the API layer too. - Consider shipping admin as a separate JS bundle (lazy-loaded) so regular users don't pay the download cost.

Refinement: - Top-level tabs: Users, Orgs, Datasets, Jobs, Audit, System, Branding. - Each tab is a thin UI over existing backbone endpoints (most exist from Phase 2). - Missing endpoints are additive: admin job search, cache inspector, system health aggregator.

Cross-stack: UI (admin routes + RBAC gate), backbone (admin-scoped endpoints, mostly exist), small data model additions (feature flags table, if we want runtime flags). Priority: Tier 2. Not a foundational item but very high-leverage once multi-tenancy has more than one tenant.


22. External dataset polling

What it means: a scheduled mechanism that watches external dataset publishers (JRC, Copernicus CDS, NOAA, Worldpop, CMIP6 ESGF, etc.) for new or updated raw datasets. On change detection, notify an admin (via #21) for review before ingestion, or auto-ingest for trusted sources.

Why: the platform's value grows with its dataset catalog. Manual curation doesn't scale. This is how a research platform stays current.

Risks: - Licensing is the hard part. Some sources (JRC, Copernicus) are free but require click-through acceptance. Auto-downloading without accepting may breach terms. Need a per-source policy: which sources allow programmatic fetch? - Source formats are heterogeneous — netCDF, Zarr, GeoTIFF, HDF5, proprietary binary. Each source needs an adapter. - False positives (small metadata churn triggers noise). Diff detection needs care — hash the payload, not the page. - Storage / egress cost if we download on every change instead of just flagging.

Refinement: - Design a DatasetSource abstraction: list_available() returns manifest entries; get_metadata(id) returns a descriptor; download(id, dest) fetches the file. Small plugin per source. - Celery beat task runs daily/weekly per source. Computes diff against dataset_source_catalog table. On change, creates an entry in pending_ingests queue visible in admin panel. - Admin reviews → approves → triggers ingest. No auto-ingest in v1; it's too easy to pull in bad data. - First sources to implement: JRC European flood (we already use it), Copernicus ERA5 (climate reanalysis), Worldpop (population). Skip CMIP6 ESGF initially — it's its own federation problem.

Cross-stack: backbone (new module ingest/sources/, DatasetSource ABC, Celery beat schedule), UI (pending-ingests list in admin panel). Priority: Tier 3. High platform value but full-feature effort — file as an initiative, not a single issue.


23. Desktop application readiness

What it means: the UI should be architected so that a future transition to a desktop app (Electron, Tauri, or similar) is cheap. Riskwise-v2 proved the Electron + local FastAPI pattern works; we don't need to port now, but we should not paint ourselves into a corner.

Why: desktop matters for two audiences — researchers in air-gapped environments (central banks, security research) and field use (NGOs without reliable connectivity).

Architectural rules to adopt now (cheap, hard to retrofit): - No hard dependency on browser-only APIs without abstraction. localStorage is fine (Electron/Tauri support it). Service workers are fine too. Specific to avoid: direct navigator.* calls in components, PDF-via-window.print (desktop WebView may differ), map tiles hardcoded to remote CDN. - API client works against any base URL. Already true — just keep it configurable. - Auth works without OIDC. A "local mode" (anonymous, single-user) must be a first-class config, not a bolted-on hack. Current auth scaffold (Phase 2) supports this via API-key mode. - Map tiles can be sourced locally. MapLibre supports offline tile packs via MBTiles; Leaflet does too with plugins. Don't hardcode a specific tile CDN. - i18n is bundled, not fetched at runtime. Already the case with i18next's default. - Settings must persist without a backend (LocalStorage fallback if no server). Cheap to add.

For the backbone to become desktop-embeddable (later work): - Single-binary compile via Nuitka or PyInstaller. The engine is already lean (no CLIMADA). Feasible. - Embedded DB option: SQLite with SpatiaLite instead of Postgres + PostGIS for the desktop profile. Non-trivial — would need SQL dialect checks in migrations. - Filesystem storage option: local folder instead of MinIO. Trivial if the storage abstraction is respected (it is).

Tauri vs Electron recommendation (when we get there): Tauri. Rust-backed, much smaller bundle (~10MB vs ~150MB), uses system WebView. More modern. Trade-off: Rust core means less community plugin ecosystem than Electron.

Cross-stack: UI architectural discipline now; no backbone work until we actually port. Priority: Tier 3 discipline (architectural affordances, minimal effort), Tier 4 for the port itself. File as an ADR so the rules are documented.


24. Engine pluggability

What it means: three distinct levels. The user's question was whether this is similar to my earlier N — yes, but extended.

Level 1 — already done. ModelInterface ABC in worker/models/base.py and EngineAdapter in worker/models/engine_adapter.py wrap climate_lama_engine. Adding a second engine means implementing the ABC.

Level 2 — pluggable engines at request time. Ship multiple engines; user picks which runs a given job (X-Engine: climate_lama / X-Engine: alternative). Examples: a CLIMADA subprocess adapter (runs CLIMADA in an isolated sidecar container), a GPU-accelerated fork of climate-lama-engine for large scenarios.

Level 3 — user-provided engines (user's specific ask). Let users bring their own computation. Two sub-options: - 3a. Python package reference: user specifies a package name + class; backbone imports it. Do not do this — arbitrary code execution in our workers is a security disaster. Even with sandboxing (gVisor, nsjail), the attack surface is huge. - 3b. Remote engine webhook: user specifies a URL + credentials. Backbone sends a request (with hazard, exposure, impact-fn data) and awaits a response conforming to our output contract. The engine runs entirely on the user's infrastructure; we just proxy.

Risks of Level 3: - Reproducibility is broken: we can't pin an external engine's version or code. Saved scenarios referencing a user-engine are reproducible only if the user's engine is stable and addressable. - Reliability: remote engines can be down, slow, or broken. - Result validation: we have to validate engine outputs strictly (shape, units, reasonable ranges) or users can corrupt their own results. - Security even for 3b: engine responses are untrusted input; treat with care.

Refinement: - Level 1 stays. - Level 2 as a design goal for N (existing idea): write the ADR, pick one alternative engine to prove the pattern. Candidates: a CLIMADA-via-subprocess adapter (interesting as a compat bridge), a pure-Python reference implementation for audit purposes. - Level 3b as Tier 4: file as GitHub issue with a security analysis. Not architecturally hard, just dangerous. Acceptable only if we ship result-validation guarantees. - Level 3a: reject in the ADR; document why.

Cross-stack: backbone (registry mechanism, request-level dispatch, result validation), engine (stays untouched; it's one implementation among many). Priority: Tier 1 ADR (#N write-up), Tier 3 Level 2 implementation, Tier 4 Level 3b.


Feedback on Round 1 additions

H — Bulk scenario submission (user unsure)

The honest read: bulk submission is a power-user feature for researchers running parameter sweeps. Classic use case: "I want EAD for Greece × 3 scenarios × 4 time horizons × 2 exposure variants = 24 runs." Today that's 24 button clicks.

Two framings: - If researchers are a prioritized persona (they are — see answered question #2), bulk submission is eventually table stakes. - If the UI is single-scenario-oriented (it is today), adding bulk submission creates pressure on the wizard flow to support batch states.

Recommendation: defer. But make sure the scenario API + data model are batch-friendly from the start (stateless submission, per-run status queryable independently). Then when a user asks, bulk is a thin UI on top. Don't build the UI; don't build the backend; just don't close the door.

Priority: Tier 4 (filed as issue, revisited on demand).


I — API client SDKs (user wants more info)

What it would be: a pip install climate-lama Python package and (later) npm install @climate-lama/sdk TypeScript package. Each wraps the REST API with typed classes, ergonomic methods, and error handling.

Example usage (illustrative, Python):

from climate_lama import Client

client = Client(base_url="http://localhost:8000", api_key=...)
hazard = client.hazards.get(id="...")
exposure = client.exposures.upload("exposures.csv", name="Athens buildings")
result = client.compute.impact(
    hazard_dataset_id=hazard.id,
    exposure_dataset_id=exposure.id,
    impact_function_id=...,
    year=2050,
    scenario="rcp45",
).wait()  # blocks until Celery job completes
print(result.ead, result.aai)

Compared to: writing raw requests.post("/v1/compute/impact", json={...}) and polling /v1/jobs/{id} manually.

Why it matters: - Audience fit: researchers live in Jupyter notebooks. A good Python SDK is the difference between "yet another REST API we had to wrap" and "a platform we integrate." - OSS signaling: a published package on PyPI says "this is ready to use." - Documentation: auto-generated SDK docs double as API docs.

How to build: - Option A — full auto-gen via openapi-generator-cli (free, Apache 2.0). Reads OpenAPI spec, emits SDK code. Cons: generated code is often clunky (weird type names, awkward parameter ordering). - Option B — hand-curated wrapper on top of auto-gen. Auto-gen provides the typed primitives; a thin hand-written layer adds client.hazards.list(...), .wait() for jobs, retry semantics. Best developer experience. More maintenance. - Option C — Stainless (paid SaaS). Polished SDK generation, but paid. Probably overkill for a side project.

Recommendation: Option B. Auto-generate the inner layer, wrap it by hand for the fun parts. Python first (matches researcher persona); TypeScript SDK only if an actual integration need emerges.

Priority: Tier 3. Not foundational; high value once OSS goes public.


M — Cost model / metering (user wants more info)

Round 1 framing: metering was Tier-1 critical for managed-hosting billing.

Round 2 reframe (after user deprioritized managed hosting pressure): drop from Tier 1. Metering for billing is premature until managed hosting is an active effort.

Butobservability remains useful even without billing: - "How many compute-seconds did tenant X use last month?" is an answer an admin panel wants. - "Is this tenant abusing the cache?" is useful. - The schema (usage table with org_id, user_id, period, compute_seconds, run_count, result_bytes) is simple and takes a few hours to instrument.

Recommendation: keep the minimal version (instrumentation + table + admin-panel chart) in Tier 2 for observability. Drop the Stripe integration to Tier 4. The table being there from early makes future billing trivial; it's not a lock-in.

Priority: Tier 3 observability slice (already reflected in #21 admin panel); Tier 4 Stripe integration.


K — Map drawing tools (user said "excellent")

Confirmed. Short restatement: user draws a polygon/rectangle/circle on the map; backbone runs impact calc with an auto-clipped default exposure layer (e.g., LitPop or Worldpop) restricted to the drawn geometry. Bypasses uploading an exposure file for ad-hoc exploration.

Dependencies: requires at least one "default exposure layer" available in the catalog (#19) + a "compute on bbox/polygon" variant of the impact API (new backbone endpoint).

Priority: Tier 3 — confirmed as a genuine differentiator. File as a feature issue.


Landing site placement (user asked)

User's question: separate project or subfolder inside the current project with later migration?

Options: - A. Separate repo (climate-lama-site or climate-lama-www). Clean separation, different stack (Astro is the recommendation — SSG, minimal JS, SEO-good, simple). Cost: one more repo to maintain; CI + deploy pipeline per repo. - B. Subdirectory in the backbone repo (site/ or marketing/). Co-located; no extra repo. Cost: the backbone repo is Python-centric and a Node/Astro subtree is ugly; also mixes deploy concerns (backbone Docker image vs static site upload to Cloudflare Pages). - C. Subdirectory in the UI repo — problematic because UI licensing is TBD, and we want the marketing/landing site to be open regardless. - D. Merge marketing into the docs site (#17, MkDocs Material). Unified "project site" with homepage + docs + blog. MkDocs Material actually supports this pattern via the "landing page" feature (first page of the docs is a marketing-styled hero).

Recommendation (Round 2): Option D for now, with the option to split later if needed. Reasons: - Marketing and docs have the same audience today (anyone curious about the project). - One deploy pipeline, one domain, one stack. - MkDocs Material's landing-page support is genuinely good. - If we ever need a dedicated marketing stack (designers involved, SEO-heavy strategy), we can split off at that point with no regret — the docs stay where they are.

Fallback to Option A if marketing becomes a serious effort that clashes with docs rhythm. Skip Options B and C.

Round 3 resolution: start with Option D, plan for Option A eventually. The user's additional argument for A: mobile-shareable demo URL. Showing a stranger a clean, polished landing page from a phone is significantly easier than "let me send you a PDF." This is real — agencies and curious individuals often get their first impression on mobile. Sequence: 1. Today: Option D (MkDocs Material with a landing-styled first page). Costs nothing extra if we're already building docs. 2. Later (e.g., when we go public with Apache 2.0): Option A — split marketing into climate-lama-site on Astro (see #F). A minimalist Astro landing page is cheap to build (1-2 days for something credible-looking) and pays off every time we want to share. 3. No fallback direction — both options are planned, just in sequence.

The docs site survives either way; the landing page moves from "first page of docs" to "dedicated site linking to docs."


Deep dive on the remaining open questions

Q1 — UI license (open vs proprietary vs BSL)

Options: - Open (Apache 2.0, same as backbone + engine). One license across the project. Anyone can run, fork, embed. Forks can sell managed hosting. - BSL (Business Source License, MariaDB's model). Code is public; non-commercial use is free; commercial use (selling hosting) requires a paid license until the license converts to open after N years (typically 4). Adopted by MariaDB, CockroachDB, MinIO (historically), Sentry. Social enforcement only. - Proprietary (closed source). UI is a bundled binary shipped with managed hosting. Self-hosters can't run the UI without a license agreement.

Pros / cons:

Open (Apache 2.0) BSL Proprietary
Adoption Max — anyone can deploy Good — free for non-commercial Low — pay-to-play barrier
Forkable for hosting Yes No (license restricts) No
Coherence with backbone/engine Identical license → simple story Confusing ("is it open?") Conflicting message
Revenue protection None directly Protects managed-hosting moat Strong gate
Contributor friendliness High Medium (contributors have to sign CLA for non-copyleft use) Zero
Simplicity Trivial Needs custom license text + clock Trivial
Story for agencies / regulators Clean ("open source") Caveats Harder to sell "auditability" on closed code

Claude's take: Apache 2.0 for the UI. Reasons: 1. Coherence: "backbone + engine Apache 2.0, UI TBD" is a question mark that undermines the marketing. "All three Apache 2.0" is a statement. 2. Moat thesis: for this audience (agencies, researchers, NGOs), the moat is operational (running hosting well, curating data, support) and reputational (credibility built through docs + contributions), not code-exclusivity. Even if a third party forks and sells hosting, they take on the ops burden; we keep the thought leadership. 3. For a private side project, the BSL overhead (drafting, converting clock, CLA discussions) isn't worth it for zero immediate competitive threat. 4. If a specific competitor threat emerges later, we can license newer versions differently — BSL is not impossible to add later. Starting open is the low-regret default.

Caveats: If the user plans to pursue managed hosting aggressively AND expects direct competitors, BSL becomes more defensible. But the user specifically said this is a side project without commercial pressure — that's the condition under which Apache 2.0 is right.

Round 3 resolution: target Apache 2.0 as the eventual UI license (matches backbone + engine), but keep the repo private for now. Rationale: the user's realistic read is that agencies with internal IT won't pay for managed hosting, so betting on UI-license revenue is weak. Going open preserves the "clean open-source story" (which has more persuasive weight with agencies than a private/BSL hybrid ever would), while "private for now" keeps the flip-it-open decision reversible until we're ready. The move to a public repo is itself the future decision point — at that time we commit to Apache 2.0 unless the competitive context has materially changed.

Recommendation: Apache 2.0 target, private repo today, flip when ready.


Q2 — Personas (reframed)

Not a license blocker anymore; reframed. Since this is a private side project, "which customer type?" isn't the framing — the framing is "which personas do we optimize features for?"

Candidate personas: - (a) Researcher (academic, PhD candidate, climate scientist) — needs reproducibility, Jupyter-friendly SDKs, bulk runs, cite-ability, transparent math. - (b) Government / agency analyst — needs self-hosting, data residency, auditability, regulator-ready report exports. - (c) Development bank analyst — needs CB analysis, discount rates, CRED-like macro context, multi-country views. - (d) Smaller insurer actuary — needs portfolio views, frequency curves, insurance overlays (deductible/cover), reinsurance treaties. - (e) Myself + anyone curious — build what's interesting; aim at durable, not niche-fit.

Claude's take: optimize for (a) researcher + (b) agency. These are the most compatible with the platform's ethos (open, transparent, self-hostable, reproducible) and with how it's actually built today. Feature prioritization leans toward reproducibility (#C), SDKs (#I), dataset catalog (#19), audit (#E), docs (#17), report export (#14). De-prioritizes insurer-specific features (portfolio optimization, reinsurance treaties) and dev-bank-specific features (heavy CB tooling) unless they also serve researchers/agencies.

Recommendation: adopt researcher + agency as the primary personas. Use them when resolving "should we add feature X?" — if it doesn't help either persona, defer.


Q4 — Leaflet vs MapLibre GL

Dimension Leaflet MapLibre GL
Rendering DOM + Canvas WebGL
Maturity Since 2011; rock-solid Forked 2020 from pre-v2 Mapbox GL; maturing fast
Plugin ecosystem Enormous (every conceivable plugin exists) Smaller but growing (most Mapbox GL plugins portable)
Vector tiles (MVT) Via Leaflet.VectorGrid plugin (works but awkward) Native, core feature
Raster tiles Native Native
Heatmaps leaflet.heat plugin (workable) Native heatmap layer
Performance with 100k+ points Struggles without clustering Smooth (GPU)
3D (terrain, buildings) No Yes
Styling CSS-style objects via JS JSON style spec (Mapbox-compatible) — very powerful
Feel Looks like 2015 Looks like 2025
GPU requirements Negligible Moderate (dips on very old hardware)
Learning curve Minimal Steeper (style spec, layer types)
Offline tile packs Via plugins Native via MBTiles
License BSD-2 BSD-3
Migration cost from Leaflet N/A Moderate — map-using components need rewriting; API client + GeoJSON data unchanged

Migration effort concretely (based on current UI file structure): - Affected files: MapView.tsx, ResultMap.tsx, AssetDetailPanel.tsx (map interactions), possibly HazardUploadModal.tsx (preview thumbnail). - Unaffected: API client, data formats (GeoJSON), hooks (useJobPoller), auth, wizard, panels. - New dependencies: maplibre-gl, @maplibre/maplibre-gl-leaflet (bridge, optional during migration), typing packages. - New concepts to learn: style spec (JSON), layer types, sources, interaction model.

Claude's take: migrate to MapLibre, but don't do it "pre-emptively." Trigger the migration when the first feature request that Leaflet makes painful arrives — most likely #4 multi-modal hazard rendering or #5 zoom-aware aggregation via vector tiles. Both are Tier 2.

If we stay on Leaflet: we can still ship most of Tier 2, but with workarounds. Heatmaps via plugin; vector tiles via Leaflet.VectorGrid (clunky); point clustering via markercluster. Visual ceiling is clearly lower.

Trade-off on "never worked with MapLibre" concern: the style spec takes a week or two to get fluent in; after that it's not harder than Leaflet. The community docs are good. And — crucially — MapLibre's style spec is the same as Mapbox's, so learning it pays off beyond this project.

Recommendation (Round 2): plan for migration, sequence it with the first map-centric Tier 2 feature. Don't migrate for migration's sake.

Round 3 resolution: plan MapLibre from the start, not as a deferred migration. The user's preference is to avoid leaving this as "later work" and to get the modern feel now. Migration happens during Tier 2 map work rather than waiting for a Leaflet-breaking feature to force it. Guardrail (user direction): only adopt libraries that are well-designed, well-maintained, and actively used by the community — no random packages that may be abandoned in a year. Concretely: MapLibre GL core is fine (BSD-3, actively maintained, backed by multiple companies post-Mapbox fork). For plugins (MVT, clustering, drawing), vet each against activity (commits in last 6 months), test coverage, and real-world adopters before pulling in.

Implementation notes: - Libraries to pin: maplibre-gl (core), @maplibre/maplibre-gl-inspect (debugging), @mapbox/mapbox-gl-draw works against MapLibre (drawing tools, needed for #K). For MVT we use MapLibre's native support — no separate plugin needed. - Base tile source: use OpenStreetMap via a well-maintained tile proxy (MapTiler, Stadia Maps) or self-host via a container. Avoid free-tier throttled sources. - Migration order: MapView.tsxResultMap.tsxAssetDetailPanel.tsx (map interactions) → HazardUploadModal.tsx (preview thumbnail). Do as one Tier 2 initiative, not leaked changes. - ADR: write an ADR that freezes the decision and records the library whitelist so future contributors don't drift.


Q5 — Managed-hosting-first vs self-hosted-first

Context: the user leans managed but wants Claude's take.

Managed-first pros: - Consistent env (we control Postgres version, Redis config). - Faster iteration loop (one place to deploy, one set of logs). - Observability baked in from day one. - Revenue path is cleaner (if/when monetization matters).

Managed-first cons: - Self-hosters become second-class → fragments the community. - Features subtly accrete managed-only assumptions (cloud storage URLs, hosted auth providers, managed Redis cluster configs) that make self-hosting a pain. - "Self-hostable" stops being a feature and starts being a legacy support burden. - The personas we prioritized (#2: researcher + agency) will self-host, always.

Self-hosted-first pros: - Forces discipline: no external dependencies, everything Docker-composable, clean env-var contract. - Matches the transparency/auditability brand — agencies and researchers will only self-host. - Makes the OSS story real: anyone can spin up a full instance in 10 minutes. - Managed hosting layers on top later with mostly infra work (Terraform, TLS, CI/CD); it's not a rewrite.

Self-hosted-first cons: - Slower feedback loop on real usage (we don't see anyone's telemetry). - Every infra change has to be validated against multiple deployment modes. - Revenue is indirect (support, data packages) if we want it later.

Claude's take: self-hosted-first, with the explicit plan that managed hosting is a subsequent layer, not a parallel track. Reasons: - Matches the persona choice (researcher + agency both self-host). - The backbone is already designed to be self-hostable (Docker Compose in place; no external calls). Leaning into that is less work than fighting it. - Avoids the classic OSS mistake: building a "cloud version" that diverges from the "community version." Every dev-productivity tool ever has hit this wall (GitLab CE vs EE, Sentry, etc.). Starting managed-first makes it almost inevitable; starting self-hosted-first makes it optional. - Zero immediate revenue pressure → no reason to front-load hosting infra work.

Even for a side project where we might eventually offer managed hosting, starting self-hosted-first gets you to "someone can try this at all" faster, because the bar is "does it work on my laptop" not "is the hosting infra ready."

Recommendation: self-hosted-first. Managed hosting becomes a separate phase when/if it becomes relevant.


Q6 — Docs tooling (Sphinx vs MkDocs Material vs Docusaurus)

User only has Sphinx experience. Full pros/cons:

Sphinx: - Pros: Python ecosystem default; great for auto-generated API docs via autodoc; deep cross-referencing; LaTeX / PDF output built in. - Cons: reStructuredText is awkward (though Markdown support exists via myst-parser); default themes look dated; "ReadTheDocs" aesthetic is tired; ecosystem skews Python-only.

MkDocs Material: - Pros: Markdown-first; beautiful default theme; Python-native; mkdocstrings for auto-generated API docs; trivial to set up (pip install mkdocs-material + one config file); easy to migrate from Sphinx (your rST knowledge transfers); good for mixed technical+user docs; active maintenance. - Cons: Less feature-rich than Sphinx for very complex books (multi-volume, indexing); search is good but not Algolia-grade without DocSearch integration; no built-in versioning (plugin exists).

Docusaurus: - Pros: Modern React-based; built-in versioning, i18n, Algolia DocSearch, MDX (React components in docs); strong for interactive docs; fast; good for OSS projects with many contributors. - Cons: Node.js toolchain (extra dep); heavier than needed for small-to-medium docs; MDX power invites markup complexity; feels overkill for Python-backed projects.

Evaluation against our needs: - Content: technical ref (API, ADRs, deploy) + user docs (how-to, glossary, tutorials). - Audience: developers + domain users; comfort with Python docs, React optional. - Rhythm: one contributor initially; docs evolve alongside code. - Fit with rest of stack: backbone is Python; engine is Python; UI is TypeScript/React. Even split. - Migration from Sphinx: trivial with MkDocs Material (rST → Markdown is near-1:1); moderate with Docusaurus.

Claude's take: MkDocs Material. Reasons: 1. Markdown-first, which matches the existing doc conventions (CLAUDE.md, plan.md are all Markdown). 2. Python-native, staying in the same ecosystem as backbone + engine. 3. Minimal config: mkdocs.yml, one theme line, and you have a beautiful site. 4. Low risk: easy to swap to Docusaurus later if we ever want MDX or deep versioning. 5. Migration cost from Sphinx is low: most rST concepts port, and the user already understands the domain.

Pick Docusaurus only if there's a concrete need for MDX (embedding React components in docs) or multi-version docs at scale — neither applies now.

Recommendation: MkDocs Material. Start simple, set up DocSearch (free for OSS) later if search matters.


Other topics flagged from Round 1

#5 zoom aggregation — deep dive (user wanted more info)

Concretely: what the feature does. - Current: GET /v1/results/{id}/geojson returns every exposure point as a GeoJSON feature. At 100k points, Leaflet chokes; at 1M points, the browser crashes. - Goal: at country-level zoom, show one feature per country. At admin-1 (state/province) zoom, one feature per admin-1 unit. At admin-2 (county), one per admin-2. At city zoom, raw points.

What needs to be built: 1. Admin boundary data — a new table admin_boundaries(id, iso3, admin_level, name, geom, parent_id). Source: GADM (global, ~5M rows across 3 admin levels) or Natural Earth (coarser, free). Loaded once during deployment. 2. Spatial aggregation — at the end of an impact job, compute result_aggregates(result_id, admin_boundary_id, ead_sum, aai_sum, point_count) via PostGIS ST_Contains + GROUP BY. For 100k points: ~seconds. For 10M points: ~minutes. Pre-computed, not live. 3. Query endpointGET /v1/results/{id}/geojson?admin_level=1&bbox=... returns the right aggregation level. 4. UI zoom handler — map listens to zoom changes; swaps admin level accordingly; refetches.

Phased approach: - Tier 2 lite: skip pre-computation. Live aggregate via PostGIS per query. Works for <1M points. Leaflet can render polygon aggregates (country/admin-1). No tile server needed. - Tier 3 full: introduce martin (Rust tile server, PostGIS-native) to serve MVT vector tiles. Aggregates are precomputed and served as tile pyramid. Scales to 100M points. Requires MapLibre (since Leaflet's MVT support is painful).

Dependencies: - Admin boundary data (political sensitivities — GADM is the de-facto standard for research; using it is defensible). - Spatial indexes on exposure.geometry (should exist; verify). - UI: zoom handler + layer swap (simpler on MapLibre).

Claude's take: ship the lite version (no martin, no vector tiles) as the Tier 2 baseline. It's a huge UX upgrade over "dots everywhere" and can be built without MapLibre migration. When the dataset size or UX polish demands it, graduate to full vector tiles as a Tier 3 effort.


#14 reports — "almost a phase in itself"

Agreed with the user. This decomposes cleanly into five subsets (a-e) spanning backbone (Scenario model, report-rendering worker, template store) + UI (save/load/compare/export + scenario list page) + content (template design, charting).

Proposal: a parallel "Phase R — Reports and reproducibility" track, scoped explicitly. Not "another phase" in the linear timeline sense, but a dedicated workstream with its own issues and the Scenario model (#A) as its single dependency.

Phase R scope (strawman): - (a) Scenario model + save/load API + UI. - (d) Standardized PDF report via Quarto (recommended — see below). - (b) Map screenshot (canvas export; light version). - Integration with #B (compare) and #C (reproducibility metadata on every scenario).

PDF generation deep-dive: - Options evaluated: - WeasyPrint: Python, CSS-based HTML→PDF. Good for tabular layouts; charts via SVG. Simple. But chart rendering via matplotlib-SVG is fiddly. - ReportLab: Python, low-level. Powerful but verbose; "draw a table cell at x, y" tier of control. - Headless Chrome via Playwright: HTML → PDF via real browser. Highest fidelity; heavy dependency (~200MB of Chromium). - Pandoc: Markdown → PDF via LaTeX. Old reliable; requires LaTeX toolchain installed. - Quarto: Markdown + Python code blocks → multi-format output (PDF, HTML, DOCX). Handles matplotlib/plotly charts natively. Becoming the de facto standard for academic publishing. Aligns with "transparent, reproducible" branding.

  • Claude's recommendation: Quarto as the primary generator, with WeasyPrint as a fallback for environments that can't install Quarto. Quarto's "render this .qmd template with these parameters" model matches exactly what we want for scenario reports: a template file that can be shipped with the backbone, re-rendered per scenario via a Celery task.

(e) Custom report templates — deferred per user confirmation: "it's something that we can have it as an issue for later." File as a Tier 4 issue.

Phase R would be proposed as a sibling/successor to Phase 3, not a blocker. Open it as a planning discussion once Tier 1/2 work is underway.


Policy: Tier 4 ideas as issues

Per user direction, every idea in this document — even the ones we defer or reject — should eventually become a GitHub issue in the appropriate repo (CortoMaltese3/climate-lama for cross-cutting; climate-lama-engine for compute; climate-lama-ui for UI-only) with the right milestone/label.

Why: (a) discussion is better distributed across issue threads than in one mega-doc; (b) priorities can change and Tier 4 items can become real fast; (c) issues are searchable in a way this doc isn't.

Labels to adopt: - tier-1-foundational - tier-2-core - tier-3-extension - tier-4-parking-lot - needs-adr — for ideas that should graduate to an ADR before implementation - epic — for multi-issue initiatives (Phase R, admin panel, etc.)

Process (updated in Round 3): the user prefers to plan phases first and open issues scoped to each phase as it starts, rather than spawning 30+ issues up front against a single large doc. This file stays the master list; per-phase issue creation is the mechanism. If issue-up-front later makes sense (e.g., many contributors, want parallel discussion), we can revisit — nothing stops us from doing both.


Cross-cutting themes and previously-unflagged gaps

Items that are not stand-alone ideas but cut across many of them, plus things I missed in Round 1.

Gaps I didn't flag in Round 1

  1. Onboarding for self-hosters. Docker Compose is the current story; still requires Docker fluency. A uvx climate-lama quickstart command that seeds demo data and prints a URL lowers the bar dramatically. Aligns with #D.
  2. Test data fixtures. Every new hazard/exposure integration needs realistic test fixtures, which can be as hard to produce as the feature. Maintain a small fixture generator or document how to shrink real datasets for CI.
  3. Upgrade path for existing deployments. Alembic handles schema migrations; impact-function seed changes and data-format changes are less clean. Needs a policy: versioned impact functions, data-schema compatibility contracts.
  4. Internationalization of data. Dataset names, impact-function descriptions, measure names live in the DB — they also need translation, not just UI chrome strings. Design schema with locale-capable fields.
  5. GPU acceleration for the engine. Engine 0.2.0 is numpy/scipy. For 10M-point scenarios, JAX or CuPy could 10-100× speedup. Tier 4 / much later, but an ADR would pin the design.
  6. Performance benchmark suite. Regression tests for the engine. Without benchmarks, any refactor can silently slow the compute path 2× and we don't know. Per-hazard fixtures + pytest-benchmark.
  7. Error taxonomy for end users. Current backbone has internal exceptions; UI sees generic messages. Introduce a catalogued error-code system (E_HAZARD_INTENSITY_UNIT_MISMATCH, E_EXPOSURE_OUT_OF_BBOX, etc.) with human messages and stable codes. Also unblocks i18n (#15) and SDK quality (#I).
  8. Data versioning. When a dataset updates (JRC publishes flood v2024.2), how do we version it? Copy + new ID? Overwrite + bump field? Saved scenarios pinned to a version must still work. Design now to avoid painting into a corner. Related to #P (scenario versioning).
  9. Security threat model. For regulated self-hosters (central banks, government), we need a threat-model doc: what attacks are in scope (malicious dataset upload, SQL injection, MitM on API), what's out (physical access, insider threats). Aligns with the "auditability" claim.
  10. Performance budgets. "How many points before this architecture breaks?" is unclear. Publish a benchmark ("100k points: <1s; 1M points: ~10s; 10M points: plan ahead"). Guides both engineering and user expectations.
  11. Cost of engine calls. Each job allocates CPU + memory. Knowing "N points ≈ X seconds" is useful for capacity planning (#M observability) and for users to estimate their runs.
  12. Log sensitivity classification. structlog captures a lot; some fields (user IDs, exposure values, tenant metadata) may be sensitive in regulated contexts. Document which log fields are PII-grade and allow them to be filtered per deployment.
  13. Dependency policy. The engine stays lean (20KB vs CLIMADA's 500MB) as an explicit stance. The backbone and UI don't have a similar stance. Decide a policy: every added dep requires an ADR or a committed rationale.
  14. Engine performance parity with CLIMADA. We removed CLIMADA for leanness. Have we benchmarked that the engine 0.2.0 is within X% of CLIMADA's speed on the same hazard? Credibility risk: if a researcher asks "is this slower than CLIMADA?" we need a defensible answer.

Risk clusters — ideas that travel together

These groupings matter because solving one forces (or enables) the others.

  • Map stack: #4 (multi-modal layers) + #5 (zoom aggregation) + #K (drawing tools) + potentially MapLibre migration. Deciding MapLibre yes opens all three; Leaflet no-migration ceilings all three.
  • Scenario core: #A (Scenario model) + #14 (reports) + #B (compare) + #20 (cache) + #C (reproducibility metadata) + #P (versioning). Scenario is the foundation; everything above bolts onto it.
  • Multi-tenancy UX: #21 (admin panel) + #E (audit log) + #2 (settings) + #M (metering observability) + #19 (dataset curation). Once Phase 2 plumbing has an admin surface, all these naturally attach to it.
  • Extensibility surface: #6 + #7 (Datasets surface) + #22 (external polling) + #N + #24 (pluggable engines) + #12 (non-CLIMADA formats). This is the platform's "openness" story; half-building it leaves a demo, not a platform.
  • Branding / polish: #1 (customization) + #15 (i18n) + #16 (tooltips) + #3 (redesign). Best designed together; the glossary that powers #16 also surfaces in #17 (docs) and #15 (translation keys).
  • PDF + reproducibility: #14 (reports) + #C (metadata) + #G (attribution). A report that fails any of {reproducible inputs, cited sources, stamped versions} fails all three.

Explicit dependency map

Bottom-up (things at top have no dependencies; things at bottom depend on things above):

  • No deps: #1 brand, #15 i18n infra, #17 docs, #18 monitoring instrumentation, #16 tooltips, #9 time horizon, #G attribution, #E audit log write side.
  • Needs Scenario model (#A): #14 reports, #B compare, #20 cache, #P versioning.
  • Needs Datasets surface (#6/#7): #19 catalog UI, #22 polling UI, #K drawing (partially — needs default catalog layer).
  • Needs admin panel (#21): user-facing dataset curation (#19), audit log browsing (#E UI), metering chart (#M observability), pending-ingests review (#22).
  • Needs MapLibre: #5 full zoom aggregation via vector tiles, #4 some layer types (heatmaps work on Leaflet but feel constrained).
  • Needs IngestAdapter ABC: #12 non-CLIMADA formats, #22 external sources.
  • Needs engine registry + dispatch: #N level 2, #24 level 2/3b.
  • Needs error taxonomy: #15 i18n of errors, #I SDK error classes.

Open questions from Round 2 — Round 3 resolutions

# Question Round 3 resolution
A1 Seed a test-data generator alongside the real catalog? Yes. Start with test data; as real-world datasets become available, seed the demo catalog with them for a "feels real" effect. Both, not either/or.
A2 Upgrade-path policy for impact functions + data schemas? Go with the suggested direction (versioned impact functions + data-schema compatibility contracts). Write it up as an ADR when #P scenario versioning lands.
A3 Engine parity with CLIMADA? Yes, but much later. Approach: run the same scenarios with same inputs through both engines and check results programmatically. Can also provide both as external testing tools for researchers to validate. Not a P3/P4 item.
A4 ADR committing to MapLibre now? Yes — per Q4 Round 3 resolution, commit to MapLibre now and write the ADR as part of the Tier 2 map-stack work. The ADR should also pin the library whitelist (see Q4).
A5 Single MkDocs site vs separate marketing? Start single (D), plan to split (A) later — per the landing-site resolution above.
A6 Issue-filing pass — all at once or incrementally? Plan phases first, file issues per-phase. User reasoning: narrower scope per phase is easier to maintain than a 1500-line document with satellites. This also matches how agents consume context (a phase-scoped milestone is tractable; 30+ parallel issues against a monolithic draft is not). Keep this doc as the master index; per-phase plans draw from it. If the user later wants the up-front-all-issues approach, we can add it without regret.
A7 Explicit ADR on dependency policy (gap 13)? Yes, especially given the MapLibre decision brought library-vetting to the front. The ADR should codify: (a) every new dep needs a one-paragraph rationale in the PR; (b) activity / maintainer / adopter criteria (last-commit-recency, test coverage, production users); (c) preference for stdlib or already-pulled-in deps; (d) license compatibility (Apache 2.0 / MIT / BSD only).

All A1-A7 now resolved. New questions surfaced in Round 3 are captured in the next section.


Round 3 — new ideas, settled questions, gap feedback

New ideas from the user

25. Building-level disaggregation + minor 3D / street-view

What it means: at extreme zoom, disaggregate down to individual buildings or street segments. Explore per-building flood / fire / earthquake / TC risk. Optionally render a light 3D (extruded buildings, maybe a Google-Street-View-style panorama overlay for specific locations).

Why: at a portfolio level, the product answers "what's the EAD for this country?" At a building level, it answers "what's the flood depth at this specific address, and how does the building react?" — that's the difference between macro analysis and operational planning (which is where municipal planners and reinsurance actuaries live).

Risks: - Data quality is the gating constraint. Building footprints (Microsoft Building Footprints, Overture Maps, OSM) are good in some regions, awful in others. Street-level imagery (Mapillary, KartaView) has even spottier coverage. - Compute cost explodes — 1M buildings × N return periods × M scenarios = a lot of points. - 3D rendering works on MapLibre (extrusions + terrain) but adds UX complexity. - Google Street View is licensed; Mapillary (Meta, open) is the realistic alternative.

Refinement: - Would require: building-footprint ingest adapter (Microsoft / Overture / OSM), per-building impact aggregation, MapLibre 3D extrusion layer, optional external-imagery iframe. - Makes sense only after #4 (multi-modal rendering) + #5 (full vector-tile aggregation) are in place.

Cross-stack: major engine + backbone + UI work. Priority: Tier 4 (user explicitly flagged this as "much later"). File as a single exploratory issue so it stays discoverable without promising any of it.


26. Map layering — hazard maps, exposure maps, filter-driven views

What it means: today the UI primarily renders impact results. The user wants first-class visualization for hazard intensity maps and exposure maps as independent layers, plus a filter model that produces different views (e.g., "show only residential buildings" or "show only exposures where damage > €1M"). This complements #4 (multi-modal rendering).

Why: "impact = exposure × hazard × impact function" is the model. Users need to inspect each input layer on its own to build trust and to debug. "Why is the flood EAD so high in this neighborhood?" is sometimes answered by looking at exposure, sometimes at hazard — not by looking at impact.

Risks: - Layer management UX can sprawl. "13 toggleable layers with opacity sliders" is the anti-pattern. Keep it disciplined. - Hazard intensity rendering (raster) and exposure rendering (points/polygons) are technically different — but MapLibre handles both natively. - Filters that produce client-side views are easy; server-side filtered data fetches require backend support and can combinatorially explode query paths.

Refinement: - Tier 2: a three-layer toggle (Exposure / Hazard / Impact) with sensible default styling per hazard type. Legend, opacity, on/off. That's it. - Tier 3: filter layer — attribute-based (residential/commercial, building age), threshold-based (damage > X), spatial (within drawn area — ties to #K). - Leverage the LayerSpec model from #4 — hazard and exposure layers are just LayerSpec entries.

Cross-stack: backbone serves hazard and exposure as tile or GeoJSON layers via existing Datasets endpoints; UI adds the layer panel + filter UI. Priority: Tier 2. Relevant users will ask for this on day one of trying the tool; it's also a credibility fix for the "we only show pre-cooked impact maps" perception.


27. Data volume — admin-only dataset downloads, pipelines, schedulers, async/parallel ingest

What it means: the platform must handle large raw datasets safely. Two sides to this:

  1. Download side: admins (not end users) trigger raw dataset downloads from external sources (CLIMADA data API, JRC, Copernicus, etc.) into DO Spaces. The download itself must be resumable, observable, and async. End users never see the raw bytes; they see the processed models.
  2. Ingest pipeline side: once raw data is in DO Spaces, an admin-triggered pipeline transforms it into our DB models (scenario, hazard intensity grid, etc.). This pipeline must be async, chunked/parallel, and robust against partial failure. The app must stay responsive during ingest — no blocking the main service.

Why: climate datasets are huge (CMIP6 outputs are gigabytes per model-experiment-variable). Syncing "download new hazard" to "visible in UI" naively breaks everything. Separating the admin download path from the live user path is the only scalable design.

Risks: - Long-running downloads need robust state management (paused, resumed, partial-failed). Celery + Redis handles this but needs intentional design. - Memory pressure during ingestion is real — 5GB netCDFs don't fit in RAM. - Operators need visibility: a progress bar, logs, retry, cancel. This is ops work, not a 1-day feature. - Security: admin endpoints to download-from-URL are an obvious SSRF surface. Must URL-whitelist + audit.

Refinement (breaks into clear pieces): - Admin download service: Celery task that accepts a source URL + target path in DO Spaces, streams bytes (chunked, not loaded in memory), records progress in a downloads table. Admin UI surface: "Download this dataset" with progress, logs, retry, cancel. - Ingest pipeline: Celery chord (parallel subtasks + callback) that consumes a DO-Spaces URI, validates, chunks, and writes to the DB. Chunk size configurable per hazard type. - Notification hooks: download complete → notify admin (email + in-app via #30); ingest complete → same. - Scheduler integration: admin can schedule a download-then-ingest chain for a specific time (overnight). - Overlap with #22 (external dataset polling): the polling flow notifies admin; the admin triggers this download+ingest pipeline. These are two sides of one workflow.

Cross-stack: backbone (new Celery tasks, downloads table, streaming helpers, SSRF hardening), admin UI (download-manager surface in admin panel, see #21), infra (DO Spaces bucket layout, monitoring alerts on stalled downloads). Priority: Tier 3 (Round 4 adjustment). Ingest and downloads are admin-facing and happen before production use — they're not a runtime bottleneck for end users and can be handled manually or semi-manually during Phase 3 without blocking the product. The scalable async pipeline is still the right design target, but it is not Tier 2 critical. The Tier 2 critical piece is the read side — see #27b below and #5 — because that's where end users experience data volume.


27b. Large-dataset read-side performance (map rendering)

What it means (Round 4, split out of #27): when users open a hazard / exposure / impact map for a large dataset (10M+ rows, continental-scale hazard rasters, multi-variable CMIP6 outputs), the UI must stay smooth — sub-second interactions, no browser-tab memory blow-up, no dropped frames on pan/zoom. This is the user-facing performance concern. The ingest side (#27) can be slow and admin-gated; the read side cannot.

Why: the product is "you can explore climate risk on a map." If the map stalls at the scale of real datasets, the product does not work, full stop. This is the single highest-impact performance concern in Phase 3.

Risks: - Naive "fetch all points as GeoJSON" breaks above ~100k features. - Client-side filtering over millions of features is infeasible — forces us into server-side queries with careful indexing. - Raster hazards (netCDF → COG) and vector exposures (points/polygons) need different rendering paths; one strategy does not fit both. - Caching invalidation is its own problem (rebuild tiles when underlying data updates).

Refinement (consolidated with #4 and #5): - Vector exposures: server-side aggregation by zoom (country → admin1 → admin2 → raw points), served as MVT via martin (or pg_tileserv). Client renders with MapLibre's native MVT support. This is exactly #5 — call it out as Tier 2 critical, not "P3 lite + P4 full." - Raster hazards: publish as Cloud-Optimized GeoTIFF (COG); serve via titiler or similar; render with MapLibre raster source. Avoid GeoJSON for rasters. - Viewport-based loading: bbox query param on dataset fetch endpoints; never return the whole dataset unless explicitly requested. - Tile caching: tiles generated on first request, cached in DO Spaces under tiles/{dataset-id}/{z}/{x}/{y}.pbf (or .tif); invalidated when the dataset version bumps. See B2 bucket layout resolution below. - Acceptance target (Round 4 calibration, to be refined): pan/zoom on a 10M-point exposure at country scale stays at 30+ FPS; first tile visible within 500ms at zoom 4-6; memory footprint stays under 500MB for the browser tab.

Cross-stack: backbone (tile server, COG pipeline, bbox endpoints, cache layer), engine (no change), UI (MapLibre integration per #4/Q4; zoom-aware style per #5). Priority: Tier 2 critical. This is the real "data volume" problem. #5 becomes the concrete vehicle for it.


28. Web testing tools — Storybook + Playwright

What it means: a structured web testing pyramid. Specifically:

  • Storybook for UI component documentation + visual regression. Every reusable component (panels, buttons, charts, map controls) has a story; designers and contributors see the inventory in one place. Optional: Chromatic / Percy for automated visual diffs on PRs.
  • Playwright for end-to-end tests: "user logs in, uploads exposure, runs scenario, opens report." Covers the critical paths that unit tests miss. Cross-browser support is a bonus (Firefox, WebKit, Chromium out of the box).

Why: the UI is currently tested at the unit level (Jest/Vitest). Component inventory is only in the code, not visible. E2E paths have no automated coverage. Both gaps grow in cost as the UI grows; cheap to seed now.

Risks: - Storybook is maintenance work. Stories drift when components change; if no one uses them, they rot. - Playwright tests can be flaky. CI time grows. Requires discipline to maintain. - Both are additional toolchains with their own config + CI budget.

Refinement: - Storybook: introduce after #3 (UI redesign direction) so stories reflect the new components, not the old ones. Scope to shared components first (buttons, panels, chart wrappers) — don't try to story every page. Use @storybook/test for interaction tests if we want; skip Chromatic until CI stabilizes. - Playwright: seed with 5-10 smoke tests covering login, upload, run-scenario, view-result, export. Run against a docker-compose stack in CI. Add tests only when fixing bugs ("test that fails for the bug, then the fix makes it pass" pattern). - What not to add: Cypress (duplicates Playwright), Selenium (outdated), image-diff-heavy systems (unmaintainable at this scale).

Cross-stack: UI only (for now). CI pipeline changes. Priority: Tier 3 for Storybook (high ROI once redesign settles), Tier 2 for a Playwright smoke suite (critical paths should have regression coverage before any managed rollout).


29. Deployment evolution — beyond Docker Compose

What it means: Docker Compose is fine for development and single-host self-hosters, but not for production managed hosting. Plan the path to proper orchestration: Kubernetes for managed hosting, with proper CI/CD pipelines (GitOps via ArgoCD/Flux, or build+deploy via Terraform + Helm charts).

Why: if managed hosting ever launches (see Q5 — self-hosted-first but keeping the path open), Docker Compose doesn't cut it. Multi-tenant isolation, horizontal scaling of workers, zero-downtime deploys, secrets management — all need orchestration. Better to document the path now so we don't design ourselves into a corner.

Risks: - K8s is expensive to operate if we don't need it. For a single-host managed deployment, it's overkill. - Premature Helm-ifying a Docker Compose file leads to weird, leaky abstractions. - The community around self-hosters will stay on Docker Compose; we need to keep both paths working.

Refinement: - Self-hosters stay on Docker Compose indefinitely. That path is explicitly supported. - Managed hosting plan (sketched only): K8s (probably DigitalOcean Kubernetes or Hetzner), Helm chart per component, ArgoCD for GitOps, cert-manager for TLS, external-dns for domains. All standard-shape CNCF stack. - Write an ADR when we're ~1 phase out from the managed hosting decision. For now, just ensure the Docker images are production-usable (already the case post-Phase 2), that secrets are env-var not mounted files, and that the DB migration flow is CI-friendly. - Start the CI/CD pipeline story now: the GitHub Actions we have for test/lint/build are the foundation. Add a release pipeline that cuts images tagged on git tags. This is cheap and pays off for both self-hosters (pull a versioned image) and future K8s (reference the same tag).

Cross-stack: backbone (Dockerfile polish), infra (Helm charts when we get there), CI (release workflow). Priority: Tier 3 ADR + release pipeline (do the cheap stuff now so the K8s path is available later), Tier 4 actual K8s implementation (gated on whether managed hosting becomes real).


30. In-app notification system

What it means: an in-app notification surface (bell icon top-right, dropdown with a list) where the platform sends users (and admins) messages: "your scenario finished," "new dataset available," "system maintenance window," "your download is ready" (ties to #27), "a new feature was released" (ties to #31).

Why: long-running jobs, async operations, admin-approved workflows (#22 polling → review → ingest) all need a way to reach users. Email alone is noisy and often lands in spam; in-app notifications are targeted and timely.

Risks: - Notification fatigue. Every feature wants to ping; most of them shouldn't. - Multi-channel (in-app + email + maybe webhook) adds engineering surface; start with one channel. - WebSockets / SSE for real-time push adds infrastructure complexity.

Refinement (start minimal): - notifications table: id, user_id, org_id, type, payload, created_at, read_at, dismissed_at. - Server publishes notifications via a Celery signal. UI polls /v1/users/me/notifications every N seconds. SSE/WebSocket upgrade is Tier 3+. - Taxonomy: job_complete, dataset_available, system_announcement, admin_approval_needed. Gate every new type on "do users actually need this?" - Email mirror is optional, opt-in per user (from #2 settings).

Cross-stack: backbone (table + API + Celery signal helpers), UI (bell + dropdown). Priority: Tier 3. Not required for single-user demo; becomes valuable once #27 downloads and #22 polling exist.


31. In-app news / changelog landing

What it means: a "news" / "what's new" page inside the app (not the marketing landing site from #F). Acts as an in-app changelog: new hazards added, new features shipped, ops updates, community news. Ties to #30 (notifications can surface headlines).

Why: gives users a reason to come back between scenario runs. Also a low-cost way to show that the platform is alive and maintained (critical for OSS credibility — a tool with a stale feel loses trust).

Risks: - Becomes a content-creation treadmill that nobody maintains. - Duplicates the docs-site changelog or blog if those exist.

Refinement: - Simple: an in_app_posts table with markdown entries. Admin-editable. UI renders a page with the latest N posts. - Source-of-truth question: is this the same as the docs-site changelog, or distinct? Recommendation: same content, different surface. Posts live in a markdown file in the docs site; the app fetches and renders the latest N. Single-source, zero duplication. - No comments / discussions / likes. This is a read-only news feed. If community discussion is wanted, use GitHub Discussions.

Cross-stack: backbone (minimal — maybe a fetch proxy to the docs-site markdown), UI (news tab or in-app landing route). Priority: Tier 4 (user said "not a strong opinion" — parking-lot is fine; revisit when a user actually asks).


32. Adapter-extensibility-by-design (CLIMADA and beyond)

What it means: the user's framing: we can't write adapters for every competitor product. But we should architect the platform so that adding an adapter is cheap. Starting target: a CLIMADA adapter (run the platform against CLIMADA as the compute engine, instead of climate-lama-engine). Extending later to other compute backends.

This reinforces and is closely related to #N (engine plugin contract) and #24 (engine pluggability). The difference is framing: #N/#24 are "pluggable engine levels." #32 is the broader commitment that the whole platform should be adapter-friendly, not just the compute engine — also ingestion (#12), reporting template engines, notification channels, auth backends.

Why: adapter-friendliness is the "open platform" story. It's how we stay relevant as the field evolves — we can plug into whichever compute library or data source has momentum at any given time.

Risks: - "Pluggable everything" is over-architecting. Every extension point has a cost (defining the interface, documenting it, maintaining it). - CLIMADA-as-engine has real value (compat bridge for existing CLIMADA users) but also undermines the "lean engine" story. Be honest about the tradeoff. - Remote engines (#24 Level 3b) remain security-sensitive and distinct from in-process adapters.

Refinement: - Philosophy: in-process Python adapters (good) vs remote webhook adapters (security-sensitive, treat separately). - Prioritized adapter slots: 1. Compute engine — already pluggable via ModelInterface; design an adapter for CLIMADA as the reference. Tier 2 / 3 (covered by #N + #24). 2. Ingest formatsIngestAdapter ABC for CLIMADA HDF5, GEM, Hazus, OASIS LMF. Tier 3 (covered by #12). 3. Report template engines — abstract the Quarto/WeasyPrint choice behind an interface. Tier 3. 4. Dataset sources — already in #22 (DatasetSource ABC). 5. Auth providers — already abstracted via FastAPI dependency pattern; OIDC-compatible. Low-lift to add adapters. - CLIMADA engine adapter specifically: implementable as a subprocess-based adapter that invokes CLIMADA in a sidecar container (honoring the ADR-024 backbone-never-imports-CLIMADA constraint). Works as a compatibility bridge for users who already have CLIMADA-based pipelines. Useful Tier 3 milestone to prove #24 Level 2 is real. - ADR scope: one ADR per extension point. Each ADR should document (a) the interface, (b) two real implementations (first-party + one adapter), (c) a template for adding a third.

Cross-stack: backbone (ABCs for each extension point), separately-deliverable adapter packages (possibly in their own repos if we want). Priority: Tier 1 architectural commitment (the design stance flows through #N and #24), Tier 3 first non-default adapter (CLIMADA compute engine as the reference), Tier 4+ everything else.


33. Multilingual translation workflow

What it means: the i18n infrastructure from #15 is cheap — the hard part is the translation pipeline. The user wants a workflow similar to riskwise-v2 where translations can be handed to partners (agency translators, academic contributors, or commercial translation agencies) without them needing to touch JSON files or the codebase.

Two candidate approaches:

  • XLSX round-trip: export a single xlsx with key, english, <target language> columns, send to partner, import their filled-in sheet back into the i18n JSON files via a small script. Simple, ubiquitous (everyone can edit xlsx), no hosted service dependency.
  • Hosted translation UI (Rosetta / Weblate / Crowdin / Lokalise): partners log in to a dedicated UI, translate strings in a structured surface with review workflow, translations sync back to the repo via git integration.

Why: multilingual support only pays off if translations actually happen. Asking a partner to edit JSON in a git PR is a non-starter; that's why CLIMADA-family tools stay English-only. Solving the workflow is what makes the i18n infrastructure valuable.

Risks: - Hosted translation tools add an external dependency (SaaS or self-hosted service to maintain). - XLSX workflows drift — if someone updates EN and forgets to re-export, the sheet becomes stale. - Either way, needs quality-control (typos, broken interpolation placeholders, cultural tone).

Refinement (proposed sequence): - Tier 2: XLSX round-trip tooling. Write two scripts: export_translations.py (pull current i18n JSONs → xlsx, one sheet per language) and import_translations.py (xlsx → JSON, validate placeholders, fail on regressions). Ship as a Makefile target. This scales to ~5 languages and small translation teams. - Tier 3: Evaluate a hosted tool only if the XLSX workflow proves painful. Candidates: - Weblate — open source, self-hostable, mature. Git integration. Good. - Crowdin / Lokalise — commercial SaaS, polished. Not great for an OSS ethos. - Tolgee — newer, open source, good DX. Worth tracking. - Don't do: build a custom in-app translation UI from scratch. That's a product inside the product; the existing tools are better at it.

Cross-stack: UI (i18n JSON structure — already agreed in #15), tooling (export/import scripts), infra (if Weblate, a container to run). Priority: Tier 2 scripts (cheap, enables partners immediately), Tier 3 evaluate hosted tool (gated on real translation volume).


Feedback on gaps 1-14 — Round 3 annotations

User agreed with all 14 gaps. Noted nuances for re-prioritization:

Gap Annotation
2 — Test data fixtures Start with test data; seed with real-world data when accessible. Both approaches used.
5 — GPU acceleration Much later; stays Tier 4.
7 — Error taxonomy Promoted: error / warning / info UI presentation is a huge step-improvement. Fold into Tier 2 (not Tier 3) — tied to #15 i18n and #I SDK quality.
9 — Security threat model Much later; keep in mind as we build.
10 — Performance budgets Important but not immediate. Not Tier 4; keep in Tier 3 as a "publish as we go" rolling artifact.
11 — Cost of engine calls Same as 10 — Tier 3, not immediate.
12 — Log sensitivity classification Same as 10/11 — Tier 3, not immediate.
13 — Dependency policy ADR a priority (see A7). Tier 2.
14 — Engine parity with CLIMADA Needed 100%, but not immediate. Tier 3, not Tier 4. See A3.

Gaps 1, 3, 4, 6, 8 remain as originally characterized.


Open questions B1-B6 — Round 4 resolutions

Q Topic R4 resolution
B1 #27 acceptance criteria Deprioritize the ingest side. User: download/ingest is not a bottleneck — it's pre-production admin work and can be done manually/semi-manually during Phase 3 without blocking users. The real performance problem is the read side (map rendering of large datasets). Split: keep #27 (download + ingest pipelines) as Tier 3; promote the read-side concern as #27b (large-dataset map rendering) at Tier 2 critical, consolidated with #5. See updated sections above.
B2 DO Spaces bucket layout Settled — proposal below. Raw datasets go under raw/{source}/{data-type}/{version}/{dataset-id}/. Each dataset carries a manifest.json with provenance (source URL, fetched_at, checksum, license, crs, units). Separate buckets (or top-level prefixes) for raw/, processed/{org_id}/, and tiles/{dataset-id}/{z}/{x}/{y}.*. ADR to freeze the layout adjacent to #19. See detail below.
B3 #26 filter split (server vs client) Settled — hybrid, start lean. Client-side for simple attribute filters over rendered features (fast, no backend round-trip). Server-side for "filter then re-aggregate" (ties to #5 zoom aggregation — the filter applies before the aggregate, so it must run where the aggregation runs). Expand server-side support as scaling demands; don't pre-build it.
B4 SSRF policy for admin downloads Settled, low urgency. Per-org allowlist of source domains, with a platform-level default list (CLIMADA data API, JRC, Copernicus CDS, NOAA, OSM). New domains require admin + platform-admin dual approval. Not urgent since #27 ingest is Tier 3; but bake it in from the first admin-download endpoint — retrofitting SSRF hardening is expensive.
B5 Hosted translation tool Stay with XLSX for now; defer hosted tool adoption. User: if introducing Weblate/Tolgee later is non-destructive, waiting is fine. Exit criterion: when any of the following holds — (a) >3 concurrent translators, (b) >10 actively translated languages, (c) translator feedback consistently complains about XLSX vs. a hosted alternative — re-evaluate. Confirm during Tier 2 XLSX implementation that the JSON i18n format stays compatible with Weblate's git-integration model, so migration is not destructive.
B6 Adapter packaging Settled — separate packages. First-party adapters live in their own repos (climate-lama-adapter-climada, climate-lama-adapter-gem, etc.) and publish to PyPI. Backbone declares optional extras (pip install climate-lama[climada] pulls the adapter). This keeps the install surface minimal, prevents architectural coupling from creeping back, and matches the ecosystem norm (scikit-learn, sqlalchemy dialects). User explicitly agreed: engineer for openness without overengineering or compromising architecture.

B2 — DO Spaces bucket layout (proposed)

raw/
  {source}/                     # climada | jrc | copernicus | noaa | osm | gadm | ...
    {data-type}/                # hazard | exposure | impact-function | context-overlay | boundary
      {version}/                # upstream publisher version string (pinned literal)
        {dataset-id}/           # our catalog UUID; the authoritative identifier
          data.{ext}            # raw artifact (netcdf, hdf5, geotiff, csv, ...)
          manifest.json         # provenance + checksum + license + crs + units
          README.md             # optional human-readable source notes

processed/
  {org_id}/                     # org-scoped processed artifacts (uploaded exposures, fitted funcs)
    {dataset-id}/
      data.{ext}
      manifest.json

tiles/
  {dataset-id}/                 # tile pyramids derived from raw or processed sources
    {z}/{x}/{y}.pbf             # MVT for vector layers
    {z}/{x}/{y}.tif             # COG tiles for raster layers (or single COG per dataset)
    manifest.json               # tile-schema version, min/max zoom, built_at, source_checksum

reports/
  {org_id}/
    {report-id}.pdf

Invariants: - {dataset-id} is our catalog UUID and the join key to the PostgreSQL catalog; never rename. - manifest.json is mandatory at every leaf; absence = broken dataset. - {version} pins the upstream publisher's label literally (e.g., v2024.1, era5-land-2024-10-15). Never normalize. - Tiles are derived; they can always be regenerated from raw/processed. If invalidation is ever in doubt, truncate tiles/{dataset-id}/ and rebuild. - Org-scoped artifacts stay under processed/{org_id}/ — no org data in raw/. raw/ is read-shared across orgs.

ADR: write this up adjacent to #19 catalog and #27/#27b, as "DO Spaces bucket layout and dataset versioning."


Open questions remaining after Round 4

All B1-B6 now have resolutions. No new unresolved blockers surfaced in R4. Downstream open threads are tracked as ADRs (dependency policy, MapLibre library whitelist, DO Spaces layout, adapter packaging) and will be opened as issues during per-phase planning per A6.


Revision log

  • 2026-04-22 (R1) — Initial draft. Assessed user's 20 ideas + added 16 of my own (A-P). Strawman P3 scope proposed with timelines and resource model.
  • 2026-04-22 (R2) — Added new ideas 21-24 (admin panel, dataset polling, desktop readiness, engine pluggability). Deep-dived H/I/M/K, UI license, personas, MapLibre vs Leaflet, managed vs self-hosted, docs tooling, zoom aggregation, reports-as-phase. Reframed #13 as "context overlays." Stripped timeline/resource language throughout. Reframed strawman as priority tiers. Added cross-cutting themes, unflagged gaps (1-14), dependency map, risk clusters. New open questions A1-A7.
  • 2026-04-22 (R3) — Added new ideas 25-33 (building-level + 3D, map layering, data volume pipelines, Storybook + Playwright, deployment-beyond-Compose, in-app notifications, in-app news, adapter-by-design, translation workflow). Settled Q1 (Apache 2.0 target + private repo for now), Q2 (researcher + agency), Q4 (MapLibre from the start + library whitelist), Q5 (self-hosted-first + managed-path-open), Q6 (MkDocs Material), landing site (D now, A later). Renamed "Longshot" tier to Tier 4. Resolved A1-A7. Promoted gap-7 (error taxonomy) to Tier 2, gap-13 (dependency policy) to Tier 2 ADR, gap-14 (engine parity) to Tier 3. Reclassified several Tier 4 items (#M observability slice, #10, #11, #12, #13, #14c) back to "later-in-Tier-3." New open questions B1-B6.
  • 2026-04-22 (R4) — Resolved B1-B6. B1: deprioritized #27 ingest to Tier 3 per user ("done before we hit production, not a bottleneck"); split out #27b large-dataset map rendering as the real Tier 2 critical concern; #5 zoom aggregation reframed as the concrete vehicle for it (martin tile server + COG raster path pulled into Phase 3). B2: DO Spaces bucket layout settled — raw/{source}/{data-type}/{version}/{dataset-id}/ with mandatory manifest.json provenance; separate processed/, tiles/, reports/ prefixes; ADR to follow. B3: hybrid filter model confirmed (client-side for attribute filters over rendered features, server-side for filter-then-aggregate); start lean, expand. B4: per-org SSRF allowlist with platform default list; low urgency given B1 deprioritization but bake in from first admin-download endpoint. B5: stay with XLSX; defer hosted tool until >3 concurrent translators OR >10 active languages OR translator pain is consistent. B6: adapter packaging as separate repos/packages (climate-lama-adapter-climada) with backbone extras.