Virridy Home | Lume — Water Quality Sensing Water for Carbon

New TLF cohort: ladder and drift locked

24 units · ladder and drift phases both locked

Page locked final

This page and its dataset are frozen at 2026-09-10 16:50:00 UTC, 10:50 in Denver. Both phases are closed and nothing on it recomputes. Later data on these sensors will not appear here.

The cohort is 24 units. 23 produced usable data; 500194 did not, and the reason is recorded in the ladder section below.

Tryptophan ladder locked

The cohort moved onto a dilution ladder when the drift window closed at 09:00. The point of it is the thing the drift window could not supply: a known analyte concentration, so the amplitude becomes a slope in counts per ppb and the 18× gain spread can be expressed as a calibration.

🚩 500194 is dead and every figure on this page is 23 units, not 24. Its SiPM bias has been railed at the 4095 DAC maximum since at least 3 September, giving a peak net of 46–74 counts where healthy units read 3000, and at 2026-09-09 19:30 UTC the bias collapsed to about 1080 and the net went to 0–1 counts across all 105 cells. It has produced nothing usable through the whole drift window and the whole ladder. Both failures predate anything done to the cohort in this campaign: the firmware target was not set on it until 10 September.
It was excluded silently, which is the part worth fixing. The cohort was selected on the pumphaus gateway description, which only matches the 23 units provisioned on 9 September. 500194 was already provisioned under its own build note, so it fell out of every query without appearing as a failure. The selector now names the 24 barcodes, so a dead unit shows up as dead rather than absent.

Calibration: normalized TLF against concentration

full range, log–log
0–1 ppb, linear

Low-level response: significance against each unit's own blank

How many blank standard deviations each step sits above that unit's blank. The blank is quiet enough that this is a demanding test rather than a generous one: its median CV is 0.070%.

Per-unit calibration, and what sets it

Each unit's own slope on its own steps at 1 ppb and above, against the number of LED-512 bias points its sweep returns. The relationship is nearly deterministic.

Each step is summarised by the median amplitude at 20 °C over its window, using each unit's own ρ from the locked drift fit. Within-step CV is the scatter across that window, which is how a step that was still equilibrating gives itself away.

⚠️ The blank straddles the lock. The 0 ppb window ran 08:45–09:04 and the drift record closed at 09:00, so 347 of its 439 sweeps sit inside the locked window and 92 after it. The water was clean throughout, so the blank is sound, but the last 15 minutes of the drift record are also the first ladder step.
✅ The calibration. Fitted on the settled steps at 1, 10 and 50 ppb, the median unit gives rise% = 1.98 + 1.804 × ppb, and that line reproduces every point to within 0.5 percentage points, including the 0.1 ppb step it was not fitted on. The +1.98% intercept is the part that is not tryptophan: a one-off offset that arrived with the first dose, and it is the whole of what the 0.1 ppb step appears to show.
🚩 A truncated bias sweep costs 7.3× of calibration sensitivity, and this is the campaign's main result so far. Per-unit slope runs 0.146 to 2.295 %/ppb, a 15.7× spread, and it is set almost entirely by how far that unit's bias ramp gets: corr(slope, LED-512 cells returned) = 0.99 and corr(slope, top bias reached) = 0.99. The six units that return 11 cells or fewer respond at a median of 0.292 %/ppb against 2.124 for the twelve that return 20 or more. 500189, the shortest sweep in the cohort at six cells, is the least sensitive unit at 0.146 %/ppb.
So the intake finding has a price attached now. The auto-range is working as designed, and the high-gain units stop early because they reach the top of the range sooner. The consequence is that those same units are the worst calibrated: they see a tenth of the response per ppb. Starting the bias ramp lower on the high-gain units is the fix, and it is no longer a suggestion. The correlation is 0.99 across 19 units, which leaves little room for another explanation.
The 50 ppb step has settled. All 23 units now report it, the within-step CV has fallen from 11.2% to 7.0%, and 0.31% of cells sit at the ADC ceiling. The estimator carries a railed cell as a bound, so the point stands, but 50 ppb is close to where the strong units run out of range.
What to watch as steps arrive. On 50043 nothing below about 1 ppb moved: 0 / 0.1 / 0.5 / 1 ppb read 1.356 / 1.331 / 1.339 / 1.355 amp@20, so any apparent response at the bottom of this ladder needs to clear the blank's own scatter before it counts. The cohort was also still settling at roughly 0.2 %/h when the ladder began, which is about 0.07% over a 20-minute step.

