mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-07 06:59:46 +00:00
docs: revise washer lifecycle specification
Issue: #164 User-Visible: no
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Issue #164 — активный цикл стиральной машины должен быть жёлтым
|
||||
|
||||
Статус: **первая редакция, S3-spec; на ревью не отправлено**
|
||||
Статус: **вторая редакция после ревью, S3-spec; повторно на ревью не отправлено**
|
||||
|
||||
Дата: 2026-08-16
|
||||
|
||||
@@ -63,10 +63,13 @@ paused, stopped или terminal, жёлтая подложка снимаетс
|
||||
|
||||
В задачу входят:
|
||||
|
||||
1. строгий generic resolver явной appliance lifecycle-сущности;
|
||||
2. её приоритет над auxiliary switches и над нейтральным `Power=on`;
|
||||
3. дополнение actual-work и terminal/idle словарей минимальными значениями,
|
||||
необходимыми для отчёта и симметричного завершения цикла;
|
||||
1. строгий generic resolver явной appliance lifecycle-сущности в уже
|
||||
распознанной составной топологии с выделенным Power;
|
||||
2. её приоритет над auxiliary switches и над нейтральным composite `Power=on`
|
||||
без изменения семантики lone relay;
|
||||
3. отдельное lifecycle-расширение actual-work словаря и дополнение общего
|
||||
terminal/idle словаря минимальными значениями, необходимыми для отчёта и
|
||||
симметричного завершения цикла;
|
||||
4. явная матрица совместной работы lifecycle и выделенного Power;
|
||||
5. одинаковый результат в full card, `houseplan-space-card`, desktop View,
|
||||
touch View и kiosk;
|
||||
@@ -103,18 +106,24 @@ paused, stopped или terminal, жёлтая подложка снимаетс
|
||||
|
||||
### 6.1. Порядок ролей
|
||||
|
||||
Действующий resolver сохраняет приоритеты whole-device доменов и semantic
|
||||
binary. Новая appliance lifecycle-роль вставляется **после** них и **до**
|
||||
switch fallback:
|
||||
Действующий resolver сохраняет приоритеты whole-device доменов, semantic
|
||||
binary и lone relay. Новая appliance lifecycle-роль включается только тогда,
|
||||
когда существующее топологическое правило уже распознало составной прибор с
|
||||
выделенным Power: среди entities больше одного uncategorised switch, а
|
||||
выбранный switch подтверждён `isDevicePowerSwitch()`. В этой ветке порядок
|
||||
такой:
|
||||
|
||||
1. canonical whole-device domain (`vacuum`, `climate`, `cover`, ...);
|
||||
2. semantic binary (`running`, `power`, presence/contact/alarm, ...);
|
||||
3. строгая appliance lifecycle-сущность;
|
||||
4. representative switch / выделенный Power;
|
||||
3. строгая appliance lifecycle-сущность + выделенный Power как gate;
|
||||
4. действующий composite Power fallback;
|
||||
5. passive fallback.
|
||||
|
||||
Так lifecycle не перехватывает устройство, для которого HA уже предоставляет
|
||||
более сильную стандартную state machine.
|
||||
более сильную стандартную state machine. Если топология не распознана как
|
||||
composite Power, существующий representative switch остаётся источником:
|
||||
одиночное реле `on` не теряет working из-за соседнего `Status=connected`,
|
||||
`Status=idle` или другого lifecycle-похожего sensor.
|
||||
|
||||
### 6.2. Что считается явной lifecycle-сущностью
|
||||
|
||||
@@ -136,9 +145,16 @@ Power не должно скрывать от resolver точный `Status` т
|
||||
- `run_state` / `running_state` / `machine_state`;
|
||||
- `job_state` / `operation_state` / `activity_state`.
|
||||
|
||||
Русская локализация отображаемого имени (`Статус`, `Состояние работы`) может
|
||||
поддерживаться тем же строгим словарём, но английский `translation_key`/
|
||||
`original_name`, когда он есть, остаётся предпочтительным доказательством.
|
||||
Русские отображаемые имена (`Статус`, `Состояние работы`) в первой итерации не
|
||||
являются доказательством lifecycle-роли. Локализованный `friendly_name` не
|
||||
мешает выбору, если canonical `translation_key`, `original_name`, registry name
|
||||
или object id уже доказывает роль, но сам по себе роль не создаёт.
|
||||
|
||||
Конечный сегмент `_status` матчится, поэтому до allowlist применяется явный
|
||||
connectivity stop-list. Кандидаты, чьи нормализованные metadata/object-id
|
||||
сегменты содержат `wifi`, `connection`, `signal` или `battery`, исключаются:
|
||||
`wifi_status`, `connection_status`, `signal_status` и `battery_status` не
|
||||
являются lifecycle прибора.
|
||||
|
||||
Общий токен `state` без уточнения, substring внутри произвольного слова и
|
||||
эвристика по имени всего устройства запрещены. `mode`, `program`, `stage`,
|
||||
@@ -157,46 +173,62 @@ Live state не меняет выбранную сущность: временн
|
||||
## 7. Контракт actual-work значений
|
||||
|
||||
Сравнение state и поддерживаемых action attributes остаётся case-insensitive и
|
||||
whitespace-insensitive. Существующий `WORKING_STATES` сохраняется и получает
|
||||
минимальный набор lifecycle-глаголов:
|
||||
whitespace-insensitive. В одном модуле хранятся два уровня working vocabulary:
|
||||
|
||||
1. базовый `WORKING_STATES` без изменений — для generic entity projection и
|
||||
действующих action attributes;
|
||||
2. `LIFECYCLE_WORKING_STATES` = базовый набор плюс минимальный набор
|
||||
lifecycle-глаголов, применяемый **только** к выбранной appliance
|
||||
lifecycle-роли:
|
||||
|
||||
- `start`, `started`, `run`, `active`, `in_progress`;
|
||||
- короткие формы физических этапов, уже представленных длительными формами:
|
||||
`wash`, `rinse`, `spin`, `dry`.
|
||||
|
||||
Существующий `IDLE_STATES` сохраняется и получает terminal-пары:
|
||||
Существующий общий `IDLE_STATES` сохраняется и получает terminal-пары:
|
||||
|
||||
- `stop`, `end`, `done`, `inactive`.
|
||||
|
||||
`paused` остаётся неработающим состоянием. `unknown`, `unavailable`, пустое и
|
||||
missing не становятся working. Новые значения применяются только к уже
|
||||
выбранной lifecycle-роли либо к действующим action attributes; они не дают
|
||||
права сканировать произвольные Mode/Program/Stage sensors.
|
||||
`paused` остаётся неработающим состоянием. Глобальное расширение idle-набора
|
||||
допустимо и применяется по действующим веткам, включая climate; глобальное
|
||||
расширение базового working-набора запрещено. `unknown`, `unavailable`, пустое
|
||||
и missing не становятся working.
|
||||
|
||||
Новые active-значения применяются только к уже выбранной lifecycle-роли и не
|
||||
расширяют классификацию generic sensors либо action attributes. Поэтому
|
||||
`dry`, `start` или `active` у устройства без lifecycle-роли остаются neutral.
|
||||
`wash`, `rinse`, `spin` и `dry` описывают допустимое **значение выбранной
|
||||
lifecycle-роли**; сущность Stage/Phase сама по себе по-прежнему не является
|
||||
authority и не сканируется.
|
||||
|
||||
Неоднозначные значения вроде `normal`, `auto`, `eco`, `mixed_wash`, чисел и
|
||||
температур остаются neutral.
|
||||
|
||||
## 8. Матрица lifecycle + Power
|
||||
|
||||
Для составного прибора выделенный Power остаётся availability/lifecycle gate,
|
||||
но не источником actual work:
|
||||
Новая матрица применяется только к топологии, которую существующее правило
|
||||
распознало как составной прибор с выделенным Power. В ней Power остаётся
|
||||
availability/lifecycle gate, но не источником actual work:
|
||||
|
||||
| Power | Явный lifecycle | Итог |
|
||||
|---|---|---|
|
||||
| `on` | active (`start`, `washing`, ...) | available + yellow working/running |
|
||||
| `on` | idle/terminal (`idle`, `stop`, `done`, ...) | available + neutral |
|
||||
| `on` | unknown/unavailable/missing | available + neutral |
|
||||
| `on` | lifecycle отсутствует | действующий composite Power: available + neutral |
|
||||
| `on` | lifecycle отсутствует | действующий composite Power fallback: available + neutral |
|
||||
| `off` | любое, включая stale active | действующий Power-off вид: unavailable/faded + neutral |
|
||||
| unavailable/missing | любое | unavailable/faded + neutral |
|
||||
| Power отсутствует | active lifecycle | available + yellow working/running |
|
||||
| Power отсутствует | idle lifecycle | available + neutral |
|
||||
| Power отсутствует | unavailable lifecycle | unavailable/faded + neutral |
|
||||
|
||||
Auxiliary switches не могут сделать прибор working и не перебивают выбранный
|
||||
lifecycle. Alarm critical sources сохраняют действующий высший визуальный
|
||||
приоритет над working.
|
||||
|
||||
За пределами распознанной composite Power топологии новая роль не меняет
|
||||
источник состояния. В частности, lone relay `on` остаётся working даже при
|
||||
соседнем lifecycle-похожем sensor с neutral/unknown значением; устройство без
|
||||
выделенного Power следует прежнему resolver и не получает новых
|
||||
lifecycle-specific active tokens по контракту #164.
|
||||
|
||||
## 9. Presentation и UX
|
||||
|
||||
- Результат использует существующий `status: working` и существующую жёлтую
|
||||
@@ -248,14 +280,18 @@ neutral результат, что desktop View. Новых жестов или
|
||||
|
||||
Обязательные инварианты:
|
||||
|
||||
1. один classifier владеет working/idle vocabulary;
|
||||
1. один модуль classifier владеет базовым working-набором, его scoped
|
||||
lifecycle-расширением и общим idle-набором; второй список в presentation
|
||||
запрещён;
|
||||
2. один role resolver используется full и static cards;
|
||||
3. выбор lifecycle не зависит от текущей доступности/значения и не скачет между
|
||||
peers на state update;
|
||||
4. анализ выполняется по уже построенному списку entities устройства, без
|
||||
полного сканирования `hass.states` для каждого marker;
|
||||
5. integration/model/device-name исключения запрещены;
|
||||
6. Power-off и alarm precedence не размножаются отдельными render-ветками.
|
||||
6. Power-off и alarm precedence не размножаются отдельными render-ветками;
|
||||
7. lifecycle override существует только внутри уже распознанной composite
|
||||
Power топологии и не меняет lone relay/обычный switch fallback.
|
||||
|
||||
## 13. Критерии приёмки
|
||||
|
||||
@@ -264,7 +300,9 @@ neutral результат, что desktop View. Новых жестов или
|
||||
switches) детерминированно выбирает lifecycle `Status` плюс Power gate, а не
|
||||
auxiliary switch; uncategorised Status выигрывает у diagnostic, config
|
||||
исключается, а registry order и локализованный friendly name не меняют
|
||||
результат при наличии canonical metadata.
|
||||
результат при наличии canonical metadata. Без canonical metadata русские
|
||||
`Статус`/`Состояние работы` роль не создают; `wifi_status`,
|
||||
`connection_status`, `signal_status` и `battery_status` всегда исключаются.
|
||||
- **AC2 (`unit`):** `Status=start` при `Power=on` даёт
|
||||
`{ availability: available, status: working, activity: running }`; те же
|
||||
assertions проходят для существующих длительных и новых коротких active
|
||||
@@ -272,21 +310,29 @@ neutral результат, что desktop View. Новых жестов или
|
||||
- **AC3 (`unit`):** при `Power=on` значения `idle`, `paused`, `stop`, `done`,
|
||||
`finished` дают available + neutral; `Power=off/unavailable` подавляет даже
|
||||
stale active lifecycle и сохраняет действующий faded neutral вид.
|
||||
- **AC4 (`unit`):** Power-on без явной lifecycle-сущности остаётся neutral;
|
||||
Mode/Program/Stage/positive remaining time и auxiliary switches сами по себе
|
||||
не создают working.
|
||||
- **AC5 (`unit`):** без Power активный lifecycle даёт working, idle — neutral,
|
||||
unavailable — unavailable; live availability не ретаргетит выбранную роль.
|
||||
- **AC6 (`unit`):** регрессии сохраняют действующий статус lone relay, climate
|
||||
actual action/fallback, passive media, vacuum cleaning/returning, cover
|
||||
transition, automation enabled, semantic binary, alarm priority и static
|
||||
display suppression.
|
||||
- **AC4 (`unit`):** в распознанной composite Power топологии `Power=on` без
|
||||
явной lifecycle-сущности остаётся neutral; Mode/Program/Stage/positive
|
||||
remaining time и auxiliary switches сами по себе не создают working.
|
||||
Generic sensor со state `dry`, `start` или `active` у устройства без
|
||||
выбранной lifecycle-роли также остаётся neutral.
|
||||
- **AC5 (`unit`):** при `Power=on` временно unavailable lifecycle остаётся
|
||||
выбранной ролью и даёт neutral, не ретаргетясь на peer. Топологии без
|
||||
выделенного composite Power следуют прежнему resolver: новые
|
||||
lifecycle-specific tokens их состояние не меняют.
|
||||
- **AC6 (`unit`):** lone relay `on` плюс lifecycle-похожий sensor со значением
|
||||
`connected`, `idle` или unavailable по-прежнему даёт working от relay;
|
||||
semantic binary остаётся выше lifecycle. Остальные регрессии сохраняют
|
||||
climate actual action/fallback, passive media, vacuum cleaning/returning,
|
||||
cover transition, automation enabled, alarm priority и static display
|
||||
suppression.
|
||||
- **AC7 (`unit` + `ревью кода`):** full card и `houseplan-space-card` получают
|
||||
один и тот же resolved presentation для active/idle washer snapshot; preview
|
||||
использует тот же reason без нового classifier.
|
||||
- **AC8 (`golden`):** synthetic composite washer в active snapshot визуально
|
||||
имеет существующую жёлтую подложку, а парный idle snapshot — нейтральный;
|
||||
эталон не содержит реальных пользовательских данных и проверяется по обычному
|
||||
- **AC8 (`golden`):** synthetic composite washer добавлен в
|
||||
`demo/fixtures/visual-matrix.mjs`; active snapshot визуально имеет
|
||||
существующую жёлтую подложку, а парный idle snapshot — нейтральный. Сцены
|
||||
перечислены в `GOLDEN_SCENARIOS`, `GOLDEN_MATRIX_VERSION` увеличен; эталон не
|
||||
содержит реальных пользовательских данных и проверяется по обычному
|
||||
baseline-review процессу.
|
||||
- **AC9 (`ревью кода`):** desktop View, touch View и kiosk не имеют отдельных
|
||||
state branches; поведение не зависит от pointer/viewport и не меняет editor
|
||||
@@ -308,15 +354,40 @@ neutral результат, что desktop View. Новых жестов или
|
||||
1. Добавить device fixture, повторяющий состав сущностей пользовательского
|
||||
отчёта, с canonical registry metadata и локализованными display names.
|
||||
2. Покрыть role priority и deterministic tie-break независимо от registry order.
|
||||
3. Таблично покрыть active/idle/unknown словари и Power-матрицу из раздела 8.
|
||||
4. Оставить негативную матрицу Mode/Program/Stage/time/aux switches.
|
||||
5. Расширить regression tests существующих device roles и presentation modes.
|
||||
3. Таблично покрыть базовый working-набор, scoped lifecycle-расширение, общий
|
||||
idle-набор и Power-матрицу из раздела 8.
|
||||
4. Оставить негативную матрицу Mode/Program/Stage/time/aux switches и добавить
|
||||
generic `dry`/`start`/`active` без lifecycle-роли.
|
||||
5. Добавить lone relay `on` с neutral lifecycle-похожим peer и connectivity
|
||||
`_status` candidates.
|
||||
6. Расширить regression tests существующих device roles и presentation modes.
|
||||
|
||||
### Mutation gate (#85)
|
||||
|
||||
До код-ревью исполнитель вручную вносит каждый мутант по одному и фиксирует,
|
||||
что указанный тест краснеет; после возврата корректного кода весь unit suite
|
||||
снова зелёный:
|
||||
|
||||
1. lifecycle-specific tokens добавлены в базовый working-набор вместо scoped
|
||||
набора → краснеет
|
||||
`keeps lifecycle-only active tokens neutral outside lifecycle role` (AC4);
|
||||
2. снят Power-off gate для stale `Status=start` → краснеет
|
||||
`suppresses stale active lifecycle when composite Power is off` (AC3);
|
||||
3. lifecycle-роль поднята выше semantic binary → краснеет
|
||||
`keeps semantic binary ahead of appliance lifecycle` (AC6);
|
||||
4. unavailable lifecycle ретаргетится на соседнюю сущность → краснеет
|
||||
`keeps lifecycle role identity stable while live state is unavailable`
|
||||
(AC5);
|
||||
5. в `device-presentation`/space-card добавлен второй vocabulary/classifier →
|
||||
краснеет
|
||||
`shares one lifecycle presentation between full and space cards` (AC7).
|
||||
|
||||
### Golden
|
||||
|
||||
Targeted synthetic scenario показывает рядом active и idle варианты одного
|
||||
составного прибора либо делает два детерминированных capture. Меняется только
|
||||
уже существующая working-подложка; новые visual tokens не принимаются.
|
||||
В `demo/fixtures/visual-matrix.mjs` добавляются synthetic active и idle варианты
|
||||
одного составного прибора. Оба capture регистрируются в `GOLDEN_SCENARIOS`, а
|
||||
`GOLDEN_MATRIX_VERSION` увеличивается. Меняется только уже существующая
|
||||
working-подложка; новые visual tokens не принимаются.
|
||||
|
||||
### Цикл реализации и pre-beta
|
||||
|
||||
@@ -339,19 +410,26 @@ registry/state snapshots. Новых service calls, permissions, URLs и HTML н
|
||||
|
||||
## 16. Риски
|
||||
|
||||
1. **Ложный yellow от режима/программы.** Закрывается строгим role allowlist и
|
||||
1. **Ложный yellow от lifecycle-глагола у generic sensor.** Базовый working
|
||||
vocabulary не расширяется, lifecycle tokens scoped выбранной ролью; AC4 и
|
||||
первый мутант защищают границу.
|
||||
2. **Ложный yellow от режима/программы.** Закрывается строгим role allowlist и
|
||||
AC4.
|
||||
2. **Stale stage после завершения.** Stage не является authority в #164;
|
||||
3. **Stale stage после завершения.** Stage не является authority в #164;
|
||||
terminal Status и Power-off закрывают цикл.
|
||||
3. **Ложный neutral из-за слишком узкого metadata matcher.** Fixture покрывает
|
||||
object id, translation key, original name и локализованный display name.
|
||||
4. **Power-off проигрывает stale Status=start.** Явная матрица и AC3 задают
|
||||
4. **Ложный neutral из-за слишком узкого metadata matcher.** Fixture покрывает
|
||||
object id, translation key и original name. Русское отображаемое имя без
|
||||
canonical metadata намеренно не поддерживается первой итерацией.
|
||||
5. **Connectivity status ошибочно принят за lifecycle.** Stop-list и AC1
|
||||
исключают wifi/connection/signal/battery candidates.
|
||||
6. **Power-off проигрывает stale Status=start.** Явная матрица и AC3 задают
|
||||
жёсткий gate.
|
||||
5. **Регрессия soundbar/composite switches.** Существующий Power-on neutral
|
||||
сохраняется и защищён AC4/AC6.
|
||||
6. **Retarget на state update.** Выбор роли опирается на metadata, не live
|
||||
7. **Регрессия lone relay или soundbar/composite switches.** Lifecycle override
|
||||
ограничен распознанной composite Power топологией; AC4/AC6 защищают обе
|
||||
стороны границы.
|
||||
8. **Retarget на state update.** Выбор роли опирается на metadata, не live
|
||||
значение; AC5 защищает identity.
|
||||
7. **Расхождение full/static card.** Запрещён второй classifier, AC7 проверяет
|
||||
9. **Расхождение full/static card.** Запрещён второй classifier, AC7 проверяет
|
||||
один resolved presentation.
|
||||
|
||||
## 17. Release-артефакты
|
||||
@@ -392,5 +470,6 @@ registry/state snapshots. Новых service calls, permissions, URLs и HTML н
|
||||
не поводом ослаблять matcher без доказательства.
|
||||
4. Lifecycle и Power могут быть представлены двумя resolved samples либо одним
|
||||
составным result; наблюдаемая матрица раздела 8 обязательна, форма — нет.
|
||||
5. Golden может расширить существующую visual-matrix fixture либо добавить
|
||||
узкую новую; реальные screenshot/user data в репозиторий не попадают.
|
||||
5. Golden расширяет существующую `demo/fixtures/visual-matrix.mjs`, регистрирует
|
||||
обе сцены в `GOLDEN_SCENARIOS` и увеличивает `GOLDEN_MATRIX_VERSION`; реальные
|
||||
screenshot/user data в репозиторий не попадают.
|
||||
|
||||
Reference in New Issue
Block a user