mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
SolarPotential carries panel dims + per-segment centre/area 🟩
Enrich each SolarRoofSegment from roofSegmentStats (centre + areaMeters2, keyed by segmentIndex) and read panelHeightMeters/panelWidthMeters onto SolarPotential — the geometry the Dwelling-Roof Cap (ADR-0038) needs. All Optional; existing projection + config-selection tests stay green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cf2780ed77
commit
d6a59be950
1 changed files with 58 additions and 7 deletions
|
|
@ -62,6 +62,14 @@ class SolarRoofSegment:
|
||||||
azimuth_degrees: float
|
azimuth_degrees: float
|
||||||
pitch_degrees: float
|
pitch_degrees: float
|
||||||
yearly_energy_dc_kwh: float
|
yearly_energy_dc_kwh: float
|
||||||
|
# Per-segment centre + roof-plane area, enriched from the top-level
|
||||||
|
# `roofSegmentStats` (keyed by `segmentIndex`) — the per-config
|
||||||
|
# `roofSegmentSummaries` omit them. Used by the Dwelling-Roof Cap
|
||||||
|
# (ADR-0038) to rank segments by distance from the dwelling and bound the
|
||||||
|
# array by usable roof area. None when the stats block lacks the segment.
|
||||||
|
center_latitude: Optional[float] = None
|
||||||
|
center_longitude: Optional[float] = None
|
||||||
|
area_m2: Optional[float] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sap_orientation(self) -> int:
|
def sap_orientation(self) -> int:
|
||||||
|
|
@ -94,6 +102,11 @@ class SolarPotential:
|
||||||
panel_capacity_watts: float
|
panel_capacity_watts: float
|
||||||
max_array_panels_count: int
|
max_array_panels_count: int
|
||||||
configurations: tuple[SolarPanelConfiguration, ...]
|
configurations: tuple[SolarPanelConfiguration, ...]
|
||||||
|
# Physical panel footprint (Google `panelHeightMeters` / `panelWidthMeters`)
|
||||||
|
# — the Dwelling-Roof Cap (ADR-0038) converts a usable roof-area budget into
|
||||||
|
# a panel count via this. None for partial blocks lacking the fields.
|
||||||
|
panel_height_m: Optional[float] = None
|
||||||
|
panel_width_m: Optional[float] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_building_insights(
|
def from_building_insights(
|
||||||
|
|
@ -111,18 +124,46 @@ class SolarPotential:
|
||||||
or "panelCapacityWatts" not in solar_potential
|
or "panelCapacityWatts" not in solar_potential
|
||||||
):
|
):
|
||||||
return None
|
return None
|
||||||
|
# Per-segment centre + area live on the top-level `roofSegmentStats`,
|
||||||
|
# keyed by `segmentIndex`; the per-config `roofSegmentSummaries` carry
|
||||||
|
# only the panel/orientation fields. Build the lookup once.
|
||||||
|
stats_by_index: dict[int, Mapping[str, Any]] = {
|
||||||
|
int(stats["segmentIndex"]): stats
|
||||||
|
for stats in solar_potential.get("roofSegmentStats", [])
|
||||||
|
}
|
||||||
|
|
||||||
|
def _segment(summary: Mapping[str, Any]) -> SolarRoofSegment:
|
||||||
|
index: int = int(summary["segmentIndex"])
|
||||||
|
stats: Optional[Mapping[str, Any]] = stats_by_index.get(index)
|
||||||
|
center: Mapping[str, Any] = (
|
||||||
|
stats.get("center", {}) if stats is not None else {}
|
||||||
|
)
|
||||||
|
area: Optional[float] = (
|
||||||
|
float(stats["stats"]["areaMeters2"])
|
||||||
|
if stats is not None and "stats" in stats
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
return SolarRoofSegment(
|
||||||
|
segment_index=index,
|
||||||
|
panels_count=int(summary["panelsCount"]),
|
||||||
|
azimuth_degrees=float(summary["azimuthDegrees"]),
|
||||||
|
pitch_degrees=float(summary["pitchDegrees"]),
|
||||||
|
yearly_energy_dc_kwh=float(summary["yearlyEnergyDcKwh"]),
|
||||||
|
center_latitude=(
|
||||||
|
float(center["latitude"]) if "latitude" in center else None
|
||||||
|
),
|
||||||
|
center_longitude=(
|
||||||
|
float(center["longitude"]) if "longitude" in center else None
|
||||||
|
),
|
||||||
|
area_m2=area,
|
||||||
|
)
|
||||||
|
|
||||||
configurations: tuple[SolarPanelConfiguration, ...] = tuple(
|
configurations: tuple[SolarPanelConfiguration, ...] = tuple(
|
||||||
SolarPanelConfiguration(
|
SolarPanelConfiguration(
|
||||||
panels_count=int(config["panelsCount"]),
|
panels_count=int(config["panelsCount"]),
|
||||||
yearly_energy_dc_kwh=float(config["yearlyEnergyDcKwh"]),
|
yearly_energy_dc_kwh=float(config["yearlyEnergyDcKwh"]),
|
||||||
segments=tuple(
|
segments=tuple(
|
||||||
SolarRoofSegment(
|
_segment(summary)
|
||||||
segment_index=int(summary["segmentIndex"]),
|
|
||||||
panels_count=int(summary["panelsCount"]),
|
|
||||||
azimuth_degrees=float(summary["azimuthDegrees"]),
|
|
||||||
pitch_degrees=float(summary["pitchDegrees"]),
|
|
||||||
yearly_energy_dc_kwh=float(summary["yearlyEnergyDcKwh"]),
|
|
||||||
)
|
|
||||||
for summary in config.get("roofSegmentSummaries", [])
|
for summary in config.get("roofSegmentSummaries", [])
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -132,4 +173,14 @@ class SolarPotential:
|
||||||
panel_capacity_watts=float(solar_potential["panelCapacityWatts"]),
|
panel_capacity_watts=float(solar_potential["panelCapacityWatts"]),
|
||||||
max_array_panels_count=int(solar_potential["maxArrayPanelsCount"]),
|
max_array_panels_count=int(solar_potential["maxArrayPanelsCount"]),
|
||||||
configurations=configurations,
|
configurations=configurations,
|
||||||
|
panel_height_m=(
|
||||||
|
float(solar_potential["panelHeightMeters"])
|
||||||
|
if "panelHeightMeters" in solar_potential
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
panel_width_m=(
|
||||||
|
float(solar_potential["panelWidthMeters"])
|
||||||
|
if "panelWidthMeters" in solar_potential
|
||||||
|
else None
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue