mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
Dwelling-Roof Cap bounds the PV array to the dwelling's own roof 🟥
select_conservative_configs must accept the dwelling's roof area and cap panels to its usable roof (ADR-0038) — bounding a 55m² dwelling to ~16 panels under Google footprint conflation, while staying a no-op on correctly-matched homes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d6a59be950
commit
904c205e3a
1 changed files with 51 additions and 0 deletions
|
|
@ -111,6 +111,57 @@ def test_cap_excludes_configs_above_seventy_percent() -> None:
|
|||
assert [c.panels_count for c in configs] == [6]
|
||||
|
||||
|
||||
def _south(panels: int) -> SolarRoofSegment:
|
||||
return _segment(panels=panels, azimuth=180.0, energy=panels * 100.0)
|
||||
|
||||
|
||||
def _potential_with_panel_dims(
|
||||
max_panels: int, panel_counts: tuple[int, ...]
|
||||
) -> SolarPotential:
|
||||
# Google panel footprint 1.879 × 1.045 ≈ 1.964 m².
|
||||
return SolarPotential(
|
||||
panel_capacity_watts=400.0,
|
||||
max_array_panels_count=max_panels,
|
||||
configurations=tuple(
|
||||
SolarPanelConfiguration(
|
||||
panels_count=n,
|
||||
yearly_energy_dc_kwh=n * 100.0,
|
||||
segments=(_south(n),),
|
||||
)
|
||||
for n in panel_counts
|
||||
),
|
||||
panel_height_m=1.879,
|
||||
panel_width_m=1.045,
|
||||
)
|
||||
|
||||
|
||||
def test_dwelling_roof_cap_bounds_a_small_dwelling() -> None:
|
||||
# ADR-0038: Google's maxArrayPanelsCount (58) reflects a conflated whole-
|
||||
# building roof; a 55 m² dwelling's own usable roof (≈ 55/cos30° × 0.5 ≈
|
||||
# 32 m² ≈ 16 panels) must bound the array, well below the 0.70×58 ≈ 40 cap.
|
||||
potential = _potential_with_panel_dims(58, (4, 12, 20, 30, 41, 58))
|
||||
|
||||
# Google cap alone allows up to the 30-panel rung (41/58 exceed 0.70×58).
|
||||
assert max(c.panels_count for c in select_conservative_configs(potential)) == 30
|
||||
|
||||
capped = select_conservative_configs(potential, dwelling_roof_area_m2=55.0)
|
||||
|
||||
assert capped # still offers the small rungs
|
||||
assert all(c.panels_count <= 16 for c in capped)
|
||||
assert max(c.panels_count for c in capped) == 12
|
||||
|
||||
|
||||
def test_dwelling_roof_cap_is_a_no_op_on_a_matched_home() -> None:
|
||||
# ADR-0038: on a correctly-matched home Google's roof ≈ the dwelling's, so
|
||||
# the area budget is ≳ what Google offers and the cap does NOT bite.
|
||||
potential = _potential_with_panel_dims(58, (4, 12, 20, 30, 41, 58))
|
||||
baseline = [c.panels_count for c in select_conservative_configs(potential)]
|
||||
|
||||
matched = select_conservative_configs(potential, dwelling_roof_area_m2=300.0)
|
||||
|
||||
assert [c.panels_count for c in matched] == baseline
|
||||
|
||||
|
||||
def test_all_north_or_empty_yields_no_configs() -> None:
|
||||
# Arrange — every plane faces north
|
||||
potential = SolarPotential(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue