Skip to content

Repository files navigation

Hydra — the sentinel of freshwater

🐉 H Y D R A

Every drop. Every angle. Watched.

Autonomous freshwater monitoring — a deterministic remote-sensing core fused with a five-agent Gemini workflow that turns satellite pixels into field-ready risk briefs.

License: MIT Theme: Earth Forward Python 3.11+ Next.js 15 FastAPI 0.115 PostgreSQL 16 + PostGIS Gemini 2.5 Flash Tests: 102 passed

🌍 Problem · 💧 Solution · 📸 Screenshots · 🧬 Agents · 🏗️ Architecture · 🚀 Quick Start · 🏆 Judge Sheet

Warning

Advisory only. Hydra does not certify water safety, detect toxins, or replace laboratory testing. It is a triage instrument that tells field teams where to sample first — so limited lab budgets go where they matter most.


🌍 Problem — Earth Forward

Freshwater is the smallest slice of Earth's water budget — <3% of all water, and most of it is locked in ice. What remains is fought over by agriculture, industry, and billions of people — while its quality degrades invisibly, from space, week after week.

🚨 The hard numbers Source
~2 billion people lack safely managed drinking water at home WHO/UNICEF JMP
~80% of global wastewater is discharged untreated UN Water
Most lakes & rivers are never sampled — lab capacity is the bottleneck, satellite coverage is not

The result: field teams and NGOs fly blind. Algal blooms, turbidity spikes, and shoreline degradation go unnoticed until they become crises — because field sampling doesn't scale, but sentinel monitoring can.

Hydra's thesis: every point sampled is precious — so let a satellite watch every angle, and let agents turn pixels into a prioritized sampling plan.


💧 The Solution

Hydra is an autonomous freshwater triage system with two linked pipelines:

  1. 🛰️ A deterministic remote-sensing core — picks your area of interest, pulls a fresh Sentinel-2 scene, computes six water-quality spectral indices, and produces a reproducible 0–100 risk score. Same pixels, same number, every time.
  2. 🤖 A five-agent Gemini workflow — plans the analysis, scouts the best scene, recalls historical context, drafts and self-critiques the expert brief, and writes a citizen-friendly summary. All traceable, all with deterministic fallbacks.

The LLM writes the narrative. It can never move the risk band. Numbers stay deterministic; prose stays grounded; users stay safe.

Every session is captured end-to-end:

  • 📊 Spectral indices + risk rows → Postgres
  • 🧾 Every agent's tool calls, args, results, latency, tokens → agent_traces (JSONB)
  • 🧠 Cross-session memory → agent_memory with text-embedding-004 vectors + pgvector HNSW index, so recurring water bodies get smarter over time

📸 Screenshots

Landing Dashboard
Landing — the pitch, live Dashboard — sessions, water bodies, average risk
Map pick Risk card
Monitor — search / coords / map pick Risk card — score + index deltas + AI narrative
Pipeline Indices
Live session — deterministic pipeline → agent handoff Index cards — distribution sparklines per index
Index table Showcase
Index table — full stats, one-click CSV export Methodology — six band-math views explained

🧠 How It Works — Two Pipelines

flowchart LR
    subgraph P1[Pipeline 1 · Deterministic core 🛰️]
        direction LR
        A[Pick area<br/>search · coords · map] --> B[Fetch Sentinel-2 L2A<br/>Planetary Computer STAC]
        B --> C[Compute 6 indices<br/>NDWI · MNDWI · NDTI<br/>NDCI · NDVI · WRI]
        C --> D[Risk score 0–100<br/>level · urgency]
    end

    subgraph P2[Pipeline 2 · Gemini agent workflow 🤖]
        direction LR
        E[Coordinator<br/>thinking mode] --> F[Scout<br/>function calling + Vision]
        E --> G[Historian<br/>search · memory · code exec]
        F --> H[Analyst<br/>draft → critique → rewrite]
        G --> H
        H --> I[Reporter<br/>citizen summary]
    end

    D --> E
    I --> J[Session detail + agent trace + branded PDF]

    classDef det fill:#0e3c2f,stroke:#34d399,color:#d1fae5,stroke-width:1.4px;
    classDef agent fill:#33205a,stroke:#a78bfa,color:#ede9fe,stroke-width:1.4px;
    classDef out fill:#1e293b,stroke:#94a3b8,color:#e2e8f0,stroke-width:1.4px;
    class A,B,C,D det;
    class E,F,G,H,I agent;
    class J out;
Loading

Pipeline 1 is the trusted numeric core. Pure-Python deterministic numpy band math; the LLM has zero influence over the risk number.

Pipeline 2 is the agent layer. Five specialist Gemini agents choose inputs, gather grounded context, write prose. Each degrades gracefully — every agent has a deterministic fallback.


