tbox: v1.6 ...
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate the Markdown processbook, with Mermaid views, from processes.yaml
|
||||
(EV-008). The BPMN files remain the authoritative form; the views here are
|
||||
derived from the same source and cannot drift from it.
|
||||
|
||||
USAGE
|
||||
python3 generate_processbook_md.py [output.md]
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
import yaml
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
SRC = os.path.join(HERE, "processes.yaml")
|
||||
OUT = sys.argv[1] if len(sys.argv) > 1 else os.path.join(HERE, "PR_TBox_Processbook.md")
|
||||
|
||||
SHAPE = {
|
||||
"start": ("([", "])"),
|
||||
"end": ("([", "])"),
|
||||
"userTask": ("[", "]"),
|
||||
"scriptTask": ("[", "]"),
|
||||
"callActivity": ("[[", "]]"),
|
||||
"gateway": ("{", "}"),
|
||||
}
|
||||
KIND = {
|
||||
"userTask": "human",
|
||||
"scriptTask": "script",
|
||||
"callActivity": "calls",
|
||||
"gateway": "decision",
|
||||
"start": "start",
|
||||
"end": "end",
|
||||
}
|
||||
|
||||
|
||||
def flow(t):
|
||||
return " ".join((t or "").split())
|
||||
|
||||
|
||||
def mermaid(proc):
|
||||
out = ["```mermaid", "flowchart TD"]
|
||||
for n in proc["flow"]:
|
||||
o, c = SHAPE[n["type"]]
|
||||
label = n["name"].replace('"', "'")
|
||||
if n["type"] == "callActivity":
|
||||
label = "%s: %s" % (n["calls"], label)
|
||||
out.append(' %s%s"%s"%s' % (n["id"], o, label, c))
|
||||
for f in proc["flows"]:
|
||||
if f.get("condition"):
|
||||
out.append(' %s -->|%s| %s' % (f["from"], f["condition"], f["to"]))
|
||||
else:
|
||||
out.append(" %s --> %s" % (f["from"], f["to"]))
|
||||
for n in proc["flow"]:
|
||||
if n["type"] == "userTask":
|
||||
out.append(" class %s human;" % n["id"])
|
||||
elif n["type"] == "gateway":
|
||||
out.append(" class %s decision;" % n["id"])
|
||||
out.append(" classDef human fill:#FFF4CE,stroke:#B08900;")
|
||||
out.append(" classDef decision fill:#E8F0F7,stroke:#3E6E96;")
|
||||
out.append("```")
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
doc = yaml.safe_load(open(SRC, encoding="utf-8"))
|
||||
m, out = doc["meta"], []
|
||||
w = out.append
|
||||
|
||||
w("# %s" % m["title"])
|
||||
w("")
|
||||
w("**Version %s** — %s — generated %s" % (m["version"], m["status"], m["date"]))
|
||||
w("")
|
||||
w("> Generated from `processes.yaml`. The BPMN files in `bpmn/` are the "
|
||||
"authoritative form; the views below are derived from the same source. "
|
||||
"Do not edit this document (EV-007, EV-008).")
|
||||
w("")
|
||||
w(flow(m["scope"]))
|
||||
w("")
|
||||
w("## Procedures")
|
||||
w("")
|
||||
w("| Id | Family | Procedure | Scope |")
|
||||
w("|---|---|---|---|")
|
||||
for p in doc["processes"]:
|
||||
w("| **%s** | %s | %s | %s |" % (p["id"], p["family"], p["name"], p.get("scope", "")))
|
||||
w("")
|
||||
w("## Reading a diagram")
|
||||
w("")
|
||||
w("- A **rounded** node is a start or end state.")
|
||||
w("- A **diamond** is a decision; every outgoing path is labelled.")
|
||||
w("- A **shaded** box is a human task: it cannot be automated, and the work "
|
||||
"stops there until someone acts.")
|
||||
w("- A **double-bordered** box calls another procedure by its identifier.")
|
||||
w("")
|
||||
|
||||
for p in doc["processes"]:
|
||||
w("---")
|
||||
w("")
|
||||
w("# %s — %s" % (p["id"], p["name"]))
|
||||
w("")
|
||||
w("| | |")
|
||||
w("|---|---|")
|
||||
w("| Family | %s |" % p["family"])
|
||||
w("| Scope | %s |" % p.get("scope", ""))
|
||||
w("| Trigger | %s |" % p.get("trigger", ""))
|
||||
w("| Inputs | %s |" % ", ".join(p.get("inputs") or []))
|
||||
w("| Outputs | %s |" % ", ".join(p.get("outputs") or []))
|
||||
rules = sorted({r for n in p["flow"] for r in (n.get("rules") or [])})
|
||||
w("| Rules enforced | %s |" % (", ".join(rules) or "-"))
|
||||
calls = sorted({n["calls"] for n in p["flow"] if n["type"] == "callActivity"})
|
||||
w("| Calls | %s |" % (", ".join(calls) or "-"))
|
||||
w("")
|
||||
if p.get("parameter"):
|
||||
w("**Parameter.** %s" % flow(p["parameter"]))
|
||||
w("")
|
||||
if p.get("note"):
|
||||
w("**Note.** %s" % flow(p["note"]))
|
||||
w("")
|
||||
out.extend(mermaid(p))
|
||||
w("")
|
||||
w("| Step | Type | Rules |")
|
||||
w("|---|---|---|")
|
||||
for n in p["flow"]:
|
||||
if n["type"] in ("start", "end"):
|
||||
continue
|
||||
name = n["name"]
|
||||
if n["type"] == "callActivity":
|
||||
name = "%s (%s)" % (name, n["calls"])
|
||||
w("| %s | %s | %s |"
|
||||
% (name, KIND[n["type"]], ", ".join(n.get("rules") or []) or "-"))
|
||||
w("")
|
||||
|
||||
w("---")
|
||||
w("")
|
||||
w("# Rule coverage")
|
||||
w("")
|
||||
w("Which procedures enforce each rule. Derived, not maintained: a rule cited "
|
||||
"in a diagram appears here automatically.")
|
||||
w("")
|
||||
cover = {}
|
||||
for p in doc["processes"]:
|
||||
for n in p["flow"]:
|
||||
for r in n.get("rules") or []:
|
||||
cover.setdefault(r, set()).add(p["id"])
|
||||
w("| Rule | Procedures |")
|
||||
w("|---|---|")
|
||||
for r in sorted(cover):
|
||||
w("| %s | %s |" % (r, ", ".join(sorted(cover[r]))))
|
||||
w("")
|
||||
|
||||
open(OUT, "w", encoding="utf-8").write("\n".join(out))
|
||||
print("written: %s (%d lines, %d procedures, %d rules covered)"
|
||||
% (OUT, len(out), len(doc["processes"]), len(cover)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user