feat(P5): contextual domain classification — DPT is one signal, not the verdict

A 1-bit switch is domain-agnostic, yet DPT 1.001 defaulted to 'lighting', so an
"AC on/off" was classified lighting and tripped downstream checks. The domain is
now a combination: explicit name > strong domain-encoding DPT > main-group name;
a bare 1-bit GA with no signal is honestly 'unknown', never a guessed lighting.
An explicit name contradicting a STRONG DPT yields 'unknown' (a genuine conflict,
shown by explain_ga as contested), not a silent pick. Word-boundary matching so
"ac" no longer fires inside "terrace"; name terms broadened (EN+RU HVAC incl.
ac/a-c/air-conditioner, RGB/colour lighting).

check_policy taxonomy-outlier now flags only misplaced ACTUATORS (lighting/
shutter/hvac); sensors, scene links, thresholds and timeout params are the
cross-cutting GAs they are. HA generator still emits a switch for a now-HVAC
simple on/off, so no device is dropped.

Verified on 3 real projects: every AC on/off is HVAC; outlier noise fell demo
11->2, Minsk 31->6, Razdory 200->57. tests/test_domain_classifier.py (13/13 green).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Nikolay1
2026-07-15 10:44:04 +02:00
co-authored by Claude Opus 4.8
parent 1a60264c78
commit e9105d45aa
8 changed files with 277 additions and 67 deletions
+19
View File
@@ -20,6 +20,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
violation callers return a normal `{"error": ...}` rather than a traceback. Still strictly read-only.
Raised by external security review. `tests/test_safexml.py`.
### Changed
- **Contextual domain classification — a DPT alone no longer decides the domain** (`project.py`,
`explain.py`, `generate_ha.py`, `policy.py`). A 1-bit switch is domain-agnostic (a light just as
easily as a pump or an AC), yet the classifier defaulted DPT 1.001 → *lighting*, so "Living room AC
on/off" came out as lighting and then tripped downstream checks. The domain is now a **combination of
signals**: an explicit name domain wins; a strong, domain-encoding DPT (shutter 1.008, HVAC-mode
20.102, temperature 9.001, dimming 3.007) keeps its domain; for a domain-soft DPT (1.001/1.011/5.001)
the **main group name** disambiguates; with no signal at all a bare 1-bit GA is honestly **unknown**
rather than a guessed lighting. When an explicit name contradicts a *strong* DPT the result is
`unknown` (a genuine conflict, surfaced by `explain_ga` as `contested`) — not a silent pick. Matching
is word-boundary, not substring, so "ac" no longer fires inside "terrace". Name terms broadened
(English + Russian HVAC incl. `ac`/`a/c`/`air conditioner`, RGB/colour lighting). Verified on three
real projects: every "AC on/off" is now HVAC; policy taxonomy-outlier **noise fell sharply** (demo
11→2, one real project 31→6, another 200→57) because `check_policy` now flags only misplaced
**actuators** (lighting/shutter/HVAC) and treats sensors, scene links, thresholds and timeout
parameters as the cross-cutting GAs they are. `explain_ga` shows the winning signal and tier; the HA
generator still emits a switch for a now-HVAC simple on/off, so no device is dropped.
### Added
- **Explainable aggregate scores** (`advanced.py`, `handover.py`). Every headline percentage now ships