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

1"""Tests for NIWA solar availability-factor helpers.""" 

2 

3import importlib.util 

4from pathlib import Path 

5 

6import pandas as pd 

7import pytest 

8 

9 

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 

23 

24 

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 ) 

51 

52 result = module.aggregate_island_curves(zone_factors, zone_weights) 

53 

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)) 

59 

60 

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 ) 

87 

88 with pytest.raises(ValueError, match="Missing configured solar zone weights"): 

89 module.aggregate_island_curves(zone_factors, zone_weights) 

90 

91 

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 ) 

113 

114 result = module.merge_solar_and_static_curves(solar, static) 

115 

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 ]