documentation

Importing your data

Bringing in history you already have: Apple Health exports, cycle data, a spreadsheet tracker, lab results, and backfilled UV.

Already tracking somewhere else? These scripts bring it in. Each one takes --dry-run, so you can see what it would do before it writes anything. Run them from the sardinetracker folder with your virtual environment active (see the README).

From Apple Health

Sardinetracker 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:

  1. Open the Health app on your iPhone
  2. Tap your profile picture in the top right
  3. Scroll down and tap Export All Health Data
  4. AirDrop the export to your Mac, or save to Files and transfer via iCloud
  5. The file is going to be huge, just a warning.

Recommended: Use the Health Export app (free tier is fine)

  1. Download Health Export from the App Store
  2. Select: Heart Rate Variability, Sleep Analysis, Apple Sleeping Wrist Temperature, Time in Daylight
  3. Set your date range, export as CSV daily average
  4. Transfer the CSV to your computer
  5. 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-new

From Apple Health — Menstrual Cycle Data

If you enabled cycle tracking during setup, import your cycle history from an Apple Health XML export:

  1. Export from Health app (see above)
  2. Use the Health Export app — select Menstrual Flow and Intermenstrual Bleeding, export as CSV
  3. Import:
# Preview first:
python import_cycle.py --csv your_cycle_export.csv --dry-run

# Import:
python import_cycle.py --csv your_cycle_export.csv

Flow 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.

From Your Own Symptom Tracker

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.csv

Lab Results

Date,Test,Value,Units,Lab,Doctor
2021-04-16,C4,28,mg/dL,LabCorp,Dr. Smith
python import_labs.py path/to/labs.csv --dry-run
python import_labs.py path/to/labs.csv

The script auto-detects reference ranges and flags common tests (C3, C4, CRP, ESR, anti-dsDNA, etc.).

Lab Results from Apple Health or any EMR/EHR PDF

On the clinical record's labs tab, ↥ import PDF takes a lab PDF: the one the Health app exports, or any report a patient portal (MyChart/Epic, Cerner, a lab's own site) hands you as a document. Sardinetracker checks the file's first bytes, not its name, so whatever the sharing sheet called it is fine.

From the Health app: Health → Medical Records → share/export as PDF. What comes out is not one report. It is every lab a provider ever sent, in blocks headed by the draw date and the lab that ran it. Each block is read under its own date and facility. Health repeats every row once per record it holds, and those repeats are collapsed, so one draw arrives once rather than nine times.

A PDF has no fields to read, so the results are picked off the printed page. The importer handles these layouts:

  • a results table (Test Result Units Reference, or Health's own TST / CURRNT / RFRNC / FLAG),
  • an Epic/MyChart "Normal range" chart, including two tests printed side by side,
  • a "Value" / "Reference Range" block.

What comes across: the value, the unit, the range the lab printed, and its flag (H/L, the (*) some labs use, or Health's OUT OF RANGE). Bounded results (<20) and titers (1:40) stay as text, the same way the CSV import keeps them. When a name is too long for its column, it is printed on its own line with the value underneath, and the importer puts the two back together. Vital signs are skipped, since those arrive through the daily sync.

The date is the collection date. For a Health export it comes from the block header. For a single report it comes from whichever label the portal used (Collected on, Collection date, Ordered On). The print date in the page header and the birth date in the patient banner are both ruled out, because a panel filed under your birth date disappears quietly. A result with no date at all is dropped and counted in the note above the review table.

Two things it cannot do:

  • Scans. A photographed or scanned report has no text in it. There is no OCR here, so file it under Documents and enter the values by hand.
  • Promise it read the page correctly. It is reading print, not structured data. Check the dates and the values in the review table before you commit. Nothing is saved until you do.

The limit is 20 MB, which is a lot for one report.

UV Data Backfill

After importing historical data, fetch UV values for those dates:

python backfill_uv.py

You'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.

← all documentation