docassert 0.20.0__tar.gz → 0.20.1__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.
Files changed (125) hide show
  1. {docassert-0.20.0/docassert.egg-info → docassert-0.20.1}/PKG-INFO +1 -1
  2. {docassert-0.20.0 → docassert-0.20.1}/docassert/__init__.py +1 -1
  3. {docassert-0.20.0 → docassert-0.20.1}/docassert/status/render.py +56 -10
  4. {docassert-0.20.0 → docassert-0.20.1/docassert.egg-info}/PKG-INFO +1 -1
  5. {docassert-0.20.0 → docassert-0.20.1}/tests/test_status.py +12 -0
  6. {docassert-0.20.0 → docassert-0.20.1}/LICENSE +0 -0
  7. {docassert-0.20.0 → docassert-0.20.1}/NOTICE +0 -0
  8. {docassert-0.20.0 → docassert-0.20.1}/README.md +0 -0
  9. {docassert-0.20.0 → docassert-0.20.1}/docassert/__main__.py +0 -0
  10. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/consistency.yaml +0 -0
  11. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/criteria/adr.criteria.yaml +0 -0
  12. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/criteria/benefits-realization.criteria.yaml +0 -0
  13. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/criteria/brd.criteria.yaml +0 -0
  14. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/criteria/business-case.criteria.yaml +0 -0
  15. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/criteria/charter.criteria.yaml +0 -0
  16. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/criteria/data-migration-plan.criteria.yaml +0 -0
  17. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/criteria/frnfr.criteria.yaml +0 -0
  18. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/criteria/hypercare-plan.criteria.yaml +0 -0
  19. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/criteria/operations.criteria.yaml +0 -0
  20. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/criteria/post-implementation-review.criteria.yaml +0 -0
  21. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/criteria/prd.criteria.yaml +0 -0
  22. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/criteria/project.criteria.yaml +0 -0
  23. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/criteria/qa-test-plan.criteria.yaml +0 -0
  24. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/criteria/raci-stakeholder.criteria.yaml +0 -0
  25. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/criteria/release-cutover-plan.criteria.yaml +0 -0
  26. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/criteria/risk-register.criteria.yaml +0 -0
  27. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/criteria/rollback-plan.criteria.yaml +0 -0
  28. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/criteria/runbook.criteria.yaml +0 -0
  29. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/criteria/status-report.criteria.yaml +0 -0
  30. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/criteria/test-cases.criteria.yaml +0 -0
  31. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/criteria/user-story.criteria.yaml +0 -0
  32. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/profiles/agile-delivery.yaml +0 -0
  33. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/profiles/lean-startup.yaml +0 -0
  34. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/profiles/operations.yaml +0 -0
  35. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/profiles/regulated-industry.yaml +0 -0
  36. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/schema/adr.schema.json +0 -0
  37. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/schema/benefits-realization.schema.json +0 -0
  38. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/schema/brd.schema.json +0 -0
  39. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/schema/business-case.schema.json +0 -0
  40. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/schema/charter.schema.json +0 -0
  41. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/schema/data-migration-plan.schema.json +0 -0
  42. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/schema/frnfr.schema.json +0 -0
  43. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/schema/hypercare-plan.schema.json +0 -0
  44. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/schema/operations.schema.json +0 -0
  45. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/schema/post-implementation-review.schema.json +0 -0
  46. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/schema/prd.schema.json +0 -0
  47. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/schema/project.schema.json +0 -0
  48. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/schema/qa-test-plan.schema.json +0 -0
  49. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/schema/raci-stakeholder.schema.json +0 -0
  50. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/schema/release-cutover-plan.schema.json +0 -0
  51. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/schema/risk-register.schema.json +0 -0
  52. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/schema/rollback-plan.schema.json +0 -0
  53. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/schema/runbook.schema.json +0 -0
  54. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/schema/status-report.schema.json +0 -0
  55. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/schema/test-cases.schema.json +0 -0
  56. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/schema/user-story.schema.json +0 -0
  57. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/skills/doc-to-pmo/SKILL.md +0 -0
  58. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/templates/adr.template.md +0 -0
  59. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/templates/benefits-realization.template.md +0 -0
  60. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/templates/brd.template.md +0 -0
  61. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/templates/business-case.template.md +0 -0
  62. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/templates/charter.template.md +0 -0
  63. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/templates/data-migration-plan.template.md +0 -0
  64. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/templates/frnfr.template.md +0 -0
  65. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/templates/hypercare-plan.template.md +0 -0
  66. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/templates/operations.template.md +0 -0
  67. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/templates/post-implementation-review.template.md +0 -0
  68. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/templates/prd.template.md +0 -0
  69. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/templates/project.template.md +0 -0
  70. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/templates/qa-test-plan.template.md +0 -0
  71. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/templates/raci-stakeholder.template.md +0 -0
  72. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/templates/release-cutover-plan.template.md +0 -0
  73. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/templates/risk-register.template.md +0 -0
  74. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/templates/rollback-plan.template.md +0 -0
  75. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/templates/runbook.template.md +0 -0
  76. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/templates/status-report.template.md +0 -0
  77. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/templates/test-cases.template.md +0 -0
  78. {docassert-0.20.0 → docassert-0.20.1}/docassert/_data/templates/user-story.template.md +0 -0
  79. {docassert-0.20.0 → docassert-0.20.1}/docassert/bridge/__init__.py +0 -0
  80. {docassert-0.20.0 → docassert-0.20.1}/docassert/bridge/board.py +0 -0
  81. {docassert-0.20.0 → docassert-0.20.1}/docassert/bridge/gh.py +0 -0
  82. {docassert-0.20.0 → docassert-0.20.1}/docassert/bridge/ops.py +0 -0
  83. {docassert-0.20.0 → docassert-0.20.1}/docassert/bridge/plan.py +0 -0
  84. {docassert-0.20.0 → docassert-0.20.1}/docassert/cli.py +0 -0
  85. {docassert-0.20.0 → docassert-0.20.1}/docassert/config.py +0 -0
  86. {docassert-0.20.0 → docassert-0.20.1}/docassert/consistency.py +0 -0
  87. {docassert-0.20.0 → docassert-0.20.1}/docassert/extract.py +0 -0
  88. {docassert-0.20.0 → docassert-0.20.1}/docassert/graph.py +0 -0
  89. {docassert-0.20.0 → docassert-0.20.1}/docassert/loader.py +0 -0
  90. {docassert-0.20.0 → docassert-0.20.1}/docassert/models.py +0 -0
  91. {docassert-0.20.0 → docassert-0.20.1}/docassert/profiles.py +0 -0
  92. {docassert-0.20.0 → docassert-0.20.1}/docassert/projects.py +0 -0
  93. {docassert-0.20.0 → docassert-0.20.1}/docassert/report.py +0 -0
  94. {docassert-0.20.0 → docassert-0.20.1}/docassert/rtm.py +0 -0
  95. {docassert-0.20.0 → docassert-0.20.1}/docassert/scaffold.py +0 -0
  96. {docassert-0.20.0 → docassert-0.20.1}/docassert/semantic.py +0 -0
  97. {docassert-0.20.0 → docassert-0.20.1}/docassert/status/__init__.py +0 -0
  98. {docassert-0.20.0 → docassert-0.20.1}/docassert/status/derive.py +0 -0
  99. {docassert-0.20.0 → docassert-0.20.1}/docassert/structural.py +0 -0
  100. {docassert-0.20.0 → docassert-0.20.1}/docassert.egg-info/SOURCES.txt +0 -0
  101. {docassert-0.20.0 → docassert-0.20.1}/docassert.egg-info/dependency_links.txt +0 -0
  102. {docassert-0.20.0 → docassert-0.20.1}/docassert.egg-info/entry_points.txt +0 -0
  103. {docassert-0.20.0 → docassert-0.20.1}/docassert.egg-info/requires.txt +0 -0
  104. {docassert-0.20.0 → docassert-0.20.1}/docassert.egg-info/top_level.txt +0 -0
  105. {docassert-0.20.0 → docassert-0.20.1}/pyproject.toml +0 -0
  106. {docassert-0.20.0 → docassert-0.20.1}/setup.cfg +0 -0
  107. {docassert-0.20.0 → docassert-0.20.1}/tests/test_badge.py +0 -0
  108. {docassert-0.20.0 → docassert-0.20.1}/tests/test_bridge.py +0 -0
  109. {docassert-0.20.0 → docassert-0.20.1}/tests/test_config.py +0 -0
  110. {docassert-0.20.0 → docassert-0.20.1}/tests/test_consistency.py +0 -0
  111. {docassert-0.20.0 → docassert-0.20.1}/tests/test_defects.py +0 -0
  112. {docassert-0.20.0 → docassert-0.20.1}/tests/test_draft_relaxation.py +0 -0
  113. {docassert-0.20.0 → docassert-0.20.1}/tests/test_extract.py +0 -0
  114. {docassert-0.20.0 → docassert-0.20.1}/tests/test_graph.py +0 -0
  115. {docassert-0.20.0 → docassert-0.20.1}/tests/test_json_report.py +0 -0
  116. {docassert-0.20.0 → docassert-0.20.1}/tests/test_kinds_delivery.py +0 -0
  117. {docassert-0.20.0 → docassert-0.20.1}/tests/test_kinds_governance.py +0 -0
  118. {docassert-0.20.0 → docassert-0.20.1}/tests/test_kinds_operate.py +0 -0
  119. {docassert-0.20.0 → docassert-0.20.1}/tests/test_kinds_reporting.py +0 -0
  120. {docassert-0.20.0 → docassert-0.20.1}/tests/test_profiles.py +0 -0
  121. {docassert-0.20.0 → docassert-0.20.1}/tests/test_projects.py +0 -0
  122. {docassert-0.20.0 → docassert-0.20.1}/tests/test_report_extract.py +0 -0
  123. {docassert-0.20.0 → docassert-0.20.1}/tests/test_scaffold.py +0 -0
  124. {docassert-0.20.0 → docassert-0.20.1}/tests/test_semantic.py +0 -0
  125. {docassert-0.20.0 → docassert-0.20.1}/tests/test_structural.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docassert
