MakerFLOSS/notes/dev/specs/2026-06-24-rack-power-design.md
2026-06-24 14:28:48 +02:00

6.7 KiB

Rack Power (Phase 2) Design

Date: 2026-06-24 Status: Approved Parent spec: notes/dev/specs/2026-06-24-rack-documentation-design.md (Phase 2)

Goal

Add power-distribution data to the rack documentation pipeline and render it as a mermaid graph on the generated rack page, so the published page at docs.makerfloss.eu shows which PDU/outlet feeds each device and which devices have redundant (dual-PSU) feeds. This reuses every Phase 1 mechanism: the same scripts/gen_rack.py generator, the same generated files under docs/infrastructure/racks/, and the same CI drift guard.

Context

  • Phase 1 (rack elevation) is merged. scripts/gen_rack.py reads docs/hardware/*.md files carrying a rack: field, validates placement (U range, overlap, 0U rules), and writes <rack>-elevation.svg + <rack>.md per rack. Tests in tests/test_gen_rack.py.
  • The pdu value is already in the hardware kind enum (scripts/overview_config.yml), so PDU files already validate and already appear in the hardware index under "PDUs".
  • Phase 1 already renders 0U rack_face: left|right items as side-rails in the SVG, so PDU files need no new SVG code to appear in the elevation.
  • The Makefile docs-check target and CI Fail on drift step already diff the entire docs/infrastructure/racks/ directory, so a regenerated page with a power graph is already drift-covered — no Makefile/CI edits required.
  • Mermaid is not yet enabled in mkdocs.yml (superfences has no mermaid custom fence). Enabling it was nominally a Phase 3 item; Phase 2's graph needs it, so it is pulled forward into this phase (decided during brainstorming).
  • The mfNN rack positions are fictional placeholders proving the pipeline. The power data added here is similarly provisional until real values are given.

Data model

Powered devices — power: frontmatter

Each powered device gains a power list; each entry is one feed:

power:
  - { pdu: pdu01, outlet: 1 }
  - { pdu: pdu02, outlet: 1 }   # a second entry = a redundant PSU feed

power is optional — a device with no power field simply contributes no power edges.

PDU files — new lightweight hardware items

# docs/hardware/pdu01.md
hostname: pdu01
kind: pdu
status: in-use
rack: rack01
rack_face: left
outlets: 8

PDUs are 0U side-rail items (rack_face: left|right, no rack_u/u_height), exactly the shape Phase 1's validator and SVG already handle. outlets is a new field, validated by gen_rack.py (below). No overview_config.yml change is needed: kind: pdu is already an enum value, and outlets is an extra field that gen_overview.py ignores.

Provisional data populated by this phase

  • pdu01rack_face: left, outlets: 8.
  • pdu02rack_face: right, outlets: 8.
  • mf00..mf04 — fed from pdu01 outlets 1..5 respectively.
  • mf00 — additionally fed from pdu02 outlet 1 (the redundant demonstration).

Validation (rule 3 from the parent spec)

A new validate_power(items: list[dict]) -> None in gen_rack.py, called from generate() after per-item placement validation and before/with overlap checking. It raises SchemaError (→ stderr + exit 1, nothing written) when:

  1. A kind: pdu file does not declare outlets as a positive integer.
  2. A power value is not a list, or an entry is not a mapping.
  3. An entry lacks a non-empty string pdu or an integer outlet.
  4. An entry's pdu does not resolve to a loaded file whose kind == pdu.
  5. An entry's outlet is outside 1..outlets of the referenced PDU.

PDU resolution is by hostname against all loaded rack items (a PDU lookup map {hostname: fm for fm in items if kind == pdu}).

Rendering

render_power(rack, items) -> str

Returns a fenced mermaid block, or "" when no device in the rack has any power entry (so the ## Power section is omitted for power-less racks).

  • ```mermaid + flowchart LR.
  • One node per PDU that is referenced or placed in the rack: label pdu01<br/>8 outlets.
  • One node per powered device: label = hostname.
  • One edge per feed: pduNode -->|outlet N| deviceNode.
  • Node ids are the hostname with non-alphanumeric characters replaced by _ (display text keeps the real hostname via the quoted label), guarding against ids that mermaid would reject.
  • Deterministic: PDU nodes sorted by hostname, device nodes by (rack_u, hostname), edges sorted by (pdu, outlet, device).

Redundant feeds render naturally as two edges into one device node, from two different PDUs.

render_page change

Insert a ## Power section containing render_power(...) between the Elevation and Occupancy sections — only when render_power returns non-empty.

mkdocs (pulled forward)

Add the mermaid custom fence to the existing pymdownx.superfences entry in mkdocs.yml:

  - pymdownx.superfences:
      custom_fences:
        - name: mermaid
          class: mermaid
          format: !!python/name:pymdownx.superfences.fence_code_format

Material ships mermaid.js and activates it on this fence, so mkdocs build --strict renders the graph as a diagram.

Integration

File Change
scripts/gen_rack.py Add validate_power; call it in generate; add render_power; insert ## Power in render_page
tests/test_gen_rack.py Add validate_power + render_power + generate power cases
mkdocs.yml Enable mermaid via superfences custom fence
docs/hardware/pdu01.md, pdu02.md New 0U PDU files (kind: pdu, outlets: 8)
docs/hardware/mf00.md..mf04.md Add power: lists
docs/hardware/index.md Regenerated (PDUs now listed)
docs/infrastructure/racks/rack01.md, rack01-elevation.svg Regenerated (power section + PDU side-rails)

No Makefile, .forgejo/workflows/docs.yml, or overview_config.yml changes.

Test plan

  • Unit — validate_power: accept a valid feed; reject unknown pdu, pdu pointing at a non-pdu kind, outlet of 0 / above outlets, a malformed entry (non-mapping / missing keys), and a pdu file with missing/zero/non-int outlets.
  • Unit — render_power: PDU and device nodes present; a redundant device has two incoming edges; returns "" when no device has power; deterministic for reordered input.
  • Integration — generate: with valid fixtures the page contains the ## Power section and the mermaid fence; with a dangling pdu reference it returns 1 and writes nothing.
  • Drift: make docs-check exits 0 after regeneration (existing guard, unchanged).
  • Visual: mkdocs build --strict succeeds and the rack page shows the power graph as a rendered diagram (not a raw code block), with mf00 showing two incoming feeds.