Two features here are LLM-generated: the trend summary and the swim/heater advisor. Both run on a local model on the same Mac that fetches the pool data โ here's exactly what each one sees, what model answers it, and what happens if the model isn't available.
Both AI features call Ollama, a
local LLM runtime, at localhost:11434 on the same Mac that runs the twice-daily fetch.
The prompts (pool readings, weather forecast, today's date) never leave that machine, and neither
model has internet access or tool use โ they just read the numbers they're given and write text
back. The only things that do leave the machine each run are the calls to WaterGuru, the National
Weather Service, and โ if you've enabled push alerts โ ntfy.sh or Pushover.
The 2-3 sentence readout at the top of the dashboard on whether chlorine, pH, and water temp are trending up, down, or holding steady.
The great/good/marginal/poor verdict on each of the next 5 days, plus the heater lead-time advice paragraph (skipped entirely for a pool with no heater โ see below).
great/good/marginal/poor) and a one-phrase
note per day, plus the heater-advice paragraph. That shape is handed to Ollama as a JSON
Schema, so the sampler is constrained to it token by token rather than merely asked for
it โ the model physically can't emit an invalid verdict, or a date outside the forecast it was
given. The response is still validated before the dashboard trusts it: every forecast date has
to actually appear, since a schema that only rules out wrong dates doesn't rule out
missing ones โ a model that quietly drops a day is rejected just like one that invents a
bad one. For a heaterless pool, the heater-advice field is forced to empty regardless of what
the model wrote, so a model that ignores the instruction and invents heater text anyway never
reaches the dashboard.Three local models were compared for the swim advisor specifically, since it has to weigh several factors into a judgment call and return valid JSON โ that's a meaningfully harder ask than the trend summary.
| Model | Result |
|---|---|
| llama3.2:3b | Fast, valid JSON, but inconsistent or illogical verdicts โ e.g. once rated a sunny 78ยฐF day worse than a stormy one. |
| gpt-oss:20b | Ignored the JSON output constraint entirely and returned rambling chain-of-thought prose instead of the requested structure. |
| qwen2.5:32b | Reliable JSON every time, and verdicts that were internally consistent (worse weather reliably scored worse). Takes roughly 30-45 seconds per run, which is a non-issue for something that runs twice a day in the background. |
The trend summary's ask is simpler (summarize a table of numbers in a few sentences), so a small, much faster model handles it fine.
Since that comparison, the advisor's JSON shape is enforced by constrained decoding rather than by
prompt instructions, which takes "returns valid JSON" off the table as a differentiator โ what's
left to compare is judgment quality and speed. Mid-size models like gemma3:12b become
viable on that basis, and the model is now a config setting rather than a code change. The tags
above show what actually produced the text you're reading on the dashboard.
These are convenience reads, not safety systems. The chemistry alerts (RED/YELLOW/GREEN, cassette replacement) are WaterGuru's own thresholds, not AI-generated โ the LLMs only touch the trend summary and the swim/heater advice. Treat both as a second opinion worth a glance, not a replacement for checking the water yourself.