diff --git a/docs/design-basis.md b/docs/design-basis.md new file mode 100644 index 0000000..e729b1b --- /dev/null +++ b/docs/design-basis.md @@ -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. \ No newline at end of file diff --git a/docs/requirements.md b/docs/requirements.md index 171fa64..bb05696 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -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 diff --git a/src/kitlist/data/library/transformer_kva.csv b/src/kitlist/data/library/transformer_kva.csv new file mode 100644 index 0000000..147a8c7 --- /dev/null +++ b/src/kitlist/data/library/transformer_kva.csv @@ -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 \ No newline at end of file diff --git a/src/kitlist/library.py b/src/kitlist/library.py new file mode 100644 index 0000000..ceecbbe --- /dev/null +++ b/src/kitlist/library.py @@ -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) diff --git a/tests/test_library.py b/tests/test_library.py new file mode 100644 index 0000000..a814b4b --- /dev/null +++ b/tests/test_library.py @@ -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