{
  "schemaVersion": 1,
  "slug": "ovulation-calculator",
  "category": "health",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "1e11d666c077ef2c363eeb8c9e2c5c6044e85de13e2435978c266898f99ca704",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/ovulation-calculator.json",
    "src/engine.js"
  ],
  "runtimeSource": "/sources/engine.txt",
  "runtimeHash": "5c888d8898afbdee36267bb9977829e1e2ac568f6677c8ee87469c139864645c",
  "scope": "The compute function records implemented equations, constants, lookup tables and custom rules. Inputs record defaults and allowed values; outputs record precision and units. Defaults are examples, not recommendations. General background references are not evidence for this specific algorithm.",
  "publishedFormula": {
    "formula": "D_ov = D_LMP + (C − L); fertile = D_ov − 5 -> D_ov + 1; D_next = D_LMP + C",
    "vars": [
      [
        "D_LMP",
        "First day of last period"
      ],
      [
        "C",
        "Cycle length (days)"
      ],
      [
        "L",
        "Luteal phase length (days)"
      ],
      [
        "D_ov",
        "Ovulation date"
      ]
    ],
    "note": "Estimated ovulation day = cycle length − user-entered luteal length after the last-period start. Fertile window is modeled as the five preceding days through one day after; combinations that place it before cycle day 1 are rejected. This calendar estimate cannot confirm ovulation or prevent pregnancy."
  },
  "externalEvidence": null,
  "contract": {
    "compute": "if(cycleLength-lutealLength<6)return{ovulationDate:null,fertileStart:null,fertileEnd:null,nextPeriod:null,daysToOvulation:null,__error:'inputError'};const DAY=86400000,lmp=new Date(lastPeriod+'T00:00:00Z');if(Number.isNaN(lmp.getTime()))return{ovulationDate:null,fertileStart:null,fertileEnd:null,nextPeriod:null,daysToOvulation:null,__error:'inputError'};const ovu=new Date(lmp.getTime()+(cycleLength-lutealLength)*DAY),iso=d=>d.toISOString().slice(0,10),today=new Date();today.setUTCHours(0,0,0,0);return{ovulationDate:iso(ovu),fertileStart:iso(new Date(ovu.getTime()-5*DAY)),fertileEnd:iso(new Date(ovu.getTime()+DAY)),nextPeriod:iso(new Date(lmp.getTime()+cycleLength*DAY)),daysToOvulation:Math.round((ovu-today)/DAY)};",
    "inputs": [
      {
        "name": "lastPeriod",
        "type": "date",
        "default": "@today-2w",
        "step": 1
      },
      {
        "name": "cycleLength",
        "type": "number",
        "default": 28,
        "min": 20,
        "max": 45,
        "step": 1,
        "slider": true
      },
      {
        "name": "lutealLength",
        "type": "number",
        "default": 14,
        "min": 8,
        "max": 18,
        "step": 1,
        "slider": true
      }
    ],
    "outputs": [
      {
        "name": "ovulationDate",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "fertileStart",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "fertileEnd",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "nextPeriod",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "daysToOvulation",
        "precision": 0,
        "unit": "days"
      }
    ],
    "interpret": null
  }
}
