feat: support %_generation column in solar profile CSV
Some checks are pending
CI / Engine — lint / typecheck / test (push) Waiting to run
CI / API — lint / typecheck / test (push) Waiting to run
CI / Web — typecheck / lint / build (push) Waiting to run

- Add column %_generation (percentage, 0-100%) to solar profile CSV
- Update loader to read %_generation and convert to normalized fraction
- Fallback to old E_Grid_Kwp format for backwards compatibility
This commit is contained in:
Manohar Gupta 2026-05-16 11:44:29 +05:30
parent 59da8280dc
commit 0ada193bb0
2 changed files with 8793 additions and 8776 deletions

View file

@ -58,22 +58,39 @@ def load_solar_profile(location_id: str) -> np.ndarray:
Results are cached in-process each location is read from disk only once.
"""
path = _profile_path("solar", location_id)
data: np.ndarray | None = None
# First try: read header to check for %_generation column
with open(path, "r") as f:
header = f.readline().strip().lower()
if "%_generation" in header:
# New format: %_generation column (already percentage, 0-100)
cols = header.split(",")
try:
# Format: col0=Hour_of_Year, col1=Date, col2=month, col3=day, col4=Hour_of_Day, col5=E_Grid_Kwp
data = np.loadtxt(path, delimiter=",", skiprows=1, usecols=5) # E_Grid_Kwp
col_idx = cols.index("%_generation")
data = np.loadtxt(path, delimiter=",", skiprows=1, usecols=col_idx)
if data.max() > 1.0:
data = data / 100 # Convert % to fraction
except ValueError:
pass
if data is None:
try:
# Try old format: E_Grid_Kwp in kW (absolute for 1MW reference)
data = np.loadtxt(path, delimiter=",", skiprows=1, usecols=5)
if data.max() > 1.0:
data = data / 1000 # Convert kW to MW (for 1MW reference)
data = data / data.max() # Normalize to [0,1]
except:
try:
# Format: hour,irradiance_norm
# Legacy: hour,irradiance_norm
data = np.loadtxt(path, delimiter=",", skiprows=1, usecols=1)
except:
raise ValueError(f"Cannot parse solar profile {path}")
if data.shape[0] != 8760:
raise ValueError(f"Solar profile {path} has {data.shape[0]} rows, expected 8760")
# Normalize to [0, 1] if values are absolute (kWp)
max_val = data.max()
if max_val > 1.0:
data = data / max_val # Normalize
if data is None or data.shape[0] != 8760:
raise ValueError(f"Solar profile {path} has {data.shape[0] if data is not None else 'None'} rows, expected 8760")
return data.astype(np.float64)