Skip to content

About

Self-management web app for Immune Thrombocytopenia (ITP): platelet tracking, medication cycles, cost forecasting, and n-of-1 dose-response analysis. Zero dependencies, standard-library Python and vanilla JS with hand-drawn SVG charts.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

ITP Tracker

A self-management web app for Immune Thrombocytopenia (ITP). It tracks platelet counts, follows a medication cycle, and forecasts monthly treatment costs.

Built with zero third-party dependencies: standard-library Python on the back end, vanilla JavaScript and hand-drawn SVG on the front end. No pip install, no npm install, no CDN. Clone it and run it.

▶ Try the live demo (sample data, no sign-up)

py -3 server.py     # → http://localhost:8000
Light Dark
ITP Tracker in light theme ITP Tracker in dark theme

Why this exists

ITP is an autoimmune condition in which the immune system destroys platelets, raising the risk of bleeding. Managing it means answering two questions every single day:

What is my platelet count doing? · What do I take today?

Existing tools answer neither well. Generic health apps don't understand platelet reference ranges, and none of them model the rotating dose cycles that treatments like avatrombopag actually use (e.g. "2 pills for 1 day, then 1 pill for 3 days, repeating"). Medication is also expensive and largely self-funded, so predicting monthly cost matters as much as the clinical picture.

This app is built around those needs, and around one design rule: logging a reading must take under five seconds.

Features

Platelet tracking

  • Readings plotted against the four clinical reference zones used in Chinese hospitals (×10⁹/L): critical <30, high-risk 30-75, borderline 75-125, normal ≥125. Thresholds are configurable.
  • Interactive SVG chart: hover/touch crosshair with tooltip, y-axis gridlines, rolling average line, min/max annotations, and 7 / 30 / 90 / all-time ranges.
  • Summary bar: latest, average, range, and net change over the window.

Medication cycles

  • Compose a cycle from phases ("N pills × D days") that repeat from a start date, modelling real prescribing patterns instead of a fixed daily dose.
  • Today's dose, position in the cycle (day X of Y), and a 7-day look-ahead strip.
  • Mark each day taken or missed.

Dose-response analysis

  • Estimates whether the medication is associated with higher platelet counts, accounting for the fact that the marrow responds over days rather than hours.
  • Scans candidate lags of 0-21 days, correlating smoothed dose exposure against counts measured later, and reports the best-supported delay.
  • Reports effect size, sample size and significance, and states plainly when the evidence is weak, absent, or when dose hasn't varied enough to test.
  • Statistics are implemented from first principles in analysis.py and unit tested, including against synthetic data with a known planted lag.

Cost forecasting

  • Computes pills required for the current calendar month directly from the cycle.
  • Derives per-pill price from box price ÷ box size automatically.
  • Adds doctor visits and CBC blood tests to produce an itemised monthly total and an annual projection.

Interface

  • Light and dark themes, remembered between visits.
  • Four tabs (Overview · Medication · Analysis · History), responsive down to phone width.

Architecture

flowchart LR
    subgraph Client["Browser (index.html)"]
        UI["Vanilla JS UI<br/>hand-drawn SVG charts"]
    end

    subgraph Server["Python, standard library only"]
        R["route()<br/>pure request handler"]
        DB[("SQLite<br/>itp.db")]
    end

    UI -->|"JSON over fetch()"| R
    R --> DB

    L["server.py<br/>http.server, local"] --> R
    W["wsgi.py<br/>WSGI, hosted"] --> R
Loading

The core design decision is that route() is a pure function:

route(method, path, body, auth_header) -> (status, headers, body)

It touches no globals from the web layer, which means the same handler serves both the local http.server runner and any WSGI host, and the entire HTTP surface is testable without opening a socket.

Project layout

File Purpose
server.py Router, SQLite persistence, auth, static files, dev server
analysis.py Dose-response statistics (correlation, lag scan, significance)
wsgi.py WSGI entry point for hosted deployment
schema.sql Database schema, applied idempotently at start-up
seed_demo.py Generates sample data for a public demo deployment
index.html Page structure
static/app.css Design system: themes, layout, components
static/app.js UI logic and the hand-drawn SVG charts
tests/ 81 unit tests (unittest, no dependencies)
DEPLOY.md Step-by-step free hosting guide
itp.db Your data. Git-ignored, never committed

Getting started

Requires Python 3.9+. Nothing else. (CI runs the test suite on 3.9, 3.11 and 3.13.)

git clone https://github.com/abdessamadr46/itp-tracker.git
cd itp-tracker
py -3 server.py           # Windows;  python3 server.py  on macOS/Linux

Open http://localhost:8000. The database is created automatically on first run.

Configuration

All optional, via environment variables:

