Coverage for tests / test_solar_availability_factors.py: 100%
33 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-01 22:14 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-01 22:14 +0000
1"""Tests for NIWA solar availability-factor helpers."""
3import importlib.util
4from pathlib import Path
6import pandas as pd
7import pytest
10def load_solar_build_curves():
11 """
12 Load the script module directly from its path for test usage.
13 """
14 module_path = (
15 Path(__file__).resolve().parents[1]
16 / "scripts/stage_3_scenarios/electricity/solar_build_curves.py"
17 )
18 spec = importlib.util.spec_from_file_location("solar_build_curves", module_path)
19 module = importlib.util.module_from_spec(spec)
20 assert spec.loader is not None
21 spec.loader.exec_module(module)
22 return module
25def test_aggregate_island_curves_uses_configured_zone_weights():
26 """
27 Island curves should use configured zone weights within each island.
28 """
29 module = load_solar_build_curves()
30 zone_factors = pd.DataFrame(
31 {
32 "Tech_TIMES": ["SolarDistSmall"] * 4,
33 "ZoneCode": ["AK", "HN", "CC", "DN"],
34 "Region": ["Auckland", "Hamilton", "Christchurch", "Dunedin"],
35 "TimeSlice": ["SUM-WK-D"] * 4,
36 "Island": ["NI", "NI", "SI", "SI"],
37 "AvailabilityFactor": [0.2, 0.4, 0.6, 0.8],
38 "HoursInTimeSlice": [600, 100, 200, 400],
39 }
40 )
41 zone_weights = pd.DataFrame(
42 {
43 "ZoneCode": ["AK", "HN", "CC", "DN"],
44 "Region": ["Auckland", "Hamilton", "Christchurch", "Dunedin"],
45 "Island": ["NI", "NI", "SI", "SI"],
46 "Weight": [0.75, 0.25, 0.1, 0.9],
47 "IslandWeightTotal": [1.0, 1.0, 1.0, 1.0],
48 "NormalizedWeight": [0.75, 0.25, 0.1, 0.9],
49 }
50 )
52 result = module.aggregate_island_curves(zone_factors, zone_weights)
54 row = result.to_dict(orient="records")[0]
55 assert row["TimeSlice"] == "SUM-WK-D"
56 assert row["Tech_TIMES"] == "SolarDistSmall"
57 assert row["NI"] == pytest.approx((0.2 * 0.75) + (0.4 * 0.25))
58 assert row["SI"] == pytest.approx((0.6 * 0.1) + (0.8 * 0.9))
61def test_aggregate_island_curves_requires_weights_for_every_zone():
62 """
63 Island aggregation should fail if any zone lacks a configured weight.
64 """
65 module = load_solar_build_curves()
66 zone_factors = pd.DataFrame(
67 {
68 "Tech_TIMES": ["SolarDistBifacial"],
69 "ZoneCode": ["AK"],
70 "Region": ["Auckland"],
71 "TimeSlice": ["SUM-WK-D"],
72 "Island": ["NI"],
73 "AvailabilityFactor": [0.2],
74 "HoursInTimeSlice": [600],
75 }
76 )
77 zone_weights = pd.DataFrame(
78 {
79 "ZoneCode": ["HN"],
80 "Region": ["Hamilton"],
81 "Island": ["NI"],
82 "Weight": [1.0],
83 "IslandWeightTotal": [1.0],
84 "NormalizedWeight": [1.0],
85 }
86 )
88 with pytest.raises(ValueError, match="Missing configured solar zone weights"):
89 module.aggregate_island_curves(zone_factors, zone_weights)
92def test_merge_solar_and_static_curves_replaces_only_solar_rows():
93 """
94 Generated solar curves should replace only the solar rows in the static table.
95 """
96 module = load_solar_build_curves()
97 solar = pd.DataFrame(
98 {
99 "TimeSlice": ["SUM-WK-D", "SUM-WK-D"],
100 "Tech_TIMES": ["SolarDistSmall", "SolarDistBifacial"],
101 "NI": [0.3, 0.35],
102 "SI": [0.4, 0.45],
103 }
104 )
105 static = pd.DataFrame(
106 {
107 "TimeSlice": ["SUM-WK-D", "SUM-WK-D", "SUM-WK-D"],
108 "TechCode": ["SolarDistSmall", "SolarDistBifacial", "WindOn"],
109 "NI": [0.1, 0.2, 0.5],
110 "SI": [0.2, 0.3, 0.5],
111 }
112 )
114 result = module.merge_solar_and_static_curves(solar, static)
116 assert result.to_dict(orient="records") == [
117 {
118 "TimeSlice": "SUM-WK-D",
119 "Tech_TIMES": "SolarDistBifacial",
120 "NI": 0.35,
121 "SI": 0.45,
122 },
123 {
124 "TimeSlice": "SUM-WK-D",
125 "Tech_TIMES": "SolarDistSmall",
126 "NI": 0.3,
127 "SI": 0.4,
128 },
129 {"TimeSlice": "SUM-WK-D", "Tech_TIMES": "WindOn", "NI": 0.5, "SI": 0.5},
130 ]