Skip to content

Cycle Tracking Design and Status

github-actions[bot] edited this page Nov 9, 2026 · 1 revision

Status: Feature catalog and build plan, 2026-09-27. Sections marked done are implemented on this branch; the rest are queued in the order given. Related: Cycle tracking (current behavior), Feature playbook, Architecture.

Why

OpenVitals reads and writes the seven Health Connect cycle record types. Before this work it predicted the next period with a mean-and-spread rule and showed observations read-only. This design adds a better-sourced estimator, a conservative phase model, subjective daily logging, sourced tips, a variability view, a thermal chart, reminders, and a home-screen widget, all built around one rule: what you recorded and what the app calculated are never presented as the same thing.

Ground rules

  • Recorded facts and estimates get different labels and colours. Every estimate is a range with the history it came from.
  • Missing days are unknown, never "no symptoms".
  • Nothing assumes a 28-day cycle, ovulation, fertility intent, or a condition. Calendar data never confirms ovulation.
  • Tracking contexts (PMS, PMDD, endometriosis, PCOS, perimenopause, thyroid) are statements by the user. They change what the app offers and how wide its uncertainty is. They never move a predicted date and are never inferred.
  • No streaks, scores or rewards.
  • Health Connect stays the source of truth for everything it has a record type for. Cycle starts are derived from flow days.
  • Room holds only what Health Connect cannot: the subjective journal, cycle exclusions.
  • No network. Partner sharing and crash reporting are out of scope; the journal moves with the phone-to-phone sync like everything else.
  • Strings follow the app's sentence-case, plain-language copy. Sources are cited on the card that uses them.

Catalog

Status legend: exists already in OpenVitals · new built for this design · adapted built on an existing piece · skip not built, with the reason.

1. Daily observations (the day log)

Feature Detail Store Status
Bleeding scale Not recorded / none / spotting / light / medium / heavy. "None" is an explicit observation. light..heavy: MenstruationFlowRecord; spotting: IntermenstrualBleedingRecord; none: journal adapted
Pain, mood, energy 1..5 each, with a glyph per step (dots, mood arcs, battery). Direction stated in copy. journal new
Symptoms 15 sourced observations in three groups (pain, body, mood and energy). Endometriosis adds pelvic pain outside the period; PMS/PMDD add breast tenderness, mood changes, anxiety, skin. journal new
Copy yesterday's symptoms One tap. — new
Private notes Free text, never in a widget or notification. journal new
Basal body temperature Value, measurement time, disturbance flags (fever, alcohol, poor sleep, time shift, late measurement, stress, medication). value and time: BasalBodyTemperatureRecord; flags: journal adapted
Cervical fluid Sensation (dry, damp, wet, slippery) and texture (sticky, creamy, egg white, watery). texture: CervicalMucusRecord appearance; sensation: journal adapted
Ovulation test (LH) Negative, high, positive, inconclusive. OvulationTestRecord exists
Pregnancy test (hCG) Negative, positive, faint or uncertain. journal (no Health Connect type) new
Sexual activity Protected, unprotected, unknown. SexualActivityRecord exists
Cycle start A flow day after more than one free day starts a cycle. derived adapted
One thing at a time A chooser lists what can be logged; each card opens the day log on that section alone. The full sheet stays one tap away and is still the editor for a day that has entries. Everything is saved together, with a discard guard. — adapted: a chooser in front of the day log

2. Cycle management

Feature Detail Status
Add a past period Date picker limited to the last 92 days. adapted: writes a light-flow day, which starts a cycle
Edit a cycle start Moves the start; neighbours re-close. adapted: edit the flow record from the entries list
Delete a cycle Removes the start, keeps the day's observations. adapted: delete the flow records from the entries list
Exclude a cycle from estimates With a reason: illness, medical treatment, contraception change, stress or travel, other. new: cycle_exclusions table

3. Estimates and phase

