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.
187 lines
5.7 KiB
Python
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
|