Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions docs/design-basis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Design basis

Every value in kitlist's equipment libraries traces to a cited source, so any
standard size can be checked against the document it came from.

## How sources are recorded

- Each library CSV in `src/kitlist/data/library/` has a `source` column holding a
reference ID from the table below.
- A test fails if a library uses an ID that isn't listed here, so no value can be
added without a source.
- Paywalled standards are cited by edition and clause. Their text is not reproduced.

## References

| ID | Document | Used for | Verified | Notes |
|---|---|---|---|---|
| DOE-431.196a | US 10 CFR 431.196(a)(2), low-voltage dry-type distribution transformers, table for units made 2016–2029 | `ieee-dry` kVA ratings | Against the regulation text, 2026-11-08 | Applies legally to 60 Hz units sold in the US; cited as a public record of the North American rating series. Free at ecfr.gov. |
| DOE-431.196b | US 10 CFR 431.196(b)(2), liquid-immersed distribution transformers, Table 5 | `ieee-pad` kVA ratings, limited to the scope of IEEE-C57.12.38 (single-phase, ≤ 250 kVA) and IEEE-C57.12.34 (three-phase) | Against the regulation text, 2026-11-08 | Same note as above. |
| IEC-60076-1 | IEC 60076-1:2011, Power transformers – Part 1: General, clause 5.1.2 (preferred values of rated power, R10 series of ISO 3) | `iec` kVA ratings | **No** | Values are the R10 preferred numbers. Paywalled; verify against a copy before relying on this family. |
| IEEE-C57.12.38 | IEEE C57.12.38-2025, pad-mounted, self-cooled, single-phase distribution transformers, 250 kVA and smaller | Scope of `ieee-pad` single-phase | Scope only, from the published abstract | Scope is 60 Hz; on 50 Hz projects, specify "in general accordance with, except 50 Hz". Paywalled. |
| IEEE-C57.12.34 | IEEE C57.12.34-2022, pad-mounted, compartmental-type, self-cooled, three-phase distribution transformers, 10 MVA and smaller | Scope of `ieee-pad` three-phase | Scope only, from the published abstract | Same note. Paywalled. |

## Transformers

Library: `transformer_kva.csv`, one row per standard rating, tagged by family (D7):

- `ieee-dry`: low-voltage dry-type, North American series (DOE-431.196a).
- `ieee-pad`: liquid-filled padmount, North American series (DOE-431.196b), within the
scope of IEEE C57.12.38 (single-phase) and C57.12.34 (three-phase).
- `iec`: IEC series (IEC-60076-1). Not yet verified against the standard.

These are *standard* ratings. Sizes local suppliers rarely stock (for example
10 kVA single-phase padmounts or 31.5 kVA IEC units) are still listed; trimming the
list to what is buyable is a separate, project-level decision.
2 changes: 2 additions & 0 deletions docs/requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ it exists.
| NFR-02 | The workbook is self-contained: no links to other files. |
| NFR-03 | Every engineering rule has Python tests, and an automated test checks the Excel formula agrees with Python (ADR 0002). |
| NFR-04 | A schedule shows what a drawing needs: at most about 7 ratings per type. Detail belongs in the specification. |
| NFR-05 | Every value in an equipment library cites a source listed in `docs/design-basis.md`. A test fails if a source is missing or unlisted. |

## Non-goals (v0.1)

- Writing specifications.
- A manufacturer product database. Libraries hold standard sizes and typical dimensions only.
- Load calculations. Demand is an input, calculated elsewhere.
- Certifying compliance with standards. Library sizes come from the cited standards; whether a product complies is confirmed by the manufacturer's data.
- DXF/PDF output, AutoCAD Data Link and native AutoCAD table objects. v0.1 gets schedules into drawings by copy-paste; the rest comes later.

## Decisions
Expand Down
65 changes: 65 additions & 0 deletions src/kitlist/data/library/transformer_kva.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
family,phase,kva,source
ieee-dry,1,15,DOE-431.196a
ieee-dry,1,25,DOE-431.196a
ieee-dry,1,37.5,DOE-431.196a
ieee-dry,1,50,DOE-431.196a
ieee-dry,1,75,DOE-431.196a
ieee-dry,1,100,DOE-431.196a
ieee-dry,1,167,DOE-431.196a
ieee-dry,1,250,DOE-431.196a
ieee-dry,1,333,DOE-431.196a
ieee-dry,3,15,DOE-431.196a
ieee-dry,3,30,DOE-431.196a
ieee-dry,3,45,DOE-431.196a
ieee-dry,3,75,DOE-431.196a
ieee-dry,3,112.5,DOE-431.196a
ieee-dry,3,150,DOE-431.196a
ieee-dry,3,225,DOE-431.196a
ieee-dry,3,300,DOE-431.196a
ieee-dry,3,500,DOE-431.196a
ieee-dry,3,750,DOE-431.196a
ieee-dry,3,1000,DOE-431.196a
ieee-pad,1,10,DOE-431.196b
ieee-pad,1,15,DOE-431.196b
ieee-pad,1,25,DOE-431.196b
ieee-pad,1,37.5,DOE-431.196b
ieee-pad,1,50,DOE-431.196b
ieee-pad,1,75,DOE-431.196b
ieee-pad,1,100,DOE-431.196b
ieee-pad,1,167,DOE-431.196b
ieee-pad,1,250,DOE-431.196b
ieee-pad,3,15,DOE-431.196b
ieee-pad,3,30,DOE-431.196b
ieee-pad,3,45,DOE-431.196b
ieee-pad,3,75,DOE-431.196b
ieee-pad,3,112.5,DOE-431.196b
ieee-pad,3,150,DOE-431.196b
ieee-pad,3,225,DOE-431.196b
ieee-pad,3,300,DOE-431.196b
ieee-pad,3,500,DOE-431.196b
ieee-pad,3,750,DOE-431.196b
ieee-pad,3,1000,DOE-431.196b
ieee-pad,3,1500,DOE-431.196b
ieee-pad,3,2000,DOE-431.196b
ieee-pad,3,2500,DOE-431.196b
iec,3,25,IEC-60076-1
iec,3,31.5,IEC-60076-1
iec,3,40,IEC-60076-1
iec,3,50,IEC-60076-1
iec,3,63,IEC-60076-1
iec,3,80,IEC-60076-1
iec,3,100,IEC-60076-1
iec,3,125,IEC-60076-1
iec,3,160,IEC-60076-1
iec,3,200,IEC-60076-1
iec,3,250,IEC-60076-1
iec,3,315,IEC-60076-1
iec,3,400,IEC-60076-1
iec,3,500,IEC-60076-1
iec,3,630,IEC-60076-1
iec,3,800,IEC-60076-1
iec,3,1000,IEC-60076-1
iec,3,1250,IEC-60076-1
iec,3,1600,IEC-60076-1
iec,3,2000,IEC-60076-1
iec,3,2500,IEC-60076-1
38 changes: 38 additions & 0 deletions src/kitlist/library.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Equipment libraries: standard sizes read from the CSV files in data/library/.

Every row cites a source listed in docs/design-basis.md (NFR-05).
"""

import csv
from dataclasses import dataclass
from importlib.resources import files

FAMILIES = ("ieee-dry", "ieee-pad", "iec") # transformer rating families (D7)


@dataclass(frozen=True)
class TransformerRating:
"""One row of transformer_kva.csv."""

family: str
phase: int
kva: float
source: str


def transformer_ratings() -> list[TransformerRating]:
"""Every standard transformer rating in the library."""
path = files("kitlist").joinpath("data/library/transformer_kva.csv")
# "utf-8-sig" also accepts the invisible marker Excel adds when it saves a CSV.
text = path.read_text(encoding="utf-8-sig")
return [
TransformerRating(row["family"], int(row["phase"]), float(row["kva"]), row["source"])
for row in csv.DictReader(text.splitlines())
]


def transformer_sizes(family: str, phase: int) -> list[float]:
"""Standard kVA sizes for one family and phase count, smallest first."""
if family not in FAMILIES:
raise ValueError(f"unknown transformer family '{family}'; expected one of {FAMILIES}")
return sorted(r.kva for r in transformer_ratings() if r.family == family and r.phase == phase)
73 changes: 73 additions & 0 deletions tests/test_library.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Tests for the equipment libraries (NFR-05, REQ-11)."""

from pathlib import Path

import pytest

from kitlist.library import FAMILIES, transformer_ratings, transformer_sizes

DESIGN_BASIS = Path(__file__).parents[1] / "docs" / "design-basis.md"
RATINGS = transformer_ratings()


def reference_ids() -> set[str]:
"""The IDs in the first column of the References table in docs/design-basis.md."""
text = DESIGN_BASIS.read_text(encoding="utf-8")
references = text.split("## References")[1].split("\n## ")[0]
ids = set()
for line in references.splitlines():
if not line.startswith("|"):
continue
first_cell = line.split("|")[1].strip()
if first_cell != "ID" and not first_cell.startswith("---"):
ids.add(first_cell)
return ids


# ---- traceability (NFR-05) ----


def test_reference_table_can_be_read():
# Checks the helper above: if this fails, the other tests can't be trusted.
assert {"DOE-431.196a", "DOE-431.196b", "IEC-60076-1"} <= reference_ids()


def test_every_rating_cites_a_source_listed_in_the_design_basis():
missing = {r.source for r in RATINGS} - reference_ids()
assert not missing, f"not listed in docs/design-basis.md: {sorted(missing)}"


# ---- families (REQ-11, D7) ----


def test_every_rating_belongs_to_a_known_family():
unknown = {r.family for r in RATINGS} - set(FAMILIES)
assert not unknown, f"unknown families: {sorted(unknown)}"


def test_families_are_never_mixed():
# 25 kVA three-phase is an IEC rating, not a North American one
assert 25 in transformer_sizes("iec", phase=3)
assert 25 not in transformer_sizes("ieee-dry", phase=3)


def test_unknown_family_is_an_error():
with pytest.raises(ValueError, match="unknown transformer family"):
transformer_sizes("ansi", phase=3)


# ---- library contents ----


def test_no_rating_is_listed_twice():
keys = [(r.family, r.phase, r.kva) for r in RATINGS]
assert len(keys) == len(set(keys))


def test_sizes_come_back_smallest_first():
sizes = transformer_sizes("ieee-dry", phase=3)
assert sizes == sorted(sizes)


def test_single_phase_padmounts_stay_within_c57_12_38_scope():
assert max(transformer_sizes("ieee-pad", phase=1)) <= 250
Loading