fix(P5): classifier dogfood round — range-map depth bug, fallback-DPT softness, RU terms

Building a deliberately messy synthetic house instantly exposed four real gaps:

- _build_range_name_map: a middle group 0 starts at the same raw address as its
  main, so the modulo heuristic let "Вкл/Выкл" clobber "Освещение" and the
  main-range rescue silently died. Depth in the range tree decides now.
- a category from a whole-main DPT fallback (e.g. 20.609 -> hvac) was treated as
  strong; only an exact (main,sub) table entry is strong now (is_exact_dpt).
- DPT 1.010 start/stop marked soft: ventilation timers use it, not just shutters.
- terms learned: Cyrillic а/с, сплит, вытяжк, яркост (RU only); weather/метео in
  sensor, and sensor now outranks hvac so weather-station temperature is a
  sensor, not an HVAC actuator.

Real-project noise after the round: Minsk 1 outlier/685 GAs (was 31 pre-P5),
demo 2/239, Razdory 46/3646. Regressions in test_domain_classifier. 13/13 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Nikolay1
2026-07-15 11:27:22 +02:00
co-authored by Claude Opus 4.8
parent e9105d45aa
commit 3c086fe228
5 changed files with 66 additions and 17 deletions
+18
View File
@@ -53,10 +53,28 @@ def main():
# ...but agreement keeps the domain
assert _classify_category("Living room dimmer", "", "", 3, 7, "lighting") == "lighting"
# --- Cyrillic air-con abbreviations + colloquial 'сплит' (dogfood find) ---
for variant in ("а/с кухня вкл/выкл", "А/С детская 1 on/off", "Сплит Kids 2 status"):
assert _domain_from_text(variant) == "hvac", variant
# plural 'spots' must match the lighting prefix
assert _domain_from_text("Terrace spots on/off") == "lighting"
# --- end to end through the model: an AC switch in the HVAC main is HVAC ---
p = _proj({"3/0/1": _ga("3/0/1", "Master bedroom AC on/off", 1, 1)})
assert p.gas["3/0/1"].category == "hvac"
# --- middle-0 range must not overwrite the MAIN range's name (dogfood find):
# a middle group 0 starts at the same raw address as its main, so the old
# modulo heuristic clobbered "Освещение" with "Вкл/Выкл" and the range
# rescue silently died. Depth decides now. ---
ranges = {"r1": {"name": "Освещение", "address_start": 2048, "group_ranges": {
"r10": {"name": "Вкл/Выкл", "address_start": 2048, "group_ranges": {}}}}}
p2 = _proj({"1/0/9": _ga("1/0/9", "Kanal 9", 1, 1)}, ranges)
assert p2.gas["1/0/9"].main_name == "Освещение", p2.gas["1/0/9"].main_name
assert p2.gas["1/0/9"].middle_name == "Вкл/Выкл", p2.gas["1/0/9"].middle_name
assert p2.gas["1/0/9"].category == "lighting", \
"main-range name must rescue a context-less switch in the lighting main"
print("test_domain_classifier: OK — DPT is one signal among name + main-group context; "
"AC/kondicioner is HVAC, bare switches are unknown, 5.001 disambiguates by name, "
"strong-DPT vs name is a conflict, and 'ac' never fires inside 'terrace'.")