Features
How it Works
Overview Cycle Calculation Privacy Get FlowKeeper
FlowKeeper · Prediction Engine

How your next cycle is estimated

A look under the hood — every prediction is built from your own history, on your device, with robust statistics and an honest measure of uncertainty.

The one-sentence version

FlowKeeper predicts your next period as your last start date + a recency-weighted, outlier-cleaned average of your own cycle lengths — then wraps that single date in an 80% confidence band sized to how regular you actually are.

The whole model, in one line
Snext= Slast+ round( Σ 0.85k·Lk Σ 0.85k )  ± Δ

Where Slast is the day your current cycle began, Lk are your cleaned past cycle lengths (k = cycles ago, 0 = most recent), and Δ is the uncertainty half-width derived below. Everything else is just how we choose and clean the Lk.


1

It only ever uses your data

No population averages, no machine-learning model trained on other people, nothing leaves the phone.

Each completed cycle is reduced to two numbers: the date it started and how many days it lasted. That's the entire input — a chronological list of (startDate, cycleLength). Why so minimal? For calendar-only tracking, the accuracy ceiling is set by your own biological variability, not by model cleverness — mean, median, and ML point-predictors land within a day of each other. So the engine spends its effort where it matters: robustness to messy logs and honesty about uncertainty.

2

Clean the history — twice

A single weird cycle shouldn't move your forecast. Two filters remove the noise before any math runs.

Filter A — physiological range. Anything outside 21–45 days is treated as a mis-log or a one-off, not a cycle, and dropped. Then the window narrows to the 13 most recent survivors (≈ a year), so the math always reflects roughly the last year.

Filter B — statistical outlier. Of those, FlowKeeper computes the median and the MAD (median absolute deviation), then rejects any length whose modified z-score is too large. MAD is used instead of standard deviation precisely because one extreme value barely budges it.

modified z =  0.6745 · |Lᵢ − median|MAD   → drop Lᵢ if > 3.5
healthy range · 21–45 days 21 45 27 28, 28 29 40 z = 8.1 › 3.5 — rejected
A 40-day cycle is inside the healthy band, yet far enough from your personal cluster that Filter B still drops it. Robust stats catch what a fixed range can't.

Finally, in auto mode, if you're highly variable (MAD > 4 days) or clearly trending, the engine narrows further to just your last 4 cycles so the estimate reflects your current state.

3

Weight recent cycles more

Last month tells us more about next month than a cycle from a year ago.

Rather than a plain average, each cycle's influence decays by a factor of 0.85 for every cycle further into the past. The result is a normalized recency-weighted mean — responsive to gradual change (postpartum, perimenopause, lifestyle) without overreacting to any single month.

L̂ =  w₀L₀ + w₁L₁ + w₂L₂ + … w₀ + w₁ + w₂ + …   wₖ = 0.85k,  k = cycles ago
1.00 0.85 0.72 0.61 0.52 0.44 most recent 1 ago 2 ago 3 ago 4 ago 5 ago
Influence of each past cycle. A cycle five months back counts for about 44% of the most recent one — present, but quietly outvoted.
4

Don't pretend to know the exact day

A single date is a comforting lie. FlowKeeper shows a range — and sizes it to you.

The half-width Δ is an 80% prediction interval built from your own dispersion. The MAD is scaled into a robust standard deviation, multiplied by a Student-t factor (small samples → wider), and inflated by √(1+1/n) because we're predicting one new cycle, not the long-run average.

Δ = clampt0.8, n−1 · (1.4826·MAD) · √(1 + 1/n)  ,  min 1 day  ,  max L̂/2 )
predicted start −Δ ≈ 80% likely
The band, not the dot, is the real prediction. Regular cycles → a tight band; irregular cycles → a wide, honest one. The width is calibrated to your own MAD.
Cold start. With fewer than 3 clean cycles there isn't enough to measure spread, so the band defaults to a sensible ±4 days and confidence is marked low — never overselling a guess.
5

Translate spread into plain words

The same MAD that sizes the band also picks a confidence label you actually see.

High MAD ≤ 1.5 days — your cycles are clockwork; the band is tight.
Medium MAD ≤ 4.0 days — normal month-to-month variation.
Low MAD > 4.0 days, or fewer than 3 cycles logged — treat the date loosely.

Separately, if the spread between your shortest and longest healthy cycle exceeds 10 days, FlowKeeper flags your cycles as irregular — a cue, not a diagnosis.

6

Ovulation & fertile window come for free

Once the next start is known, two fixed offsets place the rest of the calendar.

ovulation = Snext14 days   ·   fertile window opens = ovulation − 5 days

These are coarse, population-standard offsets (a predicted period is also drawn as 5 days for calendar shading) — deliberately simple, and clearly framed as estimates rather than measured ovulation.


Worked example, end to end

Five logged cycles, one of them anomalous — watch the pipeline absorb it.

Input

Cleaned cycle lengths, oldest → newest: [28, 27, 29, 28, 40] days. Current cycle began Mon, Jun 8, 2026. Window: auto.

  • ① Range filterall of 28,27,29,28,40 ∈ 21–45 → all kept
  • ② Outlier filtermedian 28, MAD 1 → z(40)=0.6745·12 = 8.1 › 3.5, drop 40 → [28,27,29,28]
  • ③ Recency meanL̂ = (28·.61 + 27·.72 + 29·.85 + 28·1) / 3.18 = 28.04 → round 28
  • ④ Interval Δt₃·(1.4826·0.5)·√1.25 = 1.638·0.7413·1.118 ≈ 1.36±1 day
  • ⑤ ConfidenceMAD 0.5 ≤ 1.5 and n = 4 ≥ 3 → HIGH
Next period: Mon, Jul 6, 2026 (Jul 5 – Jul 7) High confidence

…and for free: ovulation ≈ Jun 22 (Jul 6 − 14), fertile window opens Jun 17 (Jun 22 − 5).

Jun 8 cycle start fertile ovul. Jun 22 opens Jun 17 Jul 6 ±1 day predicted + 28 days
The whole calendar derived from one anchor date and one cleaned average.

So how accurate is it?

Honest answer: as accurate as your body is regular — and the design leans into that, instead of hiding it.

For calendar-only prediction, the hard ceiling is your own cycle variability. No amount of modeling beats it, which is why fancier predictors gain almost nothing over a well-cleaned average. FlowKeeper's accuracy story is therefore three concrete guarantees, not a magic number:

① The band is calibrated, not cosmetic. Δ is an 80% prediction interval built from your MAD — so over many cycles the true start should land inside the band about 4 times in 5. Tight when you're regular, wide when you're not.
② One bad log can't poison the forecast. Median + MAD + modified-z rejection mean an illness month, a miscount, or a 45-day fluke is filtered before it touches the estimate — as the worked example shows.
③ Identical math, locked by tests. iOS and Android share one engine, pinned by a language-neutral golden-vector fixture: integer outputs must match exactly and raw values to 1e-9, or the build fails. The constants in this article are those verified fixture values; the worked example is an illustrative computation that uses them.

And where it's not precise, it says so — low-confidence labels, an irregularity flag, and a visible range instead of a false single date. The goal isn't to predict the unpredictable; it's to be right about how sure it is.


The tuning constants

Every load-bearing number, in one place — byte-identical across platforms.

ConstantValueRole
cycleLengthDays28Fallback when there's no history
healthyCycleRange21–45Physiological range filter
recencyDecay0.85Per-cycle weight decay
outlierZThreshold3.5Modified-z rejection cutoff
outlierScaleFactor0.6745Iglewicz–Hoaglin constant
madToSigmaFactor1.4826MAD → robust σ
maxWindowCycles13History cap (≈ 1 year)
adaptiveRecentCycles4Fallback window if variable/trending
minCyclesForInterval3Below this → cold-start band
coldStartBandDays±4Default band with little history
periodLengthDays5Length drawn for a predicted period
highConfidenceMAD / mediumConfidenceMAD1.5 / 4.0Confidence label cutoffs
ovulationOffsetDays / fertileWindowLeadDays14 / 5Derived-calendar offsets

Source of truth: shared/…/cycle/CycleEngine.kt & cycle-engine-golden-vectors.json. On-device · local-only · no network.


Back to How it Works Get FlowKeeper