Skip to content

xops-labs/ClimateRiskIQ

ClimateRiskIQ

Open Climate Due-Diligence for Real Estate · Public Hazard Data · Explainable 0-100 Risk Scores · Self-Hosted API

Turn any street address into a readable climate-risk screening report with live public data, deterministic scoring rules, buyer checklists, insurance questions, and full source-level auditability.

ClimateRiskIQ pipeline from address geocoding to public climate data, deterministic scoring, and a property risk report

License: Apache-2.0 Status .NET 10 PostgreSQL 16 Docker Compose OpenAPI 3.1 OpenTelemetry PRs Welcome

NASA POWER NASA FIRMS OSM Nominatim Explainable Rules Self Hosted


ClimateRiskIQ is an open-source climate due-diligence screener for real estate. It geocodes a property address, pulls free public climate and hazard signals, maps those signals into transparent 0-100 risk bands, and returns a plain-English report that explains what drove the score, what a buyer should inspect, and what insurance questions to ask before moving forward.

Built for: real-estate buyers doing early property screening, agents and brokers who want a transparent pre-diligence aid, civic-tech builders exploring public climate datasets, climate-risk researchers who prefer inspectable rules over opaque scores, and developers who want a self-hosted .NET reference implementation for address-to-risk report generation.

Key capabilities: address-to-report API · live OSM Nominatim geocoding · live NASA POWER climate signals · key-gated NASA FIRMS wildfire checks · FEMA flood proxy clearly labeled as phase-1 · deterministic weighted composite scoring · per-signal explanations · buyer checklist, insurance questions, and maintenance notes · PostgreSQL persistence · OpenAPI/Swagger surface · Docker Compose self-hosting · OpenTelemetry-ready instrumentation.


Self-Hosting

ClimateRiskIQ is designed to run locally or on your own infrastructure with one Docker Compose command.

git clone https://github.com/xops-labs/ClimateRiskIQ.git
cd ClimateRiskIQ
cp .env.example .env
docker compose up --build -d

Once the stack is up:

No paid API keys are required for the default local demo. Add a free NASA_FIRMS_MAP_KEY when you want the wildfire signal included in reports.


Why This Exists: Climate Risk Is Easy to Overclaim and Hard to Inspect

Climate exposure now affects real-estate diligence, insurance conversations, maintenance planning, and buyer confidence. But the tools around it tend to split into two extremes: polished commercial scores that hide their model internals, or raw public datasets that require GIS fluency before they become useful.

ClimateRiskIQ sits deliberately between those poles. It is not trying to be a catastrophe model. It is a transparent screening layer: take public data, show the rules, surface the limits, and produce a report a human can challenge. Every score should be traceable back to source code and provider status, not hidden behind a private model.

Existing path What it gives you What it misses
Commercial climate scores Polished property-risk products Model opacity, paid access, limited local auditability
Raw public climate data Source-level public information Hard to turn into a buyer-readable property report
Static hazard maps Useful regional context No unified address-to-report API or checklist
LLM-generated diligence notes Fast narrative summaries No deterministic scoring spine or reproducible calculations

ClimateRiskIQ is the auditable middle layer. One address in. Public signals out. Rules in the open. Caveats shown in the report.


How ClimateRiskIQ Makes Property Risk Legible

ClimateRiskIQ composes five data and decision surfaces into one report flow. Each surface is intentionally separable so the system can grow without hiding what changed.

# Surface What the user gets
1 Address resolution A street address converted to latitude/longitude through OSM Nominatim
2 Public data adapters NASA POWER climate data, optional NASA FIRMS wildfire detections, and the current FEMA flood proxy
3 Signal normalization Hazard-specific scores for flood, wildfire, heat, rainfall, drought, solar exposure, and long-term trend
4 Composite scoring A weighted 0-100 property score over only the signals available for that report
5 Decision support Plain-English explanation, buyer checklist, insurance questions, maintenance notes, confidence, and warnings

Features & Capabilities