Drift phase locked

Closed before the ladder began. Locked at 2026-09-10 15:00:00 UTC, 09:00 in Denver: 2026-09-09 18:15:31 to 2026-09-10 15:00:00, 20.7 h, 19,299 sweeps, 23 units. Everything from here down is computed on that frozen window and does not move as new data arrives.
No drift rate is reported from it, on purpose. The window clears /barrel's 12 h minimum and fails its other two gates: temperature spans only 2.3–3.5 °C against the 4 a quench curve needs, and time and temperature are collinear at |corr| = 0.80–0.94 against the 0.9 limit. The units were also still settling when the window closed, so a rate fitted here would report the warm-up. What the window does deliver is the quench and the settling curve.

These units arrived unprovisioned: 23 had no Notehub serial, so no barcode, no pumphaus gateway, and every sweep they sent was dropped at the ingestion boundary. They were provisioned on 2026-09-09 by writing the modem and gateway rows keyed on the Notehub device UID, then given serials and a firmware target.

Temperature response locked

22 of 23 units give an ordinary quench. Fitted jointly with a time term on each unit's own SiPM thermistor, b_T runs −0.36 to −2.21 %/°C with a median of −1.53. That brackets /findings' fleet value of −1.71 and the TLF batch's −1.78, on a cohort that has never been characterised before.
500164 is the exception and it is not marginal. It returns b_T +31.11 %/°C, a time term of +180 %/day, lag-1 noise of 0.369% against 0.062–0.085% for every other unit, and split halves that disagree by 260 percentage points. Five axes, all pointing the same way. It should come out of the cohort until someone looks at it.

Settling locked

Rate in successive 4-hour blocks since switch-on, median over the 22 sound units, on the locked window:

Cumulatively the median unit has gained 10.1% over the 20.7 h window and was still climbing when it closed, decelerating from about 0.5 %/h in the first few hours to 0.2 %/h at the end. Nothing here is flat yet.

This is why the drift baseline has not started. /barrel found the same shape and drew the same conclusion: successive 6 h blocks in its one reliable window read −1.32, −1.12, −0.76 then −0.53 %/day, so a short hold measures the settling transient sitting on the drift. Its SETTLE_H of 2 h is far too short for what this cohort is doing. A rate fitted today would be reporting the warm-up.

What 19,299 sweeps say locked

The cohort spans 18× in gain at a matched bias of 2950, 107 to 1876 net counts. Every unit starts its bias ramp in the same place and stops where the signal approaches the top of the range, so the strong units return fewer cells: corr(cells, top bias) = 1.00 and corr(cells, gain) = −0.87. The auto-range is working, and an earlier version of this page wrongly called the short sweeps a fault.

The consequence is still real. 500189, 500131 and 500140 return six or seven usable points at LED 512. They fit now that there are hundreds of sweeps to build a shape from, but their useful bias window sits below where the ramp begins, and starting it lower would buy them back the resolution the rest of the cohort has.
500112's ToF reads 421 kcps/SPAD against 28–58 for every other unit, an order of magnitude out on a channel that is otherwise tightly grouped. Unexplained.
50083 has come alive. It produced nothing for the first two hours and now carries 356 sweeps. All 23 units are reporting.

The standard diagnostic set

The same four traces every campaign page carries, plus the bias sweep, which is where the gain spread shows. One line per unit. The record is about two and a half hours long, so it is still far short of the 12 h /barrel requires before a rate can be fitted.

