main · 91 commits · 4 merged PRs · 3 contributors🔗 The Open live buttons (↗) deep-link into the deployed dashboards. Blue and Red require a login on first open; Grey is a public demo (no login).
Blue moves from "what happened" to "what's coming" — a look-ahead on soil conditions and a growth tracker that can't go stale.
A new forecast page predicts soil moisture and soil temperature for day +1 through +7, one curve per treatment group. It learns from the last 30 days of readings (using lag features) and plots a shaded uncertainty band so you can see how confident each prediction is.
d53e030 basic forecasting52ca4cf soil-model bootstrap + ephemeral model dir; promote GDD routed1c6975 soil-forecast: escape reflected HTML, offload+lock training, fix MAE bandd72d394 fixes forecastGrowing Degree Days (a running measure of accumulated heat that drives crop development) used to read the farm's own weather-station sensor, which lacked complete coverage for 2024 — leaving gaps in the analysis. It now sources daily min/max temperature from OpenMeteo modeled weather at the farm's exact location (Grubbenvorst), stitching together the historical archive, the recent past, and a 14-day forecast into an unbroken series from 1 Jan 2024 onward. That gives Blue a full calendar year of GDD to analyse for the first time.
16882a2 GDD reads OpenMeteo modeled weather, decoupled from sensors77ebf53 update GDD thresholds to Jans research955b971 give GDD its own home-page card with growth accentHand-recorded farm data (irrigation events, lab measurements) is no longer siloed — it's overlaid directly on the sensor charts where it explains what the sensors show.
Irrigation/fertigation events (uploaded as CSV or via the admin UI) now appear as vertical markers on the soil monitor, so a moisture spike lines up visually with the watering that caused it. The overlay reads from the same ingested data store as every other manual dataset — one source of truth, not a separate CSV re-read.
2e98fdf local fertigation overlayfe3ea8e fertigation upload option7aba4b9 fertigation no longer shared (blue-scoped)A dedicated hub for the hand-recorded lab measurements, with correlation and comparison views — plus mixed views that put sensor data and manual data (or manual measurements and fertigation) on the same chart for direct comparison.
cbf7650 manual measurement monitor788f100 mixed graphsA view that surfaces sensors whose data is unreliable rather than simply missing — separating soil and weather feeds — so data-quality issues are visible instead of silently skewing charts.
d3af0e0 broken sensors tab58be207 unreliable, not brokenRed's "multi-height wire" is a single device that measures light, temperature, humidity and CO₂ at several heights up the canopy. Ahead of installing three new/updated wires in the greenhouse, the platform was prepared so that onboarding them is a configuration change — not a code change.
Which wires exist is declared in metadata.yaml and never inferred from the incoming data. Wire enumeration was centralised into one module that every view, the risk engine and the export job read from — so adding the three incoming wires means new config entries, with no code to touch. This groundwork is landing now, before the hardware goes in.
dcf5efd declare third multi-height wire, warn on undeclared wiresf4ee877 export wire devices to CSV; centralise wire enumerationA shared upgrade to the unified chart page that every twin inherits. (Grey is the public demo — click straight in, no login.)
Each Y-axis on the unified chart now carries its own chart-type selector — Line, Scatter, or Boxplot — mirroring how the aggregation controls already work per axis. Boxplots are built client-side from raw points, so the bucket slider becomes a "box width" control. The choice round-trips through the URL (ct / ct_r), so a shared or bookmarked chart keeps its type; existing links are untouched and still render exactly as before.
7203710 per-Y-axis chart types (line/scatter/boxplot)ce04d78 persist per-axis chart type when saving a chartBlue draws automated data from two feeds (the SPoHF datalake and the direct "yookr" interface). This work makes the dashboards tell the truth about which feed actually has data.
The /status coverage grid used to show a sensor as "covered up to today" even when those recent days were written by the other feed — masking the datalake's staleness. It now matches coverage at the day level per source, so under the Datalake view a sensor's days reflect only datalake-owned days. The two feeds can also coexist safely now via project-aware de-duplication.
641ab81 /status coverage honors source toggle (day-granularity match)759d838 project-aware dedup so datalake & yookr-direct coexistb9272c9 stop requiring the datalake relay's ingestion timestampThe SPoHF Datalake is the intended integration for Blue. Until now the twin kept a direct Yookr API feed as a fallback, because the datalake relay wasn't yet complete enough to rely on. It now is — so the fallback, its sync job, and the whole dual-source machinery have been retired. Less code, less storage, less compute.
The SPoHF Datalake is now Blue's single, canonical source of automated readings. The direct Yookr API — kept as a fallback while the relay caught up — is no longer needed and has been removed, along with the source toggle and the readings.project column that distinguished the two feeds.
src/wp6_data/yookr/ client package, the 299-line sync orchestrator, blue's datasource.py + yookr.py, and the full-sync script. The remainder is net simplification of deps.py (every project filter gone), provider.py, ops.py and __main__.py — partly offset by the truncation fix below.project column is dropped from every remaining row and the dedup index loses a column. daily_coverage shed 4,323 stale device/sensor/day entries (44,930 → 40,607) that no longer had readings behind them. The hypertable now stands at 2.77 GB, already vacuumed.yookr-email, yookr-password) and one external dependency (api.yookr.org) are gone from the platform entirely.BlueSensorProvider instances to one, and a cookie-dispatched provider to a static badge. Blue now matches Red's single-categorical readings.source model, closing issues 024, 026 and 029 and rendering 027 moot.91cdda5 feat(blue)!: retire yookr-direct, drop readings.project68bc100 chore(sync): disable sync CronJob for the drop-project migration window50277cb docs: record the completed yookr-direct retirement; re-enable syncAuditing the two feeds before the purge turned up a 337,081-reading gap. Most of it was not the relay's fault — it was ours. The SPoHF datalake is Elasticsearch-backed and refuses to page past 10,000 records in one query. Our client paged by offset until it got a short page, so any day holding more than 10,000 readings simply stopped early, with no error. We read "the server won't give me more" as "there is no more".
fetch_window now detects that the next page would cross the cap and splits the time range in half, recursing until each half fits. It is self-correcting at any future data volume, rather than needing an operator to notice and re-tune a window size./status matches at (device, sensor, day) granularity — a day counts as covered if it has at least one reading. Losing the tail of every day is invisible there. Only a row-level comparison against the second feed exposed it, which is the one thing retiring that feed would have made impossible. It was found with a week to spare.401ca75 fix(sync): bisect windows past the relay's result cap instead of truncating0d4270f fix(sync): scope WP6_SYNC_END to full syncs only4408949 docs(blue): refresh yookr-vs-datalake state — relay fixed, gap re-scopedWith one automated source per twin, a whole layer of the platform existed only to support switching between sources that no longer exist. None of it was broken — it was vestigial, the kind of code that misleads the next reader into thinking a feature is still there. A follow-up sweep removed it: 255 fewer lines of production code (126 added / 381 removed) with no loss of function.
a8474ba refactor: collapse single-source toggle machinery + fix dupe-countdd9cd73 refactor(db): collapse daily_coverage into the cagg; rename categorical to sourceLess visible, but it keeps the twins fast, the links honest, and the codebase maintainable across three twins.
templates.py became a proper package, and heavy JS/CSS assets are now real files served at static URLs instead of being inlined into every page — smaller responses, browser-cacheable.treatments.py module.