bdeb1337 d7342d6096 feat: workout-suggester, weather-aware workouts from a local Gemma
Go + Templ + HTMX app that checks the weather (Open-Meteo, MET Norway
fallback), scores run/ride/walk with plain rules, and has a local
open-weight model (Gemma 4 E2B via any OpenAI-compatible server) write
the plan. Routes are loops from your door (BRouter) plus signposted
OpenStreetMap routes (Overpass), with GPX export.
2026-10-11 19:29:58 +02:00

workout-suggester

Tell it where you are, how hard you want to go and for how long. It checks the weather and tells you whether to run, ride or walk, or to stay in and do HIIT when it's grim. It also gives you a signposted OpenStreetMap route that fits the distance, as a GPX file for your watch.

The advice comes from Gemma 4 E2B running on your own machine. It's a Go + Templ + HTMX app in one ~9 MB binary.

Desktop: weather, activity scores, the coach's plan and a route map The same on a phone

How it works

  1. Weather: Open-Meteo (open source, no key) gives current conditions and the next 24 hours. If it's overloaded, the app retries once and then falls back to MET Norway. MET Norway has no gusts or rain chance outside the Nordics, so gusts are estimated (shown as ≈), the rain chance shows as unknown, and sunrise/sunset are computed locally. If both fail, it uses the last forecast it had, up to 3 h old. Place search falls back from Open-Meteo to Nominatim.
  2. Scoring (plain Go rules, not the model): each activity starts at 100 and loses points for rain, gusts, cold, heat, ice, fog and darkness. The amount depends on the activity, e.g. 50 km/h gusts cost a ride 40 points and a run only 15. Below 40 everywhere, you're sent inside. The app also looks up to 12 hours ahead for a clearly better daylight window ("better at 17:00").
  3. Routes:
    • Loops from your door: BRouter, an open-source OpenStreetMap router, generates round trips of your target length in three directions. It uses a foot profile for runs and walks and a bike profile for rides. The first loop heads out into the wind so the way home has a tailwind. Each loop shows its climb and how much of it is paved.
    • Signposted routes nearby: the Overpass API finds named cycling and walking routes (Belgian fietsroutes, wandelroutes) and the nearest knooppunt (node-network junction). They're matched to your distance as loops, laps or out-and-back. The way to the start counts towards your distance, and long-distance trails like the Streek-GR are left out.
  4. Coach: all of the above goes into the system prompt as a short sheet of facts. Gemma writes the plan: why, when, the session (warm-up, main set, cool-down), route, what to wear and a plan B. You can ask follow-ups ("only 30 min", "make it harder").

The split is deliberate: small models get numbers wrong, so code decides the facts and the model explains them and builds the workout.

Why open

  • The model runs on your machine. Your location, your notes ("sore knee") and your questions never reach an AI company. There's no API key and nothing to pay per token.
  • Only rough coordinates leave the machine. Weather lookups use about 1 km precision, signposted-route lookups a ~2 km grid, and loop planning ~100 m. Distances to routes are computed locally with your exact position.
  • Every part can be swapped or self-hosted. Point LLM_BASE_URL at oMLX, llama.cpp, Ollama or LM Studio and use any model. Open-Meteo, Overpass and BRouter are open source too, so OPEN_METEO_URL, OVERPASS_URL and BROUTER_URL can point at your own instances.
  • Routes keep working without a connection. Route data is cached on disk for a week. GPX export means your phone can stay in your pocket.
  • OpenStreetMap knows the local routes, down to the knooppunten network and park loops. No closed fitness API gives you that for free.

Run it

You need an OpenAI-compatible model server. On a Mac:

omlx serve                       # or: llama-server -hf unsloth/gemma-4-E2B-it-GGUF:Q4_K_M --no-mmproj
cp .env.example .env             # set LLM_BASE_URL / LLM_MODEL (and LLM_API_KEY if your server wants one)
make run                         # → http://127.0.0.1:3000

Tap 📍 Here (the browser asks for your location) or type a town.

Setting Default
LLM_BASE_URL http://127.0.0.1:8000/v1 (oMLX)
LLM_MODEL first model the server lists
LLM_API_KEY none
DEFAULT_PLACE none (prefills the town field)
ADDR 127.0.0.1:3000
BROUTER_URL https://brouter.de/brouter (self-hostable; - turns loops off)
OVERPASS_URL https://overpass-api.de/api/interpreter (space-separated list, tried in order)
OPEN_METEO_URL, OPEN_METEO_GEOCODE_URL the public Open-Meteo API

gemma-4-E2B-it-qat-4bit answers in about 10 s on an M-series Mac. E4B follows the format more closely.

Develop

make test                        # unit + handler tests, no network or model needed
uv run scripts/e2e.py --url http://127.0.0.1:3000 [--mobile] [--dark]   # browser test + screenshots
make dist                        # cross-compile for Linux, macOS, Windows
cmd/workout-suggester   entry point
internal/weather        Open-Meteo + MET Norway fallback, local sunrise maths (rounded coords, 15 min cache)
internal/coach          scoring rules, distance targets, the prompt
internal/routes         BRouter loops, Overpass client (retries, disk cache), route matching, GPX
internal/session        per-browser plan + chat
internal/llm            minimal OpenAI-compatible streaming client
internal/web            handlers, Templ views, HTMX + SSE, Leaflet map

Weather data by Open-Meteo and MET Norway (both CC BY 4.0). Route data © OpenStreetMap contributors (ODbL). Loops by BRouter.

S
Description
No description provided
Readme
2.6 MiB
0 Stars 1 Watchers 0 Forks
Languages
Go 72.4%
CSS 14.7%
templ 6.8%
JavaScript 3.5%
Python 1.9%
Other 0.7%