{
  "schemaVersion": 1,
  "slug": "wallpaper-calculator",
  "category": "construction",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "aa0f37dee6c7e311c187cbd8b314dfdd89f7f9c35f938bd45ee336fe9136db02",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/wallpaper-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": "drop length = repeat × ceil((height + trim)/repeat); cuts/roll = floor(roll length/drop length); rolls = ceil(ceil(drops × (1 + waste/100))/cuts per roll); area mode = ceil(net area × (1 + waste/100)/coverage)",
    "vars": [],
    "note": "A roll too short for one drop is invalid. Area mode is explicitly a coverage estimate and does not simulate pattern matching; the old arbitrary repeat penalty is removed."
  },
  "externalEvidence": null,
  "contract": {
    "compute": "try{if((([\"wall\"].includes(String(wallMode)))&&(!Number.isFinite(wallWidth)||wallWidth<0.01||wallWidth>5000))||((true)&&(!Number.isFinite(wallHeight)||wallHeight<1||wallHeight>40))||(([\"drops\"].includes(String(calculationMode)))&&(!Number.isFinite(rollLength)||rollLength<0.01||rollLength>1000))||(([\"drops\"].includes(String(calculationMode)))&&(!Number.isFinite(rollWidth)||rollWidth<0.01||rollWidth>20))||(([\"drops\"].includes(String(calculationMode)))&&(!Number.isFinite(patternRepeat)||patternRepeat<0||patternRepeat>120))||((true)&&(!Number.isFinite(deductions)||deductions<0||deductions>5000))||((true)&&(!Number.isFinite(wastePct)||wastePct<0||wastePct>100))||(([\"room\"].includes(String(wallMode)))&&(!Number.isFinite(length)||length<0.01||length>1000))||(([\"room\"].includes(String(wallMode)))&&(!Number.isFinite(width)||width<0.01||width>1000))||(([\"drops\"].includes(String(calculationMode)))&&(!Number.isFinite(trimIn)||trimIn<0||trimIn>120))||(([\"area\"].includes(String(calculationMode)))&&(!Number.isFinite(coverage)||coverage<0.01||coverage>10000)))throw new Error('inputError');const calculate=function(){const h=(function context() {\n  const DAY=86400000;\n  const own=(o,k)=>Object.prototype.hasOwnProperty.call(o,k);\n  const fail=()=>{throw new Error('inputError');};\n  const num=(v,min=-Infinity,max=Infinity,integer=false)=>{\n    if(!Number.isFinite(v)||v<min||v>max||(integer&&!Number.isInteger(v)))fail();return v;\n  };\n  const choice=(v,values)=>{if(!values.includes(v))fail();return v;};\n  const date=s=>{if(typeof s!=='string'||!/^\\d{4}-\\d{2}-\\d{2}$/.test(s)||s.startsWith('0000'))fail();const d=new Date(s+'T00:00:00Z');if(!Number.isFinite(+d)||d.toISOString().slice(0,10)!==s)fail();return d;};\n  const iso=d=>{if(!Number.isFinite(+d)||d.getUTCFullYear()<1||d.getUTCFullYear()>9999)fail();return d.toISOString().slice(0,10);};\n  const shift=(d,n)=>{const r=new Date(+d+n*DAY);iso(r);return r;};\n  const month=(d,n)=>{const r=new Date(+d);r.setUTCDate(1);r.setUTCMonth(r.getUTCMonth()+n);const end=new Date(+r);end.setUTCMonth(end.getUTCMonth()+1,0);r.setUTCDate(Math.min(d.getUTCDate(),end.getUTCDate()));iso(r);return r;};\n  const weekdays=['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];\n  const work=(d,count)=>{let n=Math.floor(count/7)*5;for(let k=0;k<count%7;k++){const w=(d.getUTCDay()+k)%7;if(w!==0&&w!==6)n++;}return n;};\n  const wholeMonths=(a,b)=>{let n=(b.getUTCFullYear()-a.getUTCFullYear())*12+b.getUTCMonth()-a.getUTCMonth();if(n>=0&&+month(a,n)>+b)n--;if(n<0&&+month(a,n)<+b)n++;return n;};\n  return {DAY,own,fail,num,choice,date,iso,shift,month,weekdays,work,wholeMonths};\n})();const result=(function(v,h){\n    const {num,choice,fail}=h;choice(v.wallMode,['wall','room']);choice(v.calculationMode,['drops','area']);num(v.wallHeight,0.01,100);num(v.deductions,0,1e6);num(v.wastePct,0,100);\n    let perimeter=v.wallWidth;if(v.wallMode==='room'){num(v.length,0.01,1000);num(v.width,0.01,1000);perimeter=2*(v.length+v.width);}else num(v.wallWidth,0.01,5000);\n    const totalArea=perimeter*v.wallHeight;if(v.deductions>totalArea)throw new Error('openingsExceedWallArea');const netArea=totalArea-v.deductions;\n    if(v.calculationMode==='area'){num(v.coverage,0.01,10000);const rolls=Math.ceil(netArea*(1+v.wastePct/100)/v.coverage);return {totalArea,wallArea:totalArea,netArea,perimeter,drops:null,usablePerRoll:null,rollsNeeded:rolls,rolls,repeatMultiplier:null,linearFeet:null,cutsPerRoll:null};}\n    num(v.rollLength,0.01,1000);num(v.rollWidth,0.01,20);num(v.patternRepeat,0,120);num(v.trimIn,0,120);\n    const repeat=v.patternRepeat/12,raw=v.wallHeight+v.trimIn/12,dropLength=repeat?Math.ceil(raw/repeat-1e-12)*repeat:raw,cutsPerRoll=Math.floor(v.rollLength/dropLength+1e-12);if(cutsPerRoll<1)fail();\n    const drops=v.wallMode==='room'?2*Math.ceil(v.length/v.rollWidth)+2*Math.ceil(v.width/v.rollWidth):Math.ceil(perimeter/v.rollWidth),allowanceDrops=Math.ceil(drops*(1+v.wastePct/100)-1e-12),rollsNeeded=Math.ceil(allowanceDrops/cutsPerRoll);\n    return {totalArea,wallArea:totalArea,netArea,perimeter,drops,usablePerRoll:cutsPerRoll*dropLength,rollsNeeded,rolls:rollsNeeded,repeatMultiplier:dropLength/v.wallHeight,linearFeet:rollsNeeded*v.rollLength,cutsPerRoll};\n  })({\"wallMode\":(wallMode),\"calculationMode\":(calculationMode),\"wallWidth\":(wallWidth),\"wallHeight\":(wallHeight),\"deductions\":(deductions),\"length\":(length),\"width\":(width),\"rollLength\":(rollLength),\"rollWidth\":(rollWidth),\"patternRepeat\":(patternRepeat),\"trimIn\":(trimIn),\"wastePct\":(wastePct),\"coverage\":(coverage)},h);return result;};const out=calculate();return Object.fromEntries([\"totalArea\",\"netArea\",\"drops\",\"usablePerRoll\",\"rollsNeeded\",\"repeatMultiplier\",\"linearFeet\",\"cutsPerRoll\",\"perimeter\"].map(k=>[k,out[k]??null]).concat(Object.entries(out).filter(([k])=>k.startsWith('__'))));}catch(error){return Object.assign(Object.fromEntries([\"totalArea\",\"netArea\",\"drops\",\"usablePerRoll\",\"rollsNeeded\",\"repeatMultiplier\",\"linearFeet\",\"cutsPerRoll\",\"perimeter\"].map(k=>[k,null])),{__error:error.message==='openingsExceedWallArea'?'openingsExceedWallArea':'inputError'});}",
    "inputs": [
      {
        "name": "wallWidth",
        "type": "number",
        "default": 40,
        "min": 0.01,
        "max": 5000,
        "step": 0.01,
        "slider": true,
        "strictRange": true,
        "requiredValue": true,
        "showWhen": {
          "wallMode": [
            "wall"
          ]
        }
      },
      {
        "name": "wallHeight",
        "type": "number",
        "default": 9,
        "min": 1,
        "max": 40,
        "step": 0.1,
        "slider": true,
        "strictRange": true,
        "requiredValue": true
      },
      {
        "name": "rollLength",
        "type": "number",
        "default": 33,
        "min": 0.01,
        "max": 1000,
        "step": 0.01,
        "slider": true,
        "strictRange": true,
        "requiredValue": true,
        "showWhen": {
          "calculationMode": [
            "drops"
          ]
        }
      },
      {
        "name": "rollWidth",
        "type": "number",
        "default": 1.75,
        "min": 0.01,
        "max": 20,
        "step": 0.01,
        "slider": true,
        "strictRange": true,
        "requiredValue": true,
        "showWhen": {
          "calculationMode": [
            "drops"
          ]
        }
      },
      {
        "name": "patternRepeat",
        "type": "number",
        "default": 12,
        "min": 0,
        "max": 120,
        "step": 0.01,
        "slider": true,
        "strictRange": true,
        "requiredValue": true,
        "showWhen": {
          "calculationMode": [
            "drops"
          ]
        }
      },
      {
        "name": "deductions",
        "type": "number",
        "default": 60,
        "min": 0,
        "max": 5000,
        "step": 1,
        "slider": true,
        "strictRange": true,
        "requiredValue": true
      },
      {
        "name": "wastePct",
        "type": "number",
        "default": 10,
        "min": 0,
        "max": 100,
        "step": 0.01,
        "slider": true,
        "strictRange": true,
        "requiredValue": true
      },
      {
        "type": "select",
        "default": "wall",
        "options": [
          {
            "value": "wall"
          },
          {
            "value": "room"
          }
        ],
        "advanced": true,
        "name": "wallMode"
      },
      {
        "type": "select",
        "default": "drops",
        "options": [
          {
            "value": "drops"
          },
          {
            "value": "area"
          }
        ],
        "advanced": true,
        "name": "calculationMode"
      },
      {
        "type": "number",
        "default": 14,
        "min": 0.01,
        "max": 1000,
        "step": 0.01,
        "strictRange": true,
        "requiredValue": true,
        "advanced": true,
        "name": "length",
        "showWhen": {
          "wallMode": [
            "room"
          ]
        }
      },
      {
        "type": "number",
        "default": 12,
        "min": 0.01,
        "max": 1000,
        "step": 0.01,
        "strictRange": true,
        "requiredValue": true,
        "advanced": true,
        "name": "width",
        "showWhen": {
          "wallMode": [
            "room"
          ]
        }
      },
      {
        "type": "number",
        "default": 0,
        "min": 0,
        "max": 120,
        "step": 0.01,
        "strictRange": true,
        "requiredValue": true,
        "advanced": true,
        "name": "trimIn",
        "showWhen": {
          "calculationMode": [
            "drops"
          ]
        }
      },
      {
        "type": "number",
        "default": 45,
        "min": 0.01,
        "max": 10000,
        "step": 0.01,
        "strictRange": true,
        "requiredValue": true,
        "advanced": true,
        "name": "coverage",
        "showWhen": {
          "calculationMode": [
            "area"
          ]
        }
      }
    ],
    "outputs": [
      {
        "name": "totalArea",
        "precision": 1,
        "unit": "sq ft",
        "notation": "auto"
      },
      {
        "name": "netArea",
        "precision": 1,
        "unit": "sq ft",
        "notation": "auto"
      },
      {
        "name": "drops",
        "precision": 0,
        "unit": "",
        "notation": "auto",
        "optional": true
      },
      {
        "name": "usablePerRoll",
        "precision": 2,
        "unit": "ft",
        "notation": "auto",
        "optional": true
      },
      {
        "name": "rollsNeeded",
        "precision": 0,
        "unit": "",
        "notation": "auto"
      },
      {
        "name": "repeatMultiplier",
        "precision": 2,
        "unit": "x",
        "notation": "auto",
        "optional": true
      },
      {
        "name": "linearFeet",
        "precision": 1,
        "unit": "ft",
        "notation": "auto",
        "optional": true
      },
      {
        "precision": 0,
        "unit": "",
        "optional": true,
        "name": "cutsPerRoll",
        "notation": "auto"
      },
      {
        "name": "perimeter",
        "precision": 1,
        "unit": "ft",
        "notation": "auto"
      }
    ],
    "interpret": null
  }
}