# Capability Where it lives Status
01 Address-to-report API — submit a property address and receive a persisted report Program.cs, ClimateRiskIQReportService.cs shipped
02 OSM Nominatim geocoding — live address lookup NominatimGeocodingProvider.cs shipped
03 NASA POWER climate ingestion — heat, rainfall, drought, and solar inputs NasaPowerClient.cs, NasaPowerDataParser.cs shipped
04 NASA FIRMS wildfire signal — recent active-fire proximity when keyed NasaFirmsWildfireRiskProvider.cs shipped, key-gated
05 FEMA flood branch — clearly labeled phase-1 coastal-box proxy FemaFloodRiskProvider.cs proxy
06 Deterministic scoring engine — fixed bands, weighted composite, confidence rules RiskScoringEngine.cs, RiskLevelClassifier.cs shipped
07 Report recommendations — checklist, insurance questions, maintenance notes ReportRecommendationEngine.cs, ReportContentFactory.cs shipped
08 Persistence — PostgreSQL-backed reports and signals EfClimateRiskIQDataStore.cs shipped
09 Provider status endpoint — expose configured providers and readiness DataSourceStatusProvider.cs shipped
10 Auth scaffold — demo JWT login exists but is not enforced on report routes Program.cs scaffolded
11 Redis caching — Compose service exists, no code path uses it yet docker-compose.yml planned
12 PDF export — report content helpers exist, no renderer yet ClimateRiskIQ.Reports planned

Use Cases

Role What ClimateRiskIQ does for you
Homebuyer / property researcher Run a first-pass climate diligence check before paying for deeper professional review. See what hazards deserve follow-up and which questions to ask an insurer or inspector.
Real-estate agent or broker Produce a transparent screening artifact for early conversations without pretending it is underwriting-grade or model-validated.
Civic-tech / open-data builder Study a concrete implementation that turns public climate and hazard feeds into an address-level API, provider-status endpoint, and persisted report format.
Climate-risk researcher Inspect the exact scoring bands and composite weights instead of reverse-engineering an opaque property score.
Software developer Use a clean .NET 10 modular monolith with provider abstractions, EF Core persistence, OpenAPI, Docker Compose, and OpenTelemetry-ready hosting.

Responsible Positioning

ClimateRiskIQ is useful because it is explicit about what it is and what it is not.

It is:

  • A rules-based screening tool that converts free public climate data into a readable, auditable risk summary for a property.
  • Self-hosted and transparent — every score comes from source code you can read (see RiskScoringEngine.cs and ClimateRiskIQSignalFactory.cs). No hidden model, no external scoring service.
  • Deterministic by design — the same inputs always produce the same output, and every number is explainable.

It is NOT:

  • Not a validated catastrophe model. It performs no accuracy validation and makes no accuracy claims.
  • Not a competitor to First Street, Jupiter Intelligence, ClimateCheck, Climate X, Moody's/RMS, CoreLogic, or insurer catastrophe models. Those are validated, calibrated products; this is a screening aid on open data.
  • Not for underwriting, lending, insurance pricing, or regulatory/financial disclosure. Do not make money or coverage decisions from its output.
  • Not AI. There is no ML, no LLM, and no statistical model anywhere in the scoring path — it is a deterministic rules engine on purpose. Transparency is the entire point.

Data-source status

An honest snapshot of what the current code actually does. "Live" means it makes a real network call that changes a real report.

Source Status Key Notes
NASA POWER Live Keyless Daily climate at power.larc.nasa.gov. Seven parameters are fetched; 3 currently drive scoring (T2M_MAX → heat, PRECTOTCORR → rainfall + drought, ALLSKY_SFC_SW_DWN → solar). Humidity (RH2M) and wind (WS2M) are fetched but not yet used.
OSM Nominatim Live Keyless Address → lat/lon geocoding (User-Agent only). Hard dependency today — if it fails the request fails; a fallback provider is planned.
NASA FIRMS Live, key-gated Free NASA_FIRMS_MAP_KEY Recent active-fire detections drive the wildfire signal. Without the key, wildfire is silently omitted from the composite and confidence drops.
FEMA flood Phase-1 proxy (stub) None No network call. A hardcoded coastal-box heuristic emits a flood score of 58 inside roughly lat 24–32°N / lon −98…−70°W, else 35. Flood zone is always "Unknown" and confidence is always low. Live NFHL lookup is not yet implemented.
NOAA NCEI Planned NOAA_API_TOKEN (no effect today) Placeholder only. Makes no request and contributes nothing to any report.
Long-term trend Placeholder Emitted as a constant score of 35, not yet data-driven.

