{
  "schemaVersion": 1,
  "slug": "cost-per-wear-calculator",
  "category": "shopping",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "e191776c68e5f06642efe6b83bfa7f582db934d3e6d9fcd61e9c9860ffe3c3e3",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/cost-per-wear-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": "net cost = purchase + annual care × years − resale; cost/wear = net cost/(annual wears × years); target wears = max(0, ceil(net cost/target))",
    "vars": [],
    "note": "The target is a whole number of wears rounded up from final net cost divided by target cost per wear; ongoing cash timing is not modeled. Disable comparison to leave the second item out."
  },
  "externalEvidence": {
    "reviewedAt": "2026-10-31",
    "sources": [
      {
        "name": "CFPB — Budget worksheet and expense inputs",
        "url": "https://www.consumerfinance.gov/consumer-tools/educator-tools/your-money-your-goals/toolkit/"
      }
    ],
    "referenceRole": "input",
    "implementationHash": "e191776c68e5f06642efe6b83bfa7f582db934d3e6d9fcd61e9c9860ffe3c3e3",
    "basis": "Net cost = purchase price + annual care × years − resale proceeds, using the same rule for both items. Divide by expected wears. Negative net cost means an assumed net gain, not free use guaranteed. The target is a whole number of wears rounded up from final net cost divided by target cost per wear; ongoing cash timing is not modeled. Disable comparison to leave the second item out.",
    "limitations": "The target is a whole number of wears rounded up from final net cost divided by target cost per wear; ongoing cash timing is not modeled. Disable comparison to leave the second item out."
  },
  "contract": {
    "compute": "try{if(((true)&&(!Number.isFinite(price)||price<0||price>100000))||((true)&&(!Number.isFinite(wearsPerYear)||wearsPerYear<0||wearsPerYear>2600))||((true)&&(!Number.isFinite(yearsOwned)||yearsOwned<0||yearsOwned>40))||((true)&&(!Number.isFinite(maintenancePerYear)||maintenancePerYear<0||maintenancePerYear>1000000))||(([\"yes\"].includes(String(compareMode)))&&(!Number.isFinite(comparePrice)||comparePrice<0||comparePrice>1000000))||(([\"yes\"].includes(String(compareMode)))&&(!Number.isFinite(compareWearsPerYear)||compareWearsPerYear<0.001||compareWearsPerYear>10000))||(([\"yes\"].includes(String(compareMode)))&&(!Number.isFinite(compareYears)||compareYears<0.001||compareYears>100))||((true)&&(!Number.isFinite(resaleValue)||resaleValue<0||resaleValue>1000000))||((true)&&(!Number.isFinite(targetCostPerWear)||targetCostPerWear<0.001||targetCostPerWear>10000))||(([\"yes\"].includes(String(compareMode)))&&(!Number.isFinite(compareMaintenancePerYear)||compareMaintenancePerYear<0||compareMaintenancePerYear>1000000))||(([\"yes\"].includes(String(compareMode)))&&(!Number.isFinite(compareResaleValue)||compareResaleValue<0||compareResaleValue>1000000)))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}=h;num(v.price,0,1e6);num(v.wearsPerYear,0.001,10000);num(v.yearsOwned,0.001,100);num(v.maintenancePerYear,0,1e6);num(v.resaleValue,0,1e6);num(v.targetCostPerWear,0.001,10000);choice(v.compareMode,['yes','no']);\n    const totalWears=v.wearsPerYear*v.yearsOwned,totalCost=v.price+v.maintenancePerYear*v.yearsOwned-v.resaleValue,costPerWear=totalCost/totalWears;\n    let compareCostPerWear=null,cheaperPerWear=null,timesBetterValue=null;\n    if(v.compareMode==='yes'){num(v.comparePrice,0,1e6);num(v.compareWearsPerYear,0.001,10000);num(v.compareYears,0.001,100);num(v.compareMaintenancePerYear,0,1e6);num(v.compareResaleValue,0,1e6);compareCostPerWear=(v.comparePrice+v.compareMaintenancePerYear*v.compareYears-v.compareResaleValue)/(v.compareWearsPerYear*v.compareYears);cheaperPerWear=costPerWear<compareCostPerWear?'The first item':costPerWear>compareCostPerWear?'The alternative':'Both have the same cost per wear';const lo=Math.min(costPerWear,compareCostPerWear),hi=Math.max(costPerWear,compareCostPerWear);timesBetterValue=lo>0?hi/lo:lo===0&&hi===0?1:null;}\n    const wearsToTarget=Math.max(0,Math.ceil(totalCost/v.targetCostPerWear));\n    return {costPerWear,totalWears,totalCost,netCost:totalCost,compareCostPerWear,cheaperPerWear,timesBetterValue,wearsToTarget,weeksToTarget:wearsToTarget/(v.wearsPerYear/52),reached:totalWears>=wearsToTarget?'Yes':'No'};\n  })({\"price\":(price),\"wearsPerYear\":(wearsPerYear),\"yearsOwned\":(yearsOwned),\"maintenancePerYear\":(maintenancePerYear),\"resaleValue\":(resaleValue),\"targetCostPerWear\":(targetCostPerWear),\"compareMode\":(compareMode),\"comparePrice\":(comparePrice),\"compareWearsPerYear\":(compareWearsPerYear),\"compareYears\":(compareYears),\"compareMaintenancePerYear\":(compareMaintenancePerYear),\"compareResaleValue\":(compareResaleValue)},h);return result;};const out=calculate();return Object.fromEntries([\"costPerWear\",\"totalWears\",\"totalCost\",\"compareCostPerWear\",\"cheaperPerWear\",\"timesBetterValue\",\"wearsToTarget\",\"weeksToTarget\",\"reached\"].map(k=>[k,out[k]??null]).concat(Object.entries(out).filter(([k])=>k.startsWith('__'))));}catch(error){return Object.assign(Object.fromEntries([\"costPerWear\",\"totalWears\",\"totalCost\",\"compareCostPerWear\",\"cheaperPerWear\",\"timesBetterValue\",\"wearsToTarget\",\"weeksToTarget\",\"reached\"].map(k=>[k,null])),{__error:error.message==='openingsExceedWallArea'?'openingsExceedWallArea':'inputError'});}",
    "inputs": [
      {
        "name": "price",
        "type": "number",
        "default": 180,
        "min": 0,
        "max": 100000,
        "step": 1,
        "strictRange": true,
        "requiredValue": true
      },
      {
        "name": "wearsPerYear",
        "type": "number",
        "default": 40,
        "min": 0,
        "max": 2600,
        "step": 1,
        "slider": true,
        "strictRange": true,
        "requiredValue": true
      },
      {
        "name": "yearsOwned",
        "type": "number",
        "default": 3,
        "min": 0,
        "max": 40,
        "step": 0.5,
        "slider": true,
        "strictRange": true,
        "requiredValue": true
      },
      {
        "name": "maintenancePerYear",
        "type": "number",
        "default": 15,
        "min": 0,
        "max": 1000000,
        "step": 0.01,
        "slider": true,
        "strictRange": true,
        "requiredValue": true
      },
      {
        "name": "comparePrice",
        "type": "number",
        "default": 60,
        "min": 0,
        "max": 1000000,
        "step": 0.01,
        "strictRange": true,
        "requiredValue": true,
        "showWhen": {
          "compareMode": [
            "yes"
          ]
        }
      },
      {
        "name": "compareWearsPerYear",
        "type": "number",
        "default": 8,
        "min": 0.001,
        "max": 10000,
        "step": 0.01,
        "slider": true,
        "strictRange": true,
        "requiredValue": true,
        "showWhen": {
          "compareMode": [
            "yes"
          ]
        }
      },
      {
        "name": "compareYears",
        "type": "number",
        "default": 1.5,
        "min": 0.001,
        "max": 100,
        "step": 0.01,
        "slider": true,
        "strictRange": true,
        "requiredValue": true,
        "showWhen": {
          "compareMode": [
            "yes"
          ]
        }
      },
      {
        "type": "number",
        "default": 0,
        "min": 0,
        "max": 1000000,
        "step": 0.01,
        "strictRange": true,
        "requiredValue": true,
        "advanced": true,
        "name": "resaleValue"
      },
      {
        "type": "number",
        "default": 1,
        "min": 0.001,
        "max": 10000,
        "step": 0.01,
        "strictRange": true,
        "requiredValue": true,
        "advanced": true,
        "name": "targetCostPerWear"
      },
      {
        "type": "select",
        "default": "yes",
        "options": [
          {
            "value": "yes"
          },
          {
            "value": "no"
          }
        ],
        "advanced": true,
        "name": "compareMode"
      },
      {
        "type": "number",
        "default": 0,
        "min": 0,
        "max": 1000000,
        "step": 0.01,
        "strictRange": true,
        "requiredValue": true,
        "advanced": true,
        "name": "compareMaintenancePerYear",
        "showWhen": {
          "compareMode": [
            "yes"
          ]
        }
      },
      {
        "type": "number",
        "default": 0,
        "min": 0,
        "max": 1000000,
        "step": 0.01,
        "strictRange": true,
        "requiredValue": true,
        "advanced": true,
        "name": "compareResaleValue",
        "showWhen": {
          "compareMode": [
            "yes"
          ]
        }
      }
    ],
    "outputs": [
      {
        "name": "costPerWear",
        "precision": 2,
        "unit": "$",
        "notation": "auto"
      },
      {
        "name": "totalWears",
        "precision": 0,
        "unit": "wears",
        "notation": "auto"
      },
      {
        "name": "totalCost",
        "precision": 2,
        "unit": "$",
        "notation": "auto"
      },
      {
        "name": "compareCostPerWear",
        "precision": 2,
        "unit": "$",
        "notation": "auto",
        "optional": true
      },
      {
        "name": "cheaperPerWear",
        "precision": 0,
        "unit": "",
        "type": "text",
        "optional": true
      },
      {
        "name": "timesBetterValue",
        "precision": 2,
        "unit": "x",
        "notation": "auto",
        "optional": true
      },
      {
        "name": "wearsToTarget",
        "precision": 0,
        "unit": "wears",
        "notation": "auto"
      },
      {
        "name": "weeksToTarget",
        "precision": 1,
        "unit": "weeks",
        "notation": "auto"
      },
      {
        "name": "reached",
        "precision": 0,
        "unit": "",
        "type": "text"
      }
    ],
    "interpret": null
  }
}
