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)