{
  "schemaVersion": 1,
  "slug": "aquarium-calculator",
  "category": "pets",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "d7bbdd993546334bab31bc013a3922d0970f612e8bb1e5a2d059b98177474bf9",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/aquarium-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": "nominal US gal = footprint_in² × height_in/231; water US gal = footprint × (height − substrate) × fill%/100/231; L = US gal × 3.785411784",
    "vars": [],
    "note": "Bow depth is subtracted to obtain the rectangular depth before the circular segment is added; it must not exceed the total width or half the length. Only a minor circular-segment front is modeled."
  },
  "externalEvidence": null,
  "contract": {
    "compute": "try{if(((true)&&(!Number.isFinite(length)||length<1||length>240))||(([\"rectangle\",\"bowFront\",\"corner\"].includes(String(shape)))&&(!Number.isFinite(width)||width<1||width>120))||((true)&&(!Number.isFinite(height)||height<1||height>120))||((true)&&(!Number.isFinite(substrateDepth)||substrateDepth<0||substrateDepth>1000))||((true)&&(!Number.isFinite(fillPercent)||fillPercent<0||fillPercent>100))||((true)&&(!Number.isFinite(changePercent)||changePercent<0||changePercent>100))||((true)&&(!Number.isFinite(flowRate)||flowRate<0.001||flowRate>1000))||(([\"bowFront\"].includes(String(shape)))&&(!Number.isFinite(bowDepth)||bowDepth<0.001||bowDepth>1000)))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.shape,['rectangle','cylinder','bowFront','corner']);num(v.length,0.01,1000);if(v.shape!=='cylinder')num(v.width,0.01,1000);num(v.height,0.01,1000);num(v.substrateDepth,0,1000);num(v.fillPercent,0,100);num(v.changePercent,0,100);num(v.flowRate,0.001,1000);\n    if(v.substrateDepth>=v.height)fail();let footprint=v.length*v.width;\n    if(v.shape==='cylinder')footprint=Math.PI*(v.length/2)**2;\n    if(v.shape==='corner')footprint/=2;\n    if(v.shape==='bowFront'){num(v.bowDepth,0.001,Math.min(v.length/2,v.width));const r=v.length*v.length/(8*v.bowDepth)+v.bowDepth/2;footprint=v.length*(v.width-v.bowDepth)+r*r*Math.acos((r-v.bowDepth)/r)-(r-v.bowDepth)*Math.sqrt(2*r*v.bowDepth-v.bowDepth*v.bowDepth);}\n    const cubicInches=footprint*v.height,nominalGallons=cubicInches/231,actualGallons=footprint*(v.height-v.substrateDepth)*v.fillPercent/100/231;\n    return {nominalGallons,actualGallons,litres:actualGallons*3.785411784,actualLitres:actualGallons*3.785411784,waterWeightLb:actualGallons*8.34,changeGallons:actualGallons*v.changePercent/100,gallons:nominalGallons,cubicInches,fillTime:actualGallons/v.flowRate};\n  })({\"length\":(length),\"width\":(width),\"height\":(height),\"shape\":(shape),\"substrateDepth\":(substrateDepth),\"fillPercent\":(fillPercent),\"changePercent\":(changePercent),\"flowRate\":(flowRate),\"bowDepth\":(bowDepth)},h);return result;};const out=calculate();return Object.fromEntries([\"nominalGallons\",\"actualGallons\",\"litres\",\"waterWeightLb\",\"changeGallons\",\"fillTime\",\"cubicInches\"].map(k=>[k,out[k]??null]).concat(Object.entries(out).filter(([k])=>k.startsWith('__'))));}catch(error){return Object.assign(Object.fromEntries([\"nominalGallons\",\"actualGallons\",\"litres\",\"waterWeightLb\",\"changeGallons\",\"fillTime\",\"cubicInches\"].map(k=>[k,null])),{__error:error.message==='openingsExceedWallArea'?'openingsExceedWallArea':'inputError'});}",
    "inputs": [
      {
        "name": "length",
        "type": "number",
        "default": 36,
        "min": 1,
        "max": 240,
        "step": 0.25,
        "slider": true,
        "strictRange": true,
        "requiredValue": true
      },
      {
        "name": "width",
        "type": "number",
        "default": 18,
        "min": 1,
        "max": 120,
        "step": 0.25,
        "slider": true,
        "strictRange": true,
        "requiredValue": true,
        "showWhen": {
          "shape": [
            "rectangle",
            "bowFront",
            "corner"
          ]
        }
      },
      {
        "name": "height",
        "type": "number",
        "default": 20,
        "min": 1,
        "max": 120,
        "step": 0.25,
        "slider": true,
        "strictRange": true,
        "requiredValue": true
      },
      {
        "name": "substrateDepth",
        "type": "number",
        "default": 2,
        "min": 0,
        "max": 1000,
        "step": 0.01,
        "slider": true,
        "strictRange": true,
        "requiredValue": true
      },
      {
        "name": "fillPercent",
        "type": "number",
        "default": 95,
        "min": 0,
        "max": 100,
        "step": 0.01,
        "slider": true,
        "strictRange": true,
        "requiredValue": true
      },
      {
        "name": "changePercent",
        "type": "number",
        "default": 25,
        "min": 0,
        "max": 100,
        "step": 0.01,
        "slider": true,
        "strictRange": true,
        "requiredValue": true
      },
      {
        "name": "shape",
        "type": "select",
        "default": "rectangle",
        "options": [
          {
            "value": "rectangle"
          },
          {
            "value": "cylinder"
          },
          {
            "value": "bowFront"
          },
          {
            "value": "corner"
          }
        ],
        "advanced": true
      },
      {
        "type": "number",
        "default": 5,
        "min": 0.001,
        "max": 1000,
        "step": 0.01,
        "strictRange": true,
        "requiredValue": true,
        "advanced": true,
        "name": "flowRate"
      },
      {
        "type": "number",
        "default": 3,
        "min": 0.001,
        "max": 1000,
        "step": 0.01,
        "strictRange": true,
        "requiredValue": true,
        "advanced": true,
        "name": "bowDepth",
        "showWhen": {
          "shape": [
            "bowFront"
          ]
        }
      },
      {
        "name": "resultKind",
        "type": "select",
        "default": "actualGallons",
        "options": [
          {
            "value": "actualGallons"
          },
          {
            "value": "nominalGallons"
          }
        ],
        "advanced": true
      }
    ],
    "outputs": [
      {
        "name": "nominalGallons",
        "precision": 1,
        "unit": "gal",
        "notation": "auto"
      },
      {
        "name": "actualGallons",
        "precision": 1,
        "unit": "gal",
        "notation": "auto"
      },
      {
        "name": "litres",
        "precision": 1,
        "unit": "L",
        "notation": "auto"
      },
      {
        "name": "waterWeightLb",
        "precision": 0,
        "unit": "lb",
        "notation": "auto"
      },
      {
        "name": "changeGallons",
        "precision": 1,
        "unit": "gal",
        "notation": "auto"
      },
      {
        "name": "fillTime",
        "precision": 1,
        "unit": "min",
        "notation": "auto"
      },
      {
        "name": "cubicInches",
        "precision": 0,
        "unit": "cu in",
        "notation": "auto"
      }
    ],
    "interpret": null
  }
}
