docassert 0.16.0__tar.gz → 0.18.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.16.0/docassert.egg-info → docassert-0.18.0}/PKG-INFO +3 -1
- {docassert-0.16.0 → docassert-0.18.0}/README.md +2 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/__init__.py +1 -1
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/criteria/charter.criteria.yaml +5 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/bridge/ops.py +17 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/cli.py +20 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/status/derive.py +49 -0
- docassert-0.18.0/docassert/status/render.py +865 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/structural.py +16 -0
- {docassert-0.16.0 → docassert-0.18.0/docassert.egg-info}/PKG-INFO +3 -1
- {docassert-0.16.0 → docassert-0.18.0}/docassert.egg-info/SOURCES.txt +1 -0
- {docassert-0.16.0 → docassert-0.18.0}/pyproject.toml +1 -1
- {docassert-0.16.0 → docassert-0.18.0}/tests/test_badge.py +2 -2
- docassert-0.18.0/tests/test_report_extract.py +70 -0
- {docassert-0.16.0 → docassert-0.18.0}/tests/test_status.py +10 -8
- docassert-0.16.0/docassert/status/render.py +0 -397
- {docassert-0.16.0 → docassert-0.18.0}/LICENSE +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/NOTICE +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/__main__.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/consistency.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/criteria/adr.criteria.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/criteria/benefits-realization.criteria.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/criteria/brd.criteria.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/criteria/business-case.criteria.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/criteria/data-migration-plan.criteria.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/criteria/frnfr.criteria.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/criteria/hypercare-plan.criteria.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/criteria/operations.criteria.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/criteria/post-implementation-review.criteria.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/criteria/prd.criteria.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/criteria/project.criteria.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/criteria/qa-test-plan.criteria.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/criteria/raci-stakeholder.criteria.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/criteria/release-cutover-plan.criteria.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/criteria/risk-register.criteria.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/criteria/rollback-plan.criteria.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/criteria/runbook.criteria.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/criteria/status-report.criteria.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/criteria/test-cases.criteria.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/criteria/user-story.criteria.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/profiles/agile-delivery.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/profiles/lean-startup.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/profiles/operations.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/profiles/regulated-industry.yaml +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/schema/adr.schema.json +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/schema/benefits-realization.schema.json +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/schema/brd.schema.json +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/schema/business-case.schema.json +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/schema/charter.schema.json +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/schema/data-migration-plan.schema.json +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/schema/frnfr.schema.json +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/schema/hypercare-plan.schema.json +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/schema/operations.schema.json +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/schema/post-implementation-review.schema.json +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/schema/prd.schema.json +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/schema/project.schema.json +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/schema/qa-test-plan.schema.json +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/schema/raci-stakeholder.schema.json +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/schema/release-cutover-plan.schema.json +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/schema/risk-register.schema.json +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/schema/rollback-plan.schema.json +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/schema/runbook.schema.json +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/schema/status-report.schema.json +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/schema/test-cases.schema.json +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/schema/user-story.schema.json +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/skills/doc-to-pmo/SKILL.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/templates/adr.template.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/templates/benefits-realization.template.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/templates/brd.template.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/templates/business-case.template.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/templates/charter.template.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/templates/data-migration-plan.template.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/templates/frnfr.template.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/templates/hypercare-plan.template.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/templates/operations.template.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/templates/post-implementation-review.template.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/templates/prd.template.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/templates/project.template.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/templates/qa-test-plan.template.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/templates/raci-stakeholder.template.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/templates/release-cutover-plan.template.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/templates/risk-register.template.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/templates/rollback-plan.template.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/templates/runbook.template.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/templates/status-report.template.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/templates/test-cases.template.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/_data/templates/user-story.template.md +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/bridge/__init__.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/bridge/board.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/bridge/gh.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/bridge/plan.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/config.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/consistency.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/extract.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/graph.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/loader.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/models.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/profiles.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/projects.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/report.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/rtm.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/scaffold.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/semantic.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert/status/__init__.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert.egg-info/dependency_links.txt +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert.egg-info/entry_points.txt +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert.egg-info/requires.txt +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/docassert.egg-info/top_level.txt +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/setup.cfg +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/tests/test_bridge.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/tests/test_config.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/tests/test_consistency.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/tests/test_defects.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/tests/test_draft_relaxation.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/tests/test_extract.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/tests/test_graph.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/tests/test_json_report.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/tests/test_kinds_delivery.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/tests/test_kinds_governance.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/tests/test_kinds_operate.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/tests/test_kinds_reporting.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/tests/test_profiles.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/tests/test_projects.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/tests/test_scaffold.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/tests/test_semantic.py +0 -0
- {docassert-0.16.0 → docassert-0.18.0}/tests/test_structural.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: docassert
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.0
|
|
4
4
|
Summary: Unit testing for business documents — validate structured Markdown docs against a configurable audit standard.
|
|
5
5
|
Author: C4G Enterprises Inc.
|
|
6
6
|
License: Apache-2.0
|
|
@@ -102,6 +102,8 @@ flagged as TODOs, never invented). The skill's source is
|
|
|
102
102
|
| `docassert extract <file>` | Extract plain text from a source `.docx` / `.pdf` / `.md` / `.txt` (the first step of doc-to-pmo conversion). Needs the `convert` extra: `pip install "docassert[convert]"`. |
|
|
103
103
|
|
|
104
104
|
Every document-reading command accepts `--documents-dir` (default `documents/`).
|
|
105
|
+
The full flag-by-flag reference, and what 1.x does and does not promise to
|
|
106
|
+
keep stable, live in [STABILITY.md](STABILITY.md).
|
|
105
107
|
AI alignment grades at most `alignment_limit` links per run (default 25; set it
|
|
106
108
|
in `consistency.yaml`, `0` = no cap) so API cost stays bounded on large graphs.
|
|
107
109
|
|
|
@@ -62,6 +62,8 @@ flagged as TODOs, never invented). The skill's source is
|
|
|
62
62
|
| `docassert extract <file>` | Extract plain text from a source `.docx` / `.pdf` / `.md` / `.txt` (the first step of doc-to-pmo conversion). Needs the `convert` extra: `pip install "docassert[convert]"`. |
|
|
63
63
|
|
|
64
64
|
Every document-reading command accepts `--documents-dir` (default `documents/`).
|
|
65
|
+
The full flag-by-flag reference, and what 1.x does and does not promise to
|
|
66
|
+
keep stable, live in [STABILITY.md](STABILITY.md).
|
|
65
67
|
AI alignment grades at most `alignment_limit` links per run (default 25; set it
|
|
66
68
|
in `consistency.yaml`, `0` = no cap) so API cost stays bounded on large graphs.
|
|
67
69
|
|
|
@@ -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,37 @@ 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
|
+
delta = (due - today).days
|
|
139
|
+
out.append({"label": m.group("label").strip(),
|
|
140
|
+
"date": due.isoformat(),
|
|
141
|
+
"days": delta,
|
|
142
|
+
"when": ("today" if delta == 0 else
|
|
143
|
+
"elapsed" if delta < 0 else "upcoming")})
|
|
144
|
+
return sorted(out, key=lambda x: x["date"])
|
|
145
|
+
|
|
146
|
+
|
|
116
147
|
def _operations(docs):
|
|
117
148
|
"""Operations documents with their review state."""
|
|
118
149
|
import datetime
|
|
@@ -199,6 +230,7 @@ def build_status(documents_dir=DOCUMENTS_DIR, project: str | None = None) -> dic
|
|
|
199
230
|
"coverage": _coverage(graph, cfg, code),
|
|
200
231
|
"risks": _risks(graph, code),
|
|
201
232
|
"operations": _operations(docs),
|
|
233
|
+
"milestones": _milestones(docs),
|
|
202
234
|
"broken_references": _broken_references(graph, code),
|
|
203
235
|
"latest_report": _latest_report(docs),
|
|
204
236
|
"completeness": completeness,
|
|
@@ -231,6 +263,19 @@ def completeness_report(documents_dir=DOCUMENTS_DIR) -> list[dict]:
|
|
|
231
263
|
return out
|
|
232
264
|
|
|
233
265
|
|
|
266
|
+
def _next_dated(m) -> str | None:
|
|
267
|
+
"""The nearest upcoming dated marker (milestone or operations review)."""
|
|
268
|
+
import datetime
|
|
269
|
+
cands = [x["date"] for x in m.get("milestones", []) if x["days"] >= 0]
|
|
270
|
+
for o in m.get("operations", []):
|
|
271
|
+
try:
|
|
272
|
+
if datetime.date.fromisoformat(o["review_by"]) >= datetime.date.today():
|
|
273
|
+
cands.append(o["review_by"])
|
|
274
|
+
except ValueError:
|
|
275
|
+
pass
|
|
276
|
+
return min(cands) if cands else None
|
|
277
|
+
|
|
278
|
+
|
|
234
279
|
def build_index(documents_dir=DOCUMENTS_DIR) -> dict:
|
|
235
280
|
"""The multi-project view: each project's derived RAG + headline signals,
|
|
236
281
|
plus the whole-repo rollup."""
|
|
@@ -246,6 +291,10 @@ def build_index(documents_dir=DOCUMENTS_DIR) -> dict:
|
|
|
246
291
|
"failing": m["counts"]["failing"],
|
|
247
292
|
"risks": len([r for r in m["risks"] if r.get("disposition", "open") == "open"]),
|
|
248
293
|
"coverage_gaps": sum(len(c["gaps"]) for c in m["coverage"]),
|
|
294
|
+
"coverage_pct": (round(100 * sum(c["covered"] for c in m["coverage"])
|
|
295
|
+
/ max(1, sum(c["total"] for c in m["coverage"])))
|
|
296
|
+
if any(c["total"] for c in m["coverage"]) else 100),
|
|
297
|
+
"next": _next_dated(m),
|
|
249
298
|
"broken": len(m["broken_references"]),
|
|
250
299
|
"completeness": m.get("completeness"),
|
|
251
300
|
})
|