docs(rack): Phase 2 power design spec
This commit is contained in:
parent
9253d1ca0d
commit
f4022edf3b
1 changed files with 163 additions and 0 deletions
163
notes/dev/specs/2026-06-24-rack-power-design.md
Normal file
163
notes/dev/specs/2026-06-24-rack-power-design.md
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
# 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:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
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
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# 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
|
||||||
|
|
||||||
|
- `pdu01` — `rack_face: left`, `outlets: 8`.
|
||||||
|
- `pdu02` — `rack_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`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- 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.
|
||||||
Loading…
Add table
Reference in a new issue