/* 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 });