feat(P7b): explainable aggregate scores — expose denominator + formula

Matter readiness, the completeness grade and command/status coverage each now
carry a `math` block: numerator, denominator, the exact formula that reproduces
the percentage, and (for Matter) the functions excluded from the denominator
because their category has no Matter cluster — previously a silent skip and the
biggest "why is this number what it is?" gap. Completeness also states its band
thresholds. Report-only, additive; scores unchanged, only now auditable.
Raised by external review. tests/test_explainable_aggregates.py (12/12 green).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Nikolay1
2026-07-15 10:15:40 +02:00
co-authored by Claude Opus 4.8
parent f68c85bb48
commit 1a60264c78
4 changed files with 138 additions and 5 deletions
+44 -3
View File
@@ -26,6 +26,30 @@ def _functional_commands(project: LoadedProject) -> list:
and g.dpt_main is not None]
def _ratio_explain(numerator: int, denominator: int, unit: str,
excluded: dict[str, Any] | None = None,
bands: dict[str, str] | None = None) -> dict[str, Any]:
"""A percentage a reader can audit: the numbers behind it, the exact formula,
what was left OUT of the denominator, and (optionally) the grade bands.
Every aggregate score this tool reports carries one of these so the percentage
is never a bare number — a reviewer can see the denominator and reproduce it.
"""
pct = round(100 * numerator / denominator) if denominator else 0
out: dict[str, Any] = {
"pct": pct,
"numerator": numerator,
"denominator": denominator,
"formula": f"{numerator} / {denominator} {unit} = {pct}%"
if denominator else f"0 {unit} — ratio undefined (denominator 0)",
}
if excluded:
out["excluded_from_denominator"] = excluded
if bands:
out["bands"] = bands
return out
def _status_gas(project: LoadedProject) -> list:
from .analyze import _is_status_ga
return [g for g in project.gas.values() if _is_status_ga(g)]
@@ -48,9 +72,11 @@ def matter_readiness(project: LoadedProject) -> dict[str, Any]:
"""Which controllable functions round-trip to a Matter cluster, and what's missing."""
stats = _status_gas(project)
ready, not_ready = [], []
no_cluster: Counter = Counter()
for ga in _functional_commands(project):
cluster = _MATTER.get(ga.category)
if cluster is None:
no_cluster[ga.category or "unknown"] += 1 # excluded from the ratio — count it
continue
has_status = find_status(ga, [s for s in stats if s.main == ga.main]) is not None \
or find_status(ga, stats) is not None
@@ -58,14 +84,25 @@ def matter_readiness(project: LoadedProject) -> dict[str, Any]:
"matter": cluster[0], "has_status": has_status}
(ready if has_status else not_ready).append(row)
total = len(ready) + len(not_ready)
excluded_n = sum(no_cluster.values())
math = _ratio_explain(
len(ready), total, "Matter-mappable controllable functions with a status GA",
excluded={
"controllable_functions_without_a_matter_cluster": excluded_n,
"by_category": dict(no_cluster.most_common()),
"why": "categories with no Matter cluster (e.g. scenes, diagnostics) can't "
"round-trip, so they are not counted in the readiness denominator.",
} if excluded_n else None)
return {
"controllable_functions": total,
"matter_ready": len(ready),
"ready_pct": (100 * len(ready) // total) if total else 0,
"ready_pct": math["pct"],
"math": math,
"not_ready": not_ready[:200],
"note": "Ready = has a status GA + decodable DPT, so the Matter cluster can report "
"state. A Matter bridge (e.g. HA Matter server) exposes these; functions "
"without a status GA won't round-trip. Static readiness only — no bridging here.",
"without a status GA won't round-trip. Static readiness only — no bridging here. "
"See `math` for the exact denominator and what was excluded.",
}
@@ -113,12 +150,16 @@ def completeness_grade(project: LoadedProject) -> dict[str, Any]:
if n == 0:
missing.append(label)
hit = sum(1 for v in present.values() if v)
score = round(100 * hit / len(_PATTERNS))
bands = {"as-built grade": ">=75", "near-complete": "55-74",
"functional skeleton": "30-54", "bare skeleton": "<30"}
math = _ratio_explain(hit, len(_PATTERNS), "as-built patterns present", bands=bands)
score = math["pct"]
grade = ("as-built grade" if score >= 75 else
"near-complete" if score >= 55 else
"functional skeleton" if score >= 30 else "bare skeleton")
return {
"grade": grade, "score": score,
"math": math,
"patterns_present": {k: v for k, v in present.items() if v},
"patterns_missing": missing,
"note": "Completeness = presence of the as-built patterns a professional adds beyond "
+9 -2
View File
@@ -86,7 +86,14 @@ def _feedback_coverage(project: LoadedProject,
and ga.dpt_main is not None]
gaps = sum(1 for f in missing if f["code"] == "missing_status_address")
total = len(commands)
return {"commands": total, "with_status": max(total - gaps, 0), "missing": gaps}
with_status = max(total - gaps, 0)
pct = round(100 * with_status / total) if total else 0
return {
"commands": total, "with_status": with_status, "missing": gaps,
"pct": pct,
"formula": f"{with_status} / {total} functional command GAs have a status = {pct}%"
if total else "no functional command GAs — coverage undefined",
}
# --------------------------------------------------------------------------- #
@@ -211,7 +218,7 @@ def build_handover(project: LoadedProject,
# 4. Feedback coverage
md.append("\n## 4. Command / status coverage\n")
pct = (100 * cover["with_status"] // cover["commands"]) if cover["commands"] else 0
pct = cover["pct"]
md.append(
f"- Functional command GAs: **{cover['commands']}**\n"
f"- With a status/feedback GA: **{cover['with_status']}** ({pct}%)\n"