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:
Jan-Peter von Hunnius 2026-10-03 17:38:06 +02:00
parent 7a9a5a3e7b
commit 41f1ec2887
6 changed files with 1311 additions and 0 deletions

View file

@ -0,0 +1,171 @@
# Skara Brae — EA model exporter (WI-2), adapted from keyfob-station's
# export-model-json.ps1 (COM walk of the model -> machine-readable JSON).
#
# Regenerates (generated files — never hand-edit):
# docs/openup/model/exports/use-case-model.json (actors, use cases, scenarios, steps)
# docs/openup/model/exports/uc-realizations.json (realization packages + elements)
# docs/openup/model/exports/xmi-backup.xml (lossless EA XMI 2.1 export of the root)
#
# Drift rule: markdown (use-case-model.md, uc-realization-mapping.md) and the model must
# agree; a mismatch is a defect fixed in the same cycle. uc-realizations.json content must
# match what scripts/openup/markdown-to-json.py generated from mapping §2.
#
# Platform: Windows + installed EA (design §11.c). If COM automation hits friction, the
# fallback is EA's manual Publish -> XML export into exports/ (see model/README.md).
$ErrorActionPreference = 'Stop'
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
$modelPath = Join-Path $repoRoot 'docs\openup\model\skara-brae.eapx'
if (-not (Test-Path $modelPath)) {
Write-Output "MISSING $modelPath - nothing to export"
exit 1
}
$exportsDir = Join-Path $repoRoot 'docs\openup\model\exports'
New-Item -ItemType Directory -Force -Path $exportsDir | Out-Null
$ucOutPath = Join-Path $exportsDir 'use-case-model.json'
$realOutPath = Join-Path $exportsDir 'uc-realizations.json'
$xmiOutPath = Join-Path $exportsDir 'xmi-backup.xml'
$app = $null
$eaBefore = @(Get-Process -Name EA -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id)
try {
$app = New-Object -ComObject EA.App
$app.Visible = $false
$repo = $app.Repository
$null = $repo.OpenFile($modelPath)
$root = $repo.Models.GetAt([int]0)
# ---------- Requirements / Use Case Model ----------
$ucModel = $root.Packages.GetByName('Requirements').Packages.GetByName('Use Case Model')
$actors = @()
$useCases = @()
for ($i = 0; $i -lt $ucModel.Elements.Count; $i++) {
$element = $ucModel.Elements.GetAt([int]$i)
if ($element.Type -eq 'Actor') {
$actors += [ordered]@{ name = $element.Name; notes = $element.Notes }
}
}
for ($i = 0; $i -lt $ucModel.Elements.Count; $i++) {
$element = $ucModel.Elements.GetAt([int]$i)
if ($element.Type -ne 'UseCase') { continue }
$associatedActors = @()
for ($c = 0; $c -lt $element.Connectors.Count; $c++) {
$connector = $element.Connectors.GetAt([int]$c)
if ($connector.Type -ne 'Association') { continue }
$otherId = if ($connector.ClientID -eq $element.ElementID) { $connector.SupplierID } else { $connector.ClientID }
$other = $repo.GetElementByID($otherId)
if ($other.Type -eq 'Actor') { $associatedActors += $other.Name }
}
$scenarios = @()
$element.Scenarios.Refresh()
for ($s = 0; $s -lt $element.Scenarios.Count; $s++) {
$scenario = $element.Scenarios.GetAt([int]$s)
$steps = @()
$scenario.Steps.Refresh()
for ($t = 0; $t -lt $scenario.Steps.Count; $t++) {
$scenarioStep = $scenario.Steps.GetAt([int]$t)
$steps += [ordered]@{ actor = $scenarioStep.StepType; text = $scenarioStep.Name }
}
$scenarios += [ordered]@{
name = $scenario.Name
type = $scenario.Type
notes = $scenario.Notes
steps = $steps
}
}
$useCases += [ordered]@{
name = $element.Name
notes = $element.Notes
actors = $associatedActors
scenarios = $scenarios
}
}
$ucPayload = [ordered]@{
exportedFrom = 'docs/openup/model/skara-brae.eapx (Requirements/Use Case Model)'
actors = $actors
useCases = $useCases
}
[System.IO.File]::WriteAllText($ucOutPath, ($ucPayload | ConvertTo-Json -Depth 8), [System.Text.Encoding]::UTF8)
Write-Output "exported: $ucOutPath ($($useCases.Count) use cases, $($actors.Count) actors)"
# ---------- Analysis-Design realizations ----------
$analysisDesign = $root.Packages.GetByName('Analysis-Design')
$realizations = @()
for ($p = 0; $p -lt $analysisDesign.Packages.Count; $p++) {
$realizationPackage = $analysisDesign.Packages.GetAt([int]$p)
if ($realizationPackage.Name -notlike 'UC-?? Realization') { continue }
$realizationPackage.Elements.Refresh()
$useCaseName = ''
$classes = @()
for ($e = 0; $e -lt $realizationPackage.Elements.Count; $e++) {
$element = $realizationPackage.Elements.GetAt([int]$e)
if ($element.Type -eq 'Collaboration') {
$element.Connectors.Refresh()
for ($c = 0; $c -lt $element.Connectors.Count; $c++) {
$connector = $element.Connectors.GetAt([int]$c)
if (($connector.Type -eq 'Realization' -or $connector.Type -eq 'Realisation') -and $connector.ClientID -eq $element.ElementID) {
$useCaseName = $repo.GetElementByID($connector.SupplierID).Name
}
}
}
if ($element.Type -eq 'Class') {
$classes += [ordered]@{
name = $element.Name
stereotype = $element.Stereotype
notes = $element.Notes
}
}
}
$realizations += [ordered]@{
id = $realizationPackage.Name
useCase = $useCaseName
elements = $classes
}
}
$architecture = $analysisDesign.Packages.GetByName('Architecture Overview')
$architecture.Elements.Refresh()
$crossCutting = @()
for ($e = 0; $e -lt $architecture.Elements.Count; $e++) {
$element = $architecture.Elements.GetAt([int]$e)
$crossCutting += [ordered]@{ name = $element.Name; notes = $element.Notes }
}
$realPayload = [ordered]@{
exportedFrom = 'docs/openup/model/skara-brae.eapx (Analysis-Design realization packages)'
realizations = $realizations
crossCutting = $crossCutting
}
[System.IO.File]::WriteAllText($realOutPath, ($realPayload | ConvertTo-Json -Depth 8), [System.Text.Encoding]::UTF8)
Write-Output "exported: $realOutPath ($($realizations.Count) realizations)"
# ---------- lossless XMI backup ----------
try {
$project = $repo.GetProjectInterface()
$null = $project.ExportPackageXMI($root.PackageGUID, $xmiOutPath, 1, 0)
Write-Output "exported: $xmiOutPath (XMI 2.1 backup of model root '$($root.Name)')"
}
catch {
Write-Output "WARNING: XMI export failed ($($_.Exception.Message)); use EA Publish -> XML manually per model/README.md option 2"
}
$null = $repo.CloseFile()
Write-Output 'EXPORT OK - review the exports diff, then commit .eapx + exports/ + scripts/openup/ as one commit'
}
catch {
Write-Output "FAILED: $($_.Exception.ToString())"
exit 1
}
finally {
Get-Process -Name EA -ErrorAction SilentlyContinue |
Where-Object { $eaBefore -notcontains $_.Id } |
ForEach-Object { Stop-Process -Id $_.Id -Force }
}