mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-05 06:08:59 +00:00
docs: #42 spec revision 2 — mechanism + baseline stage, measured facts
User-Visible: no Issue: #42
This commit is contained in:
@@ -1,61 +1,180 @@
|
||||
# ТЗ #42 — Измеряемое инженерное качество backend
|
||||
|
||||
- Issue: https://github.com/Matysh/houseplan-card/issues/42
|
||||
- Приоритет: P2
|
||||
- Статус ТЗ: ready for implementation by incremental gates
|
||||
- Тип: infra/tests, без изменения успешных пользовательских сценариев
|
||||
- Приоритет: P2, tests/tech-debt; полный трек (backend class A + видимое
|
||||
поведение ошибок — решение аналитики 2026-08-15)
|
||||
- Ревизия: 2 (2026-08-30) — актуализация замерами исполнением; issue
|
||||
реализуется механизмом + ступенью baseline, пороги — последующие trivial
|
||||
- Тип: infra/tests + один видимый пользователю блок (тексты ошибок)
|
||||
|
||||
## Цель
|
||||
## Замеры ревизии (HEAD, песочница)
|
||||
|
||||
Зафиксировать честное покрытие, строгую типизацию и стабильный error contract
|
||||
backend integration без массового formatting rewrite.
|
||||
Pure-coverage 89.1% (2377/258); HA-модули (~1.5К stmts, включая
|
||||
import_export 1065) измеримы только в CI. Ruff E,F,B,I: 333 (E501 — 291,
|
||||
содержательных 42). Ни одного requirements/pyproject в репо; зависимости
|
||||
бэкенд-CI — строка в validate.yml + зеркало в mutation-gate.yml. WS-коды
|
||||
де-факто стабильны и фронт мапит по code (`backup.error.<code>`, 26 ключей
|
||||
en/ru); дыры: сырой английский message в fallback и regex-парсинг message у
|
||||
`invalid_passage_fields`/`invalid_partition_opening_jamb_margin`.
|
||||
|
||||
## Coverage
|
||||
## Сценарий
|
||||
|
||||
- Добавить pinned `pytest-cov`; CI запускает Python 3.13 HA harness и pure tests
|
||||
одним coverage combine workflow.
|
||||
- Baseline публикуется по каждому `custom_components/houseplan/*.py` с branch
|
||||
coverage. Первое включение не скрывает skipped HA tests.
|
||||
- Gate вводится ступенями: не ниже baseline → 90% → минимум 95% executable
|
||||
lines и согласованный branch threshold. Generated/frontend bundle исключён.
|
||||
- `coverage.xml` artifact и human summary; новые/изменённые строки требуют 100%
|
||||
либо documented pragma для unreachable defensive branch.
|
||||
Разработчик ломает покрытие или типы бэкенда — CI краснеет с конкретным
|
||||
модулем и числом. Пользователь получает ошибку от бэкенда — видит
|
||||
локализованный текст по стабильному коду; неизвестный код даёт общий
|
||||
локализованный fallback с кодом, а не сырую английскую фразу.
|
||||
|
||||
## Typing
|
||||
## Что человек увидит до и после
|
||||
|
||||
Pyright/mypy strict включается per-module allowlist:
|
||||
Почти ничего: единственная видимая часть — тексты редких ошибок. **До**:
|
||||
неизвестная ошибка = английская message; два кода парсятся regex'ом из
|
||||
английской строки. **После**: локализованный fallback с кодом; structured
|
||||
JSON-details. Всё остальное — инженерные гейты.
|
||||
|
||||
1. `validation.py`, `store.py`, auth/const;
|
||||
2. websocket request/result boundaries;
|
||||
3. repairs/diagnostics/system_health;
|
||||
4. trails/runtime.
|
||||
## Скоуп (одна ступень, пять блоков)
|
||||
|
||||
HA dynamic APIs изолируются typed Protocol/adapter, а не `Any` по всему модулю.
|
||||
Allowlist только уменьшается.
|
||||
### 1. Tooling-фундамент
|
||||
|
||||
## Lint/format
|
||||
`pyproject.toml` (первый в репо): метаданные не нужны — только конфиг
|
||||
инструментов. Зависимости бэкенд-теста выносятся в
|
||||
`requirements_test.txt` (pinned); validate.yml и зеркало в mutation-gate.yml
|
||||
ставят из него (одна точка правды вместо двух строк).
|
||||
|
||||
Ruff (или один выбранный tool) с narrow rule set: errors/imports/bugbear и
|
||||
format-check только для новых/затронутых Python файлов. Отдельный mechanical
|
||||
PR может нормализовать остальное; feature diff не содержит repo-wide rewrite.
|
||||
### 2. Lint (ruff, narrow)
|
||||
|
||||
## WebSocket error contract
|
||||
Конфиг в pyproject: `select = ["E", "F", "B", "I"]`,
|
||||
`ignore = ["E501"]` (291 длинная строка — НЕ переписываются: «без массового
|
||||
rewrite»), target py313. Чинятся 42 содержательных нарушения: I001/F401/
|
||||
F841/E731/B905 — механически; **B023 (×17, loop-var в замыкании) — каждый
|
||||
случай разбирается отдельно**: реальная гонка → фикс с юнитом, доказанная
|
||||
синхронность → `# noqa: B023` с причиной в комментарии. CI-джоба
|
||||
`ruff check custom_components/houseplan` в validate.yml (backend).
|
||||
|
||||
Все user-facing failures имеют stable code enum, safe developer message и
|
||||
optional structured details без персональных данных. Frontend mapping ru/en
|
||||
не сравнивает английские message strings. Unknown code получает общий fallback.
|
||||
### 3. Typing (mypy strict, растущий allowlist)
|
||||
|
||||
## Quality Scale docs
|
||||
`[tool.mypy]` per-module: strict для стартового списка достижимых
|
||||
pure-модулей — `const`, `projection`, `coordinate_canonicalization`,
|
||||
`frontend_asset_manifest`, `junction_limits`, `plans` (+ те из
|
||||
`validation`/`wall_segment_model`/`geometry_migration`, что пройдут без
|
||||
каскадного рефакторинга — финальный список фиксируется по факту зелени и
|
||||
называется в handoff). Список strict-модулей может только РАСТИ —
|
||||
контракт-тест сравнивает конфиг с committed-списком и падает при удалении.
|
||||
HA-boundary модули (websocket/http/store/repairs/…) — вне ступени (нужны
|
||||
stubs HA, CI-итерации) — следующая ступень, зафиксировано здесь.
|
||||
|
||||
Добавить troubleshooting, examples и проверить manifest/quality_scale claims.
|
||||
Нельзя отмечать rule выполненным только наличием файла — acceptance следует HA
|
||||
rule text и CI evidence.
|
||||
### 4. Coverage (механизм + baseline-гейт)
|
||||
|
||||
## Приёмка
|
||||
- validate.yml backend: pytest → `--cov=custom_components/houseplan
|
||||
--cov-branch --cov-report=xml --cov-report=term`; артефакт coverage.xml.
|
||||
- `scripts/backend-coverage-baseline.txt` — одно число (стартовое =
|
||||
фактический общий % CI-прогона pure+harness, снимается первым прогоном
|
||||
ветки); шаг CI сравнивает: ниже baseline − 0.1 п.п. → красный.
|
||||
- Защита от тихого скипа harness: шаг до pytest — `python -c "import
|
||||
homeassistant"` + после collect: количество собранных
|
||||
`tests_backend/test_ha_*` ≥ 50, иначе красный.
|
||||
- Пороги 90% → 95%: последующие trivial-issues, меняющие ОДНО число в
|
||||
baseline-файле (механизм этой ступени их уже enforce'ит). Приёмка issue
|
||||
«≥95%» достигается той лестницей; данная ступень сдаёт механизм +
|
||||
«не ниже baseline», и это отражено в квалификации quality_scale
|
||||
(test-coverage остаётся `todo` с прогресс-ссылкой).
|
||||
|
||||
- CI нельзя пройти с silently skipped HA harness;
|
||||
- coverage ≥95% executable lines после staged rollout;
|
||||
- strict module allowlist и lint gates зелёные;
|
||||
- WS tests проверяют code + frontend localization;
|
||||
- docs examples исполняемы/проверяемы;
|
||||
- изменения tooling не меняют stored data/runtime result.
|
||||
### 5. WS error contract + доки
|
||||
|
||||
- `const.py`: `ERROR_CODES` — frozenset всех стабильных кодов (собранных
|
||||
фактически по websocket_api + коды исключений). Контракт-тест (pure,
|
||||
скан исходника): каждый литерал `send_error(...)`-кода ∈ ERROR_CODES;
|
||||
каждый код имеет i18n-ключ `backup.error.<code>` в en (полнота словарей —
|
||||
существующий паритет-гейт).
|
||||
- Structured details: `invalid_passage_fields` и
|
||||
`invalid_partition_opening_jamb_margin` шлют message
|
||||
JSON-строкой (`{"space":…,"opening":…,"fields":…}`); фронт парсит
|
||||
JSON.parse с fallback на прежний regex (совместимость со старым бэкендом
|
||||
одной беты). Regex-ветка помечена deprecated-комментарием с датой
|
||||
удаления.
|
||||
- Fallback `_errText`: известный код → i18n; неизвестный → общий
|
||||
локализованный текст + код; сырой английский `e.message` в UI не
|
||||
показывается (уходит в console.warn для отладки).
|
||||
- Доки: USER-GUIDE.ru получает паритетный §Troubleshooting (перевод §22);
|
||||
quality_scale.yaml: `docs-troubleshooting` → done,
|
||||
`docs-examples` → done ТОЛЬКО если текст HA-правила фактически
|
||||
удовлетворён существующими YAML-примерами гайда (проверка по тексту
|
||||
правила; иначе остаётся todo с причиной); `strict-typing` — остаётся
|
||||
todo с прогрессом (ступень).
|
||||
|
||||
## Не-скоуп (следующая ступень, зафиксировано)
|
||||
|
||||
Strict typing HA-boundary модулей; пороги coverage 90/95; формат-проверка
|
||||
всего репо; массовая нормализация E501; перевод остальных секций гайдов.
|
||||
|
||||
## Контракт поведения
|
||||
|
||||
Tooling не меняет stored data и успешные пользовательские сценарии.
|
||||
Единственное видимое изменение — тексты ошибок (блок 5): коды и структура
|
||||
ответов бэкенда с существующими кодами НЕ меняются (message двух кодов
|
||||
меняет ФОРМАТ на JSON — фронт совместим в обе стороны одну бету).
|
||||
|
||||
## Критерии приёмки
|
||||
|
||||
- **AC1** (CI): джоба backend публикует coverage.xml + summary; подмена
|
||||
baseline на большее число → красный шаг (доказательство прогоном ветки).
|
||||
- **AC2** (CI): удаление homeassistant из шага установки или фильтр
|
||||
test_ha_* → красный ещё до pytest / на collect-пороге.
|
||||
- **AC3** (локально): `ruff check` чист на выбранном наборе; каждый
|
||||
`noqa: B023` несёт объяснение (контракт-тест: noqa без текста запрещён).
|
||||
- **AC4** (локально): mypy strict зелёный на стартовом allowlist;
|
||||
контракт-тест падает при СУЖЕНИИ списка.
|
||||
- **AC5** (юнит): ERROR_CODES ⊇ все коды send_error (скан исходника);
|
||||
каждый код имеет en-ключ `backup.error.<code>`.
|
||||
- **AC6** (юнит фронта): JSON-message двух кодов парсится в structured
|
||||
details; старый regex-формат по-прежнему принимается; неизвестный код →
|
||||
локализованный fallback, английский message не попадает в DOM.
|
||||
- **AC7**: полный гейт; pytest 240/0 pure; бюджет ≈ без изменений (фронт
|
||||
меняет только обработку ошибок).
|
||||
|
||||
## План автотестов
|
||||
|
||||
- Юниты фронта: AC6 (парсер details + fallback) — test/logic или
|
||||
error-текст тесты.
|
||||
- Pure-pytest: AC5-скан; существующие 240 не слабеют.
|
||||
- Контракт-тесты: AC3-noqa, AC4-allowlist (читают pyproject/исходники).
|
||||
- Мутанты: м1 — удалить код из ERROR_CODES → красный AC5;
|
||||
м2 — вернуть regex-first парсинг (сломать JSON-ветку) → красный AC6.
|
||||
- CI-доказательства AC1/AC2 — прогоном ветки, фиксируются в handoff.
|
||||
|
||||
## Риски
|
||||
|
||||
- B023-фиксы — единственные поведенческие: каждый со своим юнитом или
|
||||
обоснованным noqa.
|
||||
- Python 3.10 (песочница) vs 3.13 (CI): ruff/mypy конфиг target 3.13,
|
||||
локальная проверка на 3.10 — синтаксис кода уже совместим.
|
||||
- JSON-message: старый фронт с новым бэком увидит JSON-строку в сыром
|
||||
fallback → в пределах одной беты допустимо (пары версий фронт/бэк
|
||||
обновляются вместе HACS'ом); отмечено в ченджлоге.
|
||||
|
||||
## Откат
|
||||
|
||||
`git revert`: конфиги/гейты исчезают, коды ошибок не менялись, формат
|
||||
message двух кодов возвращается — фронт совместим (regex-ветка ещё жива).
|
||||
Потери данных нет.
|
||||
|
||||
**DoR-примечания:** миграция/compatibility — только формат message двух
|
||||
кодов (двусторонняя совместимость на бету); touch — не влияет;
|
||||
производительность — не влияет (test/CI-time).
|
||||
|
||||
## Release-артефакты
|
||||
|
||||
- CHANGELOG×2: user-visible коротко (локализованный fallback ошибок),
|
||||
остальное — инженерная запись.
|
||||
- docs/ARCHITECTURE.md: раздел «Backend quality gates» (coverage baseline,
|
||||
ruff, mypy allowlist, ERROR_CODES) со ссылками.
|
||||
- USER-GUIDE.ru §Troubleshooting.
|
||||
|
||||
## Принятые предположения
|
||||
|
||||
- Baseline-число снимается ПЕРВЫМ CI-прогоном ветки и коммитится в неё же
|
||||
до S7 (ревьюер видит фактическое значение).
|
||||
- `backup.error.<code>` — существующее пространство ключей для всех
|
||||
WS-ошибок (не только бэкапов) — так уже используется фронтом; переименование
|
||||
пространства — вне скоупа.
|
||||
- Формат JSON-details фиксируется этим ТЗ как контракт двух кодов; общий
|
||||
механизм details для ВСЕХ кодов — следующая ступень при необходимости.
|
||||
|
||||
Reference in New Issue
Block a user