Inspiration
A fixed sprinkler timer doesn't know it rained yesterday, or that more rain is coming tomorrow. It runs on schedule anyway. I wanted to build a small tool that answers one daily question for a homeowner: do I need to water today? The answer should come with the reasoning shown, not just a verdict.
Weather-based irrigation advice already exists, and I'm not claiming a new calculation. What I wanted to build is a version that is transparent (it shows the numbers behind every call) and honest about savings (every figure is labeled an estimate and tied to a baseline you can see).
What it does
RainCheck gives one of three recommendations each day for a residential lawn:
- SKIP: rain received and forecast is enough to cover the lawn's water needs.
- REDUCE: the lawn needs some water, but less than your usual schedule.
- WATER: the lawn needs its normal watering.
Every recommendation has a plain-language reason and a "Why?" section with rainfall received, rainfall expected, estimated demand, and uncertainty in words. If weather data is unavailable, the app says it cannot recommend and gives the reason. It never guesses.
RainCheck is advisory only. It never controls sprinklers; the homeowner decides.
Estimated water avoided is shown against your own stated watering schedule. It is always labeled Estimated, and the baseline and assumptions are one tap away. It is never presented as measured savings.
How I built it
I worked solo, spec-first. Before writing features I wrote context files covering architecture, code standards, UI rules, and a calculation spec. Then I built one small, verified unit at a time.
- Frontend: React + TypeScript (strict), Vite, Recharts, mobile-first
- Backend: Python, FastAPI, Pydantic, SQLite
- Weather: Open-Meteo (rain, temperature, wind, and ET₀)
- Engine: a pure Python water-balance module with no network or database access, so every calculation is unit-tested against hand-checked values
The core is a daily soil water balance based on the FAO-56 method. Open-Meteo provides reference evapotranspiration ($ET_0$), which is scaled to the lawn by a crop coefficient:
$$ET_c = K_c \times ET_0$$
Depletion of the root zone is tracked day by day:
$$D_t = \min\left(TAW,\ \max\left(0,\ D_{t-1} + ET_{c,t} - P_{\mathrm{eff},t} - I_t\right)\right)$$
Here $D_t$ is how much water the root zone has lost, $P_{\mathrm{eff}}$ is the effective rainfall, and $I_t$ is irrigation applied. The app projects depletion forward through the forecast, up to your next scheduled watering. SKIP means the lawn stays below the stress threshold until then.
[Add when true: deployment, number of tests passing, the demo region.]
Challenges I ran into
- No invented science. I set a rule that every constant must come from a cited source or be marked as an assumption. Sources often disagree. For example, published soil water values and root-zone depths for lawns vary a lot, and root depth strongly affects the result. I record these as ranges and flag the chosen values as assumptions instead of hiding the uncertainty.
- A logic bug caught before it shipped. My first draft of the recommendation rule included today's depletion in the forecast check. That meant a dry lawn with heavy rain tomorrow would still say WATER, so SKIP could never fire when it mattered. I fixed it in the spec and added a regression test.
- Weather data is messy. I validate every field from the provider, normalize units to SI, and handle missing values, timeouts, and bad responses explicitly. All of this is tested with mocked responses, so no test depends on the live API.
- Existing tools. Weather-based watering advice is not new. I had to decide what is actually different, and I'm positioning RainCheck on transparency and honest, baseline-labeled savings instead of claiming to be the first.
Accomplishments that I'm proud of
- A spec-driven workflow where the code, the tests, and the docs stay in sync
- A calculation that is pure, tested, and explainable end to end
- Savings numbers that are labeled as estimates, with the baseline always visible
- [Update at the end: working end-to-end demo, live URL, test count]
What I learned
- Writing the spec first made the hard decisions visible early, such as what the baseline for "savings" should be.
- It's easy to produce a confident number and hard to produce an honest one. The labeling rules took as much design work as the math.
- Reviewing a formula by hand against a test case finds bugs that reading the code does not.
What's next for RainCheck
- Validate the idea with real homeowners. I have not yet run user interviews, so I'm not claiming that people would use this.
- Check the constants against local measurements and local extension guidance for specific regions.
- Notifications and a simple way to log watering.
- Better handling of different sprinkler types and soil conditions.
- A different data arrangement for any commercial use, since Open-Meteo's free tier is for non-commercial use only.
Limitations: results are estimates based on stated assumptions and a stated baseline, not measurements. The tool has not been tested against metered water use.
Built With
- css
- fastapi
- lucide
- open-meteo
- pydantic
- pytest
- python
- react
- recharts
- ruff
- sqlalchemy
- sqlite
- typescript
- vite