```python """ panel.py Modello del singolo pannello fotovoltaico. Ogni pannello riceve dal simulatore: - Irradianza [W/m²] - Temperatura ambiente [°C] - Timestamp e restituisce le principali grandezze elettriche. """ from __future__ import annotations from dataclasses import dataclass, field from datetime import datetime from typing import Dict import random import math @dataclass class PVPanel: panel_id: str nominal_power: float = 450.0 # W area: float = 2.1 # m² vmp: float = 41.5 imp: float = 10.85 voc: float = 49.8 isc: float = 11.35 temp_coeff: float = -0.0035 # -0.35 %/°C nominal_temp: float = 25.0 annual_degradation: float = 0.005 sensor_noise: float = 0.01 # stato pannello enabled: bool = True soiling_factor: float = 1.0 degradation: float = 1.0 total_energy_Wh: float = 0.0 # ------------------------------------------------------------------ def module_temperature( self, ambient_temperature: float, irradiance: float ) -> float: """ Stima della temperatura del modulo. Formula semplificata: Tmodule = Tamb + irradiance * 0.03 """ return ambient_temperature + irradiance * 0.03 # ------------------------------------------------------------------ def degradation_factor( self, years: float ) -> float: return (1.0 - self.annual_degradation) ** years # ------------------------------------------------------------------ def compute_power( self, irradiance: float, ambient_temperature: float, years_from_start: float ) -> float: if not self.enabled: return 0.0 module_temp = self.module_temperature( ambient_temperature, irradiance ) temp_factor = ( 1 + self.temp_coeff * (module_temp - self.nominal_temp) ) degr = self.degradation_factor(years_from_start) power = ( self.nominal_power * (irradiance / 1000.0) * temp_factor * degr * self.soiling_factor ) power = max(power, 0.0) noise = random.gauss(1.0, self.sensor_noise) return power * noise # ------------------------------------------------------------------ def compute_voltage( self, power: float ) -> float: if power <= 0: return 0.0 voltage = self.vmp * ( 0.98 + 0.04 * random.random() ) return voltage # ------------------------------------------------------------------ def compute_current( self, power: float, voltage: float ) -> float: if voltage <= 0: return 0.0 return power / voltage # ------------------------------------------------------------------ def update( self, timestamp: datetime, irradiance: float, ambient_temperature: float, years_from_start: float, timestep_minutes: float ) -> Dict: power = self.compute_power( irradiance, ambient_temperature, years_from_start ) voltage = self.compute_voltage(power) current = self.compute_current( power, voltage ) module_temp = self.module_temperature( ambient_temperature, irradiance ) energy = power * timestep_minutes / 60.0 self.total_energy_Wh += energy return { "timestamp": timestamp, "panel_id": self.panel_id, "irradiance_Wm2": irradiance, "ambient_temperature_C": ambient_temperature, "module_temperature_C": module_temp, "voltage_V": voltage, "current_A": current, "power_W": power, "energy_Wh": energy, "total_energy_Wh": self.total_energy_Wh, "enabled": self.enabled, "soiling_factor": self.soiling_factor, "degradation_factor": self.degradation_factor(years_from_start) } # ------------------------------------------------------------------ def fail(self): self.enabled = False # ------------------------------------------------------------------ def repair(self): self.enabled = True # ------------------------------------------------------------------ def soil(self, loss=0.9): self.soiling_factor = loss # ------------------------------------------------------------------ def clean(self): self.soiling_factor = 1.0 # ------------------------------------------------------------------ def __repr__(self): return ( f"PVPanel(" f"{self.panel_id}, " f"{self.nominal_power}W)" )