{
  "schemaVersion": 1,
  "slug": "dj-playlist-length-calculator",
  "category": "party",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "a67a68d4498004e74bbd5db948516294ccc5f0225686d22dccb11c0a34b5f0b2",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/dj-playlist-length-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 = max(0, 60H − B); N = ceil(T ÷ s); Nb = ceil(T ÷ s × (1 + p ÷ 100))",
    "vars": [
      [
        "H",
        "booked hours"
      ],
      [
        "B",
        "break minutes"
      ],
      [
        "s",
        "average song length, min"
      ],
      [
        "p",
        "buffer percentage"
      ],
      [
        "N",
        "songs needed"
      ],
      [
        "Nb",
        "songs with buffer"
      ]
    ],
    "note": "T is the actual playing time after breaks and ceil rounds up to whole tracks. Songs per hour = 60÷s; the buffered playlist runs Nb×s minutes."
  },
  "externalEvidence": null,
  "contract": {
    "compute": "const h=Number(hours),s=Number(songMinutes),b=Number(breakMinutes),p=Number(bufferPct);if(![h,s,b,p].every(Number.isFinite)||h<=0||s<=0||b<0||b>h*60||p<0)return{playingMinutes:null,songsNeeded:null,songsWithBuffer:null,songsPerHour:null,playlistHours:null,__error:'inputError'};const playing=h*60-b,songs=playing/s,withBuffer=Math.ceil(songs*(1+p/100));return{playingMinutes:playing,songsNeeded:Math.ceil(songs),songsWithBuffer:withBuffer,songsPerHour:60/s,playlistHours:withBuffer*s/60};",
    "inputs": [
      {
        "name": "hours",
        "type": "number",
        "default": 4,
        "min": 0.5,
        "max": 16,
        "step": 0.5,
        "slider": true
      },
      {
        "name": "songMinutes",
        "type": "number",
        "default": 3.5,
        "min": 1,
        "max": 12,
        "step": 0.1,
        "slider": true
      },
      {
        "name": "breakMinutes",
        "type": "number",
        "default": 30,
        "min": 0,
        "max": 300,
        "step": 5,
        "slider": true
      },
      {
        "name": "bufferPct",
        "type": "number",
        "default": 20,
        "min": 0,
        "max": 100,
        "step": 5,
        "slider": true
      }
    ],
    "outputs": [
      {
        "name": "playingMinutes",
        "precision": 0,
        "unit": "minutes"
      },
      {
        "name": "songsNeeded",
        "precision": 0,
        "unit": ""
      },
      {
        "name": "songsWithBuffer",
        "precision": 0,
        "unit": ""
      },
      {
        "name": "songsPerHour",
        "precision": 1,
        "unit": ""
      },
      {
        "name": "playlistHours",
        "precision": 2,
        "unit": "hours"
      }
    ],
    "interpret": null
  }
}
