THE PLATFORM VIEW
The Platform Schematic
One NX sheet holding the whole platform's logical architecture — fluid, electrical, signal and thermal together, each in its own colour — with every block showing how much of itself the domains have actually implemented. It is generated from a live Teamcenter read, it is scored against the built drawing rather than against the intent, and it runs on a product it has never seen.
The gap it fills
Capital owns the electrical view. NX Schematic Designer owns the fluid view. Both are excellent, and both are silos: neither can draw the wire that leaves a controller as a signal and arrives at a pump that also carries fuel. The place those domains meet is exactly the place nobody had a picture of.
The Platform Schematic is that picture. It is not a replacement for either tool — it holds no pipe sizes and no conductor gauges. It holds the logical architecture, which is the one thing all four domains genuinely share, plus the status of everything downstream of it.

Four domains on one sheet, and the trap underneath it
NX Diagramming will do this, and the mechanism is not the one the UI implies. Discipline is a property of the OBJECT, not of the sheet — NodeBuilder, ConnectionBuilder and PortBuilder each take SetDisciplines(), so one sheet legitimately carries a Piping pump, an Electrical bus and a Data link side by side.
SetDisciplines(["Banana"]) is accepted and round-trips. NX will let a fuel line land on a signal port and report nothing, which means a drawing that looks right is not evidence that it is right. Every claim on this page is checked by reading the saved part back, never by trusting the write.| Domain | Drawn as | Comes from |
|---|---|---|
| fluid | Piping, solid line | Seg0Implements → Seg0Interface, or the port-name token registry |
| electrical | Electrical, dashed | same two carriers |
| signal | Data, dashed | same two carriers |
| thermal | Thermal, traced | same two carriers |
The interface relation is semantic truth and only exists where it was authored; the name token is lossy but survives every hop. Both are read, the conflict is reported rather than resolved silently, and each port records which carrier answered.
L2: the badge reads in DOMAINS, not in tool names
Each block carries a band showing how much of itself is implemented. The first version labelled those rows NX, CAP, SW — the tool that owes the work. That is the right answer for whoever has to go and do it, and the wrong answer for whoever is reading a platform.
A systems engineer does not ask how Capital is doing. They ask whether the electrical architecture is settled — and the tool that answers that question will change (a Polarion route for signals, a second thermal tool) without the question changing. Domain is the durable noun; the tool is this month's answer to it.

ElectricBoostPump is complete on fluid and power and 2 of 3 on signal; FuelControlUnit is 6 of 9. The bar is for the across-the-room glance, the n/m is the authoritative reading, and both are the gate's numbers rather than the generator's.Nothing is lost by aggregating. A domain row sums the branches that can carry it, and it carries the list, so SIGNAL 2/3 can always be asked "of what?" — the answer is on the L3 sheet, which keeps the tool columns precisely because L3 is where you go to find out who still owes it. The two levels now split cleanly: L2 asks whether the domain is done, L3 asks who is holding it up.
The status panel — and why it is not a full accountability table
The obvious ask is a row per block per domain. On the fuel system that is 34 rows; on the CubeSat it is 108, and it would still be 108 rows on the day everything is finished. A table whose size is set by the model rather than by the problem gets read once. Worse, it duplicates the badges — every one of those rows is already drawn inside the block it describes.
So the panel carries the readings a badge cannot give, because they are properties of the whole sheet:

| Section | What it answers | Why it is on the drawing |
|---|---|---|
| Rollup, one row per domain | Is the fluid architecture settled? | No single block can answer a question about the platform. |
| Open, by block | What do I do next? | The named reason a rollup row is not 100%. Capped at seven and the overflow counted — "+15 more" is information; a quiet cut is a lie. |
| Open cells over time | Are we converging? | A rollup says what is open today and cannot say whether today is better than last week. |
| The gate's verdict, verbatim | Would this pass? | Restating it in the panel's own words is how the two drift apart. |
bin/coverage-history.mjs reads past versions of the ledger out of the commits that produced them, so every point is a real measurement taken at the date it claims and cannot be back-filled. The ledger itself deliberately carries no timestamp — it is a pure function of its inputs, so an unchanged rerun produces no diff, and that is exactly what makes the derived series meaningful. The panel dates it from the file instead.Drawing it found a defect that reading it had not
The panel's first build read 31 open two lines above the gate's own verdict reading 29 blocking — on the same sheet, in the same plot. Both numbers were computed correctly by their own rules, and the rules disagreed: the panel counted with the badge's bucket map, and UNWIRED_IN_SOURCE had been added to the gate one commit earlier and never to that map, so it fell through to the "problem" default.
Two fixes, and the second is the one that matters. The panel now counts with BLOCKING_STATES — the gate's own policy — so the two numbers cannot diverge by construction rather than by vigilance. And the bucket map gains the state explicitly as missing, because the default had been drawing it hatched: "it landed but does not reconcile", said about a port the source model never wired in the first place.
Colour: hue is identity, never status
The sheet is drawn in colour by domain, and the colour was in the model the whole time. NX ships LineTypes carrying strokeColor — Primary.xml, which every fluid wire here already used, is 0 0 225 — and the plot was throwing it away at the last step with PrintPDFBuilder.Color.BlackOnWhite. Measured rather than assumed: the same part plotted with PartColors produced 18,083 saturated pixels against 7,177, and all of the latter were the Siemens logo in the title block.
The shipped palette is still the wrong one for this drawing, because it was chosen for P&IDs:
| Domain | NX ships | Why that fails here | Used instead |
|---|---|---|---|
| fluid | 0 0 225 blue | — | 0 114 178 blue |
| electrical | 0 153 255 cyan | a second blue, beside the domain it sits next to most often | 213 94 0 vermillion |
| signal | 0 0 0 black | the colour of every outline, stub and label — the commonest domain reads as "not a domain" | 0 158 115 bluish green |
| thermal | 255 0 0 red | red is what a drawing should keep for something is wrong | 230 159 0 orange |
The replacement palette is Okabe-Ito, which stays distinguishable under the common forms of colour blindness, and it deliberately leaves red unassigned.
n/m count — every one of which survives a mono plot and a photocopier. Putting hue on status as well would give one channel two meanings and turn a schematic into 37 traffic lights with an architecture somewhere behind them. A block whose FLUID badge is empty is drawn in exactly the same blue as one whose badge is full. Line font and width are untouched, so nothing is lost when this prints in black and white.The status panel's rollup rows are drawn in their own domain's hue, so the sheet carries its own key rather than needing a separate legend box. Colour is opt-in behind --colour: without it an existing pipeline plots exactly what it plotted yesterday.
The trap: a line type is resolved by library, not by path
SetLineType takes an absolute path, and it is ignored unless that path sits inside the library tree Schematic Designer indexes. Nothing reports this. The path exists, so an os.path.exists check passes. Commit() succeeds. And GetLineType() reads back an empty string on a committed connection whether it worked or not. The only symptom is that a wire keeps the colour its discipline would have given it — which looks like a perfectly plausible drawing.
Two experiments settled it, in the order that eliminates the most:
| Experiment | Result | What it eliminates |
|---|---|---|
Force every wire to the shipped Tracing.xml (red) | 10,075 red pixels | SetLineType works at all |
| Copy our file into the shipped folder | 16,780 palette-blue pixels, zero NX blue | the file was fine; the folder was the problem |
Data and thermal on Thermal. An undeclared discipline has no <LineTypePath>, so there is nowhere to resolve a line type from and NX falls back to black — while SetDisciplines accepts the value, Commit succeeds, and the connection reads back carrying Data. Thermal was black for the same reason and nobody had noticed, because the only thermal port in either model is unwired.| Suspected | Test | Verdict |
|---|---|---|
| the folder | fluid and electrical from the same directory | colour fine — not the folder |
| stroke width | force NX_Width_6 to match the working ones | no change |
| line priority | drop it to a low value | no change |
| the RGB value | try magenta instead | plotted black too |
| the discipline | change ONE wire from Data to Piping, holding the line type | 0 → 2,217 green pixels |

The fix is a generated library configuration that clones the Electrical discipline entry once per missing discipline, installed by bin/install-nx-library-config.mjs. It modifies one file in the NX installation and says so; the original is backed up into the repository and --revert puts it back. The file carries the read-only attribute rather than restrictive permissions, which is why the first attempt read as "Permission denied" and looked like it needed elevation.
Schematic_Library_ConfigurationFile_WIN at the copy. The preference exists — found by enumerating all 23,045 customer defaults rather than guessing names — and is read at startup, so UGII_USER_DIR is the only hook available. A minimal .dpv placed there is simply not read: with the file present and the variable set, the preference still returned the shipped path. The shipped .dpv files carry Application/Category/SubCategory/Tab attributes that are not discoverable from the API, which is the likely reason.
Scored against the built part, not against the intent
The coverage gate originally graded the spec — what the generator meant to draw. That is grading your own homework: anything the generator gets wrong is wrong identically on both sides of the comparison. It now reads the saved .prt back and scores the artifact, which was possible only after establishing that a built part really does retain port identity.
| Verdict | Means |
|---|---|
REALIZED | bound to a declared attach point that can carry this port's domain |
DEGRADED | it landed, but not provably on the right thing — a borrowed port, or an anchor NX minted at commit |
MISBOUND | it landed on something of the wrong domain |
NO_ARTIFACT | that branch has produced nothing for this block at all |
UNWIRED_IN_SOURCE | the model never wired the port, so nobody downstream was ever asked for it — not blocking, and stated separately so it cannot inflate the realized count |
WAIVED | a blocking state a named person accepted on the record |
Standard_tank symbol declares zero ports, so no wire on a tank can be proved to be on the right nozzle. That is the symbol working as designed, and a tank is worth drawing as a tank.What retyping one port revealed
The CubeSat's thermistor declared tempOut, and the token registry read temp as thermal. A domain says what a port carries, and a thermistor carries a measurement, not heat — the heat flows into it. Typed thermal, the model read Thermistor.tempOut → Radiator.heatIn: heat flowing out of a sensor into a radiator, backwards, and consistent-looking, so nothing flagged it. It also made Thermistor.tempOut → Heater.ctrlIn cross-domain, which the P&ID refused to draw, leaving the heater's command input unrealized.
Moving one token fixed the heater and, more usefully, made the remaining error visible instead of fixing it. The radiator wire is now reported cross-domain, next to a second one nobody had noticed: Thruster.cmdIn ← Valve.propellantValveOut, a command input wired to a propellant outlet. Both are defects in the source model, and neither is the transform's to paper over.
It runs on a product it has never seen
Nothing above is fitted to the fuel system. The second product — a 37-block CubeSat, four domains, structurally nothing like an aircraft fuel system — was run to find out what had been quietly hard-coded, and it found five things.

