After every run I used to open the Mi Fitness app, take a few screenshots, and upload them to ChatGPT for analysis. On paper, that was a personal coach: the watch collects the data, I show it to AI, AI tells me what to do next.
In practice it fell apart quickly, and not because of the prompt.
A single workout doesn't fit on one screenshot: summary, charts, heart rate zones and other metrics all live on separate screens. Charts were the worst: a human can stitch several screens into one picture, but for the chat I was rebuilding the context from fragments every time.
The bigger problem: one workout means almost nothing without the previous ones. A heart rate of 168 is just a number. It is a different story if at roughly the same distance it was 158, then 164, 167, 168, 170 and 172. Useful advice needs a trend, and through screenshots I was bringing AI a small fragment of what the watch app already knew.
So the problem wasn't finding a better prompt. I needed a real channel between the watch data and the model.
What the model gets now
This is a shortened fragment of a real coach prompt that the portal assembles after a run:
=== RUN ===
Distance: 3.216 km
Heart rate: 168 bpm
Pace: 6:04/km
Cadence: 162 spm
Load: 127
Recovery: 57 h
Heart rate zones: ... extreme 96%
=== RECENT RUNS ===
09/14: 3.271 km, HR 172, load 142
09/12: 3.307 km, HR 170, load 139
09/10: 3.216 km, HR 168, load 127
...
=== RESPONSE ===
1. Assessment of the run.
2. Risks given the current state.
3. One specific recommendation for the next workout.
The real request contains more: the current run, the history of recent workouts, and stored context about my state. I can edit the template itself from the portal's UI.
That is the whole difference from screenshots. Before, I showed AI a picture of the data. Now the code gathers the facts and builds the context, I set the rules and the response format, and the model interprets the context.
The data layer: running-portal
running-portal syncs my running workouts from Mi Fitness, stores the history, and loads detailed data for a specific run. For me it is an ordinary training log. For the model it is a persistent source of context.
Mi Fitness
↓
sync
↓
running-portal
├── run history
├── detailed metrics
├── charts
├── state / how I feel
└── monthly goal
↓
AI coach
I did not reverse-engineer the private Mi Fitness API. The sync module is based on Mi-Fitness-Sync by kevinkwee (MIT License), which already implemented Xiaomi authorization, fetching workouts, and parsing detailed Mi Fitness data. A coding agent wrote running-portal and integrated that code; credit for the protocol work goes to the author of Mi-Fitness-Sync.
The integration is not pleasant: a private API and internal data formats, including separate files with detailed value series. That is one reason I treat the portal as a personal tool, not the basis for a public service.
The workout card shows me the same things that go to the model in structured form: heart rate, pace, cadence, stride length, load, recovery, heart rate zone distribution, and detailed time series.
To me, charts. To the model, raw numbers.
What the coach does with it
The analysis shows up on the same workout card.
Assessment → risks → one specific next session.
In this example the coach looks beyond average heart rate: most of the run happened in a high heart rate zone, it compares that with previous runs, and it suggests not increasing the load. I set a rigid response structure on purpose. I don't need sports motivation; I need an assessment, the risks, and the next workout.
This is where I stopped thinking of the prompt as magic text. The usefulness came from the context.
Guardrails in code, explanation from the LLM
The portal also answers a shorter daily question: run today, run easy, or rest?
At the time of this screenshot, less than the estimated recovery time had passed since the last run.
Here I don't hand the decision fully to the model. The prompt has explicit rules: for example, if the estimated recovery time after the previous workout hasn't passed yet, that is a signal in favor of rest. High heart rate, load, and saved notes about my state are factored in separately. The portal fills in the facts automatically:
last run
+ how much time has passed
+ last 7 runs
+ load
+ recovery
+ saved context
Then the model writes a short explanation. Part of the guardrails is explicit, and the LLM is used where several factors need to be connected and explained in plain language.
Why long-term context mattered
While getting back into regular running, I had problems with my feet, and later my knee bothered me for a long time. This is not a medical success story: AI didn't cure anything and didn't replace a doctor.
It was useful as a check on my own tendency to increase load too fast. When pain showed up in the context, the coach lowered the recommended load, suggested a pause, and advised seeing a doctor. After I bought new 361 KAIROS 2 shoes, the shoe change went into the context too, as one more factor against sharply increasing volume. With the knee, the portal kept the note in context and the model kept factoring it into later recommendations.
A one-off chat easily forgets what happened two weeks ago. The portal doesn't.
Arithmetic where arithmetic is enough
Later the task changed from "don't force it" to "what volume should I aim for next month?". At the start of the month, AI analyzes previous runs and proposes three goal options: conservative, recommended, and ambitious. I choose.
The model proposes scenarios and explains the recommended one; the final decision is mine.
Once a goal is accepted, AI is barely needed. Plain portal code counts kilometers run, number of workouts, remaining distance, and whether the month is on track. Where arithmetic is enough, there should be arithmetic; the LLM shows up where history needs to be interpreted.
Who wrote the portal
running-portal is 100% implemented by coding agents from my specs, except for the Mi Fitness integration foundation mentioned above. It wasn't generated once from one big prompt. The loop was:
use the portal
↓
see what's missing
↓
formulate the task for the coding agent
↓
get the change
↓
use the portal again
The repository keeps 11 prompt artifacts from different stages: sync, frontend, the AI coach, charts, analytics, monthly goals and other improvements. I can't reconstruct every agent session behind them anymore, so I don't treat the number of files as a precise development log.
Limitations
- A personal portal for a single user.
- Depends on Mi Fitness's private mechanisms, which can change.
- Simple FastAPI and SQLite architecture; some background work runs inside the app process.
- Not a product I'm ready to hand to thousands of users.
- The AI coach is not a medical system. I treat its recommendations as feedback about training load, not diagnosis or treatment.
Takeaway
When I started sending screenshots to ChatGPT, I thought the task was learning to ask the right way. A good question turned out to be the smallest part of the system.
The watch already collected the data, and the LLM could already reason about it. What was missing was a layer that stores the training history, surfaces the right details, adds up-to-date context, and hands it to the model in a predictable form. Close to a year after getting back into running, every workout lands in the portal, and AI works with my running history instead of a few pictures I show it after each run.
The source is on GitHub: hram/running-portal. Suggestions and bug reports are welcome. The full case study, with more screenshots, is on my site: https://hram.github.io/en/articles/running-portal/




Top comments (0)