Bias response at LED 512

Net counts against SiPM bias on each unit's most recent sweep, with the number of points in the legend. All 22 start at the same bias and climb the same curve; they differ in where the ramp stops, which is set by how much gain the unit has. The steep short traces belong to the high-gain units.

S-TLF amplitude

The canonical amplitude per sweep, each unit fitted against a shape built from its own sweeps. This is the trace a drift rate would eventually be fitted to, and the upward climb across it is the settling shown above.

S-TLF at 20 °C, the standard correction applied

The same amplitude with each unit's own quench removed, amp20 = amp · exp(−ρ(T − 20)), ρ fitted on that unit's own SiPM thermistor. Compare it with the panel above: the settling climb survives, because it is not temperature, and the wobble on top of it goes.

It works, and the fleet constant works just as well. Measured as residual scatter after each series' own time trend is removed, so the settling ramp is not counted, the median across the 22 sound units falls from 0.23% to 0.12%, a 49% reduction, and no unit is made worse. Substituting /findings' fleet −1.71 %/°C for the per-unit value gives 0.14%, close behind.
⚠️ That is a limit of this window rather than a reason to pool. Temperature spans only 2.3–3.5 °C here, which is too narrow for a per-unit ρ to distinguish itself from the batch value. /barrel found the same thing in water and the opposite in air, where the measurement is about seven times cleaner. The per-unit numbers stay in the table because they are what the pipeline uses; this only says the window cannot yet justify them.

SiPM temperature

The channel every quench coefficient is fitted against. A drift window needs real excursion here: /barrel will not fit a cubic below 4 °C of range.

ToF signal

Turbidity and fouling proxy. 500112 sits an order of magnitude above the rest.

Battery

/barrel treats a supply change as a confound to keep drift windows away from, so the charge state is recorded rather than corrected for.

Per unit locked

Fitted jointly as ln(amp) = a + bT(T − 20) + slope·days on each unit's own SiPM thermistor, the form /barrel uses. The time column is labeled settling rather than drift because the separability gates fail and the rate is still decaying. Lag-1 is the Allan-style noise floor.

Firmware

The cohort shipped on v0.1.18. All 24 are targeted at tlf-main-log-unsafe-1m-20m-v0_1_20$20260821202810.binpack (1 minute sampling, 20 minute sync), set per device rather than on the fleet.

The fleet-level route would have hit 44 other units. All 23 sit in the Notehub fleet New Devices, which holds 67 devices including the whole barrel and burn-in cohort (500156, 500225, 500196, 500220, 500146, 500108, 500103, 500155, 500192, 500230 among them), several mid-campaign. Setting _fw on the device overrides the fleet and touches only the intended units.

Confirmed on all 24 by reading the value back, and the Notehub serial number is now set on all 24 as well, through the reserved _sn variable. Setting a target is not the same as running it, so the sampling cadence is what confirms pickup: the old build sampled every ~900 s, the new one every 92–286 s.

⚠️ Which build a unit was on during a window is exactly the kind of thing a drift record has to carry rather than assume, so the state above is measured from the data, not from the write that requested it.

What the drift test needs before it can start

Taken from what /barrel and /findings established the hard way:

Once those are in place this page gains the drift, quench and split-half reliability panels that /barrel carries.

Provenance

Frozen artefact. The dataset behind this page is also kept at data/tlf_qaqc_locked_20260910.json, byte-identical to the live one at the moment of the lock, so the figures here remain reproducible if the working file is ever reused for a later campaign.

Sweeps read from api_samplesraw.raw_payload joined through api_payload.modem_id to api_gateway, selected on the cohort's provisioning description. Amplitudes from the canonical estimator in shared/tlf-amp.js, each unit's shape built on its own sweeps. Firmware and provisioning state from the Notehub API. Snapshot taken 2026-09-10 00:15 UTC; the figures move as data arrives.