| What the second product broke | What it had been assuming |
|---|---|
| Four wires silently dropped | Ports are single-use. The fuel system happens to have no fan-in; the CubeSat feeds one controller input from two sensors. The symbol XSD's own default is unlimited. |
| Six blocks off the bottom edge | A D-size sheet, hard-coded from the only model that had ever been drawn. |
A meaningless AMES badge | The label table listed only the branches the fuel system exercises; thermal routes to Amesim. |
| An 8-port symbol for a 4-port pump | Exchange bundle ports are per-occurrence, and the same port appears once per occurrence path. |
| Four L3 nodes overwriting each other | A symbol's file identity and its display name are the same string. Four ports of one block landing on the same symbol generated into one folder, and every node silently used whichever was written last. |
Only the last was caught by a person reading output; the rest were caught by the gate refusing to agree with the drawing. That is the argument for scoring the artifact in one sentence.
Running it
# 1. Teamcenter -> logical blocks, ports, domains, wires
node bin/tc-to-logical-blocks.mjs --item 040819 --out out/nx/040819-blocks.json
# 2. the burn-down series, out of the commits that produced each ledger
node bin/coverage-history.mjs --ledger out/nx/cubesat/CubeSat040819.portcoverage.artifact.json \
--out out/nx/cubesat/CubeSat040819.history.json
# 3. symbols + layout + status panel -> the NX journal spec
node bin/make-platform-sheet.mjs --blocks out/nx/040819-blocks.json \
--symbols out/nx/cubesat/sheet-symbols \
--coverage out/nx/cubesat/CubeSat040819.portcoverage.artifact.json \
--history out/nx/cubesat/CubeSat040819.history.json \
--out out/nx/cubesat/platform-040819-spec.json --out-dir '...\out\nx\cubesat'
# 4. draw it (interactive NX - see below), then read the SAVED part back and score it
NX_SCHEMATIC_JOURNAL=tools/nx/build_logical_platform.py tools/nx/run-nx-schematic.sh ...
node bin/audit-port-coverage.mjs --master exchange.json --nx-part artifact.json --gate
--badge-by branch restores the tool labels if a site wants them. --panel-scale trades panel legibility against sheet size: at full block scale the panel pushed a twelve-block system from a D sheet to an E.
ugraf.exe through the USER_STARTUP user exit. run_journal.exe accepts ApplicationSwitchImmediate, returns without raising, and leaves ApplicationName at APP_NONE — so the switch looks like it worked and the reuse library rejects you several calls later, pointing at a different subsystem entirely. For the Dispatcher this is solved by a long-lived NX session host consuming a directory queue, because a Dispatcher service cannot start a GUI. See the NX branch page.Not done
- Two branches have produced nothing. 6 cells on the fuel system and 29 on the CubeSat are
NO_ARTIFACTagainst software and Amesim. Both gates therefore still exit non-zero, on that alone. They are deliberately left red rather than waived: nobody has decided those domains are out of scope. - NX-native click-through between levels. The descent is stamped into the drawing (
DRILL_TO,DRILL_LEVEL) and reads back out of the saved part, but a user follows it by opening the part the stamp names. Binding it to a symbol needs Schematic Designer runs, which the shipped templates do not set up. - The panel is drawn, not linked. Its numbers are a snapshot of the ledger at plot time. A sheet reopened a week later still shows the week-old panel — correct for a drawing, and worth knowing before treating it as a dashboard.