3
- Version: 0.20.0
3
+ Version: 0.20.1
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
@@ -5,4 +5,4 @@ standard: deterministic structural checks that gate a merge, plus optional
5
5
  AI-graded semantic checks that advise.
6
6
  """
7
7
 
8
- __version__ = "0.20.0"
8
+ __version__ = "0.20.1"
@@ -142,6 +142,31 @@ def _today():
142
142
  return datetime.date.today()
143
143
 
144
144
 
145
+ def _verdict_causes(model) -> list[str]:
146
+ """The raw cause strings behind the verdict (also the verdict tooltip)."""
147
+ causes = []
148
+ gaps = [c for c in model["coverage"] if c["gaps"]]
149
+ if gaps:
150
+ causes.append(f"{len(gaps)} coverage chain(s) with gaps")
151
+ failing = [d for d in model["documents"] if not d["passing"]]
152
+ if failing:
153
+ causes.append(f"{len(failing)} document(s) failing audit")
154
+ if model["broken_references"]:
155
+ causes.append(f"{len(model['broken_references'])} broken reference(s)")
156
+ comp = model.get("completeness")
157
+ if comp and comp.get("blocks"):
158
+ causes.append("required documents missing under an enforced profile")
159
+ thr = model.get("risk_amber_score", 6)
160
+ open_r = [r for r in model["risks"] if r.get("disposition", "open") == "open"
161
+ and (thr == 0 or r.get("score", 0) >= thr)]
162
+ if open_r:
163
+ causes.append(f"{len(open_r)} open risk(s) at or above the appetite (score ≥ {thr})")
164
+ stale = [o for o in model.get("operations", []) if not o["fresh"]]
165
+ if stale:
166
+ causes.append(f"operations review overdue since {stale[0]['review_by']}")
167
+ return causes
168
+
169
+
145
170
  def _verdict(model) -> str:
146
171
  """One decision-grade sentence composed only from recorded causes."""
147
172
  rag = model["rag"]
@@ -259,8 +284,16 @@ def render_html(model) -> str:
259
284
  (f"{esc(nxt[1])} · {nxt[2]}" if nxt else "no dated milestone")),
260
285
  ("DOC SET", docset_v, docset_c, docset_sub),
261
286
  ]
287
+ stat_tips = {
288
+ "HEALTH": "Derived RAG. Red: failing approved docs, broken references, or enforced profile gaps. Amber: coverage gaps, open risks at or above the risk appetite, stale operations review, or an amber/red status report. Green: none of those.",
289
+ "DOCS APPROVED": "Documents with approved or baselined status, of all documents in this project. The sub-line counts documents passing every blocking structural check.",
290
+ "COVERAGE": "Traceability links present over links required, across every configured chain (BR→PR, PR→AC, AC→TC, and any others).",
291
+ "OPEN RISKS": "Risks with disposition open. Dispositioned risks (mitigated, accepted, closed) stay on the register as record but do not count here.",
292
+ "NEXT MILESTONE": "Days to the nearest upcoming dated marker: a dated charter milestone or the operations review. Temporal fact only.",
293
+ "DOC SET": "Profile completeness: required kinds complete (approved and passing audit) over required kinds expected.",
294
+ }
262
295
  stat_html = "".join(
263
- f'<div style="padding:22px 20px;border-left:1px solid {_C["line"]};">'
296
+ f'<div title="{esc(stat_tips.get(k, ""))}" style="cursor:help;padding:22px 20px;border-left:1px solid {_C["line"]};">'
264
297
  f'<div class="chiplabel" style="margin-bottom:14px;">{k}</div>'
265
298
  f'<div style="font-family:Archivo,system-ui,sans-serif;font-weight:800;font-size:30px;letter-spacing:-.02em;'
266
299
  f'line-height:1;color:{col};font-variant-numeric:tabular-nums;">{esc(v)}</div>'
@@ -278,8 +311,11 @@ def render_html(model) -> str:
278
311
  f'{_C["green"] if i < filled else _C["line"]};"></span>'
279
312
  for i in range(cells))
280
313
  ratio_c = _C["green"] if covered == total else _C["amberT"]
314
+ tip = ("all links traced" if not cov["gaps"] else
315
+ "gaps: " + ", ".join(cov["gaps"]))
281
316
  meters.append(
282
- f'<div><div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:11px;">'
317
+ f'<div title="{esc(tip)}" style="cursor:help;">'
318
+ f'<div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:11px;">'
283
319
  f'<span style="font-size:12.5px;color:{_C["soft"]};">{esc(cov["label"])}</span>'
284
320
  f'<span style="font-family:Archivo,system-ui,sans-serif;font-weight:700;font-size:13px;color:{ratio_c};">'
285
321
  f'{covered}/{total}</span></div><div style="display:flex;gap:4px;">{cell_html}</div></div>')
@@ -342,8 +378,11 @@ def render_html(model) -> str:
342
378
  if m["when"] == "elapsed":
343
379
  return f'color:{_C["muted"]};background:#14181b;'
344
380
  return f'color:{_C["muted"]};background:#14181b;'
381
+ ms_tip = ("Temporal fact only: elapsed means the date passed, "
382
+ "not that the milestone completed (spec §13.2).")
345
383
  cols = "".join(
346
- f'<div style="display:flex;flex-direction:column;align-items:flex-start;">'
384
+ f'<div title="{esc(m["label"])} · {esc(m["date"])} · {esc(ms_tip)}" '
385
+ f'style="cursor:help;display:flex;flex-direction:column;align-items:flex-start;">'
347
386
  f'<span style="display:block;width:14px;height:14px;border-radius:50%;'
348
387
  f'border:2px solid {_C["bg"]};{_dot(m)}"></span>'
349
388
  f'<div style="font-family:Archivo,system-ui,sans-serif;font-weight:700;font-size:13px;color:{_C["text"]};'
@@ -392,8 +431,13 @@ def render_html(model) -> str:
392
431
  width = max(6.0, (f["points"] / maxp) * (colw - 3))
393
432
  left = f["layer"] * colw + 1
394
433
  dep = f' · after {", ".join(esc(d) for d in f["after"])}' if f["after"] else ""
434
+ bar_tip = (f'{f["text"]} — {f["points"]} scope point(s) = '
435
+ f'{f["stories"]} story(ies) + {f["acs"]} acceptance criteria. '
436
+ + (f'Sequenced after {", ".join(f["after"])}. ' if f["after"] else "")
437
+ + ("State from the bridge issue." if f["id"] in ex_state
438
+ else "No execution data yet: scoped only."))
395
439
  rows_html.append(
396
- f'<div style="display:grid;grid-template-columns:186px 1fr;align-items:center;'
440
+ f'<div title="{esc(bar_tip)}" style="cursor:help;display:grid;grid-template-columns:186px 1fr;align-items:center;'
397
441
  f'border-bottom:1px solid #14181b;">'
398
442
  f'<div style="padding-right:14px;"><div style="font-size:12px;color:{_C["soft"]};'
399
443
  f'line-height:1.3;">{esc(f["text"])[:52]}</div>'
@@ -490,6 +534,8 @@ def render_html(model) -> str:
490
534
  work_aside = (f"{ex.get('stories_closed', 0)}/{ex.get('stories_total', 0)} stories closed"
491
535
  if ex else "bridge-managed issues")
492
536
  verdict = _verdict(model)
537
+ causes = _verdict_causes(model)
538
+ verdict_tip = ("Causes: " + "; ".join(causes)) if causes else "No amber or red causes recorded."
493
539
  payload = _json_embed({"risks": risks_data, "docs": docs_data, "work": work_data,
494
540
  "C": _C})
495
541
 
@@ -524,7 +570,7 @@ def render_html(model) -> str:
524
570
 
525
571
  <div style="display:flex;gap:18px;margin:28px 0 44px;padding:20px 22px;background:rgba(255,178,36,.06);border-left:3px solid {rc};">
526
572
  <span style="font-family:Archivo,system-ui,sans-serif;font-weight:900;font-size:12px;letter-spacing:.16em;color:{rt};writing-mode:vertical-rl;transform:rotate(180deg);">VERDICT</span>
527
- <p style="margin:0;font-family:Archivo,system-ui,sans-serif;font-weight:500;font-size:17.5px;line-height:1.5;color:#DDE1E5;max-width:88ch;">{esc(verdict)}</p>
573
+ <p title="{esc(verdict_tip)}" style="cursor:help;margin:0;font-family:Archivo,system-ui,sans-serif;font-weight:500;font-size:17.5px;line-height:1.5;color:#DDE1E5;max-width:88ch;">{esc(verdict)}</p>
528
574
  </div>
529
575
 
530
576
  <div style="display:grid;grid-template-columns:repeat(6,1fr);border-top:1px solid {_C['line']};border-bottom:1px solid {_C['line']};margin-bottom:56px;">{stat_html}</div>
@@ -609,7 +655,7 @@ function matrix(){
609
655
  probs.forEach(pr => {
610
656
  const inCell = DATA.risks.filter(r => r.disposition==='open' && r.prob===pr && (r.impact===im || (r.impact==='critical' && im==='high')));
611
657
  const z = zone(PV[pr]*PV[im]); const occ = inCell.length>0;
612
- const chips = inCell.map(r => `<span style="font-size:11px;font-weight:600;padding:2px 6px;border-radius:3px;border:1px solid ${z.b};color:${S.sel===r.id?C.bg:z.t};background:${S.sel===r.id?z.t:'transparent'};">${esc(r.short)}</span>`).join('');
658
+ const chips = inCell.map(r => `<span title="${esc(r.id)}: ${esc(r.desc)}" style="cursor:help;font-size:11px;font-weight:600;padding:2px 6px;border-radius:3px;border:1px solid ${z.b};color:${S.sel===r.id?C.bg:z.t};background:${S.sel===r.id?z.t:'transparent'};">${esc(r.short)}</span>`).join('');
613
659
  h += `<div data-risk="${occ?inCell[0].id:''}" style="min-height:56px;border:1px solid ${occ?z.b:'#191d21'};background:${occ?z.s:z.f};border-radius:3px;display:flex;gap:4px;align-items:center;justify-content:center;flex-wrap:wrap;cursor:${occ?'pointer':'default'};">${chips}</div>`;
614
660
  });
615
661
  });
@@ -674,7 +720,7 @@ function docs(){
674
720
  <span style="font-size:12.5px;color:${C.text};">${esc(d.kind)}</span>
675
721
  <span class="mono-tag" style="justify-self:start;">${esc(d.id)}</span>
676
722
  <span style="font-size:12px;color:${d.status==='active'?C.amberT:C.muted};">${esc(d.status)}</span>
677
- <span style="text-align:right;color:${d.passing?C.green:C.red};font-size:14px;">${d.passing?'✓':'✗'}</span></div>`).join('');
723
+ <span title="${d.passing?'passes every blocking structural check':'failing one or more blocking checks'}" style="cursor:help;text-align:right;color:${d.passing?C.green:C.red};font-size:14px;">${d.passing?'✓':'✗'}</span></div>`).join('');
678
724
  }
679
725
 
680
726
  function work(){
@@ -700,7 +746,7 @@ function work(){
700
746
  <div><div style="font-size:13px;color:${C.text};line-height:1.45;">${w.url?`<a style="text-decoration:none;" href="${esc(w.url)}">${esc(w.title)}</a>`:esc(w.title)}</div>
701
747
  <div style="font-size:10.5px;color:${C.dim};margin-top:6px;">linked ${esc(w.link)}</div></div>
702
748
  <span style="font-size:11.5px;color:${C.muted};">${esc(w.assignee)}</span>
703
- <span style="text-align:right;font-size:11.5px;color:${stC(w.state)};">${w.state==='done'?'done ✓':esc(w.state)}</span></div>`).join('');
749
+ <span title="state read from the bridge-managed issue; presentation never alters the derived RAG" style="cursor:help;text-align:right;font-size:11.5px;color:${stC(w.state)};">${w.state==='done'?'done ✓':esc(w.state)}</span></div>`).join('');
704
750
  }
705
751
 
706
752
  function render(){ matrix(); riskCtl(); riskTable(); docs(); work(); }
@@ -722,12 +768,12 @@ def _index_row(p) -> str:
722
768
  f'<div><div style="font-family:Archivo,system-ui,sans-serif;font-weight:700;font-size:15px;color:#F1F3F5;line-height:1.2;">{esc(p["name"])}</div>'
723
769
  f'<div style="font-size:10.5px;color:{_C["dim"]};margin-top:5px;display:flex;gap:10px;">'
724
770
  f'<span>{esc(p["id"])}</span><span>·</span><span>{esc(p["sponsor"])}</span></div></div>'
725
- f'<div style="text-align:center;"><div style="font-size:12.5px;color:{covc};">{cov}%</div>'
771
+ f'<div title="traceability links traced over required, all chains" style="cursor:help;text-align:center;"><div style="font-size:12.5px;color:{covc};">{cov}%</div>'
726
772
  f'<div style="height:4px;background:#16191c;margin-top:7px;border-radius:2px;overflow:hidden;">'
727
773
  f'<span style="display:block;height:100%;width:{cov}%;background:{covc};"></span></div></div>'
728
774
  f'<span style="text-align:center;font-size:12.5px;color:{_C["soft"]};">{esc(p["docs"])}</span>'
729
775
  f'<span style="text-align:center;font-size:12px;color:{_C["amberT"] if p["risks"] else _C["faint"]};">{risks}</span>'
730
- f'<span style="text-align:right;font-size:11.5px;color:{_C["muted"]};">{esc(p["next"])}</span></a>')
776
+ f'<span title="nearest upcoming dated marker (milestone or operations review)" style="cursor:help;text-align:right;font-size:11.5px;color:{_C["muted"]};">{esc(p["next"])}</span></a>')
731
777
 
732
778
 
733
779
  def render_index_html(index) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docassert
3
- Version: 0.20.0
3
+ Version: 0.20.1
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
@@ -367,3 +367,15 @@ def test_after_cycle_blocks(tmp_path):
367
367
  g = build_graph(_seq_tree(tmp_path, cyclic=True))
368
368
  r = check_sequence_acyclic(g)
369
369
  assert not r.passed and r.blocking and "after-cycle" in r.detail
370
+
371
+
372
+ def test_report_tooltips_present(tmp_path):
373
+ _cd_root()
374
+ m = S.build_status(_seq_tree(tmp_path), project="PRJ-001-SQ")
375
+ out = S.render_html(m)
376
+ assert 'title="Derived RAG.' in out # stat derivation
377
+ assert "not that the milestone completed" in out or "Temporal fact" in out
378
+ assert "scope point(s) = " in out # bar breakdown tooltip
379
+ assert 'title="Causes:' in out or "No amber or red causes" in out
380
+ idx = S.render_index_html(S.build_index(ROOT / "documents"))
381
+ assert "nearest upcoming dated marker" in idx
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes