state-hub/scripts/quality_assessment.py
tegwick fe4cfe22c9
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
feat: DoX assessment recording and soft visibility (STATE-WP-0077)
Add quality_doc/dor/dod recording convention, quality-debt CLI, promote-intake
and C-34 soft warnings, agent protocol notes, and DoD policy badge language.
Mark STATE-WP-0077 finished.
2026-07-22 21:18:40 +02:00

187 lines
5.7 KiB
Python

#!/usr/bin/env python3
"""DoX quality assessment helpers (STATE-WP-0077).
Canonical recording fields and parsing for Definition assessments.
See docs/work-record-quality-gates.md.
"""
from __future__ import annotations
import re
from dataclasses import dataclass
from typing import Any
POLICIES = ("DoC", "DoR", "DoD")
FIELD_FOR_POLICY = {
"DoC": "quality_doc",
"DoR": "quality_dor",
"DoD": "quality_dod",
}
def _norm_policy(policy: str) -> str:
p = policy.strip()
upper = p.upper().replace(" ", "").replace("_", "")
if upper in ("DOC", "COMPREHENSION", "DEFINITIONOFCOMPREHENSION"):
return "DoC"
if upper in ("DOR", "READY", "DEFINITIONOFREADY"):
return "DoR"
if upper in ("DOD", "DONE", "DEFINITIONOFDONE"):
return "DoD"
if p in POLICIES:
return p
raise ValueError(f"unknown policy {policy!r}; use DoC, DoR, or DoD")
def badge(policy: str, outcome: str) -> str:
"""Map policy + Ok|Failed (or full badge) to DoX-Ok / DoX-Failed."""
pol = _norm_policy(policy)
out = outcome.strip().strip('"')
if out in (f"{pol}-Ok", f"{pol}-Failed"):
return out
if out.lower() in ("ok", "pass", "passed"):
return f"{pol}-Ok"
if out.lower() in ("failed", "fail", "no"):
return f"{pol}-Failed"
raise ValueError(f"unknown outcome {outcome!r} for {pol}")
def assessment_from_mapping(meta: dict, policy: str) -> str | None:
"""Read assessment badge for policy from frontmatter / YAML / task fields."""
pol = _norm_policy(policy)
key = FIELD_FOR_POLICY[pol]
raw = meta.get(key)
if raw is None:
return None
s = str(raw).strip().strip('"').strip("'")
if not s or s.lower() in ("unassessed", "none", "null", "~", "-"):
return None
try:
return badge(pol, s)
except ValueError:
return None
def is_ok(badge_value: str | None) -> bool:
return bool(badge_value) and str(badge_value).endswith("-Ok")
@dataclass(frozen=True)
class QualityDebtItem:
kind: str
record_id: str
lifecycle_status: str
missing: str
path: str
detail: str = ""
def debt_for_workplan_meta(meta: dict, *, path: str) -> list[QualityDebtItem]:
"""Quality debt for a workplan frontmatter dict."""
items: list[QualityDebtItem] = []
wp_id = str(meta.get("id", "")).strip() or path
status = str(meta.get("status", "")).strip().lower()
dor = assessment_from_mapping(meta, "DoR")
dod = assessment_from_mapping(meta, "DoD")
if status == "ready" and not is_ok(dor):
items.append(
QualityDebtItem(
kind="workplan",
record_id=wp_id,
lifecycle_status=status,
missing="DoR-Ok",
path=path,
detail=f"quality_dor={dor or 'unassessed'}",
)
)
if status in ("finished", "completed") and not is_ok(dod):
items.append(
QualityDebtItem(
kind="workplan",
record_id=wp_id,
lifecycle_status=status,
missing="DoD-Ok",
path=path,
detail=f"quality_dod={dod or 'unassessed'}",
)
)
return items
def debt_for_intake_meta(meta: dict, *, path: str) -> list[QualityDebtItem]:
"""Quality debt for intake blocks in vetted/routed without DoC-Ok."""
items: list[QualityDebtItem] = []
iid = str(meta.get("id", "")).strip() or path
status = str(meta.get("status", "")).strip().lower()
doc = assessment_from_mapping(meta, "DoC")
if status in ("vetted", "routed") and not is_ok(doc):
items.append(
QualityDebtItem(
kind="intake",
record_id=iid,
lifecycle_status=status,
missing="DoC-Ok",
path=path,
detail=f"quality_doc={doc or 'unassessed'}",
)
)
return items
def intake_has_doc_ok(intake: dict) -> bool:
"""Soft check: hub intake notes/description record DoC-Ok."""
chunks: list[str] = []
for key in ("description", "routed_note", "title"):
v = intake.get(key)
if v:
chunks.append(str(v))
for note in intake.get("notes") or []:
if isinstance(note, dict):
chunks.append(str(note.get("content") or ""))
else:
chunks.append(str(note))
# structured future field
if assessment_from_mapping(intake, "DoC") and is_ok(assessment_from_mapping(intake, "DoC")):
return True
text = "\n".join(chunks)
if re.search(r"\bDoC-Ok\b", text):
return True
if re.search(r"quality_doc\s*[:=]\s*[\"']?(DoC-Ok|Ok)\b", text, re.I):
return True
return False
def progress_event_body(
*,
policy: str,
outcome: str,
record_kind: str,
record_id: str,
assessed_by: str | None = None,
note: str | None = None,
topic_id: str | None = None,
workplan_id: str | None = None,
author: str | None = None,
) -> dict[str, Any]:
"""JSON body for POST /progress/ recording a quality assessment."""
b = badge(policy, outcome)
pol = b.rsplit("-", 1)[0]
body: dict[str, Any] = {
"event_type": "quality_assessment",
"summary": f"{b} for {record_kind} {record_id}",
"author": author or assessed_by or "agent",
"detail": {
"policy": pol,
"outcome": "Ok" if b.endswith("-Ok") else "Failed",
"badge": b,
"record_kind": record_kind,
"record_id": record_id,
"assessed_by": assessed_by,
"note": note,
},
}
if topic_id:
body["topic_id"] = topic_id
if workplan_id:
body["workplan_id"] = workplan_id
return body