openup: EA model preparation (COD) - checklist, scripts, realizations intermediate
Per design-wi-2-architecture-cleanup.md §11.b/c: keyfob-station pipeline studied (add-wi19-uc11.ps1, export-model-json.ps1). Platform gap: EA COM pipeline is Windows-only; this session ran on Linux (no EABase.eapx found, no pwsh) - .eapx skeleton NOT committed, documented in model/README.md with the two path options. Landed: - docs/openup/model/BUILD-CHECKLIST.md: full EA-session checklist (packages, 3 actors, 12 UCs with scenarios, 13 associations, 12 realization packages / 36 elements) derived from use-case-model.md + uc-realization-mapping.md - docs/openup/model/README.md: platform note, status, drift rule - docs/openup/model/exports/uc-realizations.json: generated from mapping §2 via scripts/openup/markdown-to-json.py (tested locally) - scripts/openup/build-model.ps1, export-model.ps1: adapted from keyfob patterns, Windows-gated (human gate per design §11.d) - scripts/openup/markdown-to-json.py: local markdown -> JSON transform
This commit is contained in:
parent
7a9a5a3e7b
commit
41f1ec2887
6 changed files with 1311 additions and 0 deletions
149
scripts/openup/markdown-to-json.py
Normal file
149
scripts/openup/markdown-to-json.py
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate the machine-readable OpenUP model intermediates from the markdown sources.
|
||||
|
||||
Transforms the approved OpenUP artifacts (never hand-edited exports):
|
||||
docs/openup/analysis-design/uc-realization-mapping.md (§2 table)
|
||||
docs/openup/requirements/use-case-model.md (canonical UC names)
|
||||
into:
|
||||
docs/openup/model/exports/uc-realizations.json
|
||||
|
||||
The use-case-model.json intermediate is NOT produced here: the markdown does not
|
||||
carry per-step actor tags, so a faithful transformation requires the EA session
|
||||
(export-model.ps1) or staged decisions. See docs/openup/model/README.md.
|
||||
|
||||
Runs on the standard library only (Linux, no EA/COM required).
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[2]
|
||||
MAPPING_MD = REPO_ROOT / "docs/openup/analysis-design/uc-realization-mapping.md"
|
||||
UC_MODEL_MD = REPO_ROOT / "docs/openup/requirements/use-case-model.md"
|
||||
OUTPUT_JSON = REPO_ROOT / "docs/openup/model/exports/uc-realizations.json"
|
||||
|
||||
UC_ID_PATTERN = re.compile(r"^(UC-\d{2})\s*(.*)$")
|
||||
|
||||
|
||||
def canonical_use_case_names() -> dict[str, str]:
|
||||
"""UC id -> full use case name, from the use-case-model.md overview table."""
|
||||
names: dict[str, str] = {}
|
||||
for line in UC_MODEL_MD.read_text(encoding="utf-8").splitlines():
|
||||
cells = [cell.strip() for cell in line.strip().strip("|").split("|")]
|
||||
if len(cells) < 2:
|
||||
continue
|
||||
match = UC_ID_PATTERN.match(cells[0])
|
||||
if match:
|
||||
names[match.group(1)] = cells[1]
|
||||
if len(names) != 12:
|
||||
raise SystemExit(
|
||||
f"expected 12 use cases in {UC_MODEL_MD}, found {len(names)}"
|
||||
)
|
||||
return names
|
||||
|
||||
|
||||
def split_table_row(line: str) -> list[str]:
|
||||
return [cell.strip() for cell in line.strip().strip("|").split("|")]
|
||||
|
||||
|
||||
def is_table_row(line: str) -> bool:
|
||||
return line.strip().startswith("|") and line.strip().endswith("|")
|
||||
|
||||
|
||||
def parse_realization_table(mapping_text: str) -> tuple[list[dict], dict[str, str]]:
|
||||
"""Parse the §2 mapping table into per-UC realization entries.
|
||||
|
||||
Returns (realizations, paragraphs) where paragraphs carries the verbatim
|
||||
cross-cutting and dependency-rule paragraphs that follow the table.
|
||||
"""
|
||||
lines = mapping_text.splitlines()
|
||||
section_started = False
|
||||
realizations: list[dict] = []
|
||||
current: dict | None = None
|
||||
|
||||
for index, line in enumerate(lines):
|
||||
if line.startswith("## 2. UC realization mapping"):
|
||||
section_started = True
|
||||
continue
|
||||
if not section_started:
|
||||
continue
|
||||
if line.startswith("## 3."):
|
||||
break
|
||||
if not is_table_row(line):
|
||||
continue
|
||||
cells = split_table_row(line)
|
||||
if cells[0] and set(cells[0]) <= {"-", " ", ":"}:
|
||||
continue
|
||||
if cells[0] in ("UC", "Analysis class"):
|
||||
continue
|
||||
|
||||
uc_cell = cells[0]
|
||||
element = {
|
||||
# Emphasis markers are formatting, not content; element names are
|
||||
# identifiers (e.g. **AdventureService** -> AdventureService).
|
||||
# All other cells stay verbatim.
|
||||
"name": cells[1].replace("**", ""),
|
||||
"stereotype": cells[2],
|
||||
"designElement": cells[3],
|
||||
"notes": cells[4] if len(cells) > 4 else "",
|
||||
}
|
||||
id_match = UC_ID_PATTERN.match(uc_cell)
|
||||
if id_match:
|
||||
current = {"id": f"{id_match.group(1)} Realization", "useCaseId": id_match.group(1), "elements": [element]}
|
||||
realizations.append(current)
|
||||
elif uc_cell == "" and current is not None:
|
||||
current["elements"].append(element)
|
||||
else:
|
||||
raise SystemExit(f"unparseable mapping row {index + 1}: {line!r}")
|
||||
|
||||
paragraphs = {}
|
||||
for paragraph_key, paragraph_prefix in (
|
||||
("crossCutting", "Cross-cutting design elements"),
|
||||
("dependencyRule", "Dependency rule check"),
|
||||
):
|
||||
paragraphs[paragraph_key] = next(
|
||||
(line for line in lines if line.startswith(paragraph_prefix)), ""
|
||||
)
|
||||
return realizations, paragraphs
|
||||
|
||||
|
||||
def build_payload() -> dict:
|
||||
canonical_names = canonical_use_case_names()
|
||||
realizations, paragraphs = parse_realization_table(
|
||||
MAPPING_MD.read_text(encoding="utf-8")
|
||||
)
|
||||
if len(realizations) != 12:
|
||||
raise SystemExit(
|
||||
f"expected 12 realizations in {MAPPING_MD}, found {len(realizations)}"
|
||||
)
|
||||
for realization in realizations:
|
||||
realization["useCase"] = canonical_names[realization.pop("useCaseId")]
|
||||
return {
|
||||
"exportedFrom": (
|
||||
"docs/openup/analysis-design/uc-realization-mapping.md §2, generated by "
|
||||
"scripts/openup/markdown-to-json.py — generated file, never hand-edit"
|
||||
),
|
||||
"realizations": realizations,
|
||||
"crossCutting": paragraphs["crossCutting"],
|
||||
"dependencyRule": paragraphs["dependencyRule"],
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
payload = build_payload()
|
||||
OUTPUT_JSON.parent.mkdir(parents=True, exist_ok=True)
|
||||
OUTPUT_JSON.write_text(
|
||||
json.dumps(payload, indent=2, ensure_ascii=False) + "\n", encoding="utf-8"
|
||||
)
|
||||
element_count = sum(len(r["elements"]) for r in payload["realizations"])
|
||||
print(
|
||||
f"wrote {OUTPUT_JSON.relative_to(REPO_ROOT)}: "
|
||||
f"{len(payload['realizations'])} realizations, {element_count} elements"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue