Asthma is managed on evidence, and the evidence is a diary. The problem with a paper diary is that it records the symptom and forgets the day around it: what the air was doing, how warm it was, where the person had been. Six months later you have a column of numbers and no way to tell what any of them correlate with.
Asma Tracker is a small web app I built for someone very close to me, so the diary keeps the context automatically. It takes about a minute in the evening, it runs on a phone like an app, and it asks once if the entry has not arrived.
The interface is entirely in Italian, because the person using it does not need it to be anything else.
The daily report
One entry per day, six fields. Breathing quality, wheezing and fatigue on a one to five scale, the kind of environment the day was spent in, the number of puffs taken, and an optional free note.
A day is never duplicated. Opening the form again after submitting loads that day’s entry back into the fields, and saving updates it in place instead of writing a second record. Symptoms get worse after dinner, and a diary that punished you for correcting an entry would simply stop being filled in.
“Today” is computed in Europe/Rome, not in the server’s timezone. Render runs in UTC, and
without that the entry made at half past eleven at night would have been filed under tomorrow.
Why the weather is attached to every entry
On submit the browser offers its coordinates, and the app asks OpenWeatherMap for that day’s conditions: temperature with its low and high, humidity, pressure, wind speed, cloud cover and the written description.
Those values are copied onto the report, not referenced. A weather API answers about the present, so a record that stored a location and a date would quietly become unanswerable as the history window rolls past. Denormalising the reading is what makes an entry from last winter still mean something this winter.
That is the whole point of the project. The symptom scores on their own say a day was bad. The symptom scores next to pressure, humidity and where the day was spent start to say why.
Trends
The trends page draws breathing quality, wheezing, fatigue and puff count as lines over a chosen month.
The month picker only lists months that actually contain entries, which comes from a separate projection query that fetches nothing but the date field of the last twelve months. The chart itself queries a single month at a time, bounded by a date range rather than filtered in Python after the fact. Firestore bills per document read, and a personal app should cost approximately nothing to run.
Closed by design
There is no repository link on this page and no public demo, and that is a decision rather than an omission.
Sign-in is Google through Firebase, but authentication is not authorisation: the server verifies
the ID token and then requires a custom claim, authorized, to be true on that account. Anyone
can sign in. Almost nobody gets past the next line. The claim is set by hand through the Admin
SDK, so the app is invite only in the most literal sense, and Firestore rules enforce the same
boundary a second time at the database, scoping every document to the user that owns it.
This is somebody’s health record. The right number of strangers reading it is zero.
Stack
- Flask on Python, served by gunicorn on Render, configured from
render.yaml. - Firebase Authentication for Google sign-in, with the ID token verified server side and a custom claim gating access. The verification result is cached in process for ten minutes, with expired entries swept out, so that opening the dashboard does not mean a round trip to Google every time.
- Firestore for storage, with security rules isolating each user’s reports.
- Jinja templates with Bootstrap 5 and Bootstrap Icons, plus a hand written stylesheet. No build step and no frontend framework: the app is five pages.
- Chart.js for the monthly trend lines.
- OpenWeatherMap One Call v3 for the conditions attached to each entry.
- OneSignal web push for the evening reminder, served through a service worker.
- A web app manifest with a full icon set, so it installs to the home screen and opens without browser chrome.
- A
/healthendpoint that reports process liveness and deliberately does not touch Firestore. A health check that fails when a dependency is slow does not report a problem, it creates one: Render would have restarted a perfectly healthy process.
Token verification also retries on the specific failure of fetching Google’s signing
certificates, which is the one transient error that turned a working session into a logout. The
pinned versions of google-auth and cachecontrol in requirements.txt are there for the same
incident.