mirror of
https://github.com/NickoScope/nickol-knx-mcp.git
synced 2026-09-29 19:31:12 +02:00
Three false-alarm downgrades derived from validating on a larger multi-vendor villa, each turning a false positive into an INFO note without hiding any real defect: - intent: divider/separator names (punctuation-only or a marker wrapped in it, e.g. "-----addition------", "---") classify as `scratch`, so a missing DPT on them is INFO, not a red error. - analyze: typed GAs wired into Zennio "[LF] ... Data Entry" type-agnostic logic-function containers surface as INFO `dpt_on_logic_object` instead of a false `dpt_mismatch_co` warning. - analyze: all-groups broadcast/central-macro commands surface as INFO `central_macro_no_status` instead of `missing_status_address`, since a fan-out broadcast has no single state to read back. Adds a regression block covering all three; real DPT mismatches and real missing-status gaps still warn. Unreleased; no version bump. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
87 lines
3.6 KiB
Python
87 lines
3.6 KiB
Python
"""Classify a group address by INTENT: functional / reserve / logic / scratch.
|
|
|
|
Real ETS projects are full of intentional NON-functional group addresses:
|
|
* **reserve** — spare placeholders ("Резерв", "Reserve", "Spare") left for
|
|
future use, often with no DPT on purpose;
|
|
* **logic** — internal/virtual signals used only inside the bus program
|
|
(intermediate logic results, summed signals, time markers, request triggers)
|
|
that have no physical state to read back;
|
|
* **scratch** — test/leftover GAs ("Новый групповой адрес", a bare "1").
|
|
|
|
Treating these like functional device GAs is what makes the tool "cry wolf" on a
|
|
real project: a spare with no DPT becomes a 🔴 error, a logic signal becomes a
|
|
"missing status" 🟡 warning. This classifier lets the checks reclassify that
|
|
noise (downgrade / skip) instead of drowning the real findings.
|
|
|
|
Name-based and deliberately conservative: when unsure we return ``functional`` so
|
|
a real problem is never hidden. Multilingual (RU / EN / DE).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
INTENT_FUNCTIONAL = "functional"
|
|
INTENT_RESERVE = "reserve"
|
|
INTENT_LOGIC = "logic"
|
|
INTENT_SCRATCH = "scratch"
|
|
|
|
# Spare / reserved placeholders. "резерв" lower-cases both "Резерв" and "РЕЗЕРВ".
|
|
_RESERVE_TOKENS = (
|
|
"резерв", "reserve", "reserved", "spare", "запас", "не использ", "unused",
|
|
"не задейств",
|
|
)
|
|
|
|
# Internal logic / virtual signals — no physical status by design.
|
|
_LOGIC_TOKENS = (
|
|
"промежуточн", "суммарн", "логик", "logic", "метка времени", "выход ф-ци",
|
|
"ф-ция управлен", "ф-ции управлен", "ф-ция упр", "дубль", "вспомогат",
|
|
"intermediate", "internal signal", "virtual", "запрос", "request",
|
|
"годовой период", "сигнал включения", "сигнал отключения",
|
|
)
|
|
|
|
# Known scratch / default ETS names (exact, lower-cased).
|
|
_SCRATCH_EXACT = (
|
|
"новый групповой адрес", "new group address", "new group object",
|
|
"neue gruppenadresse",
|
|
)
|
|
|
|
# Commissioning divider / marker words that appear wrapped in punctuation
|
|
# ("-----addition------", "--- разделитель ---"). Checked after stripping the
|
|
# surrounding separators.
|
|
_SCRATCH_CORE = ("addition", "разделитель", "separator", "divider")
|
|
|
|
# Characters used to draw separators / dividers in GA names.
|
|
_SEP_CHARS = "-–—_=+*.•·~<>| \t"
|
|
|
|
|
|
def classify_intent(name: str) -> str:
|
|
"""Return one of functional / reserve / logic / scratch for a GA name."""
|
|
raw = (name or "").strip()
|
|
if not raw:
|
|
# Empty name is a separate, real problem (``empty_name``); leave it
|
|
# functional so that check still fires.
|
|
return INTENT_FUNCTIONAL
|
|
|
|
low = raw.lower()
|
|
|
|
if low in _SCRATCH_EXACT:
|
|
return INTENT_SCRATCH
|
|
# Bare numeric placeholders like "1", "5", "12" — classic leftovers.
|
|
if re.fullmatch(r"\d{1,3}", raw):
|
|
return INTENT_SCRATCH
|
|
# Divider / separator placeholders: a name that is only punctuation ("---",
|
|
# "=====") or a marker word wrapped in it ("-----addition------"). These are
|
|
# commissioning scratch, not functional GAs, so a missing DPT on them is
|
|
# intentional rather than a 🔴 error.
|
|
core = raw.strip(_SEP_CHARS)
|
|
if not core or core.lower() in _SCRATCH_CORE:
|
|
return INTENT_SCRATCH
|
|
|
|
if any(t in low for t in _RESERVE_TOKENS):
|
|
return INTENT_RESERVE
|
|
if any(t in low for t in _LOGIC_TOKENS):
|
|
return INTENT_LOGIC
|
|
|
|
return INTENT_FUNCTIONAL
|