tbox: v1.6 ...

This commit is contained in:
Bastien Gourdon
2026-08-05 09:14:34 +02:00
parent 1c8ab77003
commit fbc63e55d8
9 changed files with 2669 additions and 0 deletions
+218
View File
@@ -0,0 +1,218 @@
#!/usr/bin/env python3
"""
Generate the Excel rulebook from rules.yaml (EV-008).
USAGE
python3 generate_rulebook_xlsx.py [output.xlsx]
rules.yaml is the single source of truth. This script never edits it.
The output must never be edited by hand (EV-007).
"""
import os
import sys
import yaml
from openpyxl import Workbook
from openpyxl.styles import Alignment, Border, Font, PatternFill, Side
from openpyxl.utils import get_column_letter
HERE = os.path.dirname(os.path.abspath(__file__))
SRC = os.path.join(HERE, "rules.yaml")
OUT = sys.argv[1] if len(sys.argv) > 1 else os.path.join(HERE, "PR_TBox_Rulebook.xlsx")
FONT = "Arial"
INK = "1F2933"
HEAD_FILL = PatternFill("solid", fgColor="1F2933")
BAND = {
"Identifier": "E8F0F7",
"Label": "EAF3EC",
"Declaration": "FBF0E4",
"Evolution": "F2EAF5",
}
MAJOR_FILL = PatternFill("solid", fgColor="FFF4CE")
THIN = Side(style="thin", color="C9CFD6")
BORDER = Border(left=THIN, right=THIN, top=THIN, bottom=THIN)
def selfcheck(doc):
"""EV-011 and EV-012 applied to the rulebook itself."""
problems = []
for r in doc["rules"]:
ctl = r.get("control") or {}
if not ctl.get("tier"):
problems.append("%s: no control tier (EV-012)" % r["id"])
if r.get("severity") == "BLOCKING" and not ctl.get("executor"):
problems.append("%s: BLOCKING without an executor (EV-011)" % r["id"])
if r.get("category") not in doc["categories"]:
problems.append("%s: unknown category" % r["id"])
return problems
def flow(t):
return " ".join((t or "").split())
def joined(v):
return ", ".join(v) if isinstance(v, list) else (v if v else "")
def header(ws, cols):
for i, (title, width) in enumerate(cols, start=1):
c = ws.cell(row=1, column=i, value=title)
c.font = Font(name=FONT, size=10, bold=True, color="FFFFFF")
c.fill = HEAD_FILL
c.alignment = Alignment(vertical="center", horizontal="left", wrap_text=True)
c.border = BORDER
ws.column_dimensions[get_column_letter(i)].width = width
ws.row_dimensions[1].height = 28
ws.freeze_panes = ws.cell(row=2, column=1)
ws.auto_filter.ref = "A1:%s1" % get_column_letter(len(cols))
def put(ws, r, c, value, wrap=False, bold=False, fill=None, size=10):
cell = ws.cell(row=r, column=c, value=value)
cell.font = Font(name=FONT, size=size, bold=bold, color=INK)
cell.alignment = Alignment(vertical="top", wrap_text=wrap)
cell.border = BORDER
if fill:
cell.fill = fill
return cell
def sheet_cover(wb, doc):
m = doc["meta"]
ws = wb.create_sheet("Cover")
ws.column_dimensions["A"].width = 24
ws.column_dimensions["B"].width = 100
put(ws, 1, 1, m["title"], bold=True, size=14)
rows = [
("Version", "%s%s" % (m["version"], m["status"])),
("Generated", "%s from rules.yaml" % m["date"]),
("Scope", flow(m["scope"])),
("Audience", flow(m["audience"])),
("Generation", "Generated from rules.yaml by generate_rulebook_xlsx.py. "
"Do not edit this workbook: edit the source and regenerate "
"(EV-007, EV-008)."),
]
r = 3
for k, v in rows:
put(ws, r, 1, k, bold=True)
put(ws, r, 2, v, wrap=True)
ws.row_dimensions[r].height = 15 * (len(v) // 95 + 1)
r += 1
r += 1
put(ws, r, 1, "Categories", bold=True, size=12)
r += 1
for k, v in doc["categories"].items():
put(ws, r, 1, k, bold=True, fill=PatternFill("solid", fgColor=BAND[k]))
put(ws, r, 2, "%s%s" % (v["title"], flow(v["intent"])), wrap=True)
ws.row_dimensions[r].height = 30
r += 1
r += 1
put(ws, r, 1, "Counts", bold=True, size=12)
r += 1
for label, formula in [
("Rules", "=COUNTA(Rules!A2:A200)"),
("of which blocking", '=COUNTIF(Rules!D2:D200,"BLOCKING")'),
("Examples", "=COUNTA(Examples!A2:A400)"),
("Classes", "=COUNTA(Abstractness!A2:A200)"),
("of which abstract", "=COUNTIF(Abstractness!B2:B200,TRUE)"),
("Display entries", "=COUNTA(Display!A2:A200)"),
]:
put(ws, r, 1, label)
put(ws, r, 2, formula)
r += 1
def sheet_rules(wb, doc):
ws = wb.create_sheet("Rules")
header(ws, [("Rule", 10), ("Category", 14), ("Title", 40), ("Severity", 11),
("Statement", 70), ("Applies to", 24), ("Enforced at", 14),
("Executor", 26), ("Procedure", 16), ("Related", 16),
("Why", 90)])
r = 2
for x in doc["rules"]:
ctl = x.get("control") or {}
band = PatternFill("solid", fgColor=BAND[x["category"]])
put(ws, r, 1, x["id"], bold=True, fill=band)
put(ws, r, 2, x["category"], fill=band)
put(ws, r, 3, x["title"], wrap=True, bold=True)
put(ws, r, 4, x["severity"],
fill=MAJOR_FILL if x["severity"] != "BLOCKING" else None)
put(ws, r, 5, flow(x["statement"]), wrap=True)
put(ws, r, 6, joined(x.get("scope")), wrap=True)
put(ws, r, 7, joined(ctl.get("tier")))
put(ws, r, 8, ctl.get("executor") or "")
put(ws, r, 9, ctl.get("procedure") or "pending")
put(ws, r, 10, x.get("filiation") or "")
put(ws, r, 11, flow(x.get("rationale")), wrap=True)
ws.row_dimensions[r].height = 78
r += 1
def sheet_examples(wb, doc):
ws = wb.create_sheet("Examples")
header(ws, [("Rule", 10), ("Category", 14), ("From", 48), ("To", 48), ("Note", 62)])
r = 2
for x in doc["rules"]:
for e in x.get("examples") or []:
band = PatternFill("solid", fgColor=BAND[x["category"]])
put(ws, r, 1, x["id"], bold=True, fill=band)
put(ws, r, 2, x["category"], fill=band)
put(ws, r, 3, str(e["from"]), wrap=True)
put(ws, r, 4, str(e["to"]), wrap=True)
put(ws, r, 5, e.get("note") or "", wrap=True)
r += 1
def sheet_abstractness(wb, doc):
ws = wb.create_sheet("Abstractness")
header(ws, [("Class", 36), ("isAbstract", 12), ("Note", 96)])
r = 2
for a in doc["abstractness"]:
put(ws, r, 1, a["term"], bold=a["is_abstract"])
put(ws, r, 2, a["is_abstract"])
put(ws, r, 3, a.get("note") or "", wrap=True)
r += 1
def sheet_display(wb, doc):
ws = wb.create_sheet("Display")
header(ws, [("IRI", 36), ("rdfs:label", 36), ("shortLabel", 22),
("acronym", 10), ("Note", 80)])
r = 2
for d in doc["display"]:
put(ws, r, 1, d["iri"], bold=True)
put(ws, r, 2, d["label"])
put(ws, r, 3, d.get("short_label") or "")
put(ws, r, 4, d.get("acronym") or "")
put(ws, r, 5, d.get("note") or "", wrap=True)
r += 1
def main():
doc = yaml.safe_load(open(SRC, encoding="utf-8"))
problems = selfcheck(doc)
if problems:
print("SELF-CHECK FAILED")
for p in problems:
print(" " + p)
sys.exit(1)
wb = Workbook()
wb.remove(wb.active)
sheet_cover(wb, doc)
sheet_rules(wb, doc)
sheet_examples(wb, doc)
sheet_abstractness(wb, doc)
sheet_display(wb, doc)
wb.save(OUT)
print("self-check passed: %d rules" % len(doc["rules"]))
print("written: %s (%d sheets)" % (OUT, len(wb.sheetnames)))
if __name__ == "__main__":
main()