Open-ended predictions, faded past six weeks; upcoming periods list
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
bb19bc0aeb
commit
ce84bee359
6 changed files with 124 additions and 36 deletions
|
|
@ -18,6 +18,9 @@
|
|||
const MAX_CYCLE = 60;
|
||||
const HISTORY = 12;
|
||||
const FORECAST = 6;
|
||||
// Predictions further out than this are shown faded.
|
||||
const FADE_AFTER = 42;
|
||||
const MAX_MARGIN = 14;
|
||||
|
||||
// Dates are handled as whole days since 1970-01-01 so month lengths and
|
||||
// DST shifts never enter the arithmetic.
|
||||
|
|
@ -138,29 +141,38 @@
|
|||
};
|
||||
|
||||
const expected = last.start + summary.cycleLength;
|
||||
// A late period keeps being expected "from tomorrow" rather than drifting
|
||||
// into the past, and the cycles after it shift with it.
|
||||
let start = expected;
|
||||
if (today > expected && !summary.current.onPeriod) {
|
||||
summary.late = today - expected;
|
||||
start = today + 1;
|
||||
}
|
||||
const thinData = summary.confidence !== "normal";
|
||||
const spread = Math.max(summary.cycleSpread || 0, thinData ? 4 : 2);
|
||||
let previousEnd = last.end;
|
||||
for (let index = 1; index <= FORECAST; index += 1) {
|
||||
const margin = Math.min(10, Math.round(spread * Math.sqrt(index)));
|
||||
const end = start + summary.periodLength - 1;
|
||||
summary.predictions.push({ start, end, earliest: start - margin, latest: start + margin });
|
||||
// The current cycle's window is anchored on the original expectation:
|
||||
// being late does not move an ovulation that already happened.
|
||||
const window = fertileWindow(index === 1 ? expected : start, previousEnd);
|
||||
if (window) summary.fertile.push(window);
|
||||
previousEnd = end;
|
||||
start += summary.cycleLength;
|
||||
}
|
||||
if (today > expected && !summary.current.onPeriod) summary.late = today - expected;
|
||||
Object.assign(summary, forecast(summary, -Infinity, FORECAST));
|
||||
return summary;
|
||||
}
|
||||
|
||||
return { PERIOD_FLOWS, FLOW_LEVELS, LEVEL_NAMES, LUTEAL_DAYS, MIN_CYCLE, MAX_CYCLE, toDay, toIso, periods, cycles, weightedStats, flowProfile, summarize };
|
||||
// Predicted periods (and the fertile window before each) up to `until`,
|
||||
// or at least `minimum` of them. There is no horizon: the range just keeps
|
||||
// widening, and anything more than FADE_AFTER days out is flagged `faded`
|
||||
// for display as a less certain prediction.
|
||||
function forecast(summary, until, minimum = 0) {
|
||||
const result = { predictions: [], fertile: [] };
|
||||
const { current, today } = summary;
|
||||
if (!current) return result;
|
||||
const expected = current.start + summary.cycleLength;
|
||||
// A late period keeps being expected "from tomorrow" rather than drifting
|
||||
// into the past, and the cycles after it shift with it.
|
||||
let start = summary.late ? today + 1 : expected;
|
||||
const spread = Math.max(summary.cycleSpread || 0, summary.confidence !== "normal" ? 4 : 2);
|
||||
let previousEnd = current.end;
|
||||
for (let index = 1; index <= minimum || start <= until; index += 1) {
|
||||
const margin = Math.min(MAX_MARGIN, Math.round(spread * Math.sqrt(index)));
|
||||
const end = start + summary.periodLength - 1;
|
||||
result.predictions.push({ start, end, earliest: start - margin, latest: start + margin, faded: start - today > FADE_AFTER });
|
||||
// The current cycle's window is anchored on the original expectation:
|
||||
// being late does not move an ovulation that already happened.
|
||||
const window = fertileWindow(index === 1 ? expected : start, previousEnd);
|
||||
if (window) result.fertile.push({ ...window, faded: window.ovulation - today > FADE_AFTER });
|
||||
previousEnd = end;
|
||||
start += summary.cycleLength;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return { PERIOD_FLOWS, FLOW_LEVELS, LEVEL_NAMES, LUTEAL_DAYS, MIN_CYCLE, MAX_CYCLE, FADE_AFTER, toDay, toIso, periods, cycles, weightedStats, flowProfile, summarize, forecast };
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue