A local-only health tracking application for patients navigating complex diagnostic journeys.
Built for people who need to see patterns in their own data when the medical system isn't connecting the dots yet.
(Well, built for one person who needed to see patterns, but she figured she couldn't be the only nut out there.)
Note: This is the public codebase of what was originally released as
biotracking, now named sardine-track (a SARDs pun — "systemic autoimmune rheumatic disease," plus a tracker that sticks with you). The GitHub rename keeps the old URL as a redirect, so existing links still work. Active experimentation continues in a private fork that may push features back upstream when they prove out. The iOS companion app lives in its own repo: sardinessync. Want to know how your data is stored and how it gets to your phone? See the remote access guide.About the family-instance framing: the app supports multiple users and was originally built hoping family with shared genetic risk might want to track alongside. In practice the daily-entry burden has kept adoption to one. Multi-user plumbing is preserved — if a family member or friend does decide to try, they can register their own account on the same instance without affecting anyone else's data.
Sardine-track helps you:
- Track daily symptoms, biometrics, and environmental factors (including UV exposure, in fact especially UV exposure)
- Visualize correlations over time (does UV exposure predict your symptom flares? does low HRV precede bad days?)
- Generate clinical reports to bring to appointments (when you know damn well your brain is not going to remember everything, plus it has graphs!)
- Keep a longitudinal record of labs, medications, & clinical events, as well a list of your clinicians
- Run flare forecasting based on your own historical patterns — transparent scoring, not a black box, and tuned on your own n=1 data
- Evaluate medical interventions (hydroxychloroquine, steroids, biologics, whatever): per-medication pre/post flare impact, autonomic shift, duration-of-effect for one-time doses, and structured logging of side effects, rebounds, and dose changes
- Auto-sync biometrics from Apple Health via the sardinessync iOS companion app (steps, HRV/SDNN, RMSSD, resting heart rate, SpO2, respiratory rate, basal body temperature, time in daylight)
- Keep all your data local — nothing leaves your computer, if you don't want it to.
This is not a medical product. This is a tool for veracity: for people who need to make their invisible patterns visible, whether for themselves or to make a case to others.
- Symptom logging: Track 9 symptom categories with detailed notes (neurological, cognitive, musculature, migraine, pulmonary, dermatological, mucosal, rheumatic, gastrointestinal)
- Environmental factors: UV exposure (pulled from Open-Meteo and Visual Crossing), temperature, sleep quality
- Physical metrics: Steps, basal body temperature, pain/fatigue scales (1-10)
- Flare documentation: Mark flare days and track what actually happened
- Quick entry mode: Stripped-down form (
?mode=quick) showing only the fields that feed the prediction model — for days when filling a full form is too much
Enabled via track_cycle: true in setup. Designed for patients where steroids, biologics, or disease activity disrupt menstrual regularity.
- Month-grid calendar: Color-coded period flow (heavy/medium/light/spotting) with phase overlays
- BBT-anchored phase detection: Uses basal body temperature biphasic shift (rule of three: 3 consecutive days ≥ 0.1°F above follicular average) to detect actual ovulation rather than relying on fixed calendar math — critical when steroids compress or extend cycle length unpredictably
- Cycle phase overlays: Luteal and PMS windows calculated from detected ovulation, not assumed 14-day countdown
- BBT heat visualization: Per-day colored border on calendar cells indicating BBT elevation, colored SVG wave graph per month
- Flare/phase correlation: Patterns card showing flare distribution by cycle phase across all historical data
- Intervention effects on cycle: Before/after average cycle length for each medication intervention
- Apple Health import:
import_cycle.pyimports Menstrual Flow and Intermenstrual Bleeding records from Apple Health export CSV
- Model dashboard (
/model, nav label "model"): score attribution over time — stacked bars showing exactly what's driving each day's risk score, with flare markers and threshold line. Includes click-to-expand trend charts for symptom burden delta, RMSSD deviation, and respiratory rate deviation (with +10% / +15% scoring thresholds dashed in). Score distribution stats (flare vs non-flare) and per-day prediction accuracy strip. - Model sub-navigation from the dashboard: accuracy, history, and pre-flare patterns are all hyperlinked from the model view so you don't have to dig through nested menus. A hidden
>>chevron in the corner opens the Forecast Lab (easter-egg style,just because that's fun). - Pre-flare pattern analysis (
/forecast/patterns): biometric averages, symptom frequencies, and RMSSD trajectories in the days before ER visits and major flares, with aggregate mean lines and confidence bands. - UV lag analysis (
/uv-lag): Pearson correlation between UV dose and each symptom at lag windows of 0, 1, 2, 3, and 4 days — identifies your personal best-predicting UV-to-symptom delay. - Intervention evaluation (
/interventions, nav label "interventions"): per-medication pre/post flare and autonomic shift analysis. Described in its own section below — it's a big enough feature to deserve one.
Transparent statistical scoring, tunable per-user, with 13 contributing categories:
- Environmental: UV dose (same-day + 4-day cumulative with flattened decay)
- Physiological: Physical overexertion (steps relative to personal baseline, sleep-adjusted), basal body temperature delta
- Symptoms: 8 symptom categories with per-category weights, plus rheumatic joint-location parsing (major joints score higher than minor)
- Pain / Fatigue / Emotional: laddered scoring (pain ≥4/5/6/7 and fatigue ≥4/5/6/7 step up in contribution — replaces the old cliff-at-7 threshold that missed function-limiting days where a single severe symptom was the whole event)
- Symptom burden delta: how many more symptoms are active than your personal 14-day baseline — "symptoms accelerating above your normal" rather than raw count, so chronic daily symptoms don't drown out the signal
- RMSSD baseline deviation: 7-day rolling vagal tone vs 30-day baseline. Drops before and during flares, mechanistically grounded in the cholinergic anti-inflammatory pathway, empirically replicates Thanou 2016's ΔRMSSD-ΔSLEDAI finding in n=1 data.
- RMSSD instability: mean day-to-day |ΔRMSSD| over prior 5 days vs 30-day baseline. Captures autonomic chaos — the pattern where RMSSD oscillates wildly in the week before major flares rather than just trending down. Independent additive signal to the level-based deviation above.
- Respiratory rate baseline deviation: 3-day rolling respiratory rate vs 14-day baseline. Motivated by ICU deterioration literature (Barfod 2017) but labeled with an honest caveat that the n=1 cross-sectional signal is weak — the
/modelresp-rate chart is the live validation tool. - Real-time risk assessment: daily flare risk score (0-25) with color-coded risk levels; separate major-flare recall tracked as the primary performance metric because function-limiting flares are the ones that matter most to catch.
- Contributing factors breakdown: every score surfaces which rules fired and how many points each contributed — nothing is hidden.
- Personalized recommendations: context-aware suggestions based on current risk level.
- Interactive weight adjustment: tune symptom weights, category multipliers, multi-day predictor weights, and the flare threshold using real-time sliders. All sliders are exposed — no hidden tunables.
- Live simulation: see how weight changes affect accuracy, recall, and precision before committing.
- Prediction flip analysis: identify which dates would change prediction with new weights.
- Apply and revert: save custom weights per-user or reset to factory defaults.
- Personal lag signature widget: shows which UV-to-symptom lag correlates strongest in your data.
- Built-in manual: accessible via
?command in the terminal-style interface. - Model transparency: the actual Python calculation code is rendered inline in the app via
inspect.getsource()— the code you see is the code that runs.
- Major flare recall as headline metric: major function-limiting flares are the ones you can't afford to miss. Separate recall figures for major/ER, minor, and combined.
- Accuracy analysis over 30/60/90/120/365/all day windows.
- Confusion matrix: true positives, false positives, true negatives, false negatives.
- Full ranked missed-majors table: every major flare the model missed, sorted by how far below threshold the score fell, with your notes and fired factors inline so you can see the context.
- Factor signal quality table: for each scoring factor, compares fire-rate on caught majors vs missed majors — positive lift means the factor is earning its weight, low or negative lift flags factors that aren't helping.
- Historical validation: compare predictions against actual flare outcomes with clickable dates that jump to the daily entry for context.
- Weight optimization: data-driven suggestions for improving model performance based on severity-aware false-positive and false-negative patterns.
Purpose-built clinical evaluation view: "did this medication actually help?" in stats rather than generalizations.
- Per-medication cards: one card for each medication flagged as primary or secondary intervention (toggle the flag in
/clinical#medications). Shows pre/post:- Flare impact: total count, major/ER count, minor count, mean gap days, delta percentages color-coded by direction
- Autonomic shift: RMSSD / SDNN / resp rate means ± SD, with n counts
- Matched-window analysis for ongoing medications: if HCQ has been running 130 days, it compares the 130 days before you started to the 130 days of use — statistically honest rather than an arbitrary fixed window
- Fixed-window analysis for one-time doses (steroid injections, IV's, etc.): 30/60/90/120/all-day selector
- Duration-of-effect for one-time interventions: days to next flare per severity tier; days until each autonomic metric returned to baseline (within ±1 SD of pre-mean for 7 consecutive days)
- Auto-detected rebound flags: if a one-time dose reduced flare rate initially (days 0-13 post) but rate surged back in days 14-45, an amber banner surfaces the pattern. Helpful for catching IV steroid rebounds you might miss manually.
- Events log per medication: structured dated observations with types
side_effect/rebound/efficacy_change/dose_change/note. Side effects get severity 0-10; other types skip the severity field. - Global HRV trend chart at the top of the page with intervention start lines and flare markers overlaid — the "am I trending up overall" glance kept from the old
/autonomicview. - Color-coded cards: primary interventions get a teal left-border, secondary get purple, supplements get orange. Stat boxes inside each card use the global palette (RMSSD purple, SDNN blue, resp teal, flare red, minor amber) so the eye tracks consistent colors across the app.
- Lab results: Track test results with numeric values, qualitative results, reference ranges, and flags
- Medications: Full medication history with doses, frequencies, start/end dates, and primary/secondary intervention flags (the flags feed the
/interventionsview) - Clinical events: Document appointments, procedures, hospitalizations with provider and facility info
- Clinician directory: Maintain contact info for your care team (specialty, clinic, network, notes)
- ANA tracking: Specialized tracking for ANA titers, patterns, and screen results
- CSV export: Export labs, medications, events, or clinician data for external analysis or records requests
- Steroid taper wizard: Pre-filled 6-day Medrol dose pack schedule with adjustable times and doses; schedules push notifications for each dose via ntfy
- Dose checklist: Today's scheduled doses appear on the daily entry page with one-tap "mark taken" tracking
- Full-text search: Search across all daily entries, clinical notes, medications, and events
- Keyword shortcuts: Type "help", "manual", "lab", "cli" in search to access Forecast Lab
- Recent note reference: Access previous notes and events by keyword
- Quick filters: Jump to specific symptom categories or date ranges
- Local-first: All data stored in local SQLite database on your machine
- No cloud sync: Data never leaves your computer by default
- Optional passcode lock: Require a PIN to access the app — useful on shared networks, with roommates, or in any situation where you need your health data to stay private from people in your physical space. Enable by adding one line to
config.json. See Optional Passcode below. - Optional remote access: Raspberry Pi + Tailscale + Oracle Cloud setup for secure mobile access (see REMOTE_ACCESS.md)
- Version control safe: Comprehensive
.gitignoreprotects health data from accidental commits - Export control: You decide what data leaves your system and when
- UV auto-backfill: Automatically fetch historical UV data from Open-Meteo and Visual Crossing based on GPS coordinates
- Responsive design: Works on desktop and mobile browsers
- Dark mode: Easy on the eyes, just toggle the moon/sun in the header.
- Light mode: Good for when you can't make out dark mode.
This application is a data tracking and visualization tool only. It is not:
- A diagnostic tool
- Medical advice
- A replacement for professional medical care
- Approved, endorsed, or reviewed by any medical authority
Always consult qualified healthcare providers for medical decisions. This app helps you organize your own observations -- what you do with that information is between you and your clinicians.
- Your data never leaves your computer. No cloud storage, no third-party APIs for health data, no analytics, no tracking.
- UV data comes from public weather APIs (Open-Meteo and Visual Crossing) using only your coordinates — no personal health information is transmitted.
- You own your data. The database is a standard SQLite file you can back up, export, or delete at any time.
- This is a single-user, local application. One instance per person, one database per instance.
- Do not use this application to track anyone's health data without their informed consent. Don't be creepy.
- macOS, Linux, or Windows (tested primarily on macOS and Linux... actually not tested on Windows. Sorry.)
- Python 3.9 or later (earlier veersions work, but watch your D's and d's)
- A web browser (Brave, Firefox, Safari, Edge, Opera, Tor...)
- Optional: iPhone with Apple Health for biometric import (I have an apple watch, because access to raw data for free and it's also a watch)
macOS/Linux: Python 3 is likely already installed. Open Terminal and check:
python3 --versionIf you see Python 3.9 or higher, you're good. If not, download from python.org.
Windows: Download Python from python.org and make sure to check "Add Python to PATH" during installation.
Option A: Download ZIP (easiest if you're not familiar with git)
- Go to the GitHub repository page
- Click the green Code button
- Click Download ZIP
- Unzip the file to a folder you can find (like
Documents/sardine-track)
Option B: Clone with git
git clone https://github.com/alaricmoore/sardine-track.git
cd sardine-trackThe repo was formerly named
biotracking; the old URL still redirects. If you want the iOS companion as well, the Swift sources live in a separate repo: github.com/alaricmoore/sardinessync.
Open Terminal (Mac/Linux) or Command Prompt (Windows), navigate to the sardine-track folder, and run:
# Create a virtual environment (recommended)
python3 -m venv .venv
# Activate it
# Mac/Linux:
source .venv/bin/activate
# Windows:
.venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
# Run first-time setup
python setup.pyThe setup script will ask you for:
- Your name (for reports)
- Location coordinates (for UV data — you can find these by Googling "my coordinates" or using latlong.net)
- Timezone (e.g.,
America/Chicago,America/New_York,Europe/London) - Baseline body temperature in Fahrenheit (your normal resting temp, usually around 97-99°F)
Important for coordinates: If you're in North America, your longitude should be negative. For example, Oklahoma City is
35.4676, -97.5164(note the minus sign on longitude). The setup script will warn you if you forget.
python app.pyYou should see:
sardinetrack
============
Patient: Your Name
Starting server...
Local: http://localhost:5000
Phone: connect to same wifi, visit http://<your-ip>:5000
A note on the name: internally, the code still calls itself
biotrackingin a lot of places (module docstrings, thebiotracking.dbfilename, some comments). That was the project's original name before it becamesardine-track. It's left alone on purpose — renaming every occurrence is churn without benefit, and the database file in particular would break existing installs if renamed. User-facing surfaces (this banner, script output,--helptext) saysardinetrack.
Open your browser and go to http://localhost:5000. Try adding today's entry to make sure everything works.
If you want to enter data from your phone while on the same WiFi network:
Find your computer's IP address:
- macOS: System Settings > Network > click your connection > look for IP Address
- Windows: Open Command Prompt, type
ipconfig, look for "IPv4 Address" - Linux: Run
hostname -I
Then on your phone (same WiFi network), open a browser and go to http://YOUR-IP-ADDRESS:5000.
Bookmark it for easy access.
Biotracking can import HRV, sleep hours, wrist temperature, and daylight exposure from Apple Health, or whatever else you are tracking. Which provides free-of-cost raw data downloads.
Export from Apple Health:
- Open the Health app on your iPhone
- Tap your profile picture in the top right
- Scroll down and tap Export All Health Data
- AirDrop the export to your Mac, or save to Files and transfer via iCloud
- The file is going to be huge, just a warning.
Recommended: Use the Health Export app (free tier is fine)
- Download Health Export from the App Store
- Select: Heart Rate Variability, Sleep Analysis, Apple Sleeping Wrist Temperature, Time in Daylight
- Set your date range, export as CSV daily average
- Transfer the CSV to your computer
- Also download Menstrual Cycle data if you're using cycle tracking — see below.
Import:
python import_apple_health.py path/to/your_export.csv
# Preview without writing:
python import_apple_health.py ~/Downloads/health_export.csv --dry-run
# Create new rows for dates that don't exist yet:
python import_apple_health.py ~/Downloads/health_export.csv --create-newIf you enabled cycle tracking during setup, import your cycle history from an Apple Health XML export:
- Export from Health app (see above)
- Use the Health Export app — select Menstrual Flow and Intermenstrual Bleeding, export as CSV
- Import:
# Preview first:
python import_cycle.py --csv your_cycle_export.csv --dry-run
# Import:
python import_cycle.py --csv your_cycle_export.csvFlow priority when multiple records exist for the same day: heavy > medium > light > spotting. Intermenstrual Bleeding is imported as spotting. Sexual Activity and Persistent Menstrual Bleeding records are skipped.
If you've been tracking in a spreadsheet, you can import it. Required column: Date (in YYYY-MM-DD, MM/DD/YYYY, or "Jul 22, 2025" format). Optional columns are mapped automatically for symptom flags, pain/fatigue scales, sleep hours, and notes.
python import_tracker.py path/to/your_tracker.csv --dry-run
python import_tracker.py path/to/your_tracker.csvDate,Test,Value,Units,Lab,Doctor
2021-04-16,C4,28,mg/dL,LabCorp,Dr. Smithpython import_labs.py path/to/labs.csv --dry-run
python import_labs.py path/to/labs.csvThe script auto-detects reference ranges and flags common tests (C3, C4, CRP, ESR, anti-dsDNA, etc.).
After importing historical data, fetch UV values for those dates:
python backfill_uv.pyYou'll need a free Visual Crossing API key. Add it to config.json:
"visual_crossing_key": "YOUR_KEY_HERE"The free tier allows 1000 records/day. Historical UV uses ~24 records per day, so you can backfill about 40 days for free. Beyond that, the metered plan is $0.0001/record.
- Open biotracking at
http://localhost:5000 - Click "Daily Entry" to log today's data
- Fill in symptoms, environmental factors, and notes
- Check "Flare occurred today" if applicable
- Submit to save
- Navigate to "Forecast" from the main menu for the current-day risk score and recommendations.
- Navigate to "Model" (
/model) for the score attribution dashboard — stacked-bar breakdown of what's driving each day, plus click-to-expand trend charts for burden delta, RMSSD deviation, and respiratory rate deviation. - From the model view, the top nav strip links to accuracy (performance grading + missed-majors table), history (predictions vs. actuals per day), and pre-flare patterns (aggregate biometric trajectories before flares).
- Navigate to "Interventions" from the main menu (or
/interventions). - If no cards appear, flag a medication as primary or secondary intervention first: go to
/clinical#medications, edit the medication, tick the intervention checkbox. - Each intervention gets a card with pre/post flare stats, autonomic shift, and (for one-time doses) duration-of-effect.
- Click + log event on any card to record a side effect (with severity 0-10), rebound, efficacy change, dose change, or general note — these events are timestamped and filterable.
- Go to forecast page and click the green
>>button (bottom-right) — or visit/forecast/labdirectly. The same easter-egg>>also exists on the model dashboard.- Or search for "lab", "help", or "manual"
- Type
?for the user manual - Type
2to adjust weights — all sliders exposed: symptom weights, category multipliers (UV, exertion, temperature, pain/fatigue), multi-day predictors (symptom burden, RMSSD deviation, RMSSD instability, respiratory rate deviation), and the flare threshold - Move sliders to customize weights
- Click "Run Simulation" to preview changes against the last 60 days
- Review accuracy/recall/precision impact and which predictions would flip
- Click "Apply These Changes" to save (or "Reset to Defaults" to revert)
- Navigate to "Clinical" from the main menu
- Add lab results, medications, events, or clinician contacts
- Use the tabs to switch between record types
- Click "Export" to download CSV files for specific date ranges
- Edit or delete records using the action buttons
- Use the search bar in search tab
- Type any keyword to search across all entries
- Results are grouped by type (daily, labs, events, medications)
- Click any result to view full details
See REMOTE_ACCESS.md for detailed instructions on setting up remote access via Raspberry Pi + Tailscale.
Biotracking uses ntfy for two kinds of phone notifications:
- Medication dose reminders — fires at the scheduled time for each dose in your taper
- Proactive flare risk alerts — fires once daily (default 8am) when your weighted risk score crosses the moderate threshold (≥ 5.0) or when you're about to enter a PMS/luteal phase. The alert includes your score, top contributing factors, and current cycle phase if relevant. High-risk alerts (≥ 8.0) use higher priority and a different tag so they stand out.
ntfy is a dead-simple open-source notification service. No account required. The Pi sends an HTTP POST; your phone receives a push notification. That's it.
- Install the ntfy app on your phone (App Store or Google Play, free, by Philipp Heckel)
- Open the app and tap Subscribe
- Enter a topic name — make it long and unguessable, like
biotracking-k7x9qm3p(ntfy topics are public: anyone who knows the name can read and send to it, so don't use your name, your pet's name, or anything else obvious) - Add two keys to your
config.jsonon the machine running biotracking:
"ntfy_topic": "biotracking-k7x9qm3p",
"ntfy_server": "https://ntfy.sh"- Test it from your terminal before trusting your medication schedule to it:
curl -d "test notification" https://ntfy.sh/biotracking-k7x9qm3pYour phone should buzz within a few seconds. If it doesn't, check that the topic name matches exactly and that notifications are enabled for the ntfy app in your phone's settings.
- Go to Clinical → Medications tab
- Add your steroid (e.g. methylprednisolone 4mg)
- Click set reminders on that medication's row
- The wizard pre-fills a standard 6-day Medrol dose pack schedule (18 doses, tapering from 6 tablets on day 1 to 1 tablet on day 6). Adjust the start date, times, and amounts as needed.
- Click activate reminders — doses are saved and notifications will fire at the scheduled times as long as the app is running
- Today's pending doses appear in a checklist on the Daily Entry page. Mark them taken as you go.
The flare risk alert fires at 8am by default. To change the hour, add this to your config.json:
"flare_alert_hour": 7Then restart the app. The scheduler will pick it up. The alert rate-limits itself to once per calendar day — if risk drops below threshold later in the day, no second alert fires. If it fires and you want to reset it manually (e.g. to test), delete config/flare_alert_state.json and restart.
To disable flare alerts entirely without removing ntfy, just don't add flare_alert_hour — it won't suppress them. Instead, you can set ntfy_topic to an empty string in config.json, which disables all notifications.
- Notifications only fire if the app is running. If you're on the Raspberry Pi setup described in
REMOTE_ACCESS.md, the service runs continuously and this works reliably. - Running locally on a Mac that sleeps? The scheduler pauses when the machine sleeps and resumes when it wakes. You may miss a dose notification or morning alert if the lid was closed.
- ntfy.sh is a public service run by one person. For higher reliability or privacy, you can self-host ntfy — change
ntfy_serverinconfig.jsonto your self-hosted URL. - The taper wizard defaults to a standard Medrol 4mg dose pack. For other tapers, adjust the times and quantities in place or clear them and enter your own schedule.
Health data can be sensitive in ways that go beyond the abstract. If you share a living space, use your laptop in shared areas, or are in any situation where you need your data visible only to you, the optional passcode adds a simple lock screen to the app.
To enable:
Open config.json (in your biotracking folder) in any text editor and add one line:
"passcode": "yourpin"For example, if your config currently ends with:
"debug": false,
"secret_key": "abc123..."
}Make it:
"debug": false,
"secret_key": "abc123...",
"passcode": "yourpin"
}Restart the app. From now on, anyone visiting the app URL will see a passcode prompt before they can access any data.
A lock button will appear in the navigation bar. Clicking it ends your session immediately.
To disable: remove the "passcode" line from config.json and restart.
Notes:
- The passcode can be any string — a word, a number, a phrase. It's stored in your local
config.jsonfile, which is already gitignored and never committed to GitHub. - This is a "lock the door" measure, not a cryptographic security system. It protects against casual access (someone picking up your laptop, a roommate, a family member) on a trusted home network. It is not a substitute for full-disk encryption if your threat model involves physical device seizure.
- If you forget your passcode, open
config.jsonin a text editor and either read it there or remove the line. - Sessions expire when you close the browser tab or click lock. There is no persistent "remember me."
"Port 5000 is already in use" (common on macOS which uses 5000 for AirPlay)
Edit app.py and change port=5000 to port=5001, then visit http://localhost:5001.
UV data shows all zeros
Check your longitude sign. North America longitudes should be negative (e.g., Oklahoma City: 35.4676, -97.5164). Edit config.json and run python backfill_uv.py --force.
Can't access from phone
Make sure phone and computer are on the same WiFi. Verify the app is running. Try http:// not https://. Check there's no firewall blocking port 5000.
"No module named 'pandas'"
You're not in the virtual environment. Run source .venv/bin/activate (Mac/Linux) or .venv\Scripts\activate (Windows) first.
Your data lives in two files:
biotracking.db— the SQLite database (kept at this filename deliberately; renaming it tosardine-track.dbwould break existing installs that have the file in place, and the name inside the SQLite file is invisible to users anyway. The .gitignore correctly excludes this file regardless.)config.json— your settings and API keys
Back them up:
cp biotracking.db biotracking_backup_$(date +%Y%m%d).dbExport options:
- In-app: export buttons for labs, medications, events, clinician list (CSV)
- In-app UI delete function on search page
- DB Browser for SQLite (GUI tool, free)
- Command line:
sqlite3 biotracking.db .dump > backup.sql
Reset everything:
rm biotracking.db config.json
python setup.pyThis deletes all your data. Back up first.
The flare prediction model is a transparent, statistical approach. No black box — you can see exactly how every prediction is made, and tune it yourself.
Each day receives a risk score (0-25) based on UV dose (same-day and 4-day cumulative with flattened decay weights), physical overexertion, temperature elevation, individual symptoms with laddered pain/fatigue contributions, and four multi-day predictors:
- Symptom burden delta — how many more symptom categories are active than your personal 14-day baseline. Flares build; they don't appear from nowhere. Originally the model's strongest single predictor.
- RMSSD baseline deviation — 7-day rolling vagal tone vs 30-day baseline. A sustained drop in parasympathetic activity (measured via Apple Watch RR-interval data) precedes inflammatory flares. Mechanistically grounded in the cholinergic anti-inflammatory pathway; empirically replicates Thanou 2016's ΔRMSSD-ΔSLEDAI finding.
- RMSSD instability — mean day-to-day |ΔRMSSD| over prior 5 days vs 30-day baseline. Captures autonomic chaos before major flares — RMSSD oscillates wildly (surge/crash/surge/collapse) rather than simply drifting down. Independent signal from the level-based deviation; both can fire together.
- Respiratory rate baseline deviation — 3-day rolling rate vs 14-day baseline. ICU-literature-motivated (Barfod 2017); validation on personal data is ongoing via the dashboard chart.
All four multi-day predictors use baseline-relative scoring rather than raw values, because chronic daily symptoms become constant offsets that don't distinguish flare days from non-flare days.
Threshold: score ≥ 8.0 = flare risk (default; tunable). All weights are tunable in the Forecast Lab. Major flare recall is tracked as the primary performance metric since function-limiting flares are the ones that matter most to catch.
For full details on every scoring category, the math behind multi-day context injection, severity-specific trajectory analysis, and relevant literature (Thanou 2016, Poliwczak 2017, Barfod 2017, Huston & Tracey 2011), see MODEL.md (rendered in-app at /model/docs).
This project welcomes contributions, especially from people with lived experience of diagnostic complexity. Whether as patients, clinicians, loved ones, or those for whom this is their special interest.
Areas where help is needed:
- Additional data import formats (Fitbit, Garmin, etc.)
- More correlation analysis methods
- PDF export improvements
- Accessibility improvements
- Documentation and tutorials
- Translations
- New designs to include other evolving hard-to-diagnose disease that isn't my flavor of lupus.
Please open an issue before starting work on a major feature.
Also reach out to me at alaric.moore@pm.me
sardine-track/
├── app.py # Flask routes, scoring model, forecast lab, migrations hook
├── db.py # All database operations; idempotent run_migrations() at startup
├── uv_fetcher.py # UV API integration (Open-Meteo + Visual Crossing)
├── setup.py # First-run DB schema and per-user config
├── create_user.py # CLI for adding additional users post-setup
├── MODEL.md # Full flare prediction model documentation (rendered at /model/docs)
├── CHANGELOG.md # Dated list of substantive changes
├── CONTRIBUTING.md # Contributor guidelines
├── COMMERCIAL_LICENSE.md # Commercial licensing terms (AGPL-3.0 for non-commercial)
├── REMOTE_ACCESS.md # Raspberry Pi + Tailscale + Oracle Cloud setup guide
├── help.md # In-app help text
├── requirements.txt # Python dependencies
├── config.json # User settings & API keys (gitignored)
├── biotracking.db # SQLite database (gitignored)
├── import_apple_health.py # Apple Health CSV importer (HRV, sleep, wrist temp, daylight)
├── import_cycle.py # Menstrual cycle Apple Health importer
├── import_labs.py # Lab results CSV importer with ref-range auto-detection
├── import_tracker.py # Generic symptom-tracker spreadsheet importer
├── import_backup.py # Import data from a prior biotracking.db backup file
├── backfill_uv.py # Historical UV data fetcher (Visual Crossing API)
├── migrate_symptoms.py # One-off migration: symptom category reorganization
├── migrate_to_multiuser.py # One-off migration: single-user → multi-user schema
├── rmssd_flare_rerun.py # Standalone RMSSD pre-flare pattern analysis (generates PNG)
├── config/
│ ├── custom_weights.json # Forecast Lab overrides (gitignored; per-user fallback)
│ └── flare_alert_state.json # Daily alert rate-limit state (gitignored)
├── backups/ # Local DB backup snapshots (gitignored)
└── templates/
├── base.html # Shared layout + global CSS palette (colors referenced app-wide)
├── login.html, register.html
├── daily_entry.html, daily_confirm.html
├── mobile_base.html, mobile_log.html, mobile_status.html # Phone-optimized entry flow
├── forecast.html # Daily flare forecast with easter-egg >> link to lab
├── timeline.html # Model dashboard (score attribution) — served at /model
├── forecast_lab.html # Weight tuning interface
├── forecast_history.html # Predictions vs actuals, ranked by score gap
├── forecast_accuracy.html # Major/minor recall, missed-majors table, factor signal quality
├── forecast_patterns.html # Pre-flare pattern analysis + RMSSD trajectories
├── interventions.html # Per-medication pre/post evaluation + side-effects log
├── hrv.html # Legacy autonomic view (still on disk, no longer nav-linked)
├── cycle.html # Menstrual cycle calendar
├── uv_lag.html # UV-symptom correlation at 0/1/2/3/4-day lags
├── clinical_record.html # Labs, medications, events, clinicians, ANA
├── settings.html, admin.html
├── report.html
├── search.html
├── readme.html # Renders README.md in-app
└── remote_access.html # Renders REMOTE_ACCESS.md in-app
The iOS companion (sardinessync) lives in its own repo at github.com/alaricmoore/sardinessync. It used to live in this repo at ios-health-sync/ but was extracted so the iOS and Flask codebases could evolve independently.
GNU Affero General Public License v3.0 (AGPL-3.0)
This software is free for individuals and non-profits with attribution. Commercial entities wishing to use, modify, or deploy this software must obtain a separate commercial license.
The AGPL-3.0 requires that if you modify and deploy this software (including as a web service), you must make your modified source code available under the same license.
See the LICENSE file for full terms. For commercial licensing inquiries, contact the author.
For bugs, feature requests, or questions, open an issue on GitHub. Check existing issues first -- your question might already be answered.
This is currently a one-person project built between doctor appointments and fixing machines and building terrariums. Response times may vary.
Take care of yourself. Trust your observations. Keep asking questions.