Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5177 +/- ##
==========================================
+ Coverage 68.90% 68.93% +0.03%
==========================================
Files 392 392
Lines 16373 16390 +17
Branches 1846 1846
==========================================
+ Hits 11281 11298 +17
Misses 4508 4508
Partials 584 584 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| return direction | ||
|
|
||
| def interpolate( | ||
| self, source: StationWindVectorSource, reference_raster_path: str |
There was a problem hiding this comment.
This'll work in Python but technically this source type defines get_uv_interpolation_data and doesn't match the Interpolator contract signature. It'd break if we accidentally passed another source type in here. Wonder if there's a better way to structure the types...
There was a problem hiding this comment.
Thanks, I overlooked that. I'll see what I can come up with, without adding too much complexity there
There was a problem hiding this comment.
Made some changes in d30d575. Hopefully that doesn't complicate things too much
There was a problem hiding this comment.
I think just not forcing the contract would be a bit simpler. With theTypeVar("SourceT") it has no constraints or bound so a type checker won't enforce that subclasses actually use a source type that satisfies any contract.
| def get_uv_interpolation_data( | ||
| self, | ||
| ) -> Tuple[NDArray[np.float32], NDArray[np.float32], NDArray[np.float32], NDArray[np.float32]]: | ||
| valid = [ |
There was a problem hiding this comment.
We might also want to include lat and lons in the valid check
There was a problem hiding this comment.
@dgboss @conbrad Looking at this brought up a thought. When we save_sfms_run, we're storing all the station codes from the actuals. But in the processing of each parameter, we're filtering these actuals based on "valid" data. I'm thinking in that case, we may still not really know which stations were used for interpolating data. What do you guys think?
There was a problem hiding this comment.
In this example - 289 stations saved to db, 288 stations used for temp/rh/precip/wind speed, 287 stations used for wind direction
2026-03-05 11:00:49,783 - __main__ - INFO - Starting SFMS daily actuals for 2025-05-13
2026-03-05 11:00:50,039 - __main__ - INFO - Using reference raster: /vsis3/gpdqha/sfms/static/fuel/2025/fbp2025_v2.tif
2026-03-05 11:00:51,421 - wps_sfms.processors.idw - INFO - Starting interpolation, output: sfms_ng/actual/2025/05/13/temperature_20250513.tif
2026-03-05 11:00:53,153 - wps_sfms.processors.temperature - INFO - Interpolating temperature for raster grid (778 x 683)
2026-03-05 11:00:53,154 - wps_sfms.processors.idw - INFO - Processing 229171 valid pixels (skipping 302203 NoData pixels)
2026-03-05 11:00:53,154 - wps_sfms.processors.idw - INFO - Running batch temperature IDW interpolation for 229171 pixels and 288 stations
2026-03-05 11:00:59,296 - wps_sfms.interpolation.common - INFO - Interpolation complete:
2026-03-05 11:00:59,296 - wps_sfms.interpolation.common - INFO - Total pixels: 531374
2026-03-05 11:00:59,296 - wps_sfms.interpolation.common - INFO - Successfully interpolated: 229171 (43.1%)
2026-03-05 11:00:59,296 - wps_sfms.interpolation.common - INFO - Failed interpolation (no stations in range): 0 (0.0%)
2026-03-05 11:00:59,296 - wps_sfms.interpolation.common - INFO - Skipped (NoData): 302203 (56.9%)
2026-03-05 11:00:59,927 - wps_sfms.processors.idw - INFO - Interpolation complete: sfms_ng/actual/2025/05/13/temperature_20250513.tif
2026-03-05 11:00:59,927 - __main__ - INFO - Temperature interpolation raster: sfms_ng/actual/2025/05/13/temperature_20250513.tif
2026-03-05 11:00:59,927 - wps_shared.db.crud.sfms_run - INFO - temperature_interpolation completed successfully -- time elapsed 0 hours, 0 minutes, 8.00 seconds
2026-03-05 11:01:00,099 - wps_sfms.processors.idw - INFO - Starting interpolation, output: sfms_ng/actual/2025/05/13/relative_humidity_20250513.tif
2026-03-05 11:01:00,603 - wps_sfms.processors.relative_humidity - INFO - Interpolating dew point for RH raster grid (778 x 683)
2026-03-05 11:01:00,603 - wps_sfms.processors.idw - INFO - Processing 229171 valid pixels (skipping 302203 NoData pixels)
2026-03-05 11:01:00,603 - wps_sfms.processors.idw - INFO - Running batch dew point IDW interpolation for 229171 pixels and 288 stations
2026-03-05 11:01:06,639 - wps_sfms.interpolation.common - INFO - Interpolation complete:
2026-03-05 11:01:06,639 - wps_sfms.interpolation.common - INFO - Total pixels: 531374
2026-03-05 11:01:06,639 - wps_sfms.interpolation.common - INFO - Successfully interpolated: 229171 (43.1%)
2026-03-05 11:01:06,639 - wps_sfms.interpolation.common - INFO - Failed interpolation (no stations in range): 0 (0.0%)
2026-03-05 11:01:06,639 - wps_sfms.interpolation.common - INFO - Skipped (NoData): 302203 (56.9%)
2026-03-05 11:01:06,988 - wps_sfms.processors.idw - INFO - Interpolation complete: sfms_ng/actual/2025/05/13/relative_humidity_20250513.tif
2026-03-05 11:01:06,988 - __main__ - INFO - RH interpolation raster: sfms_ng/actual/2025/05/13/relative_humidity_20250513.tif
2026-03-05 11:01:06,988 - wps_shared.db.crud.sfms_run - INFO - rh_interpolation completed successfully -- time elapsed 0 hours, 0 minutes, 6.00 seconds
2026-03-05 11:01:06,998 - wps_sfms.processors.idw - INFO - Starting interpolation, output: sfms_ng/actual/2025/05/13/wind_speed_20250513.tif
2026-03-05 11:01:06,998 - wps_sfms.interpolation.idw - INFO - Starting interpolation for 288 stations
2026-03-05 11:01:07,428 - wps_sfms.interpolation.idw - INFO - Interpolating for raster grid (778 x 683)
2026-03-05 11:01:07,428 - wps_sfms.interpolation.idw - INFO - Processing 229171 valid pixels (skipping 302203 NoData pixels)
2026-03-05 11:01:07,428 - wps_sfms.interpolation.idw - INFO - Running batch IDW interpolation for 229171 pixels and 288 stations
2026-03-05 11:01:13,544 - wps_sfms.interpolation.common - INFO - Interpolation complete:
2026-03-05 11:01:13,544 - wps_sfms.interpolation.common - INFO - Total pixels: 531374
2026-03-05 11:01:13,544 - wps_sfms.interpolation.common - INFO - Successfully interpolated: 229171 (43.1%)
2026-03-05 11:01:13,544 - wps_sfms.interpolation.common - INFO - Failed interpolation (no stations in range): 0 (0.0%)
2026-03-05 11:01:13,544 - wps_sfms.interpolation.common - INFO - Skipped (NoData): 302203 (56.9%)
2026-03-05 11:01:13,849 - wps_sfms.processors.idw - INFO - Interpolation complete: sfms_ng/actual/2025/05/13/wind_speed_20250513.tif
2026-03-05 11:01:13,849 - __main__ - INFO - Wind speed interpolation raster: sfms_ng/actual/2025/05/13/wind_speed_20250513.tif
2026-03-05 11:01:13,849 - wps_shared.db.crud.sfms_run - INFO - wind_speed_interpolation completed successfully -- time elapsed 0 hours, 0 minutes, 6.00 seconds
2026-03-05 11:01:13,852 - wps_sfms.processors.idw - INFO - Starting interpolation, output: sfms_ng/actual/2025/05/13/wind_direction_20250513.tif
2026-03-05 11:01:14,133 - wps_sfms.processors.wind - INFO - Interpolating wind direction for raster grid (778 x 683)
2026-03-05 11:01:14,133 - wps_sfms.processors.idw - INFO - Processing 229171 valid pixels (skipping 302203 NoData pixels)
2026-03-05 11:01:14,133 - wps_sfms.processors.idw - INFO - Running batch wind-u component IDW interpolation for 229171 pixels and 287 stations
2026-03-05 11:01:20,113 - wps_sfms.processors.idw - INFO - Processing 229171 valid pixels (skipping 302203 NoData pixels)
2026-03-05 11:01:20,113 - wps_sfms.processors.idw - INFO - Running batch wind-v component IDW interpolation for 229171 pixels and 287 stations
2026-03-05 11:01:26,205 - wps_sfms.interpolation.common - INFO - Interpolation complete:
2026-03-05 11:01:26,205 - wps_sfms.interpolation.common - INFO - Total pixels: 531374
2026-03-05 11:01:26,205 - wps_sfms.interpolation.common - INFO - Successfully interpolated: 229171 (43.1%)
2026-03-05 11:01:26,205 - wps_sfms.interpolation.common - INFO - Failed interpolation (no stations in range): 0 (0.0%)
2026-03-05 11:01:26,205 - wps_sfms.interpolation.common - INFO - Skipped (NoData): 302203 (56.9%)```
There was a problem hiding this comment.
Oh nice catch, I wonder if we should store the stations in object storage and just have a path to that object in each parameter record.
There was a problem hiding this comment.
Ya I'll make another ticket to address something like that
| *(index_keys_by_param[param] for param in index_params), | ||
| ] | ||
|
|
||
| with input_dataset_context(input_keys) as input_datasets: |
There was a problem hiding this comment.
| with input_dataset_context(input_keys) as input_datasets: | |
| with input_dataset_context(input_keys) as input_datasets: | |
| n_weather = len(weather_params) | |
| weather_datasets: WeatherDatasetMap = dict(zip(weather_params, input_datasets[:n_weather])) | |
| index_datasets: IndexDatasetMap = dict(zip(index_params, input_datasets[n_weather:])) |
With this we can remove the cast because input_datasets is already List[WPSDataset] per the MultiDatasetContext type alias. The positional dependency is now explicit — the slice boundary n_weather is the
same value used to build the key list, so they can't drift.
There was a problem hiding this comment.
This could also be pulled out into a method or class and return a FWIDatasets if this change is adopted:
https://github.com/bcgov/wps/pull/5177/changes#r2892595196
Should also have it's own unit tests because I think this setup relies on ordering being correct.
There was a problem hiding this comment.
What do you think about removing the cast but keeping the iterator? In my mind it's more explicit to get the weather and index params this way than by slicing. Open to thoughts there
ab28745
There was a problem hiding this comment.
Sure sounds good, I am still a bit concerned about the assumption that parameters and datasets follow the same ordering though.
|
| s3_key = await processor.process(s3_client, fuel_raster_path, _source, output_key) | ||
| logger.info("%s interpolation raster: %s", _job_name.value, s3_key) | ||
|
|
||
| cog_key = raster_addresser.get_cog_key(output_key) |
| await run_sfms_daily_actuals(target_date) | ||
|
|
||
| mock_dependencies.temp_processor.process.assert_called_once() | ||
| mock_dependencies.wind_speed_processor.process.assert_called_once() |
There was a problem hiding this comment.
Should the wind_direction_processor also have been called?




fwiprocessor. The reason for this is that wind speed was temporarily faked in the FFMC calculation before. Now that we have actual wind speed, the weather inputs are different for FFMC than they are for DMC/DC (this is made a bit more confusing by theCFFDRSlibrary, which includes things likeRHinDCcalculations, even though it isn't actually used in the calculation) linkTest Links:
Landing Page
MoreCast
Percentile Calculator
C-Haines
FireCalc
FireCalc bookmark
Auto Spatial Advisory (ASA)
HFI Calculator
SFMS Insights
Fire Watch