How scoring works

The pipeline (sequential)

Each report runs a sequential pipeline — every stage awaits the one before it; there is no concurrent fan-out.

  1. Geocode the address via Nominatim (hard dependency today).
  2. Fetch NASA POWER daily climate for the coordinates.
  3. Evaluate the FEMA flood proxy (in-process coastal-box heuristic).
  4. Fetch NASA FIRMS wildfire detections — only if a MAP key is set.
  5. Score & compose — build per-signal RiskSignal records, compute the weighted composite, write recommendation / checklist / questions.
  6. Persist & serve — store in PostgreSQL and return JSON.

Composite weights

The composite is a weighted mean, renormalized over only the signals that are available for this property.

Signal Weight
Flood 0.25
Wildfire 0.20
Heat 0.15
Rainfall 0.15
Drought 0.10
Long-term trend 0.10
Solar 0.05
composite = round( Σ (signalScore × weight) / Σ (weight) )

The denominator (Σ weight, called the effective weight) sums only the weights of signals that were produced. If FIRMS is not keyed, wildfire's 0.20 is excluded from both sums, so the remaining weights are 0.80 and the mean is taken over that.

Signal bands

Every signal maps a measured value to one of a fixed set of scores. These ladders are the whole model — there is nothing else.

Heat (from average daily max temp °C, or count of high-heat days):

Score Condition
90 ≥ 40 °C or ≥ 120 high-heat days
78 ≥ 35 °C or ≥ 60 days
65 ≥ 32 °C or ≥ 30 days
50 ≥ 28 °C or ≥ 10 days
35 ≥ 24 °C
15 otherwise

Rainfall (annual precipitation, mm): ≥2500→90 · ≥1800→75 · ≥1200→58 · ≥700→38 · else→20

Drought (annual precipitation, mm): <350→82 · <600→66 · <900→45 · else→20

Solar (kWh/m²/day): ≥6.5→85 · ≥5.5→70 · ≥4.5→50 · ≥3.5→30 · else→15

Wildfire (nearest FIRMS detection): <10 mi→92 · <25 mi→74 · <50 mi→52 · none→20

Flood (coastal-box proxy): 58 inside the coastal box, else 35

Long-term trend: constant 35

Risk levels

Score Level
0–20 Very Low
21–40 Low
41–60 Moderate
61–80 High
81–100 Severe

Report confidence

Confidence is derived from the effective weight and the number of warnings:

  • Low — effective weight < 0.5, or ≥ 3 warnings.
  • Medium — any warnings present, or effective weight < 0.85.
  • High — otherwise.

Because the FEMA proxy always adds a warning, report confidence can never exceed "Medium" today. That is a deliberate, honest ceiling — it will lift once a real NFHL lookup replaces the stub.

Explainability

There is no black box to explain away. Explainability here means three concrete things: (1) every rule is readable source code, (2) each signal carries a plain-language explanation string, and (3) each signal names its source as a coarse label"NASA_POWER", "FEMA_NFHL", "NASA_FIRMS". Those are labels, not URLs, DOIs, or per-record timestamps; richer provenance is on the roadmap.


Illustrative output

The block below shows the report shape and values the current code produces for a Miami Beach address without a FIRMS key (so no wildfire signal). It is arithmetically consistent with the engine:

effective weight = .25 + .15 + .15 + .10 + .05 + .10 = 0.80
composite = round( (58×.25 + 65×.15 + 58×.15 + 20×.10 + 70×.05 + 35×.10) / 0.80 )
          = round( 41.95 / 0.80 ) = round(52.44) = 52  → Moderate
// illustrative output — shape and values match what the current code produces
{
  "reportId": "6f1c2a90-3b4d-4e21-9a77-0c9f5b8e2d10",
  "property": {
    "address": "1601 Collins Ave, Miami Beach, FL 33139",
    "city": "Miami Beach",
    "state": "FL",
    "postalCode": "33139",
    "country": "United States",
    "latitude": 25.7907,
    "longitude": -80.1300
  },
  "overallRiskScore": 52,
  "overallRiskLevel": "Moderate",
  "recommendation": "Screening indicates moderate combined exposure, driven mainly by coastal flood proximity and heat. Validate flood status with an authoritative FEMA source and budget for cooling and drainage before proceeding.",
  "riskSignals": [
    { "riskType": "FLOOD", "score": 58, "riskLevel": "Moderate", "source": "FEMA_NFHL",
      "explanation": "Phase-1 coastal-box proxy (live NFHL lookup not yet enabled); flood zone reported as Unknown at low confidence." },
    { "riskType": "HEAT", "score": 65, "riskLevel": "High", "source": "NASA_POWER",
      "explanation": "Average daily maximum temperature and high-heat day count are elevated in the NASA POWER sample period." },
    { "riskType": "RAINFALL", "score": 58, "riskLevel": "Moderate", "source": "NASA_POWER",
      "explanation": "Annual precipitation totals indicate meaningful rainfall and drainage exposure." },
    { "riskType": "DROUGHT", "score": 20, "riskLevel": "Very Low", "source": "NASA_POWER",
      "explanation": "Drought is an MVP proxy from annual precipitation only; high rainfall implies low drought exposure." },
    { "riskType": "SOLAR", "score": 70, "riskLevel": "High", "source": "NASA_POWER",
      "explanation": "Average solar exposure affects cooling load, roofing, and materials." },
    { "riskType": "LONG_TERM_WEATHER_TREND", "score": 35, "riskLevel": "Low", "source": "NASA_POWER",
      "explanation": "Transparent MVP baseline constant; NOAA station trends are planned enrichment." }
  ],
  "buyerChecklist": [
    "Ask an insurer for a flood, wind, and wildfire premium estimate.",
    "Request roof, drainage, and foundation inspection.",
    "Check for prior flood or water-intrusion claims."
  ],
  "insuranceQuestions": [
    "Is flood insurance required or recommended here?",
    "Has the premium increased in the last 3 years?",
    "Are wind or storm exclusions present?"
  ],
  "maintenanceRisks": [
    "Higher cooling-cost exposure from sustained heat.",
    "Roof and drainage maintenance from rainfall exposure."
  ],
  "confidence": "Medium",
  "warnings": [
    "FEMA flood data uses the phase-1 coastal-box proxy; flood confidence is reduced.",
    "NASA FIRMS MAP key not configured; wildfire risk was excluded from the composite."
  ],
  "dataSources": ["FEMA_NFHL", "NASA_POWER"],
  "generatedAtUtc": "2026-07-02T08:00:00Z"
}

Quickstart

Prerequisites: Docker (with Compose) and outbound internet access to NASA POWER and OSM Nominatim.

# 1. Configuration — every key is optional for the default local demo
cp .env.example .env

# 2. Build and start the stack (API + Worker + PostgreSQL + Redis)
docker compose up --build -d

# 3. Health check
curl http://localhost:8080/health

Then run the three seed addresses. Space report POSTs at least 2 seconds apart — Nominatim's usage policy is roughly one request per second.

# Miami Beach — FEMA coastal-box branch (flood 58)
curl -s -X POST http://localhost:8080/api/reports \
  -H "Content-Type: application/json" \
  -d '{"address":"1601 Collins Ave, Miami Beach, FL 33139"}'
sleep 2

# Houston — coastal branch (flood 58)
curl -s -X POST http://localhost:8080/api/reports \
  -H "Content-Type: application/json" \
  -d '{"address":"1500 McKinney St, Houston, TX 77010"}'
sleep 2

# Paradise, CA — inland (flood 35); strongest FIRMS wildfire signal when a MAP key is set
curl -s -X POST http://localhost:8080/api/reports \
  -H "Content-Type: application/json" \
  -d '{"address":"5637 Skyway, Paradise, CA 95969"}'

Each POST returns a reportId. Fetch the full report and check provider status:

curl http://localhost:8080/api/reports/<reportId>
curl http://localhost:8080/api/data-sources/status

A demo login exists (POST /api/auth/login with demo / demo in Development), but no endpoint currently requires authorization — the API is effectively open today. Enforcing authorization is on the roadmap. In non-Development environments, JWT login requires JWT_SIGNING_KEY and an explicit LOCAL_AUTH_PASSWORD.


API

Endpoints are defined as ASP.NET Core minimal APIs in Program.cs.

Method Path Purpose
POST /api/auth/login Issue a JWT (demo/demo in Development). Login works but is not enforced.
POST /api/reports Generate a report from { "address": "..." }. Rate-limited to 30 requests/min/IP (in-memory).
GET /api/reports/{id} Retrieve a report by GUID.
GET /api/reports/{id}/risk-signals Return just the per-signal records for a report.
GET /api/data-sources/status Report provider availability and key configuration.
GET /health Liveness / readiness probe.

Swagger UI is served at /swagger in Development and Testing.


Architecture

flowchart TB
    subgraph Api["ClimateRiskIQ.Api — minimal API endpoints (.NET 10)"]
        E1["POST /api/auth/login"]
        E2["POST /api/reports"]
        E3["GET /api/reports/{id}"]
        E4["GET /api/reports/{id}/risk-signals"]
        E5["GET /api/data-sources/status"]
        E6["GET /health"]
    end

    subgraph App["ClimateRiskIQ.Application — services"]
        SVC["ClimateRiskIQReportService"]
        ENG["RiskScoringEngine (rules)"]
        FAC["ClimateRiskIQSignalFactory"]
    end

    subgraph Domain["ClimateRiskIQ.Domain — entities"]
        ENT["Property · ClimateRiskIQReport · RiskSignal · RawClimateData"]
    end

    subgraph Infra["ClimateRiskIQ.Infrastructure — providers + EF Core"]
        GEO["Geocoding — Nominatim (live)"]
        POWER["NASA POWER (live)"]
        FIRMS["NASA FIRMS (live, key-gated)"]
        FEMA["FEMA flood — proxy stub"]
        NOAA["NOAA NCEI — planned"]
    end

    subgraph Worker["ClimateRiskIQ.Worker"]
        HB["no-op heartbeat (scheduled jobs planned)"]
    end

    subgraph Reports["ClimateRiskIQ.Reports"]
        RC["report content factories (PDF export planned)"]
    end

    subgraph Data["Data plane"]
        PG[("PostgreSQL 16")]
        REDIS[("Redis — provisioned, not yet used")]
    end

    subgraph Ext["External"]
        X1["OSM Nominatim"]
        X2["NASA POWER"]
        X3["NASA FIRMS"]
    end

    E2 --> SVC
    E3 --> SVC
    E4 --> SVC
    E5 --> SVC
    SVC --> ENG --> FAC
    SVC --> GEO & POWER & FIRMS & FEMA
    SVC --> ENT
    SVC --> RC
    SVC --> PG
    GEO --> X1
    POWER --> X2
    FIRMS --> X3
Loading

The Worker is a heartbeat that logs one line every five minutes and touches nothing else. ClimateRiskIQ.Reports holds string-list content factories (checklist / insurance / maintenance) — there is no PDF renderer yet. Redis is wired into the compose stack but no code reads or writes it today.


Tech Stack

Layer Technology Why
API ASP.NET Core minimal APIs on .NET 10 Compact OpenAPI-first service surface for report creation, lookup, provider status, health, and local login
Application C# services and provider abstractions Keeps orchestration, scoring, recommendations, and DTO shaping separate from infrastructure adapters
Domain Pure C# entities Property, report, risk signal, and raw climate data types with no I/O dependencies
Infrastructure EF Core, PostgreSQL, OSM Nominatim, NASA POWER, NASA FIRMS Swappable data adapters and durable report storage
Reports Business-readable content factories Buyer checklist, insurance questions, and maintenance-risk text without a PDF dependency yet
Worker .NET hosted worker Scheduled portfolio/rescoring work is planned; current worker is a no-op heartbeat
Container Docker Compose API, worker, PostgreSQL, and Redis in one reproducible local stack
Observability OpenTelemetry Optional OTLP traces/metrics when OTEL_EXPORTER_OTLP_ENDPOINT is configured
Tests xUnit, EF Core InMemory, hand-written fakes Offline tests for scoring math, report assembly, and provider parsing behavior

