
This is a submission for the Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass
It's Saturday morning in Islamabad. You've got the kayak on the roof, the
weather is perfect, and the group chat is buzzing. The only question that
matters is the one nobody can answer: is the Kunhar running at a good
level right now — and will it still be next weekend?
For most of the world, paddlers answer this with gauge networks, guidebooks,
and community apps. In Pakistan, the answer today is: guess. The hydrology
data actually exists — the European Centre's GloFAS model simulates every
major river on Earth, including ours — but it's published as raw discharge
numbers for flood forecasters. There is no "should I go paddling?" layer.
Not for the Indus. Not for anything.
So for the DEV Hacktoberfest 2026 challenge, I built PaddleSense: a
river-level watcher for Pakistan's paddling waters. It watches river levels
from free public data, predicts next week's flow band, scouts access notes
in English and Urdu, and renders one plain, conservative answer per river —
with the source and date attached to every single number.
And it is built around one rule that shaped every other decision:
it must never lie to you about water.
LIVE_URL: paddle-sense.onrender.com
What it does
1. Watches four rivers daily. The Indus at Besham, the Kunhar at
Balakot, the Swat at Mingora — and Karachi's Malir, which is not a
paddling river. I'll explain why it's in the app, because it's my favorite
feature.
2. Predicts the next 7 days as a band, then proves it. Not "1,632
m³/s next Tuesday" — a band like 1,498–1,849 m³/s, with the model's
uncertainty baked in. And because a forecast you can't check is just a
vibe, the app practices retrodiction: it hides real past weeks,
predicts them blind, and publishes every hit and every miss on a public
page. Coverage metrics live in the repo. The misses stay visible.
3. Scouts the web weekly for the knowledge that isn't in any dataset.
Road closures on the Kaghan valley road. A bridge out near Balakot. An
operator running trips in Swat. The scout runs SerpApi searches in English
and Urdu (کنہار رافٹنگ, سوات کی rafting بند — because Pakistan's river
knowledge lives in Urdu), respects a hard budget of 90 searches a month,
and stores every note with its verbatim quote, source URL, and fetch date.
All of it is embedded and searchable by meaning — ask "where can I put in
near Kaghan?" and get actual sourced notes back.
4. Explains each river in one sentence. A hosted, open-weight Gemma
model turns the day's data into plain language. That sentence passes four
validation gates before a human ever sees it — more on the leash below.
5. Emails you when a river enters its verified runnable range. The
whole pipeline runs itself on a weekly schedule, retries its own failures,
and never sends the same email twice.
The rule that built the product: honesty first
Whitewater has a hard edge: the wrong flow level doesn't ruin your
weekend, it ends your life. PaddleSense is safety-adjacent software, so I
treated every claim the way you'd treat a climbing partner's callout — it
has to be checkable, or it doesn't get said.
That rule produced the five things I'm proudest of:
There is no green "safe" badge anywhere in the product. When a river
is inside its runnable range, the badge is calm water-blue. When it's
high, it says "HIGH WATER — do not go", verbatim, in every state. The
words safe, danger-free, and guaranteed are banned from the UI — and
that's not a style guide, it's an executable test: a Playwright sweep
crawls every page on every change and fails the build if a banned word
renders.
Every number carries its provenance. GloFAS values are grid-cell
estimates from a ~5 km model, not gauge readings. Every card shows the
exact cell coordinates, elevation, and fetch time. The About page explains
why a cell "near Skardu" is not a gauge in Skardu. When my first cell
sweep resolved the Indus poorly, the UI didn't hide it — it moved the
monitoring point to the Besham stretch, where the model represents the
river best, and says so.
Ranges are never invented — by the app or by me. A runnable range
only appears after a human verifies a quoted source. During development
the database schema refused my own range entry because I hadn't attached
provenance to it. The guardrail catching its author on day one is exactly
what it's for. (Today the Indus is owner-verified at 55–120 m³/s and the
Kunhar at 35–135 m³/s; the Swat remains honestly "not yet verified".)
The Malir is the honesty check. Karachi's Malir reads 0.1 m³/s most of
the year — a seasonal channel, not a river. It stays in the app on
purpose, labeled not_a_paddling_river. When the app says "not runnable"
and you can look at the dry riverbed yourself, that's the product telling
the truth even when the truth is boring. It's also the fastest way to
trust the rest of the page.
The LLM is on a leash. Gemma writes the sentences, but it never
decides your safety — a deterministic, unit-tested computeStatus()
function owns that. The model's output passes four gates before display:
- Schema — is it even the contracted JSON?
- Numerals — every number in the sentence must exist in the data we gave it. This gate earned its keep live: the model once relabeled a discharge of "1,632 m³/s" as "1,632 cfs" — same digits, a 16× lie in units. Caught, discarded, recorded as a permanent regression fixture.
- Status — the sentence's status word must equal the computed one. A model cannot soften "do not go" into "consider caution".
- Banned tokens — safe, danger-free, guaranteed. Absent or discarded.
Any gate failure falls back to a plain deterministic template. The site
never depends on the model — and the model has failed in production
enough times (thinking-token blowouts, </output>-terminated responses
with no answer JSON) that the fallback has real battle scars, all logged
in the runbook.
How it's built
Next.js 16 (App Router, PPR) ──── the app, "Indus Silt" dark theme
│
Tiger Data (Postgres + pgvector) ──── 17k+ daily readings, HNSW note search
│
FastAPI + TabPFN ──── 7-day flow bands on CPU, backtested
│
SerpApi ──── weekly scout, budget-ledgered, EN + UR
│
Hosted Gemma (Google AI Studio) ──── one validated sentence per river
│
Temporal (dev) ──── the durable weekly heartbeat
│
Resend ──── "the Kunhar is in range" email
Why TabPFN for prediction. Constraints first: no GPU, no training
infrastructure, one developer. TabPFN is a pre-trained foundation model for
tabular data — inference on a laptop CPU, calibrated uncertainty out of
the box. Calibrated uncertainty is the whole product: the app renders a
band with published error margins, never a point. The backtest harness
hides real weeks (walk-forward, leak-free), predicts them from history
only, and scores band coverage — 82–96% across rivers against a 70% gate.
Per-horizon decay metrics (day-1 vs day-7 accuracy) are published too,
because "the forecast is better tomorrow than next Sunday" is exactly the
kind of thing a paddler should be told.
Why Temporal for durability. The watcher is the product's spine — it
must survive reboots, crashes, and double-fires. Temporal gives retries
with backoff, missed-schedule catch-up, and kill-mid-run resume for free.
The workflow tests are my favorite in the repo: they literally kill
activities mid-cycle, restart, and assert the run completes with zero
duplicate rows — because every activity is idempotent (upserts,
skip-if-done-today, cache-before-call). The system treats "run it twice"
as a normal Tuesday, not an edge case.
Embeddings that cost $0. The scout's notes are embedded locally with
all-MiniLM-L6-v2 (384 dims, running in-process via transformers.js — no
API, no key, no quota) and searched with pgvector cosine distance on an
HNSW index. Semantic search over river access knowledge, running on a
budget of literally nothing.
An Urdu-aware scout with a relevance gate. On the very first scout
cycle, US rafting guidebooks leaked into the candidate pool — "runnable
between 500 and 2,000 cfs" quotes for the Colorado, attached to nothing
remotely Pakistani. The fix is now a permanent gate: native-script river
names, Pakistan-geo search parameters, and a river-relevance check on every
extracted claim. The failure is kept as a fixture so it can never quietly
come back.
A test culture that matches the stakes. The data dictionary's
invariants are executable: a range claim without a quote cannot exist in
the database; a verified range without a source cannot exist; the scout
budget cannot be blown. The QA phase added an axe-core accessibility audit
(zero violations after it caught real link-contrast issues), five rehearsed
failure drills with recorded evidence, and a cost audit that exits
non-zero if any free-tier ceiling is crossed. npm run verify green is
the definition of done, on every change.
The honest deployment story (read this before you copy my architecture)
This is the part most build-in-public posts skip. Here's exactly how
PaddleSense is deployed today, including the part that doesn't work yet.
Deployed and live: the web app (Next.js) and the prediction service
(FastAPI + TabPFN), both on Render's free tier as web services — you can
ping the predict service yourself:
https://paddle-sense-predict.onrender.com/v1/health.
The free tier sleeps idle services, so the first visitor waits ~30–50
seconds — that's disclosed in the UI culture of the project rather than
hidden.
Not deployed yet: the background worker. Render's free tier doesn't
include background workers — that needs the $7/month plan. The DEV
Hacktoberfest promo includes $50 of Render credit, which would cover it
generously, but the credit hasn't applied to my account yet. So the honest
state is:
- the watcher runs on my laptop today, fully functional — it has completed real weekly cycles, sent a real range-entry email, and proven the kill-mid-run resume live;
- the moment the credit lands, the worker deploys as a third Render
service with
WORKER_MODE=loop— the code path is already written, decided as an architecture record (ADR D5 in the repo), because a free 512 MB worker can't host a Temporal server alongside itself. Same activities, same idempotency, in-process weekly loop.
I considered not writing this paragraph. But a post about software that
tells the truth about rivers should tell the truth about its own
deployment page. The demo works today; the always-on watcher is one
applied credit away, and the repo documents exactly what happens when it
lands.
The money math, audited. Everything else runs on genuinely free
tiers — and the project proves it instead of claiming it: npm run reads the live database and fails if any ceiling is crossed
cost-audit
(SerpApi 12 of 90 searches this month, single-digit LLM calls a day against
AI Studio's daily quota, 16 of 256 MB in Tiger Data, one email sent).
Embeddings: $0 by construction. The app is adoptable by a Karachi rafting
club for nothing, which was the point.
What the app refuses to do
- No user accounts, no tracking, no PII (the notification email lives in an env var, nothing else)
- No point forecasts, ever — bands with published errors or nothing
- No "safe" — not as a word, not as a color
- No invented ranges — unverified means the app says so
- No deleting history — stored readings and predictions are the evidence the forecast is checked against
What I'll do next
- The worker on Render once the credit applies (code ready)
- More rivers, validated the same way: sweep cells, check them against a known flood month, ask local paddlers
- A public range-candidates review queue, so the community can help verify ranges with quoted sources
- Gauge cross-references where Pakistan publishes irrigation data
Try it
- Live app: paddle-sense.onrender.com — dashboard, per-river detail with verified bands, retrodiction page, and the semantic note search on the About page
- Code: github.com/abubakar-ahmed-dev/paddle-sense — the repo is the second artifact: full PRD, architecture decision records, data dictionary with executable invariants, QA report with the failure-drill evidence, cost audit, and the runbook
If you paddle in Pakistan — or anywhere with rivers and no paddling
tooling — tell me what you'd want it to watch next. And if you know why apromo credit takes its time, I'm listening too.
Built for DEV Hacktoberfest 2026. Free data (Copernicus GloFAS via
Open-Meteo), free tiers (Render, Tiger Data, SerpApi, Resend), an
open-weight model (Gemma), and a durable heartbeat (Temporal). $0 to run and honest about every number it shows you.


Top comments (0)