94 lines
3.1 KiB
Python
94 lines
3.1 KiB
Python
import math
|
|
|
|
|
|
def as_float(value, default=0.0):
|
|
try:
|
|
if value is None:
|
|
return default
|
|
return float(value)
|
|
except Exception:
|
|
return default
|
|
|
|
|
|
def roof_kwp(roof):
|
|
return as_float(roof.get("kwp", roof.get("leistung", roof.get("pv_kwp", 0.0))), 0.0)
|
|
|
|
|
|
def roof_azimuth_deg(roof):
|
|
return as_float(roof.get("azimut", roof.get("azimuth", roof.get("ausrichtung", 180.0))), 180.0)
|
|
|
|
|
|
def roof_tilt_deg(roof):
|
|
return as_float(roof.get("neigung", roof.get("tilt", 30.0)), 30.0)
|
|
|
|
|
|
def roof_features(config):
|
|
roofs = config.get("daecher", []) or []
|
|
if not roofs:
|
|
roofs = [{"kwp": as_float(config.get("ac_leistung", 0.0), 0.0), "azimut": 180.0, "neigung": 30.0}]
|
|
total_kwp = sum(roof_kwp(r) for r in roofs)
|
|
if total_kwp <= 0:
|
|
total_kwp = as_float(config.get("ac_leistung", 0.0), 0.0)
|
|
weighted_az = 0.0
|
|
weighted_tilt = 0.0
|
|
south_factor = 0.0
|
|
for roof in roofs:
|
|
w = roof_kwp(roof) / total_kwp if total_kwp > 0 else 0.0
|
|
az = roof_azimuth_deg(roof)
|
|
tilt = roof_tilt_deg(roof)
|
|
weighted_az += w * az
|
|
weighted_tilt += w * tilt
|
|
south_factor += w * max(0.0, math.cos(math.radians(az - 180.0)))
|
|
return {
|
|
"pv_kwp_total": total_kwp,
|
|
"roof_azimuth_sin": math.sin(math.radians(weighted_az)),
|
|
"roof_azimuth_cos": math.cos(math.radians(weighted_az)),
|
|
"roof_tilt_avg": weighted_tilt,
|
|
"roof_south_factor": south_factor,
|
|
}
|
|
|
|
|
|
def calc_pure_math_pv(config, dt):
|
|
ac_limit = as_float(config.get("ac_leistung", 10.0), 10.0) * 1000.0
|
|
roofs = config.get("daecher", []) or []
|
|
if not roofs:
|
|
roofs = [{"kwp": as_float(config.get("ac_leistung", 0.0), 0.0), "neigung": 30.0, "azimut": 180.0}]
|
|
|
|
day = dt.timetuple().tm_yday
|
|
hour = dt.hour + dt.minute / 60.0
|
|
lat = math.radians(as_float(config.get("latitude", 47.0), 47.0))
|
|
decl = math.radians(23.45 * math.sin(math.radians(360.0 * (day - 81) / 365.0)))
|
|
hour_angle = math.radians(15.0 * (hour - 12.0))
|
|
|
|
sin_alt = math.sin(lat) * math.sin(decl) + math.cos(lat) * math.cos(decl) * math.cos(hour_angle)
|
|
sun_alt = math.asin(max(-1.0, min(1.0, sin_alt)))
|
|
if sun_alt <= 0.0:
|
|
return 0.0
|
|
|
|
sun_az = math.atan2(
|
|
math.sin(hour_angle),
|
|
math.cos(hour_angle) * math.sin(lat) - math.tan(decl) * math.cos(lat),
|
|
)
|
|
|
|
total = 0.0
|
|
for roof in roofs:
|
|
kwp = roof_kwp(roof)
|
|
if kwp <= 0:
|
|
continue
|
|
tilt = math.radians(roof_tilt_deg(roof))
|
|
az = math.radians(roof_azimuth_deg(roof))
|
|
cos_inc = math.sin(sun_alt) * math.cos(tilt) + math.cos(sun_alt) * math.sin(tilt) * math.cos(sun_az - az)
|
|
if cos_inc > 0:
|
|
total += kwp * 1000.0 * cos_inc
|
|
|
|
return max(0.0, min(total, ac_limit))
|
|
|
|
|
|
def calc_pure_math_load(dt):
|
|
hour = dt.hour + dt.minute / 60.0
|
|
base = 650.0
|
|
morning = 220.0 * math.exp(-((hour - 7.0) ** 2) / 5.0)
|
|
evening = 380.0 * math.exp(-((hour - 19.0) ** 2) / 8.0)
|
|
weekend = 1.12 if dt.weekday() >= 5 else 1.0
|
|
return max(0.0, (base + morning + evening) * weekend)
|