mirror of
https://github.com/NickoScope/nickol-knx-mcp.git
synced 2026-09-29 19:31:12 +02:00
fix: council-review noise cuts — scenes/central-macros exempt, language-matched repairs
External expert review (real run on the demo house) flagged: scenes (18.001) wrongly marked missing_status + an unsafe DPT-1.011 status repair for them; 'All blinds down' warned while 'All lights off' was correctly INFO; and a Russian '(статус)' suffix synthesised on an English project. Fixes: scene_no_status INFO + no synth status for scenes; broadened central-macro detection (generic 'all blinds/shutters/...'); language-matched repair suffixes. Demo missing_status warnings ~7 -> 1 genuine. tests/test_council_fixes.py; full suite green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
1e924ede6f
commit
c53f388dcb
@@ -171,6 +171,9 @@ def _is_status_ga(ga: GARecord) -> bool:
|
||||
# rather than a defect — surfaced as INFO, not a 🟡 warning.
|
||||
_CENTRAL_MACRO_TOKENS = (
|
||||
"общее", "групповое", "все ", "всё", "central", "all groups", "all lights",
|
||||
# generic group/broadcast commands (multi-word so they don't false-match "wall")
|
||||
"all blinds", "all shutters", "all covers", "all windows", "all sockets",
|
||||
"all off", "all on", "master off", "всех ",
|
||||
)
|
||||
|
||||
|
||||
@@ -179,6 +182,12 @@ def _is_central_macro(name: str) -> bool:
|
||||
return any(t in low for t in _CENTRAL_MACRO_TOKENS)
|
||||
|
||||
|
||||
def _is_scene(ga: GARecord) -> bool:
|
||||
"""Scene-control / scene-number GAs (17.x / 18.x) have no single real state to
|
||||
read back — a missing status is expected, not a defect."""
|
||||
return ga.dpt_main in (17, 18) or ga.category == "scene"
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Sub-DPT sanity (A1): a name implies a specific DPT sub-type. Flag when the
|
||||
# main matches but the sub is wrong (9.001 temp vs 9.004 lux), or — for strong
|
||||
@@ -257,7 +266,15 @@ def detect_missing_status(project: LoadedProject) -> list[dict[str, Any]]:
|
||||
if self_reporting(ga, project):
|
||||
n_self_report += 1
|
||||
continue
|
||||
if _is_central_macro(ga.name):
|
||||
if _is_scene(ga):
|
||||
findings.append(_finding(
|
||||
SEVERITY_INFO, "scene_no_status", addr,
|
||||
f"Scene control '{ga.name}' (DPT {ga.dpt or '?'}) has no status GA — "
|
||||
"expected: a scene recalls/stores a preset, it has no single state to "
|
||||
"read back. No status is applicable.",
|
||||
name=ga.name, dpt=ga.dpt, category=ga.category,
|
||||
))
|
||||
elif _is_central_macro(ga.name):
|
||||
findings.append(_finding(
|
||||
SEVERITY_INFO, "central_macro_no_status", addr,
|
||||
f"Central/group macro '{ga.name}' has no status GA — expected "
|
||||
|
||||
@@ -22,6 +22,16 @@ _STATUS_DPT = {1: "1.011", 3: "5.001", 5: "5.001", 9: "9.001",
|
||||
13: "13.013", 14: "14.056", 20: "20.102"}
|
||||
|
||||
|
||||
def _has_cyrillic(s: str) -> bool:
|
||||
return any("Ѐ" <= c <= "ӿ" for c in (s or ""))
|
||||
|
||||
|
||||
def _suffix(name: str, ru: str, en: str) -> str:
|
||||
"""Match the suffix language to the GA name so an English project doesn't get a
|
||||
Russian '(статус)' and vice-versa (council feedback: mixed-language names)."""
|
||||
return ru if _has_cyrillic(name) else en
|
||||
|
||||
|
||||
def _infer_dpt(ga: Any) -> str:
|
||||
"""Best-effort DPT for a GA that has none, from its name/category/kind."""
|
||||
exp = _expected_subdpt(ga.name)
|
||||
@@ -88,7 +98,8 @@ def suggest_repairs(project: LoadedProject) -> dict[str, Any]:
|
||||
new = _next_free(used, ga.main if ga.main is not None else 1)
|
||||
proposals.append({
|
||||
"code": code, "action": "add_ga", "address": new, "for": addr,
|
||||
"name": f"{ga.name} - Значение яркости", "dpt": "5.001",
|
||||
"name": f"{ga.name}{_suffix(ga.name, ' - Значение яркости', ' - Brightness value')}",
|
||||
"dpt": "5.001",
|
||||
"rationale": "absolute-brightness GA so Home Assistant can set a level",
|
||||
})
|
||||
|
||||
@@ -103,7 +114,7 @@ def suggest_repairs(project: LoadedProject) -> dict[str, Any]:
|
||||
new = _next_free(used, ga.main if ga.main is not None else 1, prefer_middle=4)
|
||||
proposals.append({
|
||||
"code": "missing_status", "action": "add_ga", "address": new, "for": addr,
|
||||
"name": f"{ga.name} (статус)", "dpt": sdpt,
|
||||
"name": f"{ga.name}{_suffix(ga.name, ' (статус)', ' (status)')}", "dpt": sdpt,
|
||||
"rationale": "status/feedback GA so Home Assistant reads real state",
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user