🧬 The Five Gemini Agents

# Agent Action Capability
1 🧭 Coordinator plans the workflow Gemini thinking mode
2 🔭 Scout picks the satellite scene Function calling + Gemini Vision on the real RGB tile
3 📚 Historian pulls trends & grounded context History + Google Search + URL Context + code execution + pgvector memory
4 ✍️ Analyst writes & self-critiques the brief Structured output + critique-then-rewrite loop
5 📣 Reporter writes the citizen summary Structured response schema (deterministic fallback)

Each run lands in the Agentic Workflow card with tool calls, JSON outputs, latency, and token usage — the same colour vocabulary across README, UI, and PDF:

  • 🧭 Coordinator — aqua · 🔭 Scout — sky · 📚 Historian — amber · ✍️ Analyst — violet · 📣 Reporter — emerald

Deep dive: docs/agent_layer.md.


🏗️ System Architecture

flowchart TB
    subgraph Client[Browser]
        direction TB
        UI[Next.js 15 app router<br/>Tailwind 4 · TS strict]
        TRACE[Agentic Workflow card<br/>live SWR polling]
        PDFBTN[Download PDF]
    end

    subgraph Backend[FastAPI 0.115 · SQLModel · Alembic]
        direction TB
        API[/api/v1 router/]
        PIPE[Pipeline 1<br/>deterministic core]
        ORCH[Pipeline 2 orchestrator<br/>Coordinator → Scout → Historian → Analyst → Reporter]
        REPORT[WeasyPrint + Jinja2<br/>branded PDF]
    end

    subgraph Data[Persistence]
        PG[(PostgreSQL 16 + PostGIS<br/>monitoring_sessions · water_bodies<br/>spectral_indices · risk_assessments)]
        TR[(agent_traces<br/>JSONB tool-call log)]
        MEM[(agent_memory<br/>pgvector 768 · HNSW cosine)]
        DISK[/uploads + reports on disk/]
    end

    subgraph External[External providers]
        STAC[Microsoft Planetary Computer<br/>Sentinel-2 L2A STAC]
        GEMINI[Gemini API<br/>2.5 Flash · embeddings]
    end

    UI --> API
    TRACE --> API
    PDFBTN --> API
    API --> PIPE
    PIPE --> ORCH
    PIPE --> PG
    ORCH --> TR
    ORCH --> MEM
    PIPE --> STAC
    ORCH --> GEMINI
    API --> REPORT
    REPORT --> DISK

    classDef client fill:#0f172a,stroke:#38bdf8,color:#e0f2fe,stroke-width:1.3px;
    classDef backend fill:#0e3c2f,stroke:#34d399,color:#d1fae5,stroke-width:1.3px;
    classDef data fill:#1e1b4b,stroke:#a78bfa,color:#ede9fe,stroke-width:1.3px;
    classDef ext fill:#3a2a0c,stroke:#fbbf24,color:#fef3c7,stroke-width:1.3px;
    class UI,TRACE,PDFBTN client;
    class API,PIPE,ORCH,REPORT backend;
    class PG,TR,MEM,DISK data;
    class STAC,GEMINI ext;
Loading

🔄 Session Lifecycle

sequenceDiagram
    autonumber
    actor U as User
    participant FE as Next.js frontend
    participant API as FastAPI /sessions
    participant PIPE as Deterministic core
    participant ORCH as Agent orchestrator
    participant GEM as Gemini
    participant DB as Postgres

    U->>FE: Pick AOI + window + cloud ceiling
    FE->>API: POST /sessions
    API->>DB: Insert session (status=processing)
    API-->>FE: 201 session
    API->>PIPE: BackgroundTask: run_full

    PIPE->>PIPE: STAC search · COG read · index math
    PIPE->>DB: Persist indices + risk row
    PIPE->>ORCH: run_orchestrator(...) when AOI is water
    ORCH->>GEM: Coordinator plan
    ORCH->>GEM: Scout (function calling + vision)
    opt history exists
        ORCH->>GEM: Historian (search · memory · code exec)
    end
    ORCH->>GEM: Analyst draft → critique → rewrite
    ORCH->>GEM: Reporter structured citizen summary
    ORCH->>DB: Persist agent_traces (incremental)
    PIPE->>DB: status=complete + risk.field_brief = reporter

    loop while status != complete
        FE->>API: GET /sessions/{id}
        API-->>FE: status_message + partial trace
    end
    FE->>API: GET /sessions/{id}/report
    API-->>FE: WeasyPrint PDF
Loading

🗃️ Data Model

