Fish cages on Lake Victoria can lose an entire stock overnight when dissolved oxygen crashes. Most farmers still rely on experience, visual checks, and late phone calls. We wanted something better.
KIVU is a lake-wide aquaculture and environmental intelligence platform we built as a team of five. It sits between the farmer’s phone and the lake: real-time telemetry, an explainable AI risk engine, geospatial zone intelligence, and expansion suitability signals — all backed by PostGIS and served from a single Go binary.
This is the story of what we built, how we structured it, and what we learned.
The problem
Cage aquaculture is expanding rapidly around Lake Victoria (Kenya, Uganda, Tanzania). The opportunity is huge, but so are the risks:
Dissolved oxygen can drop critically (< 3 mg/L) with little warning
Temperature spikes and algal events stress fish
Farmers lack accessible historical trends or early-warning systems
Satellite data (e.g. Copernicus) exists but rarely reaches the people who need it
Deciding where to place the next cage is still largely guesswork
We set out to build a practical decision-support layer — not another dashboard that only works in a lab.
What KIVU does
We organized the product into four clear levels:
Level Focus What the farmer gets
- Cage Water quality telemetry Submit/receive DO, temp, pH, turbidity. AI Risk Engine runs automatically.
- Farm Multi-cage overview Dashboard of cage status, aggregated metrics, and active alerts.
- Lake Regional intelligence PostGIS zones (Homa Bay, Mbita, Rusinga…) with environmental metrics.
- Expansion Site suitability Spatial scoring for new cage placement + rationale. Cross-cutting features:
Tech stack
Layer Choice Why
Backend Go 1.22 + Gin + sqlx Fast, simple, great for APIs
Database PostgreSQL 15 + PostGIS 3.3 Real geospatial types & indexes
Auth JWT (access + refresh) + bcrypt Stateless, farmer-friendly (phone)
Frontend Vanilla HTML/CSS/JS Zero build step, served by Gin
Geospatial GEOGRAPHY(Point/Polygon, 4326) Accurate distance & containment
External Copernicus + SMS client Satellite context + real alerts
Packaging Docker Compose One-command demo
Everything lives in one repo. docker compose up --build brings up PostGIS (with migrations + seed data) and the backend on port 8080. The same Go process serves both the API (/api/v1/...) and the frontend.
The AI Risk Engine (the interesting part)
We deliberately kept the risk engine rule-based and explainable. Farmers need to understand why an alert fired.
When a new reading is posted for a cage, the engine:
Pulls the latest ~10 readings
Scores against clear thresholds:
Dissolved oxygen < 3.0 mg/L → critical hypoxia (+50)
DO < 5.0 mg/L → moderate hypoxia (+25)
Three consecutive declining DO readings → deteriorating trend (+20)
Temperature anomalies / high turbidity contribute further
Produces:
Risk score
Risk level (low / medium / high / critical)
List of triggered factors
Human-readable recommendation
Confidence score
Can automatically create an alert (and optionally trigger SMS)
This design made demos reliable and the logic easy to discuss with domain experts. We can always layer ML models later; for a first version, transparency won.
Geospatial with PostGIS
Cages are stored as points:
location GEOGRAPHY(Point, 4326)
Lake zones are polygons. We use GiST indexes and queries such as:
ORDER BY ST_Distance(boundary, ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography)
to find the nearest zone for expansion suitability. Seed data covers real-ish sectors around Homa Bay, Mbita Channel, and Rusinga.
Having proper geography types from day one saved us from the classic “just store lat/lon and hope” trap.
Backend structure (Go)
We followed a clean layout:
backend/
├── cmd/server/main.go
├── config/
├── database/ # migrations + seed
├── integrations/ # Copernicus, SMS
├── internal/
│ ├── handlers/
│ ├── middleware/ # CORS, logging, JWT
│ ├── models/
│ └── services/ # Auth, AI Risk, Alerts, Expansion, Copernicus sync
└── routes/
Key services:
AuthService — register/login, token issuance & rotation
AIRiskEngine — the scoring logic above
AlertsService — persistence + SMS hook
ExpansionService — zone suitability
CopernicusSyncService — background loop
The router also serves the static frontend, so a single binary (or container) is enough for a full demo.
Lessons we learned
Explainable beats black-box for domain users who have to trust the system with livestock.
PostGIS is worth the learning curve if your problem is inherently spatial.
Pairing on backend (two of us) kept the API and schema consistent under time pressure.
Serving static files from Gin removed an entire deployment concern for a hackathon/demo.
Seed data with realistic coordinates and deteriorating DO trends made the risk engine and maps come alive in demos.
Clear product levels (Cage → Farm → Lake → Expansion) helped both the team and anyone we pitched to.
What’s next
Possible directions:
Real sensor integrations (IoT)
Stronger ML models on top of the current rule engine
Multi-language SMS / USSD for last-mile reach
Deeper Copernicus / climate data layers
Mobile-first progressive web app
Final thoughts
KIVU started as a hackathon-style project, but the problem is real. Turning water-quality readings and open satellite data into timely, understandable signals for cage farmers is valuable work.
If you’re building in aquaculture, climate tech, geospatial systems, or just like clean Go + PostGIS architectures, we’d love to hear from you.
KIVU — smarter cages, healthier lakes.
Top comments (0)