/* Practice figures, part three — the four long-form panels. These four practices needed the whole chain shown rather than one moment of it, so each runs a 20-second cycle in six phases: elements arrive in order and stay, then the cycle resets. Phase classes are pf-r1…pf-r6 (reveal) and pf-m1…pf-m6 (a packet travelling during that phase, --dx/--dy on the node). Helpers resolve at render time; see part two for why. */ const LG = (props) => window.Fig(props); const LP = (props) => window.Panel(props); const LM = (...a) => window.MONO(...a); const kA = "var(--sx-accent)", kF = "var(--sx-field)", kS = "var(--sx-signal)", kAM = "var(--sx-amber)"; const kL = "var(--border-default)", kL2 = "var(--border-strong)"; const kQ = "var(--text-quiet)", kI = "var(--text-strong)", kB = "var(--text-body)"; /* A numbered phase caption — the spine of all four figures. */ function PhaseTag({ x, y, n, label, tone }) { const px = Number(x); return ( {n} {label} ); } /* ── Agri-tech ───────────────────────────────────────────────────────────── A greenhouse with the whole loop: probes and air sensors read, a gateway ships them, the platform stores them, two models run, setpoints come back down to valve, vent and lamp, and the next reading verifies the decision. */ function FigAgriLong({ mobile }) { const h = mobile ? 344 : 356; const gx = 8, gw = mobile ? 244 : 226, gy = 30, gh = mobile ? 138 : 152; const soil = gy + gh - 34; const plantsX = [gx + 44, gx + 96, gx + 148, gx + 196].slice(0, mobile ? 3 : 4); const px = gx + gw + 12, pw = 400 - px - 4; const probe = [["20 cm", 24.7, kF], ["40 cm", 21.4, kA], ["60 cm", 18.9, kQ]]; return ( {/* phase 1 — the house, its plants and its instruments */} house 3 · 1 200 m² {plantsX.map((x, i) => ( ))} {probe.map(([d], i) => ( ))} air · par {/* phase 2 — the readings themselves */} {probe.map(([d, v, c], i) => ( {d} · {v}% ))} 28.4 °c · 61% rh par 780 µmol 14 sensors · 5 min cadence · 4 032 reads/day {/* phase 3 — gateway and platform */} lora · 3 hops store and forward · 72 h buffer {[["ingest", "4 032/day"], ["series store", "3 seasons"], ["quality", "1 gap filled"]].map((r, i) => ( {r[0]} {r[1]} ))} {/* phase 4 — the two models and what they output */} {[["yield · gbm", "6.4 t/ha ±0.3"], ["growth · lstm", "harvest w42"], ["trained", "3 seasons · 1.1M"], ["back-test", "mape 4.1%"]].map((r, i) => ( {r[0]} {r[1]} ))} {/* phase 5 — setpoints returning to the house */} setpoints written back {[["valve", "9 mm · 22 min"], ["vent", "40% open"], ["lamp", "+2 h to 06:00"]].map((r, i) => ( {r[0]} {r[1]} ))} {/* phase 6 — the verification that closes the loop */} next read verifies the decision {[["expected", "24.1%"], ["measured", "24.7%"], ["error", "+0.6 pt"], ["correction", "−0.4 mm"]].map((r, i) => ( {r[0]} {r[1]} ))} grower bounds respected · every override logged with who set it {!mobile && [["read", 1], ["ship", 3], ["store", 4], ["model", 5], ["act", 5], ["verify", 6]].slice(0, 0)} ); } /* ── Applied ML ──────────────────────────────────────────────────────────── The whole modelling job, not just its output: acquisition, processing, training and selection, back-test, the forecast, then what serving costs. */ function FigForecastLong({ mobile }) { const h = mobile ? 450 : 472; const x0 = 30, x1 = 392; const acqY = 34, prcY = mobile ? 116 : 112, trnY = mobile ? 196 : 190; const top = mobile ? 288 : 282, base = top + (mobile ? 84 : 96); const sources = [["scada · feeders", "15 min", "4.2M rows"], ["weather api", "hourly", "61k rows"], ["calendar", "daily", "holidays, ramadan"]]; const steps = [["gap fill", "1.2k"], ["outlier clamp", "340"], ["resample", "15m → 1h"], ["features", "lags, roll, temp²"]]; const N = 26, actual = Array.from({ length: N }, (_, i) => 210 + 28 * Math.sin(i / 3.1) + i * 1.9); const X = (i) => x0 + (i / (N - 1 + 8)) * (x1 - x0); const Y = (v) => base - ((v - 170) / 150) * (base - top); const fc = Array.from({ length: 9 }, (_, i) => actual[N - 1] + i * 3.4 + 6 * Math.sin(i / 1.6)); const fX = (i) => x0 + ((N - 1 + i) / (N - 1 + 8)) * (x1 - x0); return ( {sources.map(([n, cad, rows], i) => ( {n} {cad} {rows} ))} 4.3M rows/day by feeder and hour {steps.map(([n, v], i) => { const w = mobile ? 90 : 92, gap = mobile ? 96 : 98, x = 4 + (i % (mobile ? 4 : 4)) * gap; return ( {n} {v} {i > 0 && } ); })} every transform versioned, so a forecast can be rebuilt from the raw reads {[["train", 0.62, "2019–2023", kL2], ["val", 0.2, "2024", kA], ["test", 0.18, "2025", kS]].map((s, i, arr) => { const w = 388 * s[1], x = 4 + arr.slice(0, i).reduce((a, b) => a + 388 * b[1], 0); return ( {s[0]} {s[2]} ); })} {[["5-fold cv", "rolling origin"], ["sweep", "48 runs"], ["chosen", "gbm + ar residual"], ["fit time", "6 min"]].map((r, i) => ( {r[0]} {r[1]} ))} {[0, 0.5, 1].map((t) => ( {Math.round(170 + t * 150)} ))} mw `${i ? "L" : "M"}${X(i)} ${Y(v)}`).join(" ")} fill="none" stroke={kA} strokeWidth="1.6" /> `${i ? "L" : "M"}${fX(i)} ${Y(v + 5 + i * 2.4)}`).join(" ")} ${fc.map((v, i) => `L${fX(fc.length - 1 - i)} ${Y(fc[fc.length - 1 - i] - 5 - (fc.length - 1 - i) * 2.4)}`).join(" ")} Z`} fill={kS} opacity="0.14" /> `${i ? "L" : "M"}${fX(i)} ${Y(v)}`).join(" ")} fill="none" stroke={kS} strokeWidth="1.6" strokeDasharray="5 3" /> now 287 mw ±14 · 24 h {!mobile && ( residuals, test year {[0.2, 0.45, 0.8, 1, 0.72, 0.34, 0.14].map((v, i) => ( ))} )} {[["mape", "2.8%"], ["re-forecast", "hourly"], ["drift check", "weekly"], ["alert at", "mape > 4%"]].map((r, i) => ( {r[0]} {r[1]} ))} re-fitted on schedule, and whenever drift crosses the bound ); } /* ── ERP and billing ─────────────────────────────────────────────────────── The cycle from validated read to reconciled payment: rating against the tariff, a bill produced and delivered, money arriving on four channels, matched and posted, with what is still outstanding shown honestly. */ function FigLedgerLong({ mobile }) { const h = mobile ? 482 : 496; const readY = 32, rateY = mobile ? 104 : 100, billY = mobile ? 208 : 200, payY = mobile ? 292 : 284, recY = mobile ? 366 : 360; const slabs = [["0–100 units", "rs 13.48"], ["101–300", "rs 22.14"], ["301+", "rs 35.22"]]; const charges = [["energy", "4 812.60"], ["fixed", "200.00"], ["fpa", "318.40"], ["tv fee", "35.00"], ["gst 18%", "965.52"], ["subsidy", "−420.00"]]; const channels = [["bank", 0.41], ["wallet", 0.27], ["counter", 0.22], ["autopay", 0.10]]; return ( {[["reads taken", "7.5M"], ["intervals", "96/day"], ["rules run", "6"], ["held", "1 204"]].map((r, i) => ( {r[0]} {r[1]} ))} a held read is worked at the circle desk — it never reaches a bill silently {slabs.map(([s, r], i) => ( {s} {r} ))} gazetted schedule {charges.map(([n, v], i) => ( {n} {v} ))} payable by 14 sept 5 911.52 {[["bills", "7.5M"], ["print batch", "18 runs"], ["qr + iban", "on every bill"], ["duplicate rate", "0.02%"]].map((r, i) => ( {r[0]} {r[1]} ))} {[["print", kL2], ["sms", kL2], ["email", kL2], ["portal", kA]].map((c, i) => ( {c[0]} ))} same bill on every channel — one document of record {channels.map(([n, share], i) => ( {n} {Math.round(share * 100)}% ))} matched three ways: bill, receipt, bank statement line {[["matched", "96.1%", kF], ["unmatched queue", "2 118", kAM], ["gl posting", "nightly", kI], ["collected", "90.6%", kA]].map((r, i) => ( {r[0]} {r[1]} ))} arrears ageing, share of billed value {[["0–30", 0.62, kF], ["31–60", 0.28, kF], ["61–90", 0.14, kAM], ["90+", 0.08, kS]].map((b, i) => ( {b[0]} ))} reported per circle, division and tariff class ); } /* ── Cloud and DevOps ────────────────────────────────────────────────────── The estate, not just the pipeline: where it runs, what stores state, how a change reaches production, how a model is deployed, and what watches it. */ function FigDevopsLong({ mobile }) { const h = mobile ? 430 : 444; const ciY = 34, infraY = mobile ? 112 : 108, dataY = mobile ? 196 : 188, mlY = mobile ? 282 : 270, obsY = mobile ? 360 : 352; const ci = [["commit", "trunk"], ["build", "1m 40s"], ["tests", "1 284"], ["scan", "sbom, cve"], ["image", "signed"]]; const clouds = [["aws", "eks · rds · s3"], ["azure", "aks · blob · entra"], ["in-country", "own datacentre"]]; const stores = [["postgres", "primary + replica"], ["redis", "cache, queues"], ["object store", "documents, images"], ["backups", "pitr · 35 d"]]; return ( {ci.map(([n, v], i) => { const w = mobile ? 74 : 74, gap = mobile ? 78 : 78, x = 4 + i * gap; return ( {i > 0 && } {n} {v} ); })} merge blocked until the whole row is green — no manual override path {clouds.map(([n, v], i) => ( {n} {v} ))} {!mobile && ( terraform plan · apply · drift )} containers on managed kubernetes · autoscaling by queue depth, not by guess {stores.map(([n, v], i) => ( {n} {v} ))} restores rehearsed quarterly — a backup nobody has restored is not a backup {[["registry", "v14 promoted"], ["inference", "gpu · 40 rps"], ["canary", "10% traffic"], ["rollback", "one command"]].map((r, i) => ( {r[0]} {r[1]} ))} model versions are artefacts like any other — promoted, canaried, reversible {["dev", "staging", "prod", "dr"].map((e, i) => ( {i > 0 && } {e} ))} gate held, rolled back in 40s {[["uptime 90 d", "99.95%"], ["p95 latency", "180 ms"], ["alerts", "paged 24×7"], ["logs · traces", "30 d"]].map((r, i) => ( {r[0]} {r[1]} ))} ); } Object.assign(window, { PF_LONG: { agri: (m) => , forecast: (m) => , ledger: (m) => , devops: (m) => }, FigAgriLong, FigForecastLong, FigLedgerLong, FigDevopsLong });