erDiagram
    WATER_BODIES ||--o{ MONITORING_SESSIONS : has
    MONITORING_SESSIONS ||--o{ SPECTRAL_INDICES : produces
    MONITORING_SESSIONS ||--o| RISK_ASSESSMENTS : has
    MONITORING_SESSIONS ||--o{ FIELD_EVIDENCE : has
    MONITORING_SESSIONS ||--o| REPORTS : has
    MONITORING_SESSIONS ||--o| AGENT_TRACES : has
    WATER_BODIES ||--o{ AGENT_MEMORY : remembers
    RISK_ASSESSMENTS ||--o| AGENT_TRACES : linked_to

    WATER_BODIES {
        UUID id PK
        string name
        geometry polygon
        point centroid
        float area_km2
        string source
    }
    MONITORING_SESSIONS {
        UUID id PK
        UUID water_body_id FK
        date start_date
        date end_date
        float max_cloud_cover
        string status
        string status_message
        string scene_id
        datetime scene_capture_date
        float scene_cloud_cover
        float water_fraction
        string aoi_type
    }
    SPECTRAL_INDICES {
        UUID id PK
        UUID session_id FK
        string name
        float value
        float min_value
        float max_value
        float stddev
        int sample_count
        json bands
        string interpretation
    }
    RISK_ASSESSMENTS {
        UUID id PK
        UUID session_id FK
        UUID agent_trace_id FK
        float score
        string level
        string urgency
        string recommendation
        string reasoning
        string limitations
        json contributors
        json field_brief
        string model_id
    }
    AGENT_TRACES {
        UUID id PK
        UUID session_id FK
        json coordinator_plan
        json agent_runs
        int total_tokens_in
        int total_tokens_out
        int total_latency_ms
        string gemini_model
    }
    AGENT_MEMORY {
        UUID id PK
        UUID water_body_id FK
        string note
        vector embedding
        datetime created_at
        bool archived
    }
    FIELD_EVIDENCE {
        UUID id PK
        UUID session_id FK
        string water_color
        string odor
        bool algae_present
        int dead_fish_count
        float rainfall_mm
        int complaints_count
        string photo_path
        string notes
    }
    REPORTS {
        UUID id PK
        UUID session_id FK
        string file_path
        int byte_size
    }
Loading

🛰️ API Surface

All endpoints live under /api/v1; full schemas in docs/api_contract.md. OpenAPI at /docs when the backend runs.

Domain Endpoint highlights
💧 Water bodies CRUD + GeoJSON polygons / buffered points · transactional bulk delete
📡 Sessions Kick off a run · paginated list · full detail · indices · risk · evidence · agent trace · PDF report
❤️ System GET /health liveness probe

📁 Repository Layout

hydra/
├── assets/                  # Brand assets · screenshots · animated SVG wordmark
├── backend/
│   ├── app/
│   │   ├── api/v1/          # FastAPI routers (sessions · evidence · water_bodies · report · health)
│   │   ├── core/            # config · logging · database · BackgroundTask runner
│   │   ├── models/          # SQLModel tables (sessions · water_bodies · risk · indices · agent_traces · agent_memory · evidence · report)
│   │   ├── schemas/         # Pydantic request/response models
│   │   ├── services/
│   │   │   ├── pipeline.py          # Deterministic core (Pipeline 1)
│   │   │   ├── indices.py · risk_model.py · reasoning.py
│   │   │   ├── citizen_summary.py   # Deterministic citizen summary fallback
│   │   │   ├── report_generator.py  # Jinja2 + WeasyPrint
│   │   │   └── agent/               # Pipeline 2 — orchestrator + 5 agents + tool layer
│   │   ├── utils/           # Geo · charts · location formatting · PDF helpers
│   │   └── main.py
│   ├── alembic/             # Database migrations
│   └── tests/               # 102 pytest tests
├── frontend/
│   ├── app/
│   │   ├── (marketing)/     # Landing · methodology · about · changelog · limitations
│   │   └── (app)/           # Dashboard · monitor · sessions · water-bodies · settings
│   ├── components/          # Marketing · session · map · evidence · ui primitives
│   ├── lib/                 # api-client · query-client · seo · location
│   └── tests/               # Vitest + Playwright
├── docs/                    # Architecture · agent_layer · risk_model · indices · api · manual · prd
├── infrastructure/          # render.yaml · vercel.json · deployment.md
├── docker-compose.yml
└── README.md

🚀 Quick Start

1. Environment

cp .env.example .env

Required

Variable Notes
DATABASE_URL Postgres string, e.g. postgres://user:pass@host:5432/hydra
GOOGLE_API_KEY Gemini 2.5 Flash + embeddings (free tier works for demos)
NEXT_PUBLIC_API_URL Backend origin, http://localhost:8000 in dev

Recommended

Variable Notes
GOOGLE_API_KEY_FALLBACK[_2] Rollover keys on quota / 429
HYDRA_AGENTIC_MODE true (default); false → Pipeline 1 only
HYDRA_FAKE_GEMINI 1 for deterministic offline mode (CI)

2. Docker 🐳

docker compose up --build

Frontend → http://localhost:3000 · Backend → http://localhost:8000 · Postgres+PostGIS sidecar.

3. Without Docker

Backend (Python 3.11+):

cd backend
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
alembic upgrade head
uvicorn app.main:app --reload --reload-dir app --host 0.0.0.0 --port 8000

Frontend (pnpm + Node 20+):

cd frontend
pnpm install
pnpm dev

⚙️ Configuration

Variable Default Purpose
HYDRA_AGENTIC_MODE true Enables the 5-agent orchestration; off → Pipeline 1 + deterministic narrator
HYDRA_FAKE_GEMINI 0 Skip real Gemini; canned outputs for CI
HYDRA_AGENT_STEP_DELAY_MS 0 Delay between agent stages for live demo sequencing
GEMINI_MODEL gemini-2.5-flash Runtime model ID
GEMINI_EMBED_MODEL text-embedding-004 Historian's pgvector memory
REPORT_DIR backend/data/reports PDF cache
MAX_UPLOAD_BYTES 8388608 Field-evidence photo ceiling
WATER_FRACTION_LAND_THRESHOLD 0.2 Below → land; agents skipped
WATER_FRACTION_MIXED_THRESHOLD 0.7 Below (≥ land) → mixed

Full schema: backend/app/core/config.py.


🛡️ Failure Modes & Fallbacks

Every layer has a deterministic safety net — a session always produces a usable brief:

💥 Failure ✅ Fallback
Gemini key 429 / quota Roll over to fallback keys
Coordinator parse error Baseline plan: Scout + Analyst + Reporter (+ Historian if history)
Scout vision timeout Freshest STAC candidate under cloud ceiling
Historian failure Analyst runs without briefing; memory skipped
Analyst failure Deterministic narrator (reasoning._fake_bundle)
Reporter failure Deterministic citizen summary
AOI = land/mixed Pipeline 2 skipped (zero Gemini cost); UI shows Not water
WeasyPrint error Regeneration per-download — no stale cached PDFs ever

Degraded behaviour is never silent — all failures are recorded in the per-session trace.


✅ Tests & Quality Gates

# Backend
cd backend
.venv/bin/python -m pytest -q           # 102 tests in <10s
.venv/bin/ruff check . && .venv/bin/black --check app/ tests/ alembic/

# Frontend
cd frontend
pnpm typecheck && pnpm lint && pnpm test && pnpm e2e

CI runs every gate plus a WeasyPrint smoke test on each PR.


🚢 Deployment

Surface Provider Notes
Backend Render (Docker) infrastructure/render.yaml
Frontend Vercel infrastructure/vercel.json
Database Postgres 16 + PostGIS + pgvector Local docker-compose.yml; managed PG in prod

Walkthrough: infrastructure/deployment.md.


🏆 Judging Criteria Scorecard

Criterion Where Hydra answers it
🎨 Originality Most water tools are static dashboards. Hydra fuses a provably-deterministic spectral core with a self-critiquing agent team that remembers every lake it has ever seen (pgvector HNSW memory) — a pattern we haven't seen at hackathons.
🌍 Earth Forward adherence Freshwater quality triage: conservation + ecosystem monitoring + community resilience. Satellite scale where lab budgets can't reach.
Completion Full-stack: map → satellite → six indices → five agents → PDF. 102 passing backend tests, CI gates, and a fallback matrix that guarantees a brief on every run.
📚 Learning STAC/COG satellite pipelines, pgvector HNSW vector memory, agent orchestration with Gemini function calling + Vision, WeasyPrint PDF pipeline.
🖌️ Design Live agent trace UI, brand-tuned dark system, distribution sparklines, one-click CSV, branded PDF reports.
⚙️ Technology The wow-factor: an LLM that can never move the risk number. Numpy band math decides; agents only narrate.

❓ Why "Hydra"?

The Hydra of myth guarded its waters with many heads — no matter the angle of approach, one head was always watching. This platform works the same way. The deterministic core (the central head) never blinks: same pixels → same number, every time. Around it, five Gemini agents (the outer heads) patrol from different angles — planning, scouting, remembering, drafting, and translating for citizens.

Many heads. One mission: watch the water. 🐉💧


📜 License

Source and docs under MIT; third-party notices in NOTICE.md.

Sentinel-2 imagery © European Union — modified Copernicus Sentinel data via Microsoft Planetary Computer.


👤 Maintainers

digiflowhelp-stack — GitHub @digiflowhelp-stack · digiflow.help@gmail.com

About

Hydra – Autonomous freshwater risk triage. Deterministic satellite indices fused with a five-agent Gemini workflow. Every drop. Every angle. Watched.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages