{
  "schemaVersion": 1,
  "slug": "marathon-pace-predictor-calculator",
  "category": "sports",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "5015d3882e02f7b105dedfe17d196cc64e870a6679bd5b3863121c0537f172db",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/marathon-pace-predictor-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": "t₁ = T ÷ 2 × (1 − s); t₂ = T ÷ 2 × (1 + s); T = 60h + m",
    "vars": [
      [
        "T",
        "goal marathon time, min"
      ],
      [
        "t₁",
        "first-half time"
      ],
      [
        "t₂",
        "second-half time"
      ],
      [
        "s",
        "Split factor"
      ],
      [
        "m",
        "goal minutes"
      ]
    ],
    "note": "Even split s=0; positive split uses a faster first half (−2% time) and slower second half (+2%); negative split reverses them. Course and conditions are not modeled."
  },
  "externalEvidence": {
    "reviewedAt": "2026-10-12",
    "sources": [
      {
        "name": "Riegel (1981) — Athletic records and time-distance extrapolation",
        "url": "https://pubmed.ncbi.nlm.nih.gov/7235349/"
      }
    ],
    "referenceRole": "context",
    "implementationHash": "5015d3882e02f7b105dedfe17d196cc64e870a6679bd5b3863121c0537f172db",
    "basis": "t₁ = T ÷ 2 × (1 − s); t₂ = T ÷ 2 × (1 + s); T = 60h + m",
    "limitations": "The references provide context and limitations. They do not validate this page’s illustrative multipliers, lookup values or predictions."
  },
  "contract": {
    "compute": "const total=goalHours*60+goalMinutes;if(!Number.isFinite(total)||total<=0)return{totalMinutes:null,overallPaceKm:null,firstHalf:null,secondHalf:null,perFiveK:null,__error:'inputError'};const split=strategy==='even'?0:strategy==='positive'?0.02:-0.02,first=total/2*(1-split),second=total/2*(1+split),fmt=min=>{const sec=Math.round(min*60),h=Math.floor(sec/3600),m=Math.floor(sec%3600/60),s=sec%60;return(h>0?h+':'+String(m).padStart(2,'0'):m)+':'+String(s).padStart(2,'0');};return{totalMinutes:total,overallPaceKm:fmt(total/42.195),firstHalf:fmt(first),secondHalf:fmt(second),perFiveK:fmt(total/(42.195/5))};",
    "inputs": [
      {
        "name": "goalHours",
        "type": "number",
        "default": 3,
        "min": 1,
        "max": 10,
        "step": 1
      },
      {
        "name": "goalMinutes",
        "type": "number",
        "default": 30,
        "min": 0,
        "max": 59,
        "step": 1,
        "slider": true
      },
      {
        "name": "strategy",
        "type": "select",
        "default": "even",
        "options": [
          {
            "value": "even"
          },
          {
            "value": "negative"
          },
          {
            "value": "positive"
          }
        ]
      }
    ],
    "outputs": [
      {
        "name": "totalMinutes",
        "precision": 1,
        "unit": "min"
      },
      {
        "name": "overallPaceKm",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "firstHalf",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "secondHalf",
        "precision": 0,
        "unit": "",
        "type": "text"
      },
      {
        "name": "perFiveK",
        "precision": 0,
        "unit": "",
        "type": "text"
      }
    ],
    "interpret": null
  }
}
