fix(handover): read domain names from GroupRanges, not mislabeled GARecord.main_name

Main/middle range names now resolve correctly (e.g. [1] Освещение 1 этаж, [5] Климат)
instead of a middle-group name leaking into the main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Nikolay Miroshnichenko
2026-07-01 14:54:44 +02:00
co-authored by Claude Opus 4.8
parent a621ced701
commit 2f2b8b1f91
2 changed files with 32 additions and 8 deletions
+6
View File
@@ -26,6 +26,12 @@ Zennio project (a 3646-GA multi-vendor villa, 5× larger, no ETS Functions).
`Все шторы - Стоп`) surface as INFO `central_macro_no_status` instead of a `Все шторы - Стоп`) surface as INFO `central_macro_no_status` instead of a
`missing_status_address` warning, since a fan-out broadcast has no single state to read back. `missing_status_address` warning, since a fan-out broadcast has no single state to read back.
### Fixed
- **Handover domain names** — the group-address map now reads main/middle range names straight
from the project's `GroupRanges` instead of `GARecord.main_name`, which the parser could
mislabel (a middle range's name leaking into the main). Domains now render correctly
(e.g. `[1] Освещение 1 этаж`, `[5] Климат`) instead of a middle-group name.
## [0.2.0] — 2026-06-30 ## [0.2.0] — 2026-06-30
**Colour/climate entity assembly and GA-intent noise reduction.** Two feature tracks **Colour/climate entity assembly and GA-intent noise reduction.** Two feature tracks
+26 -8
View File
@@ -25,23 +25,41 @@ from .intent import INTENT_FUNCTIONAL
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #
# Structure helpers # Structure helpers
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #
def _range_names(project: LoadedProject) -> tuple[dict[int, str], dict[tuple[int, int], str]]:
"""Authoritative main/middle names from the project's GroupRanges.
``GARecord.main_name`` from the parser can mislabel (it may carry a middle
range's name), so the domain map reads names straight from ``group_ranges``.
"""
main_names: dict[int, str] = {}
mid_names: dict[tuple[int, int], str] = {}
for mkey, mrange in (project.raw.get("group_ranges") or {}).items():
head = str(mkey).split("/")[0]
if not head.isdigit():
continue
mi = int(head)
main_names[mi] = mrange.get("name") or ""
for skey, srange in (mrange.get("group_ranges") or {}).items():
parts = str(skey).split("/")
if len(parts) >= 2 and parts[1].isdigit():
mid_names[(mi, int(parts[1]))] = srange.get("name") or ""
return main_names, mid_names
def _domain_map(project: LoadedProject) -> dict[int, dict[str, Any]]: def _domain_map(project: LoadedProject) -> dict[int, dict[str, Any]]:
"""main -> {name, count, middles: {middle -> {name, count}}}, sorted-ready.""" """main -> {name, count, middles: {middle -> {name, count}}}, sorted-ready."""
main_names, mid_names = _range_names(project)
mains: dict[int, dict[str, Any]] = {} mains: dict[int, dict[str, Any]] = {}
for ga in project.gas.values(): for ga in project.gas.values():
if ga.main is None: if ga.main is None:
continue continue
m = mains.setdefault(ga.main, {"name": ga.main_name or "", "count": 0, "middles": {}}) m = mains.setdefault(
ga.main, {"name": main_names.get(ga.main, ""), "count": 0, "middles": {}})
m["count"] += 1 m["count"] += 1
if not m["name"] and ga.main_name: midkey = ga.middle if ga.middle is not None else -1
m["name"] = ga.main_name
mid = m["middles"].setdefault( mid = m["middles"].setdefault(
ga.middle if ga.middle is not None else -1, midkey, {"name": mid_names.get((ga.main, midkey), ""), "count": 0})
{"name": ga.middle_name or "", "count": 0},
)
mid["count"] += 1 mid["count"] += 1
if not mid["name"] and ga.middle_name:
mid["name"] = ga.middle_name
return mains return mains