A small roof derives Sub-Ladder rungs below Google's smallest config 🟪

North-drop now runs once: the selection computes the usable (north-dropped)
ladder, feasibility filters it, and Sub-Ladder derivation consumes it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-07-07 22:10:37 +00:00
parent 5d0a7751e6
commit fbedf4541b

View file

@ -228,25 +228,18 @@ def _resized_to(
def _sub_ladder_configs(
potential: SolarPotential, panel_cap: float
usable: list[SolarPanelConfiguration], panel_cap: float
) -> tuple[SolarPanelConfiguration, ...]:
"""Sub-Ladder Configurations (ADR-0058): rungs derived BELOW Google's
smallest offered config, for when nothing on the ladder fits under the
caps a small roof gets the array its caps allow instead of no PV at
all. One rung per whole panel count from the install floor (2) up to
``floor(panel_cap)``, derived from the smallest north-dropped config."""
``floor(panel_cap)``, derived from the smallest of the (already
north-dropped) ``usable`` configs."""
rung_max: int = math.floor(panel_cap)
if rung_max < _MIN_SUB_LADDER_PANELS:
return ()
smallest: Optional[SolarPanelConfiguration] = None
for config in potential.configurations:
dropped = _drop_north_segments(config)
if not dropped.segments:
continue
if smallest is None or dropped.panels_count < smallest.panels_count:
smallest = dropped
if smallest is None:
if rung_max < _MIN_SUB_LADDER_PANELS or not usable:
return ()
smallest: SolarPanelConfiguration = min(usable, key=lambda c: c.panels_count)
return tuple(
_resized_to(smallest, panels)
for panels in range(_MIN_SUB_LADDER_PANELS, rung_max + 1)
@ -274,16 +267,19 @@ def select_conservative_configs(
)
if roof_cap is not None:
panel_cap = min(panel_cap, roof_cap)
feasible: list[SolarPanelConfiguration] = [
usable: list[SolarPanelConfiguration] = [
trimmed
for config in potential.configurations
for trimmed in (_drop_north_segments(config),)
if trimmed.segments and trimmed.panels_count <= panel_cap
if trimmed.segments
]
feasible: list[SolarPanelConfiguration] = [
config for config in usable if config.panels_count <= panel_cap
]
if not feasible:
# Nothing on Google's ladder fits under the caps — a small roof, not
# an unusable one. Derive Sub-Ladder Configurations (ADR-0058).
return _sub_ladder_configs(potential, panel_cap)
return _sub_ladder_configs(usable, panel_cap)
# Collapse rungs that trimmed to the same usable size (north-drop can make
# distinct original rungs coincide), keeping the higher-generation layout —
# the Optimiser's dial is panel count (≈ kWp ≈ cost), so duplicates of the