Mayim

Live demo: mayimgc.vercel.app

Finds the highest-leverage site for new clean-water infrastructure in an underserved town, then designs and costs it.

Billions of dollars go into water and humanitarian development, yet more than 400 million people in Africa still lack access to basic drinking water (African Development Bank). The binding constraint is no longer intent — it is deciding where a new water point does the most good.

Mayim answers that one town at a time. Enter a name; it pulls live terrain, climate and OpenStreetMap data, researches institutional sources with Exa, screens hundreds of candidate sites against mapped constraints, ranks the survivors with a transparent deterministic model, and returns a costed conceptual design on satellite imagery — every number traceable to a formula or a source.

Pre-feasibility screening. Field survey and licensed engineering are still required.


Quick start

npm install
cp .env.example .env.local   # add a Mapbox token; Exa/OpenAI optional
npm run dev

Open http://localhost:3000 and search a town (try Kisumu, Kenya).

npm run build · npm start · npm run lint · npm run typecheck · npm test (116 unit tests) · npm run evidence:refresh (re-save live sources for the demo towns; needs EXA_API_KEY) · npm run scan:refresh (re-save district rankings from WPdx) · npm run demo:recache (re-run the demo towns and overwrite data/demo; --out <dir> for a dry run)

What a run returns

Six analysis tracks stream live — water access, hydrogeology, existing infrastructure, population access, environmental risk, construction accessibility — then the recommendation lands on the map with preliminary cost (a range, rounded to $1,000), daily water output, people served and service radius, implementation time in weeks, maintenance burden, and a capped confidence score.

Five tabs back it up: Overview, Why (per-factor contributions), Evidence (retrieved sources), Alternatives (runner-up sites), Validation (assumptions and missing data).

How it works

search ──► /api/geocode ──► TownRef (Mapbox, Nominatim fallback)
                    │
                    ▼         /api/analyze — SSE stream
   ┌────────────────┼────────────────────────┐
   ▼                ▼                        ▼
Overpass       NASA POWER + Open-Elevation   Exa ──► LLM ──► EvidenceFinding[]
roads·buildings   rainfall·solar·terrain     4 parallel searches: need ·
facilities·water                             hydrogeology · infrastructure ·
points·hazards                               environment
protected areas
   └────────────────┬────────────────────────┘
                    ▼
     150–500 deterministic candidate sites
                    ▼
   Turf features → hard exclusions → weighted score → rank
                    ▼
   population · conceptual layout · cost · water output
                    ▼
             AnalysisRun (cached to disk)

The score

Seven normalised factors (lib/scoring/score.ts), weights in lib/config/coefficients.ts:

Factor Weight Factor Weight
Community need 25% Distance from existing service 10%
Population access proxy 20% Environmental safety 10%
Groundwater feasibility 18% Evidence quality 5%
Road & construction access 12%

Hard exclusions zero a candidate outright: inside a mapped protected area, within the hazard buffer of a mapped waste or industrial site, inside a waterway channel, beyond practical road access, too steep, or redundant against an existing mapped water point.

Cost and yield are formulas, not guesses

The LLM never produces a total. lib/cost-model/cost.ts sums line items from labelled planning coefficients and applies a terrain/access multiplier; every line item renders in the UI with its formula and its source. Output is pumpRate × effectivePumpingHours × efficiency, shown with the numbers substituted. Totals are ranges, never a falsely precise figure.

Determinism

Identical inputs always produce an identical AnalysisRun. Math.random() appears nowhere in the analysis path — only in animation timing. Candidate ids, grid order and tie-breaking are stable, which is what makes cached snapshots legitimate rather than decorative.

Honesty constraints

  • No authoritative global flood-zone, contamination, groundwater or land-ownership dataset is claimed. Flood exposure is a HAND terrain proxy from sampled elevation and mapped drainage, and is labelled as such.
  • Absence of mapped water points is reported as "No mapped water points were found in OpenStreetMap. This does not prove that none exist."
  • Population is an estimate shown with its method, confidence and limitations.
  • Recommendation confidence is capped — this is a desk study.

Environment

Every key is optional; the app degrades explicitly rather than failing.

Variable Scope Purpose
NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN public Satellite basemap, globe projection, geocoding. Restrict it by URL.
EXA_API_KEY server Live research over institutional sources.
OPENAI_API_KEY server Turns retrieved sources into typed findings; writes the summary.
OPENAI_MODEL server Defaults to gpt-4.1-mini.
MAPBOX_SECRET_TOKEN server Keeps geocoding off the client token.

Server keys are read only inside app/api/*/route.ts and the lib/ modules they import — never in a client component, so they never reach the browser bundle, and they are never logged.

Evidence falls back in three labelled rungs: a live Exa search → a saved snapshot of an earlier live search for that town (data/evidence/<slug>.json, written only by npm run evidence:refresh; commit these) → hand-written bundled reference sources. With an Exa key but no OpenAI key, live sources are shown as verbatim excerpts that widen the evidence base without adjusting factor scores. Town-level queries that come back thin are retried once at region/country level.

Without keys the deterministic geospatial analysis still runs end to end on bundled reference evidence; without a Mapbox token the map renders an instruction panel instead of crashing and geocoding falls back to Nominatim. Every run is labelled with its provenance — live, cache (a stored run replayed from its real events), demo, or partial.

Data endpoints

Route Returns
GET /api/brief?slug=<slug>[&download=1] Markdown project brief for a stored run (404 if that town has no stored run).
POST /api/brief { run } Markdown brief for a run the client already holds — use this for live runs on a read-only host.
GET /api/partners?country=<name>[&live=0] Verified water organisations for the country, plus search finds when EXA_API_KEY is set.
GET /api/scan?country=<name> Districts ranked by people who would regain water access if broken points were repaired (WPdx+), with share not working and a thin-data flag. Falls back to data/scan/<ISO3>.json (npm run scan:refresh).

Partner discovery is the fifth Exa query of every run (run.partners). The verified fallback list (lib/partners/verified.ts) has every URL checked by hand; search finds are labelled unreviewed. Sourced simulation rates (growth, household size, breakdown and repair time, walking speed, Sphere flow rates, demand) live in lib/config/behaviour.ts, each with its citation.

Layout

app/     page.tsx (SSE client, reveal sequence) · api/{geocode,analyze,cached-run}
components/  map/ (AquaMap) · search/ · analysis/ · recommendation/ · ui/
lib/     types.ts · pipeline.ts · config/coefficients.ts (every coefficient, sourced)
         providers/ (overpass·climate·elevation) · evidence/ (exa·llm·signals)
         geospatial/ · scoring/ · cost-model/ · water-output/ · infrastructure/

Data sources

OpenStreetMap via Overpass (ODbL) · NASA POWER climatology (MERRA-2) · Open-Elevation / OpenTopoData (SRTM) · WorldPop 2020 100 m population (CC BY 4.0; the primary people-within-reach estimate, projected forward with national growth, with mapped buildings as fallback and cross-check) · Water Point Data Exchange WPdx+ (CC BY 4.0; surveyed water points and functional status for the regional scan) · Esri World Imagery + CARTO labels via MapLibre GL · Nominatim · Exa · OpenAI · World Bank WDI, national censuses, Sphere, WHO and REACH/UPGro studies for simulation rates.

Cost coefficients are order-of-magnitude planning figures from published rural water supply benchmarking, labelled in lib/config/coefficients.ts. They are screening inputs, not quotations.

Share this project:

Updates