66from quartz_solar_forecast .data import get_nwp , make_pv_data
77from quartz_solar_forecast .forecasts import (
88 TryolabsSolarPowerPredictor ,
9+ LightGBMSolarPredictor ,
910 forecast_v1_tilt_orientation ,
1011)
1112from quartz_solar_forecast .pydantic_models import PVSite
@@ -120,6 +121,64 @@ def predict_tryolabs(site: PVSite, ts: datetime | str = None):
120121 return predictions
121122
122123
124+ def predict_lightgbm (site : PVSite , ts : datetime | str = None ):
125+ """
126+ Run the forecast with the LightGBM model.
127+
128+ This model uses enhanced feature engineering including solar position,
129+ cyclical time features, and derived weather features.
130+
131+ :param site: the PV site
132+ :param ts: the timestamp of the site. If None, defaults to the current
133+ timestamp rounded down to 15 minutes.
134+ :return: The PV forecast of the site for time (ts) for 48 hours
135+ """
136+
137+ # instantiate class to make predictions
138+ solar_power_predictor = LightGBMSolarPredictor ()
139+
140+ # set start and end time, if no time is given use current time
141+ if ts is None :
142+ start_date = pd .Timestamp .now ().strftime ("%Y-%m-%d" )
143+ start_time = pd .Timestamp .now ().round (freq = "h" )
144+ else :
145+ start_date = pd .Timestamp (ts ).strftime ("%Y-%m-%d" )
146+ start_time = pd .Timestamp (ts ).round (freq = "h" )
147+
148+ end_time = start_time + pd .Timedelta (hours = 48 )
149+ start_date_datetime = datetime .strptime (start_date , "%Y-%m-%d" )
150+
151+ # Check if the start date is more than 3 months ago
152+ three_months_ago = datetime .today () - timedelta (days = 3 * 30 )
153+
154+ if start_date_datetime < three_months_ago :
155+ print (
156+ f"Start date ({ start_date } ) is more than 3 months ago, no" ,
157+ "forecast data available." ,
158+ )
159+ return None
160+ else :
161+ # load model (will use physics-based fallback if not trained yet)
162+ solar_power_predictor .load_model ()
163+ # make predictions
164+ predictions = solar_power_predictor .predict_power_output (
165+ latitude = site .latitude ,
166+ longitude = site .longitude ,
167+ start_date = start_date ,
168+ kwp = site .capacity_kwp ,
169+ orientation = site .orientation ,
170+ tilt = site .tilt ,
171+ )
172+
173+ # postprocessing of the dataframe
174+ predictions = predictions [
175+ (predictions ["date" ] >= start_time ) & (predictions ["date" ] < end_time )
176+ ]
177+ predictions = predictions .reset_index (drop = True )
178+ predictions .set_index ("date" , inplace = True )
179+ print ("Predictions finished." )
180+ return predictions
181+
123182def run_forecast (
124183 site : PVSite ,
125184 model : str = "gb" ,
@@ -131,8 +190,10 @@ def run_forecast(
131190 Predict solar power output for a given site using a specified model.
132191
133192 :param site: the PV site
134- :param model: the model to use for prediction, choose between "ocf" and "tryolabs",
135- by default "ocf" is used
193+ :param model: the model to use for prediction. Options:
194+ - "gb": Gradient Boosting (default, OCF model)
195+ - "xgb": XGBoost (Tryolabs model)
196+ - "lgbm": LightGBM with enhanced features (experimental)
136197 :param ts: the timestamp of the site. If None, defaults to the current
137198 timestamp rounded down to 15 minutes.
138199 :param nwp_source: the nwp data source. Either "gfs", "icon" or "ukmo". Defaults to "icon"
@@ -159,5 +220,11 @@ def run_forecast(
159220 "Ignoring live_generation input." )
160221 return predict_tryolabs (site , ts )
161222
223+ elif model == "lgbm" :
224+ if live_generation is not None :
225+ log .warning ("Live generation data is currently not supported with the lgbm model. " \
226+ "Ignoring live_generation input." )
227+ return predict_lightgbm (site , ts )
228+
162229 else :
163- raise ValueError (f"Unsupported model: { model } . Choose between 'xgb' and 'gb '" )
230+ raise ValueError (f"Unsupported model: { model } . Choose between 'gb', ' xgb', or 'lgbm '" )
0 commit comments