docassert 0.17.0__tar.gz → 0.19.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {docassert-0.17.0/docassert.egg-info → docassert-0.19.0}/PKG-INFO +1 -1
- {docassert-0.17.0 → docassert-0.19.0}/docassert/__init__.py +1 -1
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/consistency.yaml +6 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/charter.criteria.yaml +5 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/bridge/ops.py +17 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/cli.py +20 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/status/derive.py +68 -1
- docassert-0.19.0/docassert/status/render.py +865 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/structural.py +16 -0
- {docassert-0.17.0 → docassert-0.19.0/docassert.egg-info}/PKG-INFO +1 -1
- {docassert-0.17.0 → docassert-0.19.0}/tests/test_badge.py +2 -2
- {docassert-0.17.0 → docassert-0.19.0}/tests/test_status.py +39 -11
- docassert-0.17.0/docassert/status/render.py +0 -397
- {docassert-0.17.0 → docassert-0.19.0}/LICENSE +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/NOTICE +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/README.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/__main__.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/adr.criteria.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/benefits-realization.criteria.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/brd.criteria.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/business-case.criteria.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/data-migration-plan.criteria.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/frnfr.criteria.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/hypercare-plan.criteria.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/operations.criteria.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/post-implementation-review.criteria.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/prd.criteria.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/project.criteria.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/qa-test-plan.criteria.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/raci-stakeholder.criteria.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/release-cutover-plan.criteria.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/risk-register.criteria.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/rollback-plan.criteria.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/runbook.criteria.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/status-report.criteria.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/test-cases.criteria.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/criteria/user-story.criteria.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/profiles/agile-delivery.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/profiles/lean-startup.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/profiles/operations.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/profiles/regulated-industry.yaml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/adr.schema.json +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/benefits-realization.schema.json +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/brd.schema.json +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/business-case.schema.json +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/charter.schema.json +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/data-migration-plan.schema.json +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/frnfr.schema.json +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/hypercare-plan.schema.json +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/operations.schema.json +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/post-implementation-review.schema.json +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/prd.schema.json +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/project.schema.json +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/qa-test-plan.schema.json +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/raci-stakeholder.schema.json +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/release-cutover-plan.schema.json +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/risk-register.schema.json +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/rollback-plan.schema.json +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/runbook.schema.json +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/status-report.schema.json +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/test-cases.schema.json +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/schema/user-story.schema.json +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/skills/doc-to-pmo/SKILL.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/adr.template.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/benefits-realization.template.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/brd.template.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/business-case.template.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/charter.template.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/data-migration-plan.template.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/frnfr.template.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/hypercare-plan.template.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/operations.template.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/post-implementation-review.template.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/prd.template.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/project.template.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/qa-test-plan.template.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/raci-stakeholder.template.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/release-cutover-plan.template.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/risk-register.template.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/rollback-plan.template.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/runbook.template.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/status-report.template.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/test-cases.template.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/_data/templates/user-story.template.md +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/bridge/__init__.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/bridge/board.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/bridge/gh.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/bridge/plan.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/config.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/consistency.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/extract.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/graph.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/loader.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/models.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/profiles.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/projects.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/report.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/rtm.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/scaffold.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/semantic.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert/status/__init__.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert.egg-info/SOURCES.txt +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert.egg-info/dependency_links.txt +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert.egg-info/entry_points.txt +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert.egg-info/requires.txt +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/docassert.egg-info/top_level.txt +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/pyproject.toml +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/setup.cfg +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/tests/test_bridge.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/tests/test_config.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/tests/test_consistency.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/tests/test_defects.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/tests/test_draft_relaxation.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/tests/test_extract.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/tests/test_graph.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/tests/test_json_report.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/tests/test_kinds_delivery.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/tests/test_kinds_governance.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/tests/test_kinds_operate.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/tests/test_kinds_reporting.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/tests/test_profiles.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/tests/test_projects.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/tests/test_report_extract.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/tests/test_scaffold.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/tests/test_semantic.py +0 -0
- {docassert-0.17.0 → docassert-0.19.0}/tests/test_structural.py +0 -0
|
@@ -39,6 +39,12 @@ coverage:
|
|
|
39
39
|
# (0 = no cap).
|
|
40
40
|
alignment_limit: 25
|
|
41
41
|
|
|
42
|
+
# Open risks move derived status to amber only at or above this
|
|
43
|
+
# probability x impact score (low=1 medium=2 high/critical=3). 0 = any open
|
|
44
|
+
# risk ambers (the strict pre-0.19 behavior). Exposure below the threshold
|
|
45
|
+
# stays fully reported; it just doesn't flatten the headline.
|
|
46
|
+
risk_amber_score: 6
|
|
47
|
+
|
|
42
48
|
# Rules match in order; the first matching rule grades a link. Typed rules
|
|
43
49
|
# (child_type/parent_type) come before the generic ones.
|
|
44
50
|
alignment:
|
|
@@ -21,6 +21,11 @@ required_sections:
|
|
|
21
21
|
- Approval
|
|
22
22
|
|
|
23
23
|
checks:
|
|
24
|
+
- id: milestones-dated
|
|
25
|
+
type: structural
|
|
26
|
+
blocking: false
|
|
27
|
+
description: "At least one Milestones bullet ends with an ISO date (- <label>: YYYY-MM-DD) so timelines can draw it. Advisory only."
|
|
28
|
+
|
|
24
29
|
- id: frontmatter-schema
|
|
25
30
|
type: structural
|
|
26
31
|
blocking: true
|
|
@@ -222,6 +222,19 @@ def status(plan: BridgePlan, gh: G.GhRunner, repo: str) -> dict:
|
|
|
222
222
|
"doc": key})
|
|
223
223
|
out: dict = {"repo": repo, "projects": [],
|
|
224
224
|
"scope": {"unverified": unverified, "orphaned": orphaned}}
|
|
225
|
+
def facts(key):
|
|
226
|
+
"""Real, presentational issue facts (never fed back into derivation)."""
|
|
227
|
+
issue = by_key.get(key) or {}
|
|
228
|
+
return {
|
|
229
|
+
"title": issue.get("title"),
|
|
230
|
+
"state": issue.get("state"),
|
|
231
|
+
"created_at": (issue.get("created_at") or "")[:10] or None,
|
|
232
|
+
"closed_at": (issue.get("closed_at") or "")[:10] or None,
|
|
233
|
+
"url": issue.get("html_url"),
|
|
234
|
+
"assignee": (issue.get("assignee") or {}).get("login"),
|
|
235
|
+
"labels": [lb.get("name") for lb in issue.get("labels") or []],
|
|
236
|
+
}
|
|
237
|
+
|
|
225
238
|
for p in plan.projects:
|
|
226
239
|
feats = []
|
|
227
240
|
for f in p["features"]:
|
|
@@ -229,6 +242,10 @@ def status(plan: BridgePlan, gh: G.GhRunner, repo: str) -> dict:
|
|
|
229
242
|
feats.append({
|
|
230
243
|
"id": f.id,
|
|
231
244
|
"issue": (by_key.get(f.id) or {}).get("number"),
|
|
245
|
+
"facts": facts(f.id),
|
|
246
|
+
"stories": [{"id": s.id,
|
|
247
|
+
"issue": (by_key.get(s.id) or {}).get("number"),
|
|
248
|
+
"facts": facts(s.id)} for s in f.stories],
|
|
232
249
|
"stories_total": len(f.stories),
|
|
233
250
|
"stories_closed": sum(1 for s in states if s == "closed"),
|
|
234
251
|
"closed": (by_key.get(f.id) or {}).get("state") == "closed",
|
|
@@ -240,9 +240,29 @@ def cmd_pages(args: argparse.Namespace) -> int:
|
|
|
240
240
|
execution[proj["id"]] = {**proj, "scope": data.get("scope"),
|
|
241
241
|
"repo": proj.get("repo") or data.get("repo")}
|
|
242
242
|
|
|
243
|
+
def _activity(project_id: str) -> list[dict]:
|
|
244
|
+
"""Recent documents/ commits for one project (best-effort, git only)."""
|
|
245
|
+
import subprocess
|
|
246
|
+
try:
|
|
247
|
+
out = subprocess.run(
|
|
248
|
+
["git", "log", "-n", "6", "--date=short",
|
|
249
|
+
"--pretty=%ad\x1f%s", "--", f"{docs_dir}/{project_id}"],
|
|
250
|
+
capture_output=True, text=True, timeout=10)
|
|
251
|
+
if out.returncode != 0:
|
|
252
|
+
return []
|
|
253
|
+
rows = []
|
|
254
|
+
for line in out.stdout.strip().splitlines():
|
|
255
|
+
when, _, text = line.partition("\x1f")
|
|
256
|
+
if text:
|
|
257
|
+
rows.append({"when": when, "text": text})
|
|
258
|
+
return rows
|
|
259
|
+
except Exception:
|
|
260
|
+
return []
|
|
261
|
+
|
|
243
262
|
plist = projects_mod.load_projects(docs_dir)
|
|
244
263
|
for p in plist:
|
|
245
264
|
model = status_mod.build_status(docs_dir, project=p["id"])
|
|
265
|
+
model["activity"] = _activity(p["id"])
|
|
246
266
|
if p["id"] in execution:
|
|
247
267
|
model["execution"] = execution[p["id"]]
|
|
248
268
|
(out / f"{p['id']}.html").write_text(status_mod.render_html(model))
|
|
@@ -113,6 +113,51 @@ def _broken_references(graph, code=None):
|
|
|
113
113
|
return broken
|
|
114
114
|
|
|
115
115
|
|
|
116
|
+
_MS_RE = re.compile(r"^(?P<label>.+?)[:\u2014\u2013-]\s*(?P<date>\d{4}-\d{2}-\d{2})\.?\s*$")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _milestones(docs):
|
|
120
|
+
"""Dated charter milestones as temporal facts (never completion claims)."""
|
|
121
|
+
import datetime
|
|
122
|
+
today = datetime.date.today()
|
|
123
|
+
out = []
|
|
124
|
+
for d in docs:
|
|
125
|
+
if d.kind != "charter":
|
|
126
|
+
continue
|
|
127
|
+
section = d.section("Milestones")
|
|
128
|
+
if section is None:
|
|
129
|
+
continue
|
|
130
|
+
for bullet in section.list_items:
|
|
131
|
+
m = _MS_RE.match(bullet.strip())
|
|
132
|
+
if not m:
|
|
133
|
+
continue
|
|
134
|
+
try:
|
|
135
|
+
due = datetime.date.fromisoformat(m.group("date"))
|
|
136
|
+
except ValueError:
|
|
137
|
+
continue
|
|
138
|
+
out.append(_ms_entry(m.group("label").strip(), due, today))
|
|
139
|
+
for d in docs:
|
|
140
|
+
if d.kind != "charter":
|
|
141
|
+
continue
|
|
142
|
+
target = (d.frontmatter.get("dates") or {}).get("target")
|
|
143
|
+
if target:
|
|
144
|
+
try:
|
|
145
|
+
due = (target if isinstance(target, datetime.date)
|
|
146
|
+
else datetime.date.fromisoformat(str(target)))
|
|
147
|
+
if not any(x["date"] == due.isoformat() for x in out):
|
|
148
|
+
out.append(_ms_entry("Charter target", due, today))
|
|
149
|
+
except (TypeError, ValueError):
|
|
150
|
+
pass
|
|
151
|
+
return sorted(out, key=lambda x: x["date"])
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _ms_entry(label, due, today):
|
|
155
|
+
delta = (due - today).days
|
|
156
|
+
return {"label": label, "date": due.isoformat(), "days": delta,
|
|
157
|
+
"when": ("today" if delta == 0 else
|
|
158
|
+
"elapsed" if delta < 0 else "upcoming")}
|
|
159
|
+
|
|
160
|
+
|
|
116
161
|
def _operations(docs):
|
|
117
162
|
"""Operations documents with their review state."""
|
|
118
163
|
import datetime
|
|
@@ -199,9 +244,11 @@ def build_status(documents_dir=DOCUMENTS_DIR, project: str | None = None) -> dic
|
|
|
199
244
|
"coverage": _coverage(graph, cfg, code),
|
|
200
245
|
"risks": _risks(graph, code),
|
|
201
246
|
"operations": _operations(docs),
|
|
247
|
+
"milestones": _milestones(docs),
|
|
202
248
|
"broken_references": _broken_references(graph, code),
|
|
203
249
|
"latest_report": _latest_report(docs),
|
|
204
250
|
"completeness": completeness,
|
|
251
|
+
"risk_amber_score": int(cfg.get("risk_amber_score", 6) or 0),
|
|
205
252
|
}
|
|
206
253
|
model["rag"] = derive_rag(model)
|
|
207
254
|
return model
|
|
@@ -231,6 +278,19 @@ def completeness_report(documents_dir=DOCUMENTS_DIR) -> list[dict]:
|
|
|
231
278
|
return out
|
|
232
279
|
|
|
233
280
|
|
|
281
|
+
def _next_dated(m) -> str | None:
|
|
282
|
+
"""The nearest upcoming dated marker (milestone or operations review)."""
|
|
283
|
+
import datetime
|
|
284
|
+
cands = [x["date"] for x in m.get("milestones", []) if x["days"] >= 0]
|
|
285
|
+
for o in m.get("operations", []):
|
|
286
|
+
try:
|
|
287
|
+
if datetime.date.fromisoformat(o["review_by"]) >= datetime.date.today():
|
|
288
|
+
cands.append(o["review_by"])
|
|
289
|
+
except ValueError:
|
|
290
|
+
pass
|
|
291
|
+
return min(cands) if cands else None
|
|
292
|
+
|
|
293
|
+
|
|
234
294
|
def build_index(documents_dir=DOCUMENTS_DIR) -> dict:
|
|
235
295
|
"""The multi-project view: each project's derived RAG + headline signals,
|
|
236
296
|
plus the whole-repo rollup."""
|
|
@@ -246,6 +306,10 @@ def build_index(documents_dir=DOCUMENTS_DIR) -> dict:
|
|
|
246
306
|
"failing": m["counts"]["failing"],
|
|
247
307
|
"risks": len([r for r in m["risks"] if r.get("disposition", "open") == "open"]),
|
|
248
308
|
"coverage_gaps": sum(len(c["gaps"]) for c in m["coverage"]),
|
|
309
|
+
"coverage_pct": (round(100 * sum(c["covered"] for c in m["coverage"])
|
|
310
|
+
/ max(1, sum(c["total"] for c in m["coverage"])))
|
|
311
|
+
if any(c["total"] for c in m["coverage"]) else 100),
|
|
312
|
+
"next": _next_dated(m),
|
|
249
313
|
"broken": len(m["broken_references"]),
|
|
250
314
|
"completeness": m.get("completeness"),
|
|
251
315
|
})
|
|
@@ -264,7 +328,10 @@ def derive_rag(model) -> str:
|
|
|
264
328
|
reported = (model["latest_report"] or {}).get("rag")
|
|
265
329
|
completeness_gap = bool(comp and (comp["missing_required"] or comp["incomplete_required"]
|
|
266
330
|
or comp["recommended_gaps"] or comp.get("unknown")))
|
|
267
|
-
|
|
331
|
+
threshold = model.get("risk_amber_score", 6)
|
|
332
|
+
open_risks = [r for r in model["risks"]
|
|
333
|
+
if r.get("disposition", "open") == "open"
|
|
334
|
+
and (threshold == 0 or r.get("score", 0) >= threshold)]
|
|
268
335
|
stale_ops = [o for o in model.get("operations", []) if not o["fresh"]]
|
|
269
336
|
if (coverage_gap or open_risks or stale_ops
|
|
270
337
|
or reported in {"amber", "red"} or completeness_gap):
|