Algorithm Detail Status
CycleEstimateCalculator Start-to-start intervals, last six inside 15..90 days, excluded cycles skipped, a gap over 90 days resets the history. Central date = last start + rounded mean. Half-width = 1.96 × √(variance shrunk toward a population prior with weight 2, or 0.5 once the record shows persistent 7-day swings), floored at 3 days, capped at 22, floored again so the window never starts inside recorded flow. Prior SD by age band (Apple Women's Health Study table), 4.54 days undeclared. A declared timing context floors the variance at the prior. Results: available, needs more history (fewer than two starts), intervals out of range. new, replaces the mean-and-spread rule
CurrentCyclePhaseCalculator Menstrual is recorded (day 1 or flow today). Days 2–7 without flow detail are indeterminate. Follicular before the ovulation band, luteal after it, ovulatory only on the central day (central − 13) and only with six stable intervals varying at most 7 days. The two days before the central date and the estimate window are the "next period window"; past the window the estimate has expired. new
MarkerEstimate Once the current cycle holds an ovulation marker (first positive LH test, first high day of the thermal shift, mucus peak followed by three lesser days), the next start is the marker day plus that marker's own interval to the next start, learned from earlier cycles and shrunk toward a prior (LH 13.8 ± 1.86, temperature 11.0 ± 2.07, mucus 13.0 ± 2.5, weight 2), half-width 1.96 SD floored at 2. An LH test 0–5 days, or a mucus peak 0–3 days, before the first high day confirms the shift; a marker before cycle day 7 is ignored; a shift and markers that do not agree fall back to the calendar and say so. See Marker estimate sources. new (2026-10)
Cycle facts 24 population facts with sources, one per day by date hash, shown when the phase is indeterminate. new
Phase tips 30 sourced tips, filtered by phase, declared contexts and the last three days' symptoms; ties broken by date hash. new

4. History and review

View Detail Status
Month calendar Recorded flow (four fills), estimated window (outline), cycle-start dot, observation dot, legend, selected-day card. adapted: the existing calendar gains the estimate window, observation dots and a legend
Timeline Entries newest first with flow drop, level bars, symptom count, note marker. adapted: journal rows join the entries list
Variability Bar per cycle against a 21–35 day reference band, badges for current, excluded and a 7-day swing, median and mean, tap for detail and exclusion. new
Thermal chart Cycle-day axis, disturbed readings hollow, coverline once a sustained shift is found (six lows, three highs, third at least 0.2 °C above the highest low, readings at most three days apart). adapted: the BBT card is rebuilt on this rule
Patterns Symptom counts by phase over all cycles, descriptive only. new
Nightly vitals The current cycle's overnight resting pulse, HRV (RMSSD) and breathing rate by cycle day, one median per sleep session, one source per signal, a chip and a colour per signal, a missing signal named and not filled in ("not allowed" apart from "no data", with an optional permission button), resting pulse falling back to the device's labelled daily value, a details section with the evidence. Information only: no phase, ovulation, fertility or contraception statement, no effect on any estimate. MetricDetailSectionId.CYCLE_NIGHTLY. new (2026-10-20)
Cycle stats Average, count, min–max of recent intervals. new

5. Reminders

Reminder Detail Status
Daily check-in Fixed time, skipped when today already has observations. new
Period window 1–3 days before the earliest estimated date, 09:00. new
Late cycle Grace days after the latest estimated date, 10:00. new
Visibility Concealed (neutral copy, default), descriptive, custom title and body. Lock-screen copy is always neutral. new
Re-arm after reboot, update, clock and zone change Same as the app's other reminders. new
Pill Daily at the set time on taking days. The scheme is taking days plus pause days, repeating from a pack's first day (PillPlan in the cycle preferences). Skipped once today is marked as taken, from the cycle screen or the notification's Taken action; taken days live in pill_intakes. Own switch, outside the master; same visibility rule. Phone-to-phone sync carries the scheme (newer edit wins) and the taken days (union); the reminder switch and time stay on their phone. Not in the JSON backup yet. new

6. Home-screen widget

Widget Detail Status
Cycle widget Starts concealed. Reveal shows cycle day (recorded), estimated range, and whether today is logged. Never shows notes, symptoms, mood or energy. Compact, standard, wide and expanded layouts. Re-renders after midnight. new, done

7. Clinical report

Feature Detail Status
Consultation summary The report's range instead of a cycle count. Cycle table (start, end, length, bleeding days, peak flow, pain days, excluded), mean, median, sample SD, range, mean bleeding days; bleeding, spotting and intermenstrual days (7+ days from a start); pain days on and off bleeding, severe days (4+), mean score; symptom frequency on and off bleeding; notes chronology; a disclaimer. done: a section of the PDF health report

8. Setup and settings

Feature Detail Status
Setup A Cycle settings section holds contexts, age band and reminders; the cycle screen links to it when nothing is set. new
One range The screen is a month calendar over today's cards, so the Day/Week/Month/Year selector is hidden and the range is fixed to the month. The arrows and the date picker move the calendar. adapted (2026-09-28)
Arranged sections The screen uses the shared metric section order (MetricDetailSectionId.CYCLE_*), so the app bar's arrange toggle reorders it like any metric screen. Default: cycle day, calendar, today's observations (only when something is logged), pill (when tracked), entries, estimate, tip, statistics, setup, history, thermal, patterns. The one-tap "period started" button is gone: the Log action's Bleeding card is the way in, and the period-start preset went with it. adapted (2026-09-28)
Tracking contexts PMS, PMDD, endometriosis (observation group); PCOS, perimenopause, thyroid (timing group). new
Age band Optional, picks the variability prior. new
Temperature unit Celsius or Fahrenheit. exists: unit overrides
App lock, screen masking PIN, biometric, FLAG_SECURE. exists: app lock
JSON export and import Versioned backup of the journal, exclusions, contexts and age band; import merges, the newer edit per day wins. done
Erase all data — adapted: the journal is erased with the app's data reset

9. Out of scope

Network sync, end-to-end encryption, partner sharing, and crash reporting. OpenVitals has no INTERNET permission by design.

Marker estimate sources

What each number rests on, and how far it was checked. The priors only matter until a user has one or two earlier cycles with the same marker.

Number Source Checked
Interval after ovulation varies less than the follicular phase (within-woman variance 3.0 vs 5.2 days², 53 women, 676 ovulatory cycles) Henry, Shirin, Goshtasebi, Prior 2024, Hum Reprod 39(11):2565 full text
Temperature prior 11.0 ± 2.07: luteal phase counted from the temperature shift, median 10.9 days; between-women variance 4.3; shift 2.4 ± 1.5 days after the LH peak Henry et al. 2024 full text
LH prior 13.8 ± 1.86: luteal phase 12.77 ± 1.86 days from the day after the LH surge, 75,981 cycles Soumpasis, Grace, Johnson 2020, Hum Reprod Open hoaa011 full text
Six-low, three-high rule, last high 0.2 °C above the six; mucus peak known only once the secretion is sticky again; end of the fertile time three days after the peak Frank-Herrmann et al. 2007, Hum Reprod 22(5):1310 full text
Mucus peak 1–3 days before the temperature rise in 74% of 2,276 cycles; basis for the mucus 0–3 day agreement bound and the mucus prior (temperature prior + 2) PubMed 3366346 (German sympto-thermal cohort) abstract via search summary only
LH peak +0.46 days from ultrasound ovulation; mucus peak and initial LH rise close to it in more than 72% of cycles Ecochard et al. 2001, BJOG 108(8):822 abstract
Backtest: on the Marquette cycle data (159 women, 1,370 cycles) a marker plus the user's own interval gave a mean absolute error of 1.3 days and a window of 8.5 days at 96.6% coverage, against 2.1 days and 15.5 days for the calendar FedCycleData071012.csv (Kaggle mirror), script outside the repository the dataset's ovulation-day definition is undocumented

Settings without a source: watery mucus is not best quality; the 7–20 day plausible interval; the 2-day minimum half-width; LH before temperature before mucus as anchor order; the 5-day LH agreement bound (a reading of the 2.4 ± 1.5 day figure); ignoring a marker before cycle day 7.

Data model

Two Room tables, migration 12 → 13. Health Connect has no record type for any of these fields.

Table Key Columns
cycle_journal_entries date (ISO day) bleeding_none, pain, mood, energy, symptoms (ids, comma-separated), notes, hcg_test, bbt_disturbances, cervical_sensation, updated_at_millis
cycle_exclusions start_date (ISO day) reason

Preferences, through a CyclePreferences contract: declared contexts, age band, reminder configuration.

Skin temperature sources

What the information card under the thermal chart quotes. Each DOI resolved on Crossref (title, journal, year and first author match); each number was read in the abstract or full text.

Statement on the card Source Checked
Night skin temperature is about 0.3 °C higher in the luteal phase (finger ring, 22 participants); skin and oral temperature correlate with r = 0.563 Maijala et al. 2019, BMC Womens Health, 10.1186/s12905-019-0844-9 full text
A sustained wrist-temperature rise showed in 357 of 437 cycles (82%); the rule is 0.2 °C above at least five of the preceding six days, for three days Shilaih et al. 2018, Biosci Rep, 10.1042/bsr20171279 full text
Ovulation day estimated in 80.8% of completed cycles with a rise of at least 0.2 °C, 89.0% within ±2 days of the LH-test day; hormonal contraception and the two months after stopping were exclusion criteria Wang et al. 2025, Hum Reprod, 10.1093/humrep/deaf005 full text
Ring: ovulation detected in 96.4% of 1,155 cycles, mean error 1.26 days against 3.44 days for the calendar method Thigpen et al. 2025, J Med Internet Res, 10.2196/60667 full text
Wrist temperature was more sensitive than basal temperature (0.62 against 0.23) and less specific (0.26 against 0.70) in 193 cycles, 23 of them anovulatory Zhu et al. 2021, J Med Internet Res, 10.2196/20710 abstract
In 27 pill users the daily rhythm of wrist skin temperature did not differ between the pseudo-luteal and pseudo-follicular phase (active-pill versus pill-free weeks; the abstract does not define the phases), while it did in 14 people with a natural cycle Dittmar et al. 2024, Chronobiol Int, 10.1080/07420528.2024.2342945 abstract
In 18 pill users morning oral temperature was 36.4 °C in the active-pill weeks and 36.1 °C in the pill-free week; the rise correlated with progestin dose (R = 0.47); the card states the association, not a mechanism Salkeld et al. 2001, Neuroimmunomodulation, 10.1159/000059389 abstract
Estrogens generally lower and progestins generally raise body temperature (not skin temperature) Charkoudian & Stachenfeld 2014, Compr Physiol, 10.1002/cphy.c130029 abstract
For combined pills and progestin-only methods, ovulation inhibition and changes in cervical mucus are among the main mechanisms (the abstract does not cover rings, patches or implants, so the card says only that many methods stop ovulation) Rivera et al. 1999, Am J Obstet Gynecol, 10.1016/s0002-9378(99)70120-1 abstract
Hormone users left out: Maijala sub-study excluded hormonal contraception (n = 8); Thigpen removed 6 cycles with hormone use and says the algorithm is not intended for people who do not ovulate due to hormonal contraceptives; Zhu enrolled only people not on hormonal therapy; Wang also allowed non-hormonal methods (condoms) full texts of the three papers above full text

The detection itself (SkinTemperatureShiftCalculator) rests on three of these papers; nothing in it feeds an estimate.

Step in the code Source Checked
Centre the series, reject outliers beyond 2 SD, fill gaps linearly (their Butterworth band-pass and hysteresis thresholding are not replicated) Thigpen et al. 2025, J Med Internet Res, 10.2196/60667 full text (centring, rejection beyond 2 SD, linear fill, the 7-17 and 10-90 day limits)
Plausibility: luteal phase 7 to 17 days (completed cycles only), follicular phase 10 to 90 days Thigpen et al. 2025, 10.2196/60667 full text
First high night: it and the next two nights each at least 0.2 °C above at least five of the six preceding nights Shilaih et al. 2018, Biosci Rep, 10.1042/bsr20171279 full text
Signal gate: best 3-night mean minus lowest 6-night mean at least 0.2 °C (a product heuristic derived from the paper's condition that evaluated cycles show a signal of at least 0.2 °C) Wang et al. 2025, Hum Reprod, 10.1093/humrep/deaf005 full text
Synthetic test rise of about 0.3 °C Maijala et al. 2019, BMC Womens Health, 10.1186/s12905-019-0844-9 full text

Product choices with no source: the nightly value is the mean of a night's samples (Shilaih uses the 99th percentile of LOESS-smoothed night data), and the six nights before the first high night are fixed for all three high nights (the paper's wording leaves the sliding reading open). A fixed 0.2 °C depends on the sensor: a smoothed set from one device showed a mean post-ovulation rise of only about 0.05 °C, which is why a weak signal is reported instead of guessed.

What was looked for and not found, so the app does not claim it: a prior for the days from a wrist or finger temperature rise to the next period (the luteal lengths found were measured with basal temperature, LH tests or mucus, not with a skin sensor), a study of temperature-based ovulation detection in PCOS, and a measured time for the skin-temperature rhythm to normalise after stopping hormonal contraception. The 60-day washout in an earlier plan comes from Apple's exclusion criterion, not from a measurement. Until such a prior exists, skin temperature stays out of MarkerEstimate.

Sources for nightly vitals

Every figure on the nightly vitals card was read from the paper (Europe PMC abstract or full text, bibliographic data checked against Crossref) before it was written. They are listed in CycleSources and attached to their statement in NightlyVitalsEvidence.

Source DOI What the card says from it
Goodale et al., J Med Internet Res 2019 10.2196/13404 237 recruited women (conception-seeking in the paper; the card does not say so) wearing the Ava bracelet at night: nightly heart rate 56.6 bpm in the follicular phase, 60.5 bpm in the late luteal phase (means 56.56 and 60.47 in the full text, which analyses the 30th-percentile nightly heart rate).
Jasinski et al., npj Digit Med 2024 10.1038/s41746-024-01394-0 11,590 participants, 45,811 cycles, WHOOP: resting heart rate lowest on cycle day 5, highest on day 26; RMSSD highest on day 5, lowest on day 27.
Lin et al., Sci Data 2026 (mcPHASES) 10.1038/s41597-026-06805-3 42 young adults, Fitbit Sense, three months: median resting heart rate differs significantly between phases, lowest follicular, peak luteal.
de Jager et al., Sports Med 2026 10.1007/s40279-025-02388-y Living systematic review, 16 studies: wearable HRV higher at the start of the cycle, lower toward the end, differences of 3 to 9%; lower HRV with hormonal contraceptives, particularly late in the cycle. Its study table is also the source for the Oura (5-minute overnight intervals) and WHOOP (last slow-wave cycle; non-wake periods) aggregation notes.
Gonzalez et al., npj Digit Med 2026 10.1038/s41746-026-02799-9 2,596 women, 42,759 cycles, WHOOP: resting heart rate +1.2% with a 10% fall in weekly sleep, regardless of phase. Full text: RHR and HRV are weighted averages over the sleep period, weighted toward its end and slow-wave windows; respiratory rate is the median over sleep.
Pietila et al., JMIR Ment Health 2018 10.2196/mental.9519 4,098 Finnish employees, first 3 hours of sleep: alcohol dose-dependently lowers parasympathetic regulation; an HRV-based recovery measure fell by 9.3, 24.0 and 39.2 percentage units at low, moderate and high intake.
Shi et al., npj Digit Med 2026 10.1038/s41746-025-02320-8 Systematic review and network meta-analysis, 27 studies: multi-parameter wearables 0.89 vs temperature alone 0.87 pooled accuracy; the authors say such parameters could slightly improve accuracy. The card quotes only "a slight gain, 0.89 versus 0.87, overlapping confidence intervals", without naming the task (fertile-window detection) and without the temperature-only interval, which the paper gives as both 0.85 to 0.88 and 0.85 to 0.89.

Not quoted, on purpose: any ovulation-detection accuracy of the app itself (it has none), and Goodale's 90% machine-learning accuracy (a different task from what the card shows).

Phases

  1. Domain and tests: estimator, phase model, facts, tips, thermal shift, patterns, longitudinal stats, catalog. done
  2. Storage: the two tables, the journal repository, the preferences contract, statistics that honour exclusions and contexts. done
  3. Logging: the day log with scales, symptoms, notes, hCG, BBT flags, cervical sensation. done
  4. Display: today card, phase, estimate, tip or fact, calendar upgrade, variability, thermal, patterns, exclusion and backfill dialogs. done
  5. Settings: contexts, age band, reminders. done
  6. Reminders. done
  7. Home-screen widget. done
  8. Alignment with the rest of the app: one day-log route, widget deep link and state, reminders restored and read strictly, dashboard tile, age band from the body profile, journal deletion, swipe-delete confirmation, accessibility, plurals, docs. done
  9. Report section and journal export. done

Generated from docs/proposals/cycle-tracking-design.md. Edit it there; changes made here are overwritten.

Home

Features

How-to

App

Reference

Engineering

Proposals

Releases

Project

Clone this wiki locally