Case study 04 / 06
AgroPulse
A precision agriculture data platform, from sensor stream to dashboard.
Streams simulated soil moisture, canopy temperature, nitrogen and irrigation readings through Kafka (Redpanda) into TimescaleDB for real-time and historical analysis, with a nightly Airflow DAG that recomputes yield forecasts.

- Type
- Streaming and batch pipeline
- Data
- Simulated field sensors
- Landing page
- Live on Netlify
- Code
- Public on GitHub
01
The problem
A commercial farm operator wants one live picture of a field instead of separate spreadsheets, gut instinct and a walk along the fence line.
Sensor gateways can report soil moisture, canopy temperature, nitrogen and irrigation draw in near real time. That data has two jobs: an operational dashboard for reacting to today, and a forecast that projects the season's yield from what has built up so far.
02
What I built
AgroPulse started as a landing page for a fictional precision agriculture company and grew into a working demo of the full data path behind it, on simulated sensor data.
01
Sensor simulator
Sends JSON readings for soil moisture, canopy temperature, nitrogen and irrigation to a Kafka topic, a new reading every few seconds.
02
ETL consumer
Subscribes to the topic, cleans and transforms each reading, and writes it to TimescaleDB.
03
Nightly forecast
An Airflow DAG recomputes yield forecasts every night from the readings collected so far.
04
Serving layer
FastAPI endpoints for each field's latest readings and its yield forecast.
05
Product UI
A React 19 and TypeScript landing page with a field card, soil analytics and an ROI calculator, built with Tailwind CSS v4, Framer Motion and Recharts.
03
How it works
Architecture
Hover a component to trace its connections.
- 1
Ingest
The simulator publishes readings to the field.readings topic on Kafka (Redpanda).
- 2
Process
The ETL consumer cleans each reading and writes it to TimescaleDB.
- 3
Recompute
Airflow runs a nightly batch job that updates each field's yield forecast.
- 4
Serve
FastAPI reads TimescaleDB and returns the latest readings and forecasts over HTTP.
- 5
Run anywhere
Each stage runs in its own container, wired together with Docker Compose, and the repo opens in GitHub Codespaces with everything installed.
04
Tech stack and why
Kafka (Redpanda)
The message broker between the sensors and processing.
TimescaleDB
Postgres with time-series support, for live and historical queries.
Apache Airflow
Runs the nightly forecast recompute, in standalone mode.
FastAPI
Serves latest readings and yield forecasts.
React 19, Vite and Tailwind CSS v4
The product UI, with Framer Motion for animation and Recharts for charts.
Docker Compose and Codespaces
Every service in its own container, and a devcontainer that installs Node, Python and Docker.
05
Challenges and what I learned
Five services on a free Codespace
Running all five backend services and the frontend at once is memory-heavy on a free Codespace. I built and checked each stage only against what it directly depends on, and brought the whole stack up together only for the final end-to-end test.
Checking every hop
Each stage has its own check: watching messages arrive on the topic, querying what landed in TimescaleDB, calling the API, and triggering the DAG by hand instead of waiting for its schedule.
What I learned
- Build a pipeline one hop at a time. A failure in one stage is far easier to read than a failure somewhere in five.
What this is and isn't
A working demo of the ingestion, ETL, storage, batch and serving pattern on simulated data. It isn't a production SaaS: there's no real hardware, no multi-tenancy, no billing, and the forecast is a simple heuristic rather than a trained model. The landing page still runs on mock data; wiring it to the live API is next.
06
Screens
A live field card streaming four sensor readings.
Soil analytics: every block of a field coloured by stress level.
An ROI estimate from farm size and crop type.
07
Links
See AgroPulse for yourself.
Have something like this in mind?
Have a project, role or research opportunity in mind? Let's talk.