Documentation Map

Document Purpose
Architecture overview Modular-monolith layout, project responsibilities, and report-generation flow
API endpoints Minimal API route list, request/response shapes, auth caveat, and provider status endpoint
Provider status Current truth table for live providers, stubs, placeholders, and keyed integrations
Roadmap Planned work, explicit non-goals, and the rule for when a capability may be called shipped
Changelog Released changes once planned work becomes real implementation

Configuration

All variables are environment-based; the Docker Compose stack sets sensible local defaults.

Variable Default Required?
ConnectionStrings__Postgres local Compose Postgres Required (defaulted in Compose)
ConnectionStrings__Redis empty (redis:6379 in Compose; unused today) Optional
Database__ApplyMigrationsOnStartup false (true in Compose) Optional
JWT_SIGNING_KEY random in Development/Testing Required outside Development/Testing
LOCAL_AUTH_USERNAME demo Optional
LOCAL_AUTH_PASSWORD demo (Development/Testing only) Optional; required for login outside Development
NASA_FIRMS_MAP_KEY empty Optional — enables the wildfire signal
NOAA_API_TOKEN empty Optional — no effect today (NOAA is planned)
GEOCODING_API_KEY empty Optional — bound but not read by the current Nominatim provider (reserved)
OTEL_EXPORTER_OTLP_ENDPOINT unset Optional — export traces/metrics to your collector

Testing

dotnet test ClimateRiskIQ.sln

Tests use xUnit only, run fully offline (EF Core InMemory plus hand-written fakes — no live HTTP), and are green today. Coverage is strongest on the aggregation math (band thresholds, weighted composite, renormalization, confidence rules); provider HTTP paths are exercised through fakes rather than against live services.

Toolchain: .NET 10 SDK (10.0.200), solution file ClimateRiskIQ.sln.


Roadmap

See ROADMAP.md for detail. In priority order:

  • Live FEMA NFHL flood lookup to replace the coastal-box proxy.
  • Real NOAA NCEI integration.
  • A data-driven long-term trend signal to replace the constant.
  • Enforced endpoint authorization (the API is open today).
  • Put fetched humidity and wind to use in scoring.
  • A geocoding fallback so Nominatim is no longer a hard dependency.
  • Redis caching for geocoding and hot reports.
  • PDF export of reports.
  • A validation study before any accuracy claim is ever made.

Standing rule: nothing is called "shipped" until it makes a real call and changes a real report.


Data attribution & responsible use

  • NASA POWER and NASA FIRMS data are courtesy of NASA. This project is not endorsed by or affiliated with NASA.
  • Geocoding © OpenStreetMap contributors, available under the Open Database License (ODbL). Use of the public Nominatim service is subject to its usage policy (roughly one request per second, descriptive User-Agent required).

Disclaimer: ClimateRiskIQ is a screening aid, not professional advice. It does not validate its outputs and must not be used for underwriting, lending, insurance pricing, or regulatory disclosure. Always confirm hazard status with authoritative sources and qualified professionals before acting.


Community

Questions and bug reports: please open a GitHub issue.


Topics This Repository Covers

climate-risk · climate-data · real-estate · due-diligence · property-risk · hazard-risk · flood-risk · wildfire-risk · heat-risk · drought · nasa-power · nasa-firms · open-data · rules-engine · self-hosted · dotnet · aspnet-core · postgresql · docker · opentelemetry


License

Licensed under the Apache License 2.0 — see LICENSE.

Copyright 2026 Yasvanth Udayakumar.


ClimateRiskIQ · Transparent climate-risk screening you can run, read, and challenge.

About

Open-source climate due-diligence screener for real estate - live NASA POWER climate signals, key-gated NASA FIRMS wildfire checks, transparent 0-100 hazard scoring, buyer checklists, and self-hosted .NET 10 + PostgreSQL + Docker Compose.

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors