PageSourceSearch

https://plant-health-scanner-sandy.vercel.app/js/economics.js

js plant-health-scanner-sandy.vercel.app collected 2026-10-03 07:30:01 UTC 5,183 bytes, 106 lines download raw bytes

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.