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