mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 04:32:42 +00:00
fix(cycle): raise the hard cycle-length ceiling so long cycles survive
The hard upper bound clamped cycle lengths at 45 days, force-excluding any longer cycle as an outlier before the robust statistics ran. Cycles over 35 days are oligomenorrhea but still real, and adolescent and perimenopausal users routinely run to fifty days or more — exactly the irregular users for whom an honest estimate matters most. Clamping their genuine cycles biased the estimate toward the population middle. Raise the ceiling to 60 days. It stays a coarse gross-error backstop; the median-plus-MAD fence and the missed-log heuristic still do the real outlier work, and because the fence is computed before exclusion, raising the ceiling does not move it — it only stops the hard rule from pre-empting the fence on long but consistent cycles.
This commit is contained in:
1 parent
f5798e20d8
commit
df67b60420
2 files changed
+27
-2
No files matched your search
@@ -88,6 +88,17 @@ describe("cycle/prediction — cycle-length estimator (§1, §5)", () => {
|
||||
expect(est.lengthRounded).toBe(28);
|
||||
});
|
||||
|
||||
it("keeps a legitimate long (oligomenorrhea) cycle inside the hard bound", () => {
|
||||
// A run of long-but-real cycles (ACOG: > 35 d is oligomenorrhea, still a
|
||||
// real cycle; perimenopause routinely runs longer). A 52-day length must
|
||||
// not be force-excluded by the hard ceiling — only the MAD fence / missed-
|
||||
// log heuristic should ever drop it. Here the whole run is long, so the
|
||||
// robust median sits near 50 and 52 is well inside the fence.
|
||||
const est = estimateCycleLength([48, 50, 52, 49, 51], "PERIMENOPAUSE");
|
||||
expect(est.cyclesObserved).toBe(5); // none clipped by HARD_CYCLE_MAX
|
||||
expect(est.lengthRounded).toBe(50);
|
||||
});
|
||||
|
||||
it("perimenopause widens the fence (OUTLIER_K 4 keeps more)", () => {
|
||||
const lengths = [28, 30, 26, 40, 24, 32];
|
||||
const normal = estimateCycleLength(lengths, "TRYING_TO_CONCEIVE");
|
||||
|
||||
+16
-2
@@ -105,9 +105,23 @@ export const FERTILE_POST = 1;
|
||||
export const OUTLIER_K = 3.0;
|
||||
export const OUTLIER_K_PERIMENOPAUSE = 4.0;
|
||||
|
||||
/** §5 — hard physiological cycle-length bounds (always outlier candidates). */
|
||||
/**
|
||||
* §5 — hard physiological cycle-length bounds (always outlier candidates).
|
||||
*
|
||||
* The ceiling is a deliberately generous outlier *backstop*, not a clinical
|
||||
* "normal" cap — the robust MAD fence + missed-log heuristic do the real
|
||||
* outlier work. ACOG flags cycles > 35 d as oligomenorrhea, but such cycles are
|
||||
* still genuine, and adolescents / perimenopausal users routinely run to ~50+ d.
|
||||
* A hard cap of 45 force-excluded those legitimate long cycles, biasing the
|
||||
* estimate toward the population middle for exactly the irregular users who most
|
||||
* need an honest estimate. 60 d keeps the rare gross-error guard (a length this
|
||||
* long is almost always a missed log, which the missed-log rule also catches)
|
||||
* while letting real long cycles reach the MAD fence. The MAD fence is
|
||||
* computed pre-exclusion, so raising the ceiling does not move the fence — it
|
||||
* only stops the hard rule from pre-empting it on long but consistent cycles.
|
||||
*/
|
||||
export const HARD_CYCLE_MIN = 21;
|
||||
export const HARD_CYCLE_MAX = 45;
|
||||
export const HARD_CYCLE_MAX = 60;
|
||||
|
||||
/** §5 — a single length ≥ this × median is treated as a probable missed-log. */
|
||||
export const MISSED_LOG_FACTOR = 1.75;
|
||||
|
||||
Reference in new issue
Block a user