Velký úklid Sheets vrstvy — 28 listů, 6 commitů, jeden den
V pondělí 5. května se ranní rutina vůbec nespustila. Místo toho, abych jen spravil ten konkrétní bug, jsem otevřel celou Google Sheets vrstvu a podíval se, co se v ní vlastně skladuje. Zjištění bylo nepříjemné: polovina listů byla dead code — buď je nikdo neplnil, nebo data lítala někam jinam, než kam měla.
Den skončil 8 commity, Apps Script v18 → v21, opravenými dashboard grafy a 3 listy smazanými ze Sheets. Tady je co všechno se odhalilo a opravilo.
1. Ranní rutina: sleep_window blokoval všechno
Začalo to jednoduchou stížností: "ranní rutina dnes vůbec nezačala".
Budík v 3:12, motion sensor v ložnici v 3:16:23, ale ranní brain skript
jen zalogoval waiting_2nd_motion a tiše se zasekl.
Root cause: proměnná sh_sleep_window_until_ts byla nastavena
na 2026-05-05T06:37:51Z (= 08:37 lokálního času) — 8 hodin
od stisku sleep tlačítka pozdě v noci. Ranní open-space router viděl
spim=no, sleep_window=yes a všechno blokoval reason=
sleep_window_guard. Nikdy se to nesmazalo, když se uživatel
probudil — nikdo neměl odpovědnost ten flag vyčistit.
Druhý problém: stejné světla se v noci sama rozsvěcela i přes
aktivní sleep_window. Vyšetření: sh_light_router_v1 kontroloval
jen sh_spim === 'yes', NE sh_sleep_window_until_ts.
Open_space_router měl tu defense už od 3.5., ale light_router ne.
Když sh_spim flickeroval na no (kvůli
time_sync_guard reset), evening light request prošel a Sektorka1 se
rozsvítila ve 22:36.
Fix:
sh_wake_confirm_v1— když multi-zone wake confirm potvrdí probuzení (sh_jsem_vstal=strong_yes), centrálně vyčistit window TS. Single source of truth, ne per-router.sh_light_router_v1v1.15 — parita s open_space router. Block SLEEP_BLOCKED_REQUESTS i když sleep_window aktivní + strong_yes override.
2. Score producer existoval, ale nikdo ho nečetl
Včera jsem dodal sh_morning_score_v1 — adaptivní engine co
každou minutu vyhodnocuje 10 senzorů a vypočítá morning score 0-100 +
state machine (night_sleep, pre_wakeup, wakeup_candidate, morning_confirmed,
…). Cron 1×/min běžel, vars se aktualizovaly, ale… nikdo z toho
nečetl. Brain rozhodoval podle vlastního 180s motion debounce a score
ignoroval.
To je proč ranní rutina nezačala. Score by řekl morning_confirmed
ve 03:18 (po wake_confirm strong_yes), ale brain nedostal žádný signál
pro re-trigger. Single bedroom motion → wait_2nd_motion → 180s window →
žádný 2nd motion v ložnici (uživatel šel do koupelny) → debounce nikdy
nevypršel.
Fix:
sh_morning_brain_v1— přidánscoreBypassparalelní k existujícímuwakeBypass. Pokudsh_morning_state ∈ {morning_confirmed, wakeup_candidate}a heartbeat čerstvý (<5 min), bypass debounce.sh_morning_score_v1v1.4 — alarm-aware (probeHomey.alarms.getAlarms(), ±30 min window, +3 score), state transitions logged do EventLogu, dual-log do nového sheetu Morning State Timeline (16 sloupců).
3. Sensor registry — adaptabilita pro budoucí čidla
Druhý nedostatek: senzory v score skriptu byly hardcoded names. Nový sensor v místnosti = edit kódu. PROMPT pro adaptivní rutinu žádal registry pattern.
Implementováno:
- Nová proměnná
sh_wakeup_sensor_registry— JSON s 10 senzory, každý má metadata: device, capability, room, type, morning_weight, night_weight, use_for_wakeup, reliability, rule, is_legacy, note. - Populator skript
_create_morning_sensor_registry_vars_v1.js— idempotentní create+update. - Score iteruje registry, non-legacy entries (
is_legacy: false) automaticky přispívají +morning_weight pokud sensor recently active <300s.
Workflow pro nový sensor: edit registru → přidat entry s is_legacy:
false, weight, use_for_wakeup: 'yes' → re-run populator.
Score automaticky používá od příštího cron ticku. Žádný edit kódu.
4. Dashboard fetchy → 404 (silent)
Audit dashboardů ukázal že 4 endpointy v Apps Script vůbec neexistují, ale dashboard je volá:
?mode=lux_history → fall-through na default filter
?mode=battery_history → totéž
?mode=morning_timeline → totéž
?mode=diagnose → jen v tooltip (ne fetch)
Apps Script nevrátil 404 — vrátil {rows:[]} (defaultní filter
response). Dashboard checkoval data.points nebo data.events
(které neexistovaly) a zobrazil "Bez dat (spusť context_full_v1)" hlášku.
Vypadalo to jako broken producer, ale ve skutečnosti broken endpoint.
Fix Apps Script v20: 3 nové endpointy:
mode=lux_history&hours=24— Lux Monitor sheet →{points: [{ts, lux_j, lux_k}]}mode=battery_history&days=30— Battery History →{points: [{ts, device, battery}]}mode=morning_timeline&days=3— MERGE Morning State Timeline + Rani rutina + TTS Monitor (filtered hour 03-11 Prague) →{events: [...]}sortováno DESC, cap 500.
5. Listy bez producera
Audit Sheets vrstvy: 28 listů celkem, ale realita:
- 12 aktivních — denní zápis, dashboard čte (EventLog, Zone Activity, TTS Monitor, FlowLog, EnergyLog, …)
- 4 periodické — depend na události (Daily Report, Gemini Briefing, EnergySummary, Ranni_Rutina_Budik)
- 6 dead-code routing — data jdou do EventLogu místo dedikovaného listu (System Health, Test Results, Audio Debug, Manual Actions, Logy, Gemini Diagnostics)
- 5 bez producera vůbec — 0 řádků, ale Apps Script má pro ně handlery (AI Tasks, ExceptionLog, Config, SanityLog, DataQualityReport)
- 1 čeká — Morning State Timeline (deploy v20)
Z 5 listů bez producera mají 2 diagnostickou hodnotu (ExceptionLog, SanityLog), 3 jsou zbytečné (AI Tasks, Config, DataQualityReport).
ExceptionLog: infrastruktura funguje, jen scripty neselhávají
ExceptionLog list měl 0 řádků, ale 24+ scriptů má logException()
v catch blocích. Test: POST přímo přes API → row se zapsala → infrastruktura
OK. Empty stav prostě reflektuje produkční stabilitu — scripty neselhávají
dost často aby naplnily list. Necháno bez změny, čekající na první real
exception.
SanityLog: patch validatoru pro per-item logging
SanityLog měl 0 řádků a 0 producerů. sh_state_validator_v1
už loguje aggregate validator_issues event do EventLogu, ale
per-item detail (drift, stuck, conflict) se nikam nepíše.
Patch v1.x: po existujícím EventLog summary log dual-loguje per-item rows
do SanityLog (warning_type, severity, zone, details, details_json). Bug E
ghost (priority_active=light bez request) bude zachycen při
příštím nečistém cycle.
3 listy smazány
Apps Script v21 odstraňuje:
- AI Tasks — workflow tracker, dashboard čte ale 0 rows nikdy.
- Config — centralized config nikdy nevyužitý.
- DataQualityReport — energy data quality denní snapshot, neutilizovaný.
Z Apps Script smazány: SHEETS entries, MAX_ROWS,
ensureAllSheets_ headerMap, headers constants, mode=tasks
+ mode=config endpointy, event_type='ai_task' POST handler.
Z Google Sheets manual delete přes pravý klik. Z 5 dashboardů
AI Tasks panel display:none + loadAITasks() no-op stub.
6. DeviceInventory: producer nikdy neposílal
DeviceInventory list měl být refreshován každých 6h cron flowem
SH – Energy – Device Inventory Cron 6h. Skript sh_device_
inventory_guard_v1 běžel, vars se aktualizovaly, ale list stál 5
dní.
Důvod: skript jen updatuje interní vars (sh_device_measurement_registry_json
atd.), ale nikdy POST do Apps Script. Apps Script má handler
pro mode=device_inventory POST od v18, ale žádný producer ho nevolal.
Fix v1.1: na konci skriptu přidán fire-and-forget POST registry → Apps
Script handleDeviceInventory_ transformuje na batch rows. Po
deployi list přijal 82 devices snapshot.
7. Dead-code routing — historický nález
Klíčový architektonický bug: logEvent() lib forsuje
sheet: 'EventLog' v payloadu. Apps Script routeEvent_
checkne p.sheet jako PRVNÍ — když je 'EventLog', routes tam a
returnuje. event_type routing pro System Health, Manual Actions,
Audio Debug, Test Results, Gemini Diagnostics se NIKDY nedosáhne.
Tj. tyhle 6 listů stagnuje 3+ týdny ne proto že producery nefungují, ale proto že data lítá do EventLogu místo. Data nejsou ztracena (jsou v EventLogu), jen v "neoptimálním" listu.
Pragmatické rozhodnutí: nechat jako historickou rezervu. Listy zachované, dashboard je nečte, smazané by byly jen kdyby dělaly nepořádek. Apps Script handlery zachované — pokud bych jednou chtěl fan-out (write to EventLog AND dedicated sheet), je to 3-řádkový patch per sheet.
Final stav
25 listů (z 28 v v20), 3 smazané, 6 commitů, deploy bez incidentu (jeden HomeyScript app crash + recovery — memory bloat ~95 MB / 125 limit, normální v rámci dnešní operational latitude).
Audit: všechny očekávané listy existují, žádný kolaterál. SanityLog + ExceptionLog ready. Morning State Timeline čeká na první state transition. Dashboard panely Lux + Battery + Morning Timeline začaly zobrazovat reálná data po deploy v21.
Lessons
1. Producer-only komponenta je červený alarm. Pokud
někdo vyrobí "X produkuje data, Y je zkonzumuje", a Y se nikdy nedoplní —
systém má dlouhodobou silent fail tendence. sh_morning_score_v1
běžel měsíc bez konzumenta a nikoho nezajímalo, dokud se ranní rutina
nezhroutila.
2. Default values v lib API jsou architektonický bug.
sheet: payload.sheet || 'EventLog' vypadalo jako sensible default.
Reálně to forsovalo všechny event_type events do jednoho listu a
mrtvé routes vznikly přes půl roku bez detekce.
3. Smaž jenom to, co máš jistotu že nikomu nechybí. Z 5 listů bez producera 2 mají diagnostic hodnotu (ExceptionLog, SanityLog). 3 jsou zbytečné. 6 dead-code listů má archivní hodnotu — data nejsou ztracena, jen v jiné lokaci. Smaz konzervativní, audit po deployi povinný.
Mimochodem, pondělní ranní rutina příští týden už proběhne s plnou pipeline: alarm-aware score → state transition log → scoreBypass v brain → registry auto-discovery + Bug E ghost detekce přes SanityLog. Pokud něco selže, EventLog + Morning State Timeline + ExceptionLog dají detailní timeline důvodů.