# Copyright 2026 Kemal Yaylali
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The dashboard: six tabs in the header, one viewport, no scrolling.
Responsive contract, in both axes. The no-scroll lock needs a floor on height
as well as width: at 1024x600 the forecast chart collapsed to 1 px, which is
worse than scrolling, so below 700 px tall the page scrolls and the charts keep
a readable minimum. Between 700 and 920 px tall the fixed furniture (stat
cards, outlook, diagnostics, the conditions column) compacts by media query so
the height goes to the chart rather than the chrome. Below 1024 px wide the
tab row becomes a native select, because six labels do not fit a phone header
and a clipped tab strip is worse than a dropdown.
Themes. The markup is dark-first Tailwind utilities, so rather than adding a
dark: variant to several hundred class attributes, light is an overlay that
remaps the slate scale and the accent hues under [data-theme="light"]. The
dark path is untouched: nothing is re-specified unless the attribute is set.
Charts cannot follow CSS, so they read the same tokens from the computed style
and are re-rendered on a change.
Layout contract. The page is a fixed three-row grid pinned to the
viewport height: header, tab bar, then a content region that takes the
remaining space and never overflows the fold. Each tab lays its panels
out on an internal grid sized in fractions of that region, so nothing
depends on content height. Where a panel genuinely holds more than fits
(the daily records table, the methods prose) that individual panel
scrolls internally while the page frame stays put. Below 1024 px the
constraint is released, because pinning five panels into a phone
viewport produces unreadable eight-pixel type, and a phone user expects
to scroll anyway.
Visual language carries over unchanged from the previous station page:
slate-950 ground, glass panels, Jakarta for prose and JetBrains Mono for
anything numeric. The one new structural device is the tab bar, and it
earns its place. Five distinct questions (what is it doing, what will it
do, what did it do, is the model any good, how does it work) were
previously one long scroll where the important things sat below the
fold.
The signature element is the estimator internals panel on the Live tab.
Most weather dashboards show numbers. This one shows the state estimator
working: self-heating coefficient, Kalman innovation, novelty distance
and drift pressure, all ticking at 2 Hz. It is the part of the system
that is normally invisible, and watching a filter converge is the most
honest possible demonstration that there is real machinery underneath.
"""
DASHBOARD_HTML = r"""
Ashvale Station
Ashvale Station
- · 0 d logged · k=-
health--:--:--
Seven day outlook
climatology plus decaying anomaly, not a synoptic forecast
skill above zero means it beats persistence · p/c/l is the ensemble weight
No matured forecasts yet. Rows appear as each horizon reaches its validity time: 15 minutes first, 24 hours tomorrow.
Temperature
self-heating
coefficient k--
cpu offset--
Enter a trusted thermometer reading in °C. One good reading is enough.
Humidity
element bias
offset--
psychrometric--
Enter a trusted hygrometer reading in %. The psychrometric term is derived, not fitted.
Estimator
novelty d²--
drift pressure--
Storage and weights
Station log
Kalman bank
NIS near 1 means honestly tuned
Compensators
Learner bank
18 independent RLS heads
Feature attribution
largest |θ| after standardisation, so these are comparable
Detectors
Climatology
Precipitation
Reliability
realised coverage against the 90% nominal
Surroundings
a change here marks a discontinuity and queues a retrain
--
where it lives
Indoors the building governs temperature and humidity, not the sky. Pressure passes through walls, which is why precipitation runs on tendency.
enclosure
Closing a door changes how strongly the sensor couples to outside. The heads carry about 55 hours of memory, so tell them rather than waiting two days.
Heated or cooled to a setpoint
A thermostat makes the room a closed loop: it returns to the setpoint instead of drifting. Persistence is the wrong baseline for that, so this adds a fourth ensemble member and lets the Hedge weights decide if it earns its place. Humidity follows at constant vapour pressure, which is why a heated house is dry.
Display
theme
Stored in this browser, not on the station: a theme belongs to the screen you read on. Auto tracks the system setting live.
8x8 display
frame rate--
24 costs about 11% of one core. 16 is still fluid and a third cheaper. Below 12 the crossfades judder.
Site and model
altitude feeds the sea-level reduction on every row
Psychrometric humidity correction
Moves RH from the element's temperature onto air temperature. Off by default: measured against a reference this board read 75.4% where the truth was 50.4%, so it reads high and this would push it higher.