1/* Treat-vs-Wait economics â turns a severity reading into a spray/no-spray 2 * decision using the grower's own field economics. This is the classic 3 * gain-threshold idea: treat when the value of loss avoided exceeds the 4 * cost of treating. Everything is on-device; figures are the user's to 5 * adjust and persist between scans. */ 6(function () { 7 const box = document.getElementById('econBox'); 8 if (!box) return; 9 10 const F = { 11 area: document.getElementById('econArea'), 12 value: document.getElementById('econValue'), 13 loss: document.getElementById('econLoss'), 14 cost: document.getElementById('econCost'), 15 apps: document.getElementById('econApps'), 16 eff: document.getElementById('econEff') 17 }; 18 const resultEl = document.getElementById('econResult'); 19 const PKEY = 'plantvision.econ.v1'; 20 21 // Restore the grower's saved crop economics (not the per-scan loss %). 22 try { 23 const saved = JSON.parse(localStorage.getItem(PKEY) || '{}'); 24 ['area', 'value', 'cost', 'apps', 'eff'].forEach(k => { 25 if (saved[k] != null && F[k]) F[k].value = saved[k]; 26 }); 27 } catch (e) { /* ignore */ } 28 29 function persist() { 30 try { 31 localStorage.setItem(PKEY, JSON.stringify({ 32 area: F.area.value, value: F.value.value, 33 cost: F.cost.value, apps: F.apps.value, eff: F.eff.value 34 })); 35 } catch (e) { /* ignore */ } 36 } 37 38 const num = (el, d) => { 39 const v = parseFloat(el.value); 40 return isFinite(v) && v >= 0 ? v : d; 41 }; 42 const money = (n) => '$' + Math.round(n).toLocaleString(); 43 44 function compute() { 45 const area = num(F.area, 1); 46 const value = num(F.value, 0); // crop revenue per unit area 47 const loss = Math.min(100, num(F.loss, 0)); // % yield lost if untreated 48 const cost = num(F.cost, 0); // treatment cost per unit area per application 49 const apps = Math.max(1, Math.round(num(F.apps, 1))); 50 const eff = Math.min(100, num(F.eff, 75)) / 100; 51 52 const valueAtRisk = area * value * (loss / 100); 53 const lossAvoided = valueAtRisk * eff; 54 const treatmentCost = area * cost * apps; 55 const net = lossAvoided - treatmentCost; 56 const ratio = treatmentCost > 0 ? lossAvoided / treatmentCost : (lossAvoided > 0 ? Infinity : 0); 57 58 let verdict, vClass, rationale; 59 if (loss <= 0 || value <= 0) { 60 verdict = 'Add your numbers'; 61 vClass = 'econ-hold'; 62 rationale = 'Enter your crop value and the yield loss youâd expect if you donât treat.'; 63 } else if (net > 0 && ratio >= 2) { 64 verdict = 'Treat now'; 65 vClass = 'econ-treat'; 66 rationale = 'Loss avoided clears treatment cost with margin â treating pays off.'; 67 } else if (net > 0) { 68 verdict = 'Treat â marginal'; 69 vClass = 'econ-marginal'; 70 rationale = 'Treating just about pays. Worth it if severity is rising; re-scan first if it isnât.'; 71 } else { 72 verdict = 'Hold / monitor'; 73 vClass = 'econ-hold'; 74 rationale = 'Value at risk is below treatment cost right now. Re-scan and re-check as severity changes.'; 75 } 76 77 const ratioText = ratio === Infinity ? 'â' : ratio.toFixed(1) + ':1'; 78 window.lastEconVerdict = (loss > 0 && value > 0) ? { verdict, ratio: ratioText, net: Math.round(net) } : null; 79 clearTimeout(window.__econAssistT); 80 window.__econAssistT = setTimeout(() => { if (window.renderAssistant && window.lastScan) window.renderAssistant(); }, 400); 81 resultEl.innerHTML = 82 '<div class="econ-verdict ' + vClass + '">' + verdict + 83 '<span class="econ-ratio">benefit:cost ' + ratioText + '</span></div>' + 84 '<table class="econ-table"><tbody>' + 85 '<tr><td>Value at risk (untreated)</td><td>' + money(valueAtRisk) + '</td></tr>' + 86 '<tr><td>Loss avoided by treating</td><td>' + money(lossAvoided) + '</td></tr>' + 87 '<tr><td>Treatment cost (' + apps + 'Ã)</td><td>' + money(treatmentCost) + '</td></tr>' + 88 '<tr class="econ-net"><td>Net benefit</td><td>' + (net >= 0 ? '+' : 'â') + money(Math.abs(net)) + '</td></tr>' + 89 '</tbody></table>' + 90 '<p class="econ-fine">
90' + rationale + ' Figures are in your own currency and are estimates â tune loss % and cost for your crop, disease and region.</p>'; 91 } 92 93 Object.values(F).forEach(el => el && el.addEventListener('input', () => { persist(); compute(); })); 94 95 // Called from the report: seed the yield-loss % from the measured 96 // severity (a conservative first estimate) unless the grower has typed 97 // their own, then reveal and compute. 98 window.renderEconomics = function (m) { 99 if (typeof m.severity === 'number' && !F.loss.dataset.touched) { 100 F.loss.value = m.severity; 101 } 102 compute(); 103 box.style.display = 'block'; 104 }; 105 F.loss.addEventListener('input', () => { F.loss.dataset.touched = '1'; }); 106})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.