feat(application): integrate measured-load ingestion training and planner source
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user