{"id":24985,"date":"2026-10-27T07:28:32","date_gmt":"2026-10-27T07:28:32","guid":{"rendered":"https:\/\/www.mechstream.com\/?p=24985"},"modified":"2026-10-27T07:28:35","modified_gmt":"2026-10-27T07:28:35","slug":"corrosion-allowance-life-estimation-calculator","status":"publish","type":"post","link":"https:\/\/www.mechstream.com\/fr\/corrosion-allowance-life-estimation-calculator\/","title":{"rendered":"Corrosion Allowance Life Estimation Calculator"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">The <strong>Corrosion Allowance Life Estimation Calculator<\/strong> is a practical online tool designed to help engineers, maintenance professionals, and corrosion specialists estimate the remaining service life of metal components. By entering current wall thickness, minimum required thickness, and corrosion rate, users can quickly calculate remaining corrosion allowance, expected thickness loss, and estimated useful life. This free calculator supports corrosion analysis, equipment maintenance planning, and material durability assessment. It is ideal for industrial pipelines, pressure vessels, storage tanks, steel structures, and other metal equipment exposed to corrosive environments.<\/p>\n\n\n\n<div id=\"cale-calculator\" class=\"cale-root\" data-cale-widget=\"corrosion-allowance-life\">\n<style>\n#cale-calculator,#cale-calculator *,#cale-calculator *::before,#cale-calculator *::after{box-sizing:border-box}\n#cale-calculator{\n  --cale-bg:#f4f6f9;--cale-card:#ffffff;--cale-navy:#14213d;--cale-text:#2b3442;--cale-muted:#5b6675;--cale-soft:#737f8e;\n  --cale-line:#d3dae3;--cale-line-soft:#e5e9ef;--cale-panel:#f6f8fb;--cale-accent:#1b4f9c;--cale-accent-dark:#143b75;--cale-accent-soft:#e8eff9;\n  --cale-err:#b42318;--cale-err-soft:#fdecea;--cale-warn:#8a4b00;--cale-warn-soft:#fff3e0;--cale-warn-bar:#e07b00;--cale-ok:#17643f;--cale-ok-soft:#e7f5ee;--cale-ok-bar:#1f8a5a;\n  display:block;width:100%;max-width:1200px;margin:24px auto;padding:28px;background:var(--cale-bg);color:var(--cale-text);\n  border:1px solid var(--cale-line-soft);border-radius:20px;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,\"Helvetica Neue\",Arial,sans-serif;\n  font-size:16px;line-height:1.55;font-weight:400;letter-spacing:normal;text-transform:none;text-indent:0;word-spacing:normal;-webkit-font-smoothing:antialiased;text-align:left;position:relative\n}\n#cale-calculator .cale-wrap{container-type:inline-size;container-name:cale;width:100%}\n#cale-calculator h1,#cale-calculator h2,#cale-calculator h3,#cale-calculator h4,#cale-calculator p,#cale-calculator ul,#cale-calculator ol,#cale-calculator li,#cale-calculator dl,#cale-calculator dt,#cale-calculator dd,#cale-calculator details,#cale-calculator summary,#cale-calculator fieldset,#cale-calculator legend,#cale-calculator label,#cale-calculator header,#cale-calculator section,#cale-calculator article,#cale-calculator aside,#cale-calculator footer,#cale-calculator table,#cale-calculator thead,#cale-calculator tbody,#cale-calculator tr,#cale-calculator th,#cale-calculator td,#cale-calculator code,#cale-calculator strong,#cale-calculator em{\n  margin:0;padding:0;border:0;background:none;color:inherit;font-family:inherit;font-size:inherit;font-weight:inherit;font-style:normal;line-height:inherit;text-indent:0;text-transform:none;letter-spacing:normal;text-shadow:none;box-shadow:none;float:none;width:auto;max-width:none;min-width:0\n}\n#cale-calculator strong{font-weight:700}\n#cale-calculator em{font-style:italic}\n#cale-calculator ul,#cale-calculator ol{list-style:none}\n#cale-calculator a{color:var(--cale-accent);text-decoration:underline}\n#cale-calculator sub,#cale-calculator sup{font-size:.72em;line-height:0;position:relative;vertical-align:baseline}\n#cale-calculator sub{bottom:-.25em}\n#cale-calculator [hidden]{display:none!important}\n#cale-calculator svg{display:inline-block;max-width:none;height:auto;vertical-align:middle;overflow:visible}\n#cale-calculator .cale-sr{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}\n\/* ---------- layout and cards ---------- *\/\n#cale-calculator .cale-layout{display:grid;grid-template-columns:minmax(0,1fr);gap:18px;align-items:start}\n#cale-calculator .cale-left,#cale-calculator .cale-right{min-width:0}\n#cale-calculator .cale-card{background:var(--cale-card);border:1px solid var(--cale-line-soft);border-radius:16px;padding:20px;box-shadow:0 1px 2px rgba(20,33,61,.04),0 4px 14px rgba(20,33,61,.05);min-width:0}\n#cale-calculator .cale-card+.cale-card{margin-top:14px}\n#cale-calculator .cale-card-title{margin:0 0 12px;color:var(--cale-navy);font-size:17.5px;font-weight:650;line-height:1.3;display:flex;align-items:center;gap:10px}\n#cale-calculator .cale-ico{display:inline-flex;align-items:center;justify-content:center;flex:0 0 28px;width:28px;height:28px;border-radius:8px;background:var(--cale-accent-soft);color:var(--cale-accent)}\n#cale-calculator .cale-ico svg{width:17px;height:17px}\n#cale-calculator .cale-group{border-top:1px solid var(--cale-line-soft);padding-top:16px;margin-top:16px}\n#cale-calculator .cale-group-title{font-size:13px;font-weight:700;color:var(--cale-muted);letter-spacing:.05em;text-transform:uppercase;margin:0 0 10px}\n\/* ---------- mode selector ---------- *\/\n#cale-calculator .cale-modes{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;border:0;margin:0;padding:0;position:relative}\n#cale-calculator .cale-modes legend{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}\n#cale-calculator .cale-modes input{position:absolute;opacity:0;width:1px;height:1px;margin:0;pointer-events:none}\n#cale-calculator .cale-modes label{display:flex;flex-direction:column;justify-content:center;gap:1px;min-height:54px;padding:8px 11px;border:1px solid var(--cale-line);border-radius:11px;cursor:pointer;background:#fff;transition:border-color .15s,background .15s,box-shadow .15s}\n#cale-calculator .cale-modes label b{font-weight:650;font-size:14.4px;color:var(--cale-navy);line-height:1.25}\n#cale-calculator .cale-modes label small{font-size:12px;color:var(--cale-soft);line-height:1.3}\n#cale-calculator .cale-modes input:checked+label{border-color:var(--cale-accent);background:var(--cale-accent-soft);box-shadow:inset 0 0 0 1px var(--cale-accent)}\n#cale-calculator .cale-modes input:focus-visible+label{outline:3px solid rgba(27,79,156,.4);outline-offset:1px}\n#cale-calculator .cale-mode-desc{margin-top:10px;font-size:14px;color:var(--cale-muted);background:var(--cale-panel);border:1px solid var(--cale-line-soft);border-radius:10px;padding:9px 12px}\n\/* ---------- fields ---------- *\/\n#cale-calculator .cale-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:14px 16px}\n#cale-calculator .cale-field{min-width:0}\n#cale-calculator .cale-label{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px;font-size:14px;font-weight:600;color:var(--cale-navy);margin:0 0 6px;line-height:1.3}\n#cale-calculator .cale-opt{font-size:11.5px;font-weight:600;color:var(--cale-soft);background:var(--cale-panel);border:1px solid var(--cale-line-soft);border-radius:999px;padding:0 7px;line-height:1.6}\n#cale-calculator .cale-opt:empty{display:none}\n#cale-calculator .cale-hint{display:block;color:var(--cale-soft);font-size:12.8px;margin-top:5px;line-height:1.45}\n#cale-calculator .cale-error{display:block;color:var(--cale-err);font-size:13px;margin-top:4px;line-height:1.4}\n#cale-calculator .cale-error:empty{display:none}\n#cale-calculator .cale-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px;align-items:stretch}\n#cale-calculator input[type=number],#cale-calculator input[type=date],#cale-calculator input[type=text],#cale-calculator select{\n  -webkit-appearance:none;appearance:none;display:block;width:100%;min-height:46px;height:auto;margin:0;padding:10px 12px;font:inherit;font-size:16px;line-height:1.3;\n  color:var(--cale-text);background-color:#fff;border:1px solid var(--cale-line);border-radius:11px;box-shadow:none;outline:none;max-width:none;transition:border-color .15s ease,box-shadow .15s ease\n}\n#cale-calculator input[type=date]{min-width:0}\n#cale-calculator input[type=number]{-moz-appearance:textfield}\n#cale-calculator input[type=number]::-webkit-inner-spin-button,#cale-calculator input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}\n#cale-calculator select{padding-right:34px;background-image:linear-gradient(45deg,transparent 50%,#5b6675 50%),linear-gradient(135deg,#5b6675 50%,transparent 50%);background-position:calc(100% - 18px) 20px,calc(100% - 13px) 20px;background-size:5px 5px,5px 5px;background-repeat:no-repeat;cursor:pointer}\n#cale-calculator select.cale-usel{width:auto;min-width:104px;background-color:#eef2f7;font-weight:600;font-size:14px;color:var(--cale-muted)}\n#cale-calculator input:focus,#cale-calculator select:focus{border-color:var(--cale-accent);box-shadow:0 0 0 4px rgba(27,79,156,.17)}\n#cale-calculator input::placeholder{color:#a7b1be;opacity:1}\n#cale-calculator .cale-invalid{border-color:var(--cale-err)!important;box-shadow:0 0 0 3px rgba(180,35,24,.12)!important}\n#cale-calculator .cale-seg{display:inline-flex;border:1px solid var(--cale-line);border-radius:10px;overflow:hidden;margin:0 0 12px;padding:0;position:relative}\n#cale-calculator .cale-seg legend{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}\n#cale-calculator .cale-seg input{position:absolute;opacity:0;width:1px;height:1px;margin:0;pointer-events:none}\n#cale-calculator .cale-seg label{padding:8px 14px;font-size:14px;font-weight:600;color:var(--cale-muted);cursor:pointer;background:#fff;line-height:1.3}\n#cale-calculator .cale-seg label+input+label{border-left:1px solid var(--cale-line)}\n#cale-calculator .cale-seg input:checked+label{background:var(--cale-accent);color:#fff}\n#cale-calculator .cale-seg input:focus-visible+label{outline:3px solid rgba(27,79,156,.4);outline-offset:-3px}\n\/* ---------- buttons ---------- *\/\n#cale-calculator .cale-actions{display:flex;justify-content:center;margin-top:20px}\n#cale-calculator .cale-btn{-webkit-appearance:none;appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:7px;min-height:46px;margin:0;padding:10px 18px;border-radius:999px;border:1px solid var(--cale-line);background:#fff;color:var(--cale-navy);font:inherit;font-size:15px;font-weight:600;line-height:1.2;cursor:pointer;text-decoration:none;text-transform:none;letter-spacing:normal;box-shadow:none;width:auto;height:auto;text-align:center;transition:background .15s,border-color .15s,color .15s}\n#cale-calculator .cale-btn svg{width:16px;height:16px;flex:0 0 16px}\n#cale-calculator .cale-btn:hover{border-color:var(--cale-accent);color:var(--cale-accent-dark)}\n#cale-calculator .cale-btn[disabled]{opacity:.5;cursor:not-allowed}\n#cale-calculator .cale-btn[disabled]:hover{border-color:var(--cale-line);color:var(--cale-navy)}\n#cale-calculator .cale-btn-primary{background:var(--cale-accent);border-color:var(--cale-accent);color:#fff;min-width:240px;padding-left:32px;padding-right:32px}\n#cale-calculator .cale-btn-primary:hover{background:var(--cale-accent-dark);border-color:var(--cale-accent-dark);color:#fff}\n#cale-calculator .cale-btn-secondary{background:var(--cale-accent-soft);border-color:#c3d4ee;color:var(--cale-accent-dark)}\n#cale-calculator .cale-btn-sm{min-height:38px;padding:7px 14px;font-size:14px}\n#cale-calculator .cale-btn:focus-visible{outline:3px solid rgba(27,79,156,.4);outline-offset:2px}\n#cale-calculator .cale-toolbar{display:flex;flex-wrap:wrap;gap:8px}\n#cale-calculator .cale-status{font-size:13.5px;color:var(--cale-ok);margin-top:8px;min-height:1px}\n#cale-calculator .cale-status:empty{display:none}\n\/* ---------- results ---------- *\/\n#cale-calculator .cale-placeholder{color:var(--cale-muted);font-size:14.5px;padding:18px;border:1px dashed var(--cale-line);border-radius:12px;background:var(--cale-panel)}\n#cale-calculator .cale-heroes{display:grid;grid-template-columns:minmax(0,1fr);gap:10px}\n#cale-calculator .cale-hero{border-radius:14px;padding:16px 18px;background:var(--cale-accent-soft);border:1px solid #a9c1e6;min-width:0}\n#cale-calculator .cale-hero.cale-t-bad{background:var(--cale-err-soft);border-color:#f1b5ae}\n#cale-calculator .cale-hero.cale-t-warn{background:var(--cale-warn-soft);border-color:#f3cf98}\n#cale-calculator .cale-hero.cale-t-neutral{background:var(--cale-panel);border-color:var(--cale-line)}\n#cale-calculator .cale-hero-label{font-size:12.8px;font-weight:700;color:var(--cale-accent-dark);letter-spacing:.04em;text-transform:uppercase}\n#cale-calculator .cale-t-bad .cale-hero-label{color:var(--cale-err)}\n#cale-calculator .cale-t-warn .cale-hero-label{color:var(--cale-warn)}\n#cale-calculator .cale-t-neutral .cale-hero-label{color:var(--cale-muted)}\n#cale-calculator .cale-hero-value{font-size:clamp(26px,3.2vw,34px);font-weight:750;color:var(--cale-navy);letter-spacing:-.015em;line-height:1.15;margin-top:4px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}\n#cale-calculator .cale-hero-value span{font-size:.5em;font-weight:650;color:var(--cale-muted);letter-spacing:0;margin-left:6px}\n#cale-calculator .cale-hero-sub{font-size:13px;color:var(--cale-muted);margin-top:4px}\n#cale-calculator .cale-hero-act{margin-top:10px}\n#cale-calculator .cale-dl{margin-top:14px;border:1px solid var(--cale-line-soft);border-radius:12px;overflow:hidden}\n#cale-calculator .cale-dl div{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:4px 14px;padding:9px 13px;font-size:14.2px;align-items:baseline}\n#cale-calculator .cale-dl div:nth-child(odd){background:var(--cale-panel)}\n#cale-calculator .cale-dl dt{color:var(--cale-muted);min-width:0}\n#cale-calculator .cale-dl dd{color:var(--cale-navy);font-weight:650;text-align:right;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}\n#cale-calculator .cale-est{display:inline-block;font-size:10.5px;font-weight:700;letter-spacing:.04em;color:var(--cale-accent-dark);background:var(--cale-accent-soft);border-radius:4px;padding:0 5px;margin-left:6px;vertical-align:1px;text-transform:uppercase}\n#cale-calculator .cale-msgs{margin-top:12px;display:grid;gap:8px}\n#cale-calculator .cale-msg{display:flex;gap:9px;align-items:flex-start;border-radius:10px;padding:10px 12px;font-size:14px;line-height:1.45;border:1px solid transparent}\n#cale-calculator .cale-msg svg{flex:0 0 17px;width:17px;height:17px;margin-top:1px}\n#cale-calculator .cale-msg.cale-t-warn{background:var(--cale-warn-soft);border-color:#f3cf98;color:#5c3300}\n#cale-calculator .cale-msg.cale-t-bad{background:var(--cale-err-soft);border-color:#f1b5ae;color:#7a1a12}\n#cale-calculator .cale-msg.cale-t-ok{background:var(--cale-ok-soft);border-color:#b5dfc8;color:#124d31}\n#cale-calculator .cale-msg.cale-t-info{background:var(--cale-accent-soft);border-color:#c3d4ee;color:#163a6d}\n\/* ---------- responsive ---------- *\/\n@container cale (min-width:560px){\n  #cale-calculator .cale-grid-2{grid-template-columns:repeat(2,minmax(0,1fr))}\n  #cale-calculator .cale-modes{grid-template-columns:repeat(3,minmax(0,1fr))}\n  #cale-calculator .cale-heroes.cale-two{grid-template-columns:repeat(2,minmax(0,1fr))}\n}\n@container cale (min-width:940px){\n  #cale-calculator .cale-modes{grid-template-columns:repeat(5,minmax(0,1fr))}\n}\n@media (max-width:700px){\n  #cale-calculator{padding:18px 14px;border-radius:16px;margin:12px auto}\n  #cale-calculator .cale-card{padding:16px 14px;border-radius:14px}\n}\n@media (prefers-reduced-motion:reduce){#cale-calculator *{transition:none!important}}\n@media print{\n  #cale-calculator{border:0;padding:0;background:#fff}\n  #cale-calculator .cale-actions,#cale-calculator .cale-hero-act{display:none!important}\n  #cale-calculator .cale-card{box-shadow:none;break-inside:avoid}\n}\n<\/style>\n<div class=\"cale-wrap\">\n<div class=\"cale-layout\">\n<form class=\"cale-left\" id=\"cale-form\" novalidate onsubmit=\"return false;\">\n<section class=\"cale-card\" aria-labelledby=\"cale-h-in\">\n<h2 class=\"cale-card-title\" id=\"cale-h-in\"><span class=\"cale-ico\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 6h10M18 6h2M4 12h4M12 12h8M4 18h12M20 18h0\"\/><circle cx=\"16\" cy=\"6\" r=\"2\"\/><circle cx=\"10\" cy=\"12\" r=\"2\"\/><circle cx=\"18\" cy=\"18\" r=\"2\"\/><\/svg><\/span>Calculation inputs<\/h2>\n<fieldset class=\"cale-modes\" id=\"cale-modes\">\n<legend>Calculation mode<\/legend>\n<input type=\"radio\" id=\"cale-m1\" name=\"cale-mode\" value=\"1\" checked><label for=\"cale-m1\"><b>Remaining Life<\/b><small>Years to minimum thickness<\/small><\/label>\n<input type=\"radio\" id=\"cale-m2\" name=\"cale-mode\" value=\"2\"><label for=\"cale-m2\"><b>Corrosion Allowance<\/b><small>Design vs. remaining<\/small><\/label>\n<input type=\"radio\" id=\"cale-m3\" name=\"cale-mode\" value=\"3\"><label for=\"cale-m3\"><b>Future Thickness<\/b><small>Thickness after a period<\/small><\/label>\n<input type=\"radio\" id=\"cale-m4\" name=\"cale-mode\" value=\"4\"><label for=\"cale-m4\"><b>Corrosion Rate<\/b><small>From two measurements<\/small><\/label>\n<input type=\"radio\" id=\"cale-m5\" name=\"cale-mode\" value=\"5\"><label for=\"cale-m5\"><b>Required Allowance<\/b><small>For a planned service life<\/small><\/label>\n<\/fieldset>\n<p class=\"cale-mode-desc\" id=\"cale-mode-desc\" aria-live=\"polite\"><\/p>\n<div class=\"cale-group\">\n<h3 class=\"cale-group-title\">Component<\/h3>\n<div class=\"cale-grid cale-grid-2\">\n<div class=\"cale-field\">\n<label class=\"cale-label\" for=\"cale-mat\">Material type<\/label>\n<select id=\"cale-mat\" aria-describedby=\"cale-mat-hint\">\n<option value=\"carbon\" selected>Carbon Steel<\/option>\n<option value=\"stainless\">Stainless Steel<\/option>\n<option value=\"aluminum\">Aluminum Alloy<\/option>\n<option value=\"copper\">Copper Alloy<\/option>\n<option value=\"galvanized\">Galvanized Steel<\/option>\n<option value=\"lowalloy\">Low-Alloy Steel<\/option>\n<option value=\"custom\">Custom Material<\/option>\n<\/select>\n<span class=\"cale-hint\" id=\"cale-mat-hint\">For documentation only. No corrosion rate is assumed from the material.<\/span>\n<\/div>\n<div class=\"cale-field\" id=\"cale-f-matname\" hidden>\n<label class=\"cale-label\" for=\"cale-matname\">Custom material name <span class=\"cale-opt\">Optional<\/span><\/label>\n<input id=\"cale-matname\" type=\"text\" maxlength=\"80\" placeholder=\"e.g. Duplex 2205\" autocomplete=\"off\">\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cale-group\">\n<h3 class=\"cale-group-title\">Wall thickness<\/h3>\n<div class=\"cale-grid cale-grid-2\">\n<div class=\"cale-field\" id=\"cale-f-nom\">\n<label class=\"cale-label\" for=\"cale-nom\">Nominal thickness <span class=\"cale-opt\" id=\"cale-nom-opt\"><\/span><\/label>\n<div class=\"cale-row\"><input id=\"cale-nom\" type=\"number\" inputmode=\"decimal\" step=\"any\" min=\"0\" placeholder=\"e.g. 12\" aria-describedby=\"cale-nom-hint cale-nom-err\"><select class=\"cale-usel\" id=\"cale-nom-u\" aria-label=\"Nominal thickness unit\"><option value=\"mm\" selected>mm<\/option><option value=\"cm\">cm<\/option><option value=\"in\">inches<\/option><\/select><\/div>\n<span class=\"cale-hint\" id=\"cale-nom-hint\">Original specified thickness.<\/span>\n<span class=\"cale-error\" id=\"cale-nom-err\" aria-live=\"polite\"><\/span>\n<\/div>\n<div class=\"cale-field\" id=\"cale-f-cur\">\n<label class=\"cale-label\" for=\"cale-cur\">Current measured thickness <span class=\"cale-opt\" id=\"cale-cur-opt\"><\/span><\/label>\n<div class=\"cale-row\"><input id=\"cale-cur\" type=\"number\" inputmode=\"decimal\" step=\"any\" min=\"0\" placeholder=\"e.g. 10\" aria-describedby=\"cale-cur-hint cale-cur-err\"><select class=\"cale-usel\" id=\"cale-cur-u\" aria-label=\"Current thickness unit\"><option value=\"mm\" selected>mm<\/option><option value=\"cm\">cm<\/option><option value=\"in\">inches<\/option><\/select><\/div>\n<span class=\"cale-hint\" id=\"cale-cur-hint\">Latest measured wall thickness.<\/span>\n<span class=\"cale-error\" id=\"cale-cur-err\" aria-live=\"polite\"><\/span>\n<\/div>\n<div class=\"cale-field\" id=\"cale-f-min\">\n<label class=\"cale-label\" for=\"cale-min\">Minimum required thickness <span class=\"cale-opt\" id=\"cale-min-opt\"><\/span><\/label>\n<div class=\"cale-row\"><input id=\"cale-min\" type=\"number\" inputmode=\"decimal\" step=\"any\" min=\"0\" placeholder=\"e.g. 6\" aria-describedby=\"cale-min-hint cale-min-err\"><select class=\"cale-usel\" id=\"cale-min-u\" aria-label=\"Minimum thickness unit\"><option value=\"mm\" selected>mm<\/option><option value=\"cm\">cm<\/option><option value=\"in\">inches<\/option><\/select><\/div>\n<span class=\"cale-hint\" id=\"cale-min-hint\">Established independently by the applicable design and inspection requirements.<\/span>\n<span class=\"cale-error\" id=\"cale-min-err\" aria-live=\"polite\"><\/span>\n<\/div>\n<div class=\"cale-field\" id=\"cale-f-prev\">\n<label class=\"cale-label\" for=\"cale-prev\">Previous thickness <span class=\"cale-opt\" id=\"cale-prev-opt\"><\/span><\/label>\n<div class=\"cale-row\"><input id=\"cale-prev\" type=\"number\" inputmode=\"decimal\" step=\"any\" min=\"0\" placeholder=\"e.g. 11\" aria-describedby=\"cale-prev-hint cale-prev-err\"><select class=\"cale-usel\" id=\"cale-prev-u\" aria-label=\"Previous thickness unit\"><option value=\"mm\" selected>mm<\/option><option value=\"cm\">cm<\/option><option value=\"in\">inches<\/option><\/select><\/div>\n<span class=\"cale-hint\" id=\"cale-prev-hint\">Historical measurement at the same location.<\/span>\n<span class=\"cale-error\" id=\"cale-prev-err\" aria-live=\"polite\"><\/span>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cale-group\" id=\"cale-f-hist\">\n<h3 class=\"cale-group-title\">Measurement interval<\/h3>\n<fieldset class=\"cale-seg\">\n<legend>Elapsed time basis<\/legend>\n<input type=\"radio\" id=\"cale-b-dates\" name=\"cale-basis\" value=\"dates\" checked><label for=\"cale-b-dates\">Inspection dates<\/label>\n<input type=\"radio\" id=\"cale-b-years\" name=\"cale-basis\" value=\"years\"><label for=\"cale-b-years\">Elapsed time<\/label>\n<\/fieldset>\n<div class=\"cale-grid cale-grid-2\" id=\"cale-f-dates\">\n<div class=\"cale-field\">\n<label class=\"cale-label\" for=\"cale-d1\">Previous inspection date<\/label>\n<input id=\"cale-d1\" type=\"date\" aria-describedby=\"cale-d1-err\">\n<span class=\"cale-error\" id=\"cale-d1-err\" aria-live=\"polite\"><\/span>\n<\/div>\n<div class=\"cale-field\">\n<label class=\"cale-label\" for=\"cale-d2\">Current inspection date<\/label>\n<input id=\"cale-d2\" type=\"date\" aria-describedby=\"cale-d2-err\">\n<span class=\"cale-error\" id=\"cale-d2-err\" aria-live=\"polite\"><\/span>\n<\/div>\n<\/div>\n<div class=\"cale-field\" id=\"cale-f-el\" hidden>\n<label class=\"cale-label\" for=\"cale-el\">Elapsed time between measurements<\/label>\n<div class=\"cale-row\"><input id=\"cale-el\" type=\"number\" inputmode=\"decimal\" step=\"any\" min=\"0\" placeholder=\"e.g. 5\" aria-describedby=\"cale-el-err\"><select class=\"cale-usel\" id=\"cale-el-u\" aria-label=\"Elapsed time unit\"><option value=\"y\" selected>Years<\/option><option value=\"mo\">Months<\/option><\/select><\/div>\n<span class=\"cale-error\" id=\"cale-el-err\" aria-live=\"polite\"><\/span>\n<\/div>\n<span class=\"cale-hint\">Date differences use actual calendar days and 365.25 days per year.<\/span>\n<\/div>\n<div class=\"cale-group\" id=\"cale-f-rategrp\">\n<h3 class=\"cale-group-title\">Corrosion and service<\/h3>\n<div class=\"cale-grid cale-grid-2\">\n<div class=\"cale-field\" id=\"cale-f-rate\">\n<label class=\"cale-label\" for=\"cale-rate\">Corrosion rate <span class=\"cale-opt\" id=\"cale-rate-opt\"><\/span><\/label>\n<div class=\"cale-row\"><input id=\"cale-rate\" type=\"number\" inputmode=\"decimal\" step=\"any\" min=\"0\" placeholder=\"e.g. 0.2\" aria-describedby=\"cale-rate-hint cale-rate-err\"><select class=\"cale-usel\" id=\"cale-rate-u\" aria-label=\"Corrosion rate unit\"><option value=\"mmy\" selected>mm\/year<\/option><option value=\"umy\">\u00b5m\/year<\/option><option value=\"mpy\">MPY<\/option><option value=\"ipy\">in\/year<\/option><\/select><\/div>\n<span class=\"cale-hint\" id=\"cale-rate-hint\">Average annual wall loss. 1 MPY = 0.0254 mm\/year.<\/span>\n<span class=\"cale-error\" id=\"cale-rate-err\" aria-live=\"polite\"><\/span>\n<\/div>\n<div class=\"cale-field\" id=\"cale-f-life\">\n<label class=\"cale-label\" for=\"cale-life\"><span id=\"cale-life-lbl\">Planned service life<\/span> <span class=\"cale-opt\" id=\"cale-life-opt\"><\/span><\/label>\n<div class=\"cale-row\"><input id=\"cale-life\" type=\"number\" inputmode=\"decimal\" step=\"any\" min=\"0\" placeholder=\"e.g. 10\" aria-describedby=\"cale-life-err\"><select class=\"cale-usel\" id=\"cale-life-u\" aria-label=\"Service life unit\"><option value=\"y\" selected>Years<\/option><option value=\"mo\">Months<\/option><\/select><\/div>\n<span class=\"cale-error\" id=\"cale-life-err\" aria-live=\"polite\"><\/span>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cale-group\">\n<div class=\"cale-grid cale-grid-2\">\n<div class=\"cale-field\">\n<label class=\"cale-label\" for=\"cale-eval\">Evaluation date <span class=\"cale-opt\">Optional<\/span><\/label>\n<input id=\"cale-eval\" type=\"date\" aria-describedby=\"cale-eval-hint\">\n<span class=\"cale-hint\" id=\"cale-eval-hint\">Reference date for projected dates. Defaults to today.<\/span>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"cale-actions\">\n<button type=\"button\" class=\"cale-btn cale-btn-primary\" id=\"cale-calc\">Calculate<\/button>\n<\/div>\n<\/section>\n<\/form>\n<div class=\"cale-right\">\n<section class=\"cale-card\" id=\"cale-results\" aria-labelledby=\"cale-h-res\" tabindex=\"-1\" hidden>\n<h2 class=\"cale-card-title\" id=\"cale-h-res\"><span class=\"cale-ico\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M12 14l4-4\"\/><path d=\"M3.3 17a9 9 0 1 1 17.4 0\"\/><circle cx=\"12\" cy=\"14\" r=\"1.2\"\/><\/svg><\/span>Results<\/h2>\n<div id=\"cale-res-body\"><\/div>\n<p class=\"cale-sr\" id=\"cale-live\" aria-live=\"polite\"><\/p>\n<\/section>\n<\/div>\n<\/div>\n<\/div>\n<script>\n(function () {\n  \"use strict\";\n  \/* =====================================================================\n     UNIT CONVERSIONS\n     All internal calculations use millimetres (mm), mm\/year and years.\n       1 cm = 10 mm, 1 inch = 25.4 mm\n       1 MPY (mil\/year) = 0.0254 mm\/year  (1 mm\/year = 39.3701 MPY)\n       1 um\/year = 0.001 mm\/year, 1 inch\/year = 25.4 mm\/year\n       1 year = 12 months; date intervals use 365.25 days per year\n     ===================================================================== *\/\n  var LEN_TO_MM = { mm: 1, cm: 10, \"in\": 25.4 };\n  var RATE_TO_MMY = { mmy: 1, umy: 0.001, mpy: 0.0254, ipy: 25.4 };\n  var TIME_TO_Y = { y: 1, mo: 1 \/ 12 };\n  var MMY_TO_MPY = 1 \/ 0.0254;\n  var DAYS_PER_YEAR = 365.25;\n  var MS_PER_DAY = 86400000;\n  var EPS = 1e-9;\n  var LEN_LABEL = { mm: \"mm\", cm: \"cm\", \"in\": \"in\" };\n  var RATE_LABEL = { mmy: \"mm\/year\", umy: \"\u00b5m\/year\", mpy: \"MPY\", ipy: \"in\/year\" };\n  var TIME_LABEL = { y: \"years\", mo: \"months\" };\n  var MATERIALS = { carbon: \"Carbon Steel\", stainless: \"Stainless Steel\", aluminum: \"Aluminum Alloy\", copper: \"Copper Alloy\", galvanized: \"Galvanized Steel\", lowalloy: \"Low-Alloy Steel\", custom: \"Custom Material\" };\n  var MODE_NAMES = { 1: \"Remaining Life\", 2: \"Corrosion Allowance\", 3: \"Future Thickness\", 4: \"Corrosion Rate\", 5: \"Required Allowance\" };\n  var MODE_DESC = {\n    1: \"Remaining life = (current thickness \u2212 minimum required thickness) \u00f7 corrosion rate.\",\n    2: \"Design allowance = nominal \u2212 minimum; remaining allowance = current \u2212 minimum; percentage = remaining \u00f7 design \u00d7 100.\",\n    3: \"Future thickness = current thickness \u2212 (corrosion rate \u00d7 service time).\",\n    4: \"Corrosion rate = (previous thickness \u2212 current thickness) \u00f7 elapsed time.\",\n    5: \"Required allowance = corrosion rate \u00d7 planned service life; required starting thickness = minimum + required allowance.\"\n  };\n  \/* Which inputs are shown (SHOW) and required (REQ) in each mode *\/\n  var SHOW = { nom: [1, 2, 3, 4, 5], cur: [1, 2, 3, 4, 5], min: [1, 2, 3, 4, 5], prev: [4], hist: [4], rate: [1, 2, 3, 5], life: [1, 3, 5] };\n  var REQ = { nom: [2], cur: [1, 2, 3, 4], min: [1, 2, 3, 5], prev: [4], rate: [1, 3, 5], life: [3, 5] };\n  \/* Numeric field definitions *\/\n  var NUMS = {\n    nom: { kind: \"len\", label: \"nominal thickness\" },\n    cur: { kind: \"len\", label: \"current measured thickness\" },\n    min: { kind: \"len\", label: \"minimum required thickness\" },\n    prev: { kind: \"len\", label: \"previous thickness\" },\n    rate: { kind: \"rate\", label: \"corrosion rate\", allowZero: true },\n    life: { kind: \"time\", label: \"planned service life\" },\n    el: { kind: \"time\", label: \"elapsed time\" }\n  };\n  var NUM_KEYS = [\"nom\", \"cur\", \"min\", \"prev\", \"rate\", \"life\", \"el\"];\n  \/* =====================================================================\n     HELPERS\n     ===================================================================== *\/\n  function isNum(v) { return typeof v === \"number\" ? isFinite(v) : false; }\n  function inList(arr, v) { return arr.indexOf(v) >= 0; }\n  function clean(v) { return isNum(v) ? Number(v.toPrecision(12)) : v; }   \/* trims binary floating-point noise *\/\n  function group(s) {\n    var neg = s.charAt(0) === \"-\", out = \"\";\n    if (neg) { s = s.slice(1); }\n    while (s.length > 3) { out = \",\" + s.slice(-3) + out; s = s.slice(0, -3); }\n    return (neg ? \"\u2212\" : \"\") + s + out;\n  }\n  \/* Fixed decimals with trailing zeros trimmed; tiny non-zero values keep 3 significant figures *\/\n  function fmt(v, dec) {\n    if (!isNum(v)) { return \"\u2014\"; }\n    if (dec === undefined) { dec = 3; }\n    if (v === 0) { return \"0\"; }\n    var a = Math.abs(v);\n    if (a < Math.pow(10, -dec)) { return String(Number(v.toPrecision(3))).replace(\"-\", \"\u2212\"); }\n    if (a >= 1e15) { return v.toExponential(3); }\n    var s = String(Number(v.toFixed(dec)));\n    var p = s.split(\".\");\n    p[0] = group(p[0]);\n    return p.join(\".\");\n  }\n  function fT(mm) { return fmt(mm, 3) + \" mm\"; }\n  function fR(mmy) { return fmt(mmy, 4) + \" mm\/year (\" + fmt(mmy * MMY_TO_MPY, 2) + \" MPY)\"; }\n  function fY(y) { return fmt(y, 2) + \" years\"; }\n  function fP(p) { return fmt(p, 2) + \"%\"; }\n  function pad2(n) { return n < 10 ? \"0\" + n : String(n); }\n  function todayStr() { var d = new Date(); return d.getFullYear() + \"-\" + pad2(d.getMonth() + 1) + \"-\" + pad2(d.getDate()); }\n  \/* Parse \"YYYY-MM-DD\" as a UTC day (ms) so day differences are exact calendar days *\/\n  function parseDate(s) {\n    var m = \/^(\\d{4})-(\\d{2})-(\\d{2})$\/.exec(String(s).trim());\n    if (!m) { return null; }\n    var t = Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]));\n    return isNum(t) ? t : null;\n  }\n  function dateStr(ms) {\n    if (!isNum(ms)) { return \"\u2014\"; }\n    var d = new Date(ms);\n    if (!isNum(d.getTime())) { return \"\u2014\"; }\n    var y = d.getUTCFullYear();\n    if (y > 9999) { return \"beyond the year 9999\"; }\n    return y + \"-\" + pad2(d.getUTCMonth() + 1) + \"-\" + pad2(d.getUTCDate());\n  }\n  \/* =====================================================================\n     CALCULATION ENGINE (pure functions, base units in: mm, mm\/year, years)\n     ===================================================================== *\/\n  var Engine = {\n    \/* Remaining Corrosion Allowance = Current Thickness \u2212 Minimum Required Thickness *\/\n    remainingAllowance: function (cur, min) { return clean(cur - min); },\n    \/* Design Corrosion Allowance = Nominal Thickness \u2212 Minimum Required Thickness *\/\n    designAllowance: function (nom, min) { return clean(nom - min); },\n    \/* Remaining Allowance % = Remaining \/ Design \u00d7 100 (only when design allowance > 0) *\/\n    allowancePercent: function (rem, des) { return des > 0 ? clean((rem \/ des) * 100) : null; },\n    \/* Remaining Useful Life (years) = (Current \u2212 Minimum) \/ Corrosion Rate\n       Returns null when the rate is not positive or no thickness margin remains (never Infinity). *\/\n    remainingLife: function (cur, min, rate) {\n      if (!(rate > 0)) { return null; }\n      if (!(cur - min > EPS)) { return null; }\n      return clean((cur - min) \/ rate);\n    },\n    \/* Future Thickness = Current Thickness \u2212 (Corrosion Rate \u00d7 Service Time) *\/\n    futureThickness: function (cur, rate, years) { return clean(cur - rate * years); },\n    \/* Corrosion Rate = (Previous Thickness \u2212 Current Thickness) \/ Elapsed Time (elapsed time > 0) *\/\n    rateFromMeasurements: function (prev, cur, years) { return years > 0 ? clean((prev - cur) \/ years) : null; },\n    \/* Required Corrosion Allowance = Corrosion Rate \u00d7 Planned Service Life *\/\n    requiredAllowance: function (rate, years) { return clean(rate * years); },\n    \/* Estimated Required Starting Thickness = Minimum Required Thickness + Required Allowance *\/\n    requiredStartThickness: function (min, reqCA) { return clean(min + reqCA); },\n    \/* Elapsed years between two UTC day timestamps using 365.25 days per year *\/\n    yearsBetween: function (d1, d2) { return (d2 - d1) \/ MS_PER_DAY \/ DAYS_PER_YEAR; },\n    daysBetween: function (d1, d2) { return Math.round((d2 - d1) \/ MS_PER_DAY); },\n    addYears: function (ms, years) { return ms + Math.round(years * DAYS_PER_YEAR) * MS_PER_DAY; }\n  };\n  \/* =====================================================================\n     MODE COMPUTATION \u2014 returns a report object used by every output\n     x: validated inputs in base units (null when not entered)\n     ===================================================================== *\/\n  function compute(m, x) {\n    var R = { mode: m, heroes: [], rows: [], warn: [], interp: [], bar: null, chart: null, calcRate: null };\n    function has(k) { return isNum(x[k]); }\n    function row(label, value, est) { R.rows.push({ label: label, value: value, est: est ? true : false }); }\n    \/* ---- consistency warnings ---- *\/\n    if (has(\"nom\")) {\n      if (has(\"cur\")) { if (x.cur - x.nom > EPS) { R.warn.push(\"Current measured thickness (\" + fT(x.cur) + \") exceeds nominal thickness (\" + fT(x.nom) + \"). Check the measurement, units, and nominal value.\"); } }\n      if (has(\"min\")) {\n        if (x.min - x.nom > EPS) { R.warn.push(\"Minimum required thickness (\" + fT(x.min) + \") exceeds nominal thickness (\" + fT(x.nom) + \"). Check the design values and units.\"); }\n        else if (Math.abs(x.min - x.nom) <= EPS) { R.warn.push(\"Nominal thickness equals the minimum required thickness, so no design corrosion allowance exists.\"); }\n      }\n    }\n    \/* ---- corrosion rate: entered (modes 1, 2, 3, 5) or calculated (mode 4) ---- *\/\n    var rate = has(\"rate\") ? x.rate : null;\n    if (m === 4) {\n      rate = null;\n      var loss = clean(x.prev - x.cur);\n      var r4 = Engine.rateFromMeasurements(x.prev, x.cur, x.elY);\n      row(\"Previous thickness\", fT(x.prev));\n      row(\"Current measured thickness\", fT(x.cur));\n      row(\"Elapsed time\", fmt(x.elY, 4) + \" years\" + (isNum(x.elDays) ? \" (\" + group(String(x.elDays)) + \" days)\" : \"\"));\n      row(\"Measured thickness change\", loss >= 0 ? fT(loss) + \" loss\" : fT(-loss) + \" gain\");\n      if (loss < -EPS) {\n        R.heroes.push({ label: \"Calculated corrosion rate\", value: \"Not reported\", unit: \"\", sub: \"The current reading is thicker than the previous reading.\", tone: \"warn\" });\n        R.warn.push(\"Measurement consistency warning: thickness increased by \" + fT(-loss) + \" between inspections. This is not reported as a negative corrosion rate. Check measurement locations, instrument calibration, coatings or deposits, and data entry.\");\n        R.interp.push({ tone: \"warn\", text: \"A thickness increase cannot be interpreted as ordinary corrosion. Verify both readings before estimating a corrosion rate or remaining life.\" });\n      } else {\n        rate = Math.abs(loss) <= EPS ? 0 : r4;\n        R.calcRate = rate;\n        R.heroes.push({ label: \"Calculated corrosion rate\", value: fmt(rate, 4), unit: \"mm\/year\", sub: fmt(rate * MMY_TO_MPY, 2) + \" MPY \u00b7 \" + fmt(rate * 1000, 2) + \" \u00b5m\/year \u00b7 average over the interval\", tone: rate === 0 ? \"neutral\" : \"\", act: rate > 0 });\n        row(\"Calculated corrosion rate\", fR(rate), true);\n        if (rate === 0) { R.interp.push({ tone: \"info\", text: \"No measurable thickness loss was recorded between the two measurements, so no corrosion rate can be derived from this data.\" }); }\n        else { R.interp.push({ tone: \"info\", text: \"Between the two measurements the wall lost \" + fT(loss) + \" over \" + fmt(x.elY, 2) + \" years, an average corrosion rate of about \" + fmt(rate, 4) + \" mm\/year (\" + fmt(rate * MMY_TO_MPY, 2) + \" MPY). The calculated rate is a linear average and may not represent future conditions.\" }); }\n      }\n    }\n    \/* ---- shared quantities ---- *\/\n    var rem = (has(\"cur\") ? has(\"min\") : false) ? Engine.remainingAllowance(x.cur, x.min) : null;\n    var des = (has(\"nom\") ? has(\"min\") : false) ? Engine.designAllowance(x.nom, x.min) : null;\n    var pct = (isNum(rem) ? isNum(des) : false) ? Engine.allowancePercent(rem, des) : null;\n    var rul = (isNum(rem) ? isNum(rate) : false) ? Engine.remainingLife(x.cur, x.min, rate) : null;\n    var minState = null; \/* \"above\" | \"at\" | \"below\" *\/\n    if (isNum(rem)) { minState = rem > EPS ? \"above\" : (rem < -EPS ? \"below\" : \"at\"); }\n    var minDate = isNum(rul) ? Engine.addYears(x.evalMs, rul) : null;\n    var lifeY = has(\"lifeY\") ? x.lifeY : null;\n    \/* ---- mode-specific main results ---- *\/\n    if (m === 1) {\n      if (minState === \"below\") { R.heroes.push({ label: \"Estimated remaining useful life\", value: \"Below minimum\", unit: \"\", sub: \"Measured thickness is \" + fT(-rem) + \" below the minimum required thickness.\", tone: \"bad\" }); }\n      else if (minState === \"at\") { R.heroes.push({ label: \"Estimated remaining useful life\", value: \"0\", unit: \"years\", sub: \"No remaining thickness margin.\", tone: \"bad\" }); }\n      else if (rate === 0) { R.heroes.push({ label: \"Estimated remaining useful life\", value: \"Not determinable\", unit: \"\", sub: \"Corrosion rate is zero; remaining life is not calculated by dividing by zero.\", tone: \"neutral\" }); }\n      else { R.heroes.push({ label: \"Estimated remaining useful life\", value: fmt(rul, 2), unit: \"years\", sub: \"\u2248 \" + fmt(rul * 12, 1) + \" months \u00b7 projected minimum-thickness date \" + dateStr(minDate), tone: \"\" }); }\n    }\n    if (m === 2) {\n      R.heroes.push({ label: \"Remaining corrosion allowance\", value: fmt(rem, 3), unit: \"mm\", sub: minState === \"below\" ? \"Negative: measured thickness is below the minimum required thickness.\" : \"Current thickness \u2212 minimum required thickness\", tone: minState === \"above\" ? \"\" : \"bad\", noEst: true });\n      if (isNum(pct)) { R.heroes.push({ label: \"Remaining allowance percentage\", value: fmt(pct, 2), unit: \"%\", sub: \"of the \" + fT(des) + \" design allowance\", tone: pct > 50 ? \"\" : (pct > 0 ? \"warn\" : \"bad\"), noEst: true }); }\n      else { R.heroes.push({ label: \"Remaining allowance percentage\", value: \"Not calculated\", unit: \"\", sub: \"Design allowance must be greater than zero.\", tone: \"neutral\" }); }\n      if (isNum(pct)) {\n        if (pct > 100 + EPS) { R.warn.push(\"The remaining allowance exceeds 100% of the design allowance because the current thickness is above nominal. Check the measurements and assumptions.\"); }\n      }\n    }\n    if (m === 3) {\n      var fut = Engine.futureThickness(x.cur, rate, lifeY);\n      var zeroT = rate > 0 ? clean(x.cur \/ rate) : null;\n      if (fut <= EPS) {\n        R.heroes.push({ label: \"Estimated thickness after \" + fmt(lifeY, 2) + \" years\", value: \"Wall consumed\", unit: \"\", sub: \"The linear projection reaches zero thickness at about \" + fmt(zeroT, 2) + \" years. A negative thickness is not physically meaningful.\", tone: \"bad\" });\n      } else {\n        R.heroes.push({ label: \"Estimated thickness after \" + fmt(lifeY, 2) + \" years\", value: fmt(fut, 3), unit: \"mm\", sub: \"Margin above minimum: \" + fT(clean(fut - x.min)), tone: fut - x.min > EPS ? \"\" : \"bad\" });\n      }\n      if (fut - x.min <= EPS) {\n        if (minState === \"above\") { R.warn.push(\"The projected thickness reaches or falls below the minimum required thickness within the \" + fmt(lifeY, 2) + \"-year period (estimated at about \" + fmt(rul, 2) + \" years).\"); }\n        else { R.warn.push(\"The projected thickness is at or below the minimum required thickness for the whole period because the current thickness is already at or below the minimum.\"); }\n      }\n      if (fut < -EPS) { R.warn.push(\"The projected value would be negative. It is shown as wall consumed rather than as a numerical thickness.\"); }\n    }\n    if (m === 5) {\n      var reqCA = Engine.requiredAllowance(rate, lifeY);\n      var reqStart = Engine.requiredStartThickness(x.min, reqCA);\n      R.heroes.push({ label: \"Required corrosion allowance\", value: fmt(reqCA, 3), unit: \"mm\", sub: fR(rate) + \" \u00d7 \" + fY(lifeY), tone: \"\" });\n      R.heroes.push({ label: \"Estimated required starting thickness\", value: fmt(reqStart, 3), unit: \"mm\", sub: \"Minimum required thickness + required allowance\", tone: \"neutral\" });\n      row(\"Required corrosion allowance\", fT(reqCA), true);\n      row(\"Estimated required starting thickness\", fT(reqStart), true);\n      if (has(\"nom\")) {\n        var d5 = clean(x.nom - reqStart);\n        row(\"Nominal vs. required starting thickness\", (d5 >= 0 ? \"+\" : \"\u2212\") + fT(Math.abs(d5)), true);\n        R.interp.push({ tone: d5 >= 0 ? \"info\" : \"warn\", text: d5 >= 0 ? \"The nominal thickness (\" + fT(x.nom) + \") exceeds the estimated required starting thickness by \" + fT(d5) + \" for a \" + fmt(lifeY, 2) + \"-year planned life at the entered rate.\" : \"The nominal thickness (\" + fT(x.nom) + \") is \" + fT(-d5) + \" less than the estimated required starting thickness for a \" + fY(lifeY) + \" planned life at the entered rate.\" });\n      }\n      if (has(\"cur\")) {\n        var avail = clean(x.cur - x.min);\n        row(\"Available remaining allowance (current \u2212 minimum)\", fT(avail));\n        R.interp.push({ tone: avail - reqCA >= -EPS ? \"info\" : \"warn\", text: avail - reqCA >= -EPS ? \"The current measured thickness provides \" + fT(avail) + \" of allowance, which covers the \" + fT(reqCA) + \" required for the planned period under this estimate.\" : \"The current measured thickness provides \" + fT(avail) + \" of allowance, which is less than the \" + fT(reqCA) + \" required for the planned period under this estimate.\" });\n      }\n      if (rate === 0) { R.interp.push({ tone: \"info\", text: \"With a corrosion rate of zero, no corrosion allowance is required by this calculation. Design codes and manufacturing tolerances may still require a minimum allowance.\" }); }\n      R.interp.push({ tone: \"info\", text: \"This is a simplified corrosion-based estimate and does not replace engineering design calculations, manufacturing tolerances, or applicable design-code requirements.\" });\n    }\n    \/* ---- supporting results (shared) ---- *\/\n    if (m !== 4) {\n      if (has(\"nom\")) { row(\"Nominal thickness\", fT(x.nom)); }\n      if (has(\"cur\")) { row(\"Current measured thickness\", fT(x.cur)); }\n    }\n    if (has(\"min\")) { row(\"Minimum required thickness\", fT(x.min)); }\n    if (m !== 4) { if (isNum(rate)) { row(\"Corrosion rate\", fR(rate)); } }\n    if (isNum(lifeY)) { row(m === 3 ? \"Projection period\" : \"Planned service life\", fY(lifeY)); }\n    if (m !== 5) {\n      if (isNum(rem)) { row(\"Remaining corrosion allowance\", fT(rem)); }\n      if (isNum(des)) { row(\"Design corrosion allowance\", fT(des)); }\n      if (isNum(pct)) { row(\"Remaining allowance percentage\", fP(pct)); }\n      else if (isNum(des)) { row(\"Remaining allowance percentage\", \"Not calculated (design allowance \u2264 0)\"); }\n      if (isNum(rate) ? isNum(lifeY) : false) {\n        row(\"Estimated thickness loss over \" + fmt(lifeY, 2) + \" years\", fT(clean(rate * lifeY)), true);\n        if (has(\"cur\")) {\n          var ft = Engine.futureThickness(x.cur, rate, lifeY);\n          row(\"Estimated thickness after \" + fmt(lifeY, 2) + \" years\", ft <= EPS ? \"Wall consumed (\u2264 0)\" : fT(ft), true);\n        }\n      }\n      if (minState === \"above\") {\n        if (isNum(rul)) {\n          row(\"Projected time to minimum thickness\", fY(rul) + \" (\" + fmt(rul * 12, 1) + \" months)\", true);\n          row(\"Projected date of reaching minimum thickness\", dateStr(minDate), true);\n        } else if (rate === 0) {\n          row(\"Projected time to minimum thickness\", \"Not determinable (zero rate)\");\n        }\n      } else if (minState !== null) {\n        row(\"Projected time to minimum thickness\", minState === \"at\" ? \"Already reached\" : \"Already below minimum\");\n      }\n      if (m === 1) { if (isNum(rul) ? isNum(lifeY) : false) {\n        var dl = clean(rul - lifeY);\n        row(\"Remaining life vs. planned service life\", (dl >= 0 ? \"+\" : \"\u2212\") + fY(Math.abs(dl)), true);\n      } }\n    }\n    row(\"Evaluation date\", dateStr(x.evalMs) + (x.evalDefault ? \" (today)\" : \"\"));\n    \/* ---- thickness status interpretation (spec section 7) ---- *\/\n    if (minState === \"below\") { R.interp.unshift({ tone: \"bad\", text: \"The measured thickness is below the specified minimum required thickness. Engineering evaluation is required. Do not interpret this calculator as approval for continued operation.\" }); }\n    else if (minState === \"at\") { R.interp.unshift({ tone: \"bad\", text: \"The measured thickness has reached the specified minimum required thickness. No remaining thickness margin is available under this calculation.\" }); }\n    else if (minState === \"above\" ? m !== 5 : false) {\n      if (isNum(rul)) { R.interp.unshift({ tone: \"ok\", text: \"Based on the supplied thickness measurements and assumed constant corrosion rate, the component has an estimated remaining corrosion-based service life of \" + fmt(rul, 2) + \" years (projected to reach the minimum thickness around \" + dateStr(minDate) + \").\" }); }\n      else if (rate === 0) { R.interp.unshift({ tone: \"info\", text: \"No measurable corrosion rate has been entered. Remaining life cannot be determined by dividing the available thickness margin by zero.\" }); }\n      else if (m === 2) { R.interp.unshift({ tone: \"info\", text: \"The component has \" + fT(rem) + \" of thickness above the minimum required thickness. Enter a corrosion rate to also estimate the remaining corrosion-based service life.\" }); }\n      if (m === 1) { if (isNum(rul) ? isNum(lifeY) : false) {\n        R.interp.push({ tone: rul >= lifeY ? \"info\" : \"warn\", text: rul >= lifeY ? \"The estimated remaining life exceeds the \" + fmt(lifeY, 2) + \"-year planned service life under the assumed rate.\" : \"The estimated remaining life is shorter than the \" + fmt(lifeY, 2) + \"-year planned service life under the assumed rate.\" });\n      } }\n    }\n    if (m === 2) { if (isNum(pct)) { R.interp.push({ tone: \"info\", text: \"The remaining allowance percentage is \" + fP(pct) + \" of the original design allowance. It represents remaining allowance relative to the design allowance and may fall outside 0\u2013100% if measurements or assumptions are inconsistent.\" }); } }\n    if (m === 4) {\n      if (minState === null) { R.interp.push({ tone: \"info\", text: \"Enter a minimum required thickness to also estimate the remaining life at the calculated rate.\" }); }\n    }\n    \/* ---- progress bar (remaining allowance as % of design allowance) ---- *\/\n    if (isNum(pct)) { R.bar = { pct: pct, rem: rem, des: des, tone: minState === \"above\" ? (pct > 50 ? \"ok\" : \"warn\") : \"bad\" }; }\n    else if (isNum(des) ? isNum(rem) : false) { R.bar = { pct: null, rem: rem, des: des, tone: \"bad\" }; }\n    \/* ---- chart configuration ---- *\/\n    R.chart = chartConfig(m, x, rate, rul, lifeY, R);\n    R.rate = rate; R.rul = rul; R.rem = rem; R.des = des; R.pct = pct; R.minState = minState;\n    return R;\n  }\n  function chartConfig(m, x, rate, rul, lifeY, R) {\n    var c = { min: isNum(x.min) ? x.min : null, rate: rate, hist: null, lifeY: null, start: null, startLabel: \"Current\", crossT: null };\n    if (m === 5) {\n      c.start = clean(x.min + rate * lifeY); c.startLabel = \"Required start\"; c.lifeY = lifeY;\n      c.crossT = rate > 0 ? lifeY : null;\n    } else {\n      if (!isNum(x.cur)) { return null; }\n      if (!isNum(rate)) {\n        if (m !== 4) { return null; }\n        c.rate = null;\n      }\n      c.start = x.cur;\n      c.crossT = isNum(rul) ? rul : null;\n      if (isNum(lifeY)) { c.lifeY = lifeY; }\n      if (m === 4) { c.hist = { t: -x.elY, th: x.prev }; }\n    }\n    var hz = 0;\n    if (isNum(c.lifeY)) { hz = c.lifeY; }\n    if (isNum(c.crossT)) { hz = Math.max(hz, c.crossT * 1.15); }\n    if (hz <= 0) { hz = (m === 4 ? Math.max(5, x.elY) : 10); }\n    c.xMax = hz;\n    c.xMin = c.hist ? c.hist.t : 0;\n    var ymax = c.start;\n    [\"nom\", \"cur\", \"prev\", \"min\"].forEach(function (k) { if (isNum(x[k])) { ymax = Math.max(ymax, x[k]); } });\n    c.yMax = ymax;\n    return c;\n  }\n  \/* =====================================================================\n     USER INTERFACE\n     ===================================================================== *\/\n  function init() {\n    var root = document.getElementById(\"cale-calculator\");\n    if (!root) { return; }\n    if (root.getAttribute(\"data-cale-init\") === \"1\") { return; }\n    root.setAttribute(\"data-cale-init\", \"1\");\n    var SVGNS = \"http:\/\/www.w3.org\/2000\/svg\";\n    function q(id) { return root.querySelector(\"#\" + id); }\n    function h(tag, a, kids) {\n      var n = document.createElement(tag);\n      if (a) { Object.keys(a).forEach(function (k) { if (k === \"class\") { n.className = a[k]; } else if (k === \"text\") { n.textContent = a[k]; } else { n.setAttribute(k, a[k]); } }); }\n      add(n, kids);\n      return n;\n    }\n    function add(n, kids) {\n      if (kids === undefined ? true : kids === null) { return; }\n      if (Array.isArray(kids)) { kids.forEach(function (k) { add(n, k); }); return; }\n      if (typeof kids === \"string\" ? true : typeof kids === \"number\") { n.appendChild(document.createTextNode(String(kids))); return; }\n      n.appendChild(kids);\n    }\n    function s(tag, a, text) {\n      var n = document.createElementNS(SVGNS, tag);\n      if (a) { Object.keys(a).forEach(function (k) { n.setAttribute(k, a[k]); }); }\n      if (text !== undefined) { n.textContent = text; }\n      return n;\n    }\n    function clear(n) { while (n.firstChild) { n.removeChild(n.firstChild); } }\n    var ICONS = {\n      warn: \"M12 3l9.5 17H2.5z M12 10v4.5 M12 17.2v.3\",\n      bad: \"M12 3a9 9 0 1 0 0 18a9 9 0 1 0 0-18z M9 9l6 6 M15 9l-6 6\",\n      ok: \"M12 3a9 9 0 1 0 0 18a9 9 0 1 0 0-18z M8 12.5l2.8 2.8L16.5 9.5\",\n      info: \"M12 3a9 9 0 1 0 0 18a9 9 0 1 0 0-18z M12 11v5.5 M12 7.8v.3\"\n    };\n    function icon(tone) {\n      var sv = s(\"svg\", { viewBox: \"0 0 24 24\", fill: \"none\", stroke: \"currentColor\", \"stroke-width\": \"2\", \"stroke-linecap\": \"round\", \"stroke-linejoin\": \"round\", \"aria-hidden\": \"true\" });\n      sv.appendChild(s(\"path\", { d: ICONS[tone] ? ICONS[tone] : ICONS.info }));\n      return sv;\n    }\n    var state = { touched: {}, report: null, shown: false, liveTimer: null, statusTimer: null };\n    function mode() { var r = root.querySelector('input[name=\"cale-mode\"]:checked'); return r ? Number(r.value) : 1; }\n    function basis() { var r = root.querySelector('input[name=\"cale-basis\"]:checked'); return r ? r.value : \"dates\"; }\n    function setRadio(name, val) { var r = root.querySelector('input[name=\"' + name + '\"][value=\"' + val + '\"]'); if (r) { r.checked = true; } }\n    \/* ---------- show only the inputs relevant to the selected mode ---------- *\/\n    function updateVisibility() {\n      var m = mode();\n      Object.keys(SHOW).forEach(function (k) {\n        var w = q(\"cale-f-\" + k);\n        if (w) { w.hidden = !inList(SHOW[k], m); }\n        var o = q(\"cale-\" + k + \"-opt\");\n        if (o) { o.textContent = (REQ[k] ? inList(REQ[k], m) : false) ? \"\" : \"Optional\"; }\n      });\n      q(\"cale-f-dates\").hidden = basis() !== \"dates\";\n      q(\"cale-f-el\").hidden = basis() !== \"years\";\n      q(\"cale-f-matname\").hidden = q(\"cale-mat\").value !== \"custom\";\n      q(\"cale-life-lbl\").textContent = m === 3 ? \"Projection period (service time)\" : \"Planned service life\";\n      q(\"cale-mode-desc\").textContent = MODE_DESC[m];\n      q(\"cale-f-rategrp\").hidden = m === 4;\n    }\n    \/* ---------- reading and validating inputs ---------- *\/\n    function readRaw(id) {\n      var el = q(id), str = String(el.value).trim();\n      if (str === \"\") { return { empty: true, bad: el.validity ? el.validity.badInput : false, val: null }; }\n      var n = Number(str);\n      if (!isNum(n)) { return { empty: false, bad: true, val: null }; }\n      return { empty: false, bad: false, val: n };\n    }\n    function validate() {\n      var m = mode(), E = {}, missing = [], x = { mode: m }, raw = {};\n      function need(k) {\n        if (k === \"el\") { return m === 4 ? basis() === \"years\" : false; }\n        return REQ[k] ? inList(REQ[k], m) : false;\n      }\n      function visible(k) {\n        if (k === \"el\") { return m === 4 ? basis() === \"years\" : false; }\n        return inList(SHOW[k], m);\n      }\n      NUM_KEYS.forEach(function (k) {\n        var d = NUMS[k], id = \"cale-\" + k;\n        x[k] = null;\n        if (!visible(k)) { return; }\n        var r = readRaw(id), cap = d.label.charAt(0).toUpperCase() + d.label.slice(1);\n        var u = q(id + \"-u\").value;\n        if (r.bad) { E[id] = \"Enter a valid number for the \" + d.label + \".\"; return; }\n        if (r.empty) { if (need(k)) { E[id] = \"Enter the \" + d.label + \".\"; missing.push(d.label); } return; }\n        if (r.val < 0) { E[id] = cap + \" cannot be negative.\"; return; }\n        if (r.val === 0) { if (!d.allowZero) { E[id] = cap + \" must be greater than zero.\"; return; } }\n        raw[k] = { v: r.val, u: u };\n        if (d.kind === \"len\") { x[k] = r.val * LEN_TO_MM[u]; }\n        else if (d.kind === \"rate\") { x[k] = r.val * RATE_TO_MMY[u]; }\n        else { x[k] = r.val * TIME_TO_Y[u]; }\n      });\n      x.lifeY = x.life; x.elY = x.el; x.elDays = null;\n      \/* dates for mode 4 *\/\n      if (m === 4) {\n        if (basis() === \"dates\") {\n          var s1 = String(q(\"cale-d1\").value).trim(), s2 = String(q(\"cale-d2\").value).trim();\n          var d1 = parseDate(s1), d2 = parseDate(s2);\n          if (s1 === \"\") { E[\"cale-d1\"] = \"Select the previous inspection date.\"; missing.push(\"previous inspection date\"); }\n          else if (d1 === null) { E[\"cale-d1\"] = \"Enter a valid date.\"; }\n          if (s2 === \"\") { E[\"cale-d2\"] = \"Select the current inspection date.\"; missing.push(\"current inspection date\"); }\n          else if (d2 === null) { E[\"cale-d2\"] = \"Enter a valid date.\"; }\n          if (d1 !== null ? d2 !== null : false) {\n            if (d2 <= d1) { E[\"cale-d2\"] = \"The current inspection date must be later than the previous inspection date.\"; }\n            else { x.elY = Engine.yearsBetween(d1, d2); x.elDays = Engine.daysBetween(d1, d2); x.d1 = d1; x.d2 = d2; raw.d1 = s1; raw.d2 = s2; }\n          }\n        }\n      }\n      \/* evaluation date (defaults to today) *\/\n      var es = String(q(\"cale-eval\").value).trim(), ev = parseDate(es);\n      x.evalDefault = ev === null;\n      x.evalMs = ev === null ? parseDate(todayStr()) : ev;\n      raw.evalStr = dateStr(x.evalMs);\n      x.matKey = q(\"cale-mat\").value;\n      var cn = String(q(\"cale-matname\").value).trim();\n      x.matName = x.matKey === \"custom\" ? (cn === \"\" ? \"Custom Material\" : \"Custom Material: \" + cn) : MATERIALS[x.matKey];\n      x.raw = raw; x.E = E; x.missing = missing; x.ok = Object.keys(E).length === 0;\n      return x;\n    }\n    var ERR_IDS = [\"cale-nom\", \"cale-cur\", \"cale-min\", \"cale-prev\", \"cale-rate\", \"cale-life\", \"cale-el\", \"cale-d1\", \"cale-d2\"];\n    function showErrors(E, force) {\n      ERR_IDS.forEach(function (id) {\n        var msg = E[id] ? (force ? E[id] : (state.touched[id] ? E[id] : \"\")) : \"\";\n        var el = q(id), er = q(id + \"-err\");\n        er.textContent = msg;\n        if (msg) { el.classList.add(\"cale-invalid\"); el.setAttribute(\"aria-invalid\", \"true\"); }\n        else { el.classList.remove(\"cale-invalid\"); el.removeAttribute(\"aria-invalid\"); }\n      });\n    }\n    \/* ---------- input summary for exports ---------- *\/\n    function inputRows(x) {\n      var r = [], raw = x.raw;\n      function lenRow(label, k) { if (raw[k]) { r.push([label, fmt(raw[k].v, 6) + \" \" + LEN_LABEL[raw[k].u] + (raw[k].u === \"mm\" ? \"\" : \" (\" + fT(x[k]) + \")\")]); } }\n      r.push([\"Calculation mode\", MODE_NAMES[x.mode]]);\n      r.push([\"Material type\", x.matName]);\n      lenRow(\"Nominal thickness\", \"nom\");\n      lenRow(\"Current measured thickness\", \"cur\");\n      lenRow(\"Minimum required thickness\", \"min\");\n      lenRow(\"Previous thickness\", \"prev\");\n      if (raw.rate) { r.push([\"Corrosion rate\", fmt(raw.rate.v, 6) + \" \" + RATE_LABEL[raw.rate.u] + (raw.rate.u === \"mmy\" ? \"\" : \" (\" + fmt(x.rate, 6) + \" mm\/year)\")]); }\n      if (x.mode === 4) {\n        if (basis() === \"dates\") { if (raw.d1) { r.push([\"Previous inspection date\", raw.d1]); r.push([\"Current inspection date\", raw.d2]); } }\n        else if (raw.el) { r.push([\"Elapsed time\", fmt(raw.el.v, 6) + \" \" + TIME_LABEL[raw.el.u]]); }\n      }\n      if (raw.life) { r.push([x.mode === 3 ? \"Projection period\" : \"Planned service life\", fmt(raw.life.v, 6) + \" \" + TIME_LABEL[raw.life.u]]); }\n      r.push([\"Evaluation date\", raw.evalStr + (x.evalDefault ? \" (today)\" : \"\")]);\n      return r;\n    }\n    \/* =====================================================================\n       RENDERING\n       ===================================================================== *\/\n    function msgEl(tone, text) { return h(\"div\", { class: \"cale-msg cale-t-\" + tone }, [icon(tone), h(\"div\", { text: text })]); }\n    function renderPlaceholder(x) {\n      var body = q(\"cale-res-body\"), text;\n      clear(body);\n      if (x.missing.length ? x.missing.length === Object.keys(x.E).length : false) { text = \"Enter the required inputs to see results. Still needed: \" + x.missing.join(\", \") + \".\"; }\n      else { text = \"Correct the highlighted inputs to see results.\"; }\n      body.appendChild(h(\"p\", { class: \"cale-placeholder\", text: text }));\n    }\n    function renderResults(R) {\n      var body = q(\"cale-res-body\");\n      clear(body);\n      var heroes = h(\"div\", { class: \"cale-heroes\" + (R.heroes.length > 1 ? \" cale-two\" : \"\") });\n      R.heroes.forEach(function (hr) {\n        var val = h(\"div\", { class: \"cale-hero-value\" }, [hr.value]);\n        if (hr.unit) { val.appendChild(h(\"span\", { text: hr.unit })); }\n        var card = h(\"div\", { class: \"cale-hero\" + (hr.tone ? \" cale-t-\" + hr.tone : \"\") }, [\n          h(\"div\", { class: \"cale-hero-label\" }, [hr.label, hr.noEst ? null : h(\"span\", { class: \"cale-est\", text: \"Estimate\" })]),\n          val,\n          h(\"div\", { class: \"cale-hero-sub\", text: hr.sub })\n        ]);\n        if (hr.act) {\n          var b = h(\"button\", { type: \"button\", class: \"cale-btn cale-btn-sm cale-btn-secondary\", text: \"Use this rate in other modes\" });\n          b.addEventListener(\"click\", useCalcRate);\n          card.appendChild(h(\"div\", { class: \"cale-hero-act\" }, [b]));\n        }\n        heroes.appendChild(card);\n      });\n      body.appendChild(heroes);\n      if (R.warn.length) {\n        var w = h(\"div\", { class: \"cale-msgs\" });\n        R.warn.forEach(function (t) { w.appendChild(msgEl(\"warn\", t)); });\n        body.appendChild(w);\n      }\n      var dl = h(\"dl\", { class: \"cale-dl\", \"aria-label\": \"Supporting results\" });\n      R.rows.forEach(function (r) {\n        var dt = h(\"dt\", {}, [r.label]);\n        if (r.est) { dt.appendChild(h(\"span\", { class: \"cale-est\", text: \"Est.\" })); }\n        dl.appendChild(h(\"div\", {}, [dt, h(\"dd\", { text: r.value })]));\n      });\n      body.appendChild(dl);\n    }\n    \/* =====================================================================\n       RUN (real-time on every change and on Calculate)\n       ===================================================================== *\/\n    function run(force) {\n      if (force) { ERR_IDS.forEach(function (id) { state.touched[id] = true; }); }\n      updateVisibility();\n      var x = validate();\n      showErrors(x.E, force);\n      \/* Results stay hidden until Calculate is clicked (or Enter pressed);\n         after that they keep updating as inputs change. *\/\n      if (force ? x.ok : false) { state.shown = true; q(\"cale-results\").hidden = false; }\n      if (!state.shown) {\n        state.report = null;\n        if (force) { var f1 = root.querySelector(\".cale-invalid\"); if (f1) { try { f1.focus(); } catch (e) { \/* ignore *\/ } } }\n        return;\n      }\n      if (!x.ok) {\n        state.report = null;\n        renderPlaceholder(x);\n        announce(\"\");\n        if (force) { var f0 = root.querySelector(\".cale-invalid\"); if (f0) { try { f0.focus(); } catch (e) { \/* ignore *\/ } } }\n        return;\n      }\n      var R = compute(x.mode, x);\n      R.inputs = inputRows(x);\n      R.x = x;\n      R.time = new Date();\n      state.report = R;\n      renderResults(R);\n      var h0 = R.heroes[0];\n      announce(h0 ? h0.label + \": \" + h0.value + \" \" + h0.unit : \"\");\n      if (force) { scrollToResults(); }\n    }\n    function announce(t) {\n      if (state.liveTimer) { clearTimeout(state.liveTimer); }\n      state.liveTimer = setTimeout(function () { q(\"cale-live\").textContent = t; }, 700);\n    }\n    function scrollToResults() {\n      var el = q(\"cale-results\"), rect = el.getBoundingClientRect();\n      var vh = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight;\n      if (rect.top < 0 ? true : rect.top > vh * 0.6) { if (el.scrollIntoView) { el.scrollIntoView({ behavior: \"smooth\", block: \"start\" }); } }\n      try { el.focus({ preventScroll: true }); } catch (e) { \/* ignore *\/ }\n    }\n    function status(t) {\n      var el = q(\"cale-status\");\n      if (!el) { return; }\n      el.textContent = t;\n      if (state.statusTimer) { clearTimeout(state.statusTimer); }\n      state.statusTimer = setTimeout(function () { el.textContent = \"\"; }, 4500);\n    }\n    function useCalcRate() {\n      var R = state.report;\n      if (!R) { return; }\n      if (!isNum(R.calcRate)) { return; }\n      q(\"cale-rate\").value = String(Number(R.calcRate.toPrecision(6)));\n      q(\"cale-rate-u\").value = \"mmy\";\n      announce(\"Calculated rate copied to the corrosion rate field.\");\n    }\n    \/* ---------- defaults, example and reset ---------- *\/\n    function setAll(v) {\n      q(\"cale-mat\").value = v.mat; q(\"cale-matname\").value = \"\";\n      [[\"nom\", \"mm\"], [\"cur\", \"mm\"], [\"min\", \"mm\"], [\"prev\", \"mm\"], [\"rate\", \"mmy\"], [\"life\", \"y\"], [\"el\", \"y\"]].forEach(function (p) {\n        q(\"cale-\" + p[0]).value = v[p[0]] === undefined ? \"\" : String(v[p[0]]);\n        q(\"cale-\" + p[0] + \"-u\").value = p[1];\n      });\n      q(\"cale-d1\").value = v.d1 ? v.d1 : \"\"; q(\"cale-d2\").value = v.d2 ? v.d2 : \"\";\n      setRadio(\"cale-basis\", v.basis);\n      q(\"cale-eval\").value = todayStr();\n    }\n    \/* Example: carbon steel, 12 \/ 10 \/ 6 mm, 0.2 mm\/year, 10 years.\n       History for mode 4: 11 mm five years earlier, giving 0.2 mm\/year. *\/\n    function loadExample() {\n      setAll({ mat: \"carbon\", nom: 12, cur: 10, min: 6, rate: 0.2, life: 10, prev: 11, el: 5, basis: \"years\" });\n      state.touched = {};\n      run(false);\n    }\n    \/* =====================================================================\n       EVENTS (bound once)\n       ===================================================================== *\/\n    var form = q(\"cale-form\");\n    form.addEventListener(\"input\", function (e) {\n      var t = e.target;\n      if (!t) { return; }\n      if (inList(ERR_IDS, t.id)) { state.touched[t.id] = true; }\n      run(false);\n    });\n    \/* Text, number and date fields already update on \"input\"; re-running on their\n       \"change\" (blur) would rebuild the results under the pointer mid-click. *\/\n    form.addEventListener(\"change\", function (e) {\n      var t = e.target;\n      if (!t) { return; }\n      if (t.tagName === \"SELECT\" ? false : t.type !== \"radio\") { return; }\n      run(false);\n    });\n    form.addEventListener(\"keydown\", function (e) {\n      if (e.key === \"Enter\" ? (e.target ? e.target.tagName === \"INPUT\" : false) : false) { e.preventDefault(); run(true); }\n    });\n    q(\"cale-calc\").addEventListener(\"click\", function () { run(true); });\n    root.caleEngine = Engine;\n    root.caleCompute = compute;\n    \/* Preloaded example on first load *\/\n    setRadio(\"cale-mode\", \"1\");\n    loadExample();\n  }\n  if (document.getElementById(\"cale-calculator\")) { init(); }\n  else if (document.readyState === \"loading\") { document.addEventListener(\"DOMContentLoaded\", init); }\n  else { init(); }\n})();\n<\/script>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>The Corrosion Allowance Life Estimation Calculator is a practical online tool designed to help engineers, maintenance professionals, and corrosion specialists estimate the remaining service life&#8230;<\/p>","protected":false},"author":2,"featured_media":24986,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"pmpro_default_level":"","_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[8647],"tags":[8786,8784,8785,8787],"class_list":["post-24985","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-materials-science-and-properties","tag-corrosion-allowance","tag-corrosion-life-calculator","tag-remaining-useful-life","tag-wall-thickness","pmpro-has-access"],"acf":[],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"https:\/\/www.mechstream.com\/wp-content\/uploads\/2026\/10\/Corrosion-Allowance-Life-Estimation-Calculator.png","_links":{"self":[{"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/posts\/24985","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/comments?post=24985"}],"version-history":[{"count":1,"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/posts\/24985\/revisions"}],"predecessor-version":[{"id":24987,"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/posts\/24985\/revisions\/24987"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/media\/24986"}],"wp:attachment":[{"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/media?parent=24985"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/categories?post=24985"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.mechstream.com\/fr\/wp-json\/wp\/v2\/tags?post=24985"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}