Coverage for tests/cim_converter/unit/test_cim_to_csv_helpser.py: 100%
36 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-11-13 17:34 -0800
« prev ^ index » next coverage.py v7.10.6, created at 2025-11-13 17:34 -0800
1# tests/unit/test_cim_to_csv_helpers.py
2import pandas as pd
3import pytest
4from distopf.cim_importer.cim_to_csv_converter import CIMToCSVConverter
7def test_fix_bus_phases_from_branches_simple():
8 conv = CIMToCSVConverter(cim_file="unused")
9 bus_df = pd.DataFrame(
10 [
11 {"id": 1, "name": "sourcebus", "phases": "abc"},
12 {"id": 2, "name": "650", "phases": None},
13 {"id": 3, "name": "rg60", "phases": None},
14 ]
15 )
16 branch_df = pd.DataFrame(
17 [
18 {"fb": 1, "tb": 2, "phases": "ab"},
19 {"fb": 2, "tb": 3, "phases": "a"},
20 ]
21 )
22 out = conv._fix_bus_phases_from_branches(bus_df.copy(), branch_df)
23 # The bus with id 2 should get phases from tb mapping (first branch)
24 assert out.loc[out["id"] == 2, "phases"].iloc[0] in ("ab", "a")
27def test_fix_downstream_phase_consistency_intersection():
28 conv = CIMToCSVConverter(cim_file="unused")
29 # branch 1: fb 1 -> tb 2 phases 'ab'; branch 2: fb 2 -> tb 3 phases 'abc' should be reduced to 'ab' intersect 'abc' => 'ab'
30 branch_df = pd.DataFrame(
31 [
32 {"fb": 1, "tb": 2, "phases": "ab"},
33 {"fb": 2, "tb": 3, "phases": "abc"},
34 ]
35 )
36 out = conv._fix_downstream_phase_consistency(branch_df.copy())
37 assert out.loc[1, "phases"] in ("ab", "ba") or set(
38 list(out.loc[1, "phases"])
39 ) == set(list("ab"))
42def test_aggregate_generators_basic():
43 conv = CIMToCSVConverter(cim_file="unused")
44 gen_df = pd.DataFrame(
45 [
46 {
47 "mrid": "m1",
48 "id": 2,
49 "name": "G1",
50 "pa": 0.1,
51 "pb": 0.0,
52 "pc": 0.0,
53 "qa": 0.01,
54 "qb": 0.0,
55 "qc": 0.0,
56 "s_base": 100.0,
57 "sa_max": 0.1,
58 "sb_max": 0.0,
59 "sc_max": 0.0,
60 "phases": "a",
61 "qa_max": 0.01,
62 "qb_max": 0.0,
63 "qc_max": 0.0,
64 "qa_min": -0.01,
65 "qb_min": 0.0,
66 "qc_min": 0.0,
67 "control_variable": "PQ",
68 },
69 {
70 "mrid": "m2",
71 "id": 2,
72 "name": "G1",
73 "pa": 0.05,
74 "pb": 0.0,
75 "pc": 0.0,
76 "qa": 0.005,
77 "qb": 0.0,
78 "qc": 0.0,
79 "s_base": 50.0,
80 "sa_max": 0.05,
81 "sb_max": 0.0,
82 "sc_max": 0.0,
83 "phases": "a",
84 "qa_max": 0.005,
85 "qb_max": 0.0,
86 "qc_max": 0.0,
87 "qa_min": -0.005,
88 "qb_min": 0.0,
89 "qc_min": 0.0,
90 "control_variable": "PQ",
91 },
92 ]
93 )
94 aggregated = conv._aggregate_generators(gen_df)
95 # Should be single row for id 2
96 assert len(aggregated) == 1
97 row = aggregated.iloc[0]
98 # MRIDs joined with '|'
99 assert "m1" in row["mrid"] and "m2" in row["mrid"]
100 # Name should indicate aggregation since >1 units
101 assert "AggGen" in row["name"]
102 # pa sums
103 assert pytest.approx(row["pa"], rel=1e-12) == 0.15
106def test_correct_generators_without_phases_and_distribution():
107 conv = CIMToCSVConverter(cim_file="unused")
108 bus_df = pd.DataFrame(
109 [
110 {"id": 1, "phases": "abc"},
111 {"id": 2, "phases": "a"},
112 ]
113 )
114 # generator without phases but with p and q columns
115 gen_df = pd.DataFrame(
116 [
117 {
118 "id": 2,
119 "phases": "",
120 "p": 0.06,
121 "q": 0.006,
122 "pa": 0.0,
123 "pb": 0.0,
124 "pc": 0.0,
125 "qa": 0.0,
126 "qb": 0.0,
127 "qc": 0.0,
128 }
129 ]
130 )
131 corrected = conv._correct_generators_without_phases(bus_df, gen_df.copy())
132 # Since bus 2 has phases 'a' only, pa should equal p (0.06)
133 assert pytest.approx(corrected.loc[0, "pa"], rel=1e-12) == pytest.approx(0.06)
134 assert corrected.loc[0, "phases"] == "a" or corrected.loc[0, "phases"] == ""
137def test_convert_secondary_loads_moves_s1_s2_into_primary_phase():
138 conv = CIMToCSVConverter(cim_file="unused")
139 # Create bus df with s1/s2 loads
140 bus_df = pd.DataFrame(
141 [
142 {
143 "id": 10,
144 "phases": "a",
145 "pl_s1": 0.02,
146 "pl_s2": 0.03,
147 "ql_s1": 0.005,
148 "ql_s2": 0.006,
149 "pl_a": 0.0,
150 "ql_a": 0.0,
151 "pl_b": 0.0,
152 "ql_b": 0.0,
153 "pl_c": 0.0,
154 "ql_c": 0.0,
155 }
156 ]
157 )
158 out = conv._convert_secondary_loads(bus_df.copy())
159 # Should have moved s1+s2 into pl_a and ql_a
160 assert pytest.approx(
161 out.loc[out["id"] == 10, "pl_a"].iloc[0], rel=1e-12
162 ) == pytest.approx(0.05)
163 assert pytest.approx(
164 out.loc[out["id"] == 10, "ql_a"].iloc[0], rel=1e-12
165 ) == pytest.approx(0.011)