Variable Purpose Default
ITP_PASSWORD Require a password (HTTP Basic auth) unset, open
ITP_DB Path to the SQLite file ./itp.db
ITP_HOST Bind address 127.0.0.1
ITP_PORT Port 8000

Tests

py -3 -m unittest discover -s tests -v

Covers schema and defaults, reading upsert/delete semantics, dose logging, cycle-phase sanitisation, cost clamping, routing, authentication, static-file serving (including a path-traversal check), port binding, the WSGI adapter, and the statistical functions in analysis.py. Tests run against a temporary database and never touch real data.

Deployment

See DEPLOY.md for a free hosting walkthrough.

Two deployment shapes are supported:

  • Private instance holding real readings. Set ITP_PASSWORD so the data is never publicly readable.
  • Public demo holding generated sample data. Run seed_demo.py, set ITP_DEMO=1 for the sample-data banner, and leave ITP_PASSWORD unset.

Real medical records should never be placed on a publicly reachable instance.

Research context

Beyond being a logging tool, the app functions as a small n-of-1 observational instrument: a single patient, densely sampled over time, with an intervention (dose) that varies and an outcome (platelet count) measured repeatedly.

Question. Within one patient's own record, is medication exposure associated with subsequent platelet counts, and at what delay?

Method. The medication cycle is expanded into a daily dose series and adjusted for logged adherence. Because platelet production responds over days, same-day comparison is not meaningful; instead, dose is smoothed into a rolling 7-day exposure window and correlated against counts at lags of 0-21 days. Pearson's r, the least-squares slope, and a two-tailed significance test at α = 0.05 are computed for each lag, and the lag with the largest |r| is reported with its sample size.

Why this framing. Chronic-illness self-tracking generates exactly this kind of irregular, single-subject time series, and it is usually left unanalysed. Treating one person's record as a legitimate unit of analysis connects to established work on n-of-1 trials and patient-reported outcomes in digital health.

Limitations, stated deliberately and surfaced in the app itself. Observational, single-subject data cannot establish causation; infection, other medication, and spontaneous remission are uncontrolled confounders. Searching 21 lags inflates the false-positive rate (multiple comparisons), and the smoothing window is a modelling choice that affects which lag wins. Dose is also strongly autocorrelated, since it changes slowly, so neighbouring lags produce near-identical correlations and the selected lag is only approximate. Sample sizes are small, so estimates are unstable. The app therefore reports effect size and n alongside every result, labels weak findings as weak, and refuses to report anything when dose has not varied. Presenting an uncertain result honestly was treated as a design requirement, not a caveat added afterwards.

Possible extensions. Pre-registering a single hypothesised lag would remove the multiple-comparisons problem; cross-correlation with confidence intervals, change-point detection around dose adjustments, or a mixed-effects model across multiple consenting users would all strengthen the inference.

Engineering notes

Why no dependencies? A tool you rely on daily for medication shouldn't break because a transitive dependency published a bad release, and it should still run in five years. The constraint also forced clearer thinking: the charts are plotted with explicit coordinate math and Catmull-Rom smoothing rather than delegated to a library, and the whole app is a handful of files anyone can read end to end.

Trade-offs. SQLite and a single-process server suit one user well and would not suit many; a multi-user version would need a client/server database and real sessions. The front end is served as plain static files with no build step, which keeps it inspectable and dependency-free but would want a module bundler to grow much further.

Testing focus. Tests target the logic most likely to cause silent harm: dose-cycle arithmetic and input sanitisation. Writing them surfaced a real connection leak: with sqlite3.connect(...) commits the transaction but does not close the connection. That is now fixed with an explicit context manager. Verifying start-up behaviour separately turned up a portability bug: Python enables SO_REUSEADDR by default, which on Windows lets a second process bind a port already in use, so two servers silently compete for requests. Address reuse is now disabled on Windows only, where the semantics differ from Unix.

Roadmap

  • Dose-response analysis with lag detection and significance testing
  • Export data as CSV / PDF for clinic appointments
  • Symptom log (bruising, petechiae, fatigue) correlated against counts
  • Multi-language interface (English / 中文)
  • Multi-user accounts, so other ITP patients can use it
  • Accessibility audit (keyboard navigation, screen readers, WCAG contrast)
  • Confidence intervals and change-point detection around dose adjustments

Disclaimer

This software is a personal record-keeping tool. It is not a medical device, it does not provide medical advice, and it has not been clinically validated. Do not use it to make treatment decisions. Always consult your haematologist.

Privacy

All data stays in a local SQLite file that you control. The app makes no outbound network requests, contains no analytics or telemetry, and sends nothing to any third party. *.db is git-ignored so health data is never committed.

License

MIT

About

Self-management web app for Immune Thrombocytopenia (ITP): platelet tracking, medication cycles, cost forecasting, and n-of-1 dose-response analysis. Zero dependencies, standard-library Python and vanilla JS with hand-drawn SVG charts.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages