← Back to the dashboard

How the AI on this dashboard works

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.

๐Ÿ”’ Runs locally

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.

Trend summary llama3.2:3b

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.

1
Input: the last 14 days of readings for the pool โ€” timestamp, status, free chlorine, pH, water temp, and skimmer flow for each one.
2
Prompt: asks the model to say, in plain prose (not a list), whether each of those three readings is trending up, down, or steady, and whether things look solid or need attention โ€” with real numbers, not vague language.
3
Fallback: if Ollama isn't reachable, a rule-based sentence is generated instead by comparing the first and last reading in the window โ€” same idea, just not written by a model. The dashboard shows which one you're looking at.

Swim & heater advisor qwen2.5:32b

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).

1
Input: today's date, the pool's most recent water-temperature reading, the 5-day National Weather Service forecast (high temp, rain chance, wind, and conditions for each day), and โ€” if the owner filled it in โ€” a sentence on how the pool actually gets used (which days, how often, what matters more to them).
2
Prompt: asks the model to weigh each day's air temperature against the water temp and against what's normal for the season, factor in rain/wind/storms, and โ€” for a heated pool that takes a couple of days to visibly move in temperature โ€” call out any day that's notably cooler or warmer than the rest of the stretch, with a concrete suggestion to adjust the heater setpoint roughly 2-3 days ahead of it, weighed against which days the owner actually swims if they said so. For a pool with no heater, this whole ask is dropped from the prompt โ€” the model is told outright there's no heater rather than being asked to invent advice for equipment that isn't there.
3
Output contract: the model must return strict JSON โ€” a verdict (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.
4
Fallback: if Ollama is unreachable, or the model's output fails validation, the dashboard falls back to a fixed point formula instead (temperature, rain chance, and wind each subtract points; any mention of storms is an automatic fail) โ€” cruder, but it never leaves the forecast card blank.

Why these two models

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.

ModelResult
llama3.2:3bFast, valid JSON, but inconsistent or illogical verdicts โ€” e.g. once rated a sunny 78ยฐF day worse than a stormy one.
gpt-oss:20bIgnored the JSON output constraint entirely and returned rambling chain-of-thought prose instead of the requested structure.
qwen2.5:32bReliable 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.

Nothing here is a substitute for judgment

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.