Skip to content

Commit

Permalink
Handle none values more gracefully for FFMC calculations (#3629)
Browse files Browse the repository at this point in the history
Co-authored-by: Darren Boss <darren.boss@gov.bc.ca>
  • Loading branch information
conbrad and dgboss authored May 16, 2024
1 parent efb88c6 commit 3291788
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 24 deletions.
52 changes: 45 additions & 7 deletions api/app/fire_behaviour/cffdrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,13 +544,28 @@ def fine_fuel_moisture_code(ffmc: float, temperature: float, relative_humidity:
"""

if ffmc is None:
ffmc = NULL
logger.error("Failed to calculate FFMC; initial FFMC is required.")
return None
if temperature is None:
temperature = NULL
if relative_humidity is None:
relative_humidity = NULL
if precipitation is None:
precipitation = NULL
if wind_speed is None:
# _ffmcCalc with throw if passed a NULL windspeed, so log a message and return None.
logger.error("Failed to calculate ffmc")
return None
result = CFFDRS.instance().cffdrs._ffmcCalc(ffmc_yda=ffmc, temp=temperature, rh=relative_humidity,
prec=precipitation, ws=wind_speed)
if len(result) == 0:
logger.error("Failed to calculate ffmc")
return None
if isinstance(result[0], float):
return result[0]
raise CFFDRSException("Failed to calculate ffmc")


logger.error("Failed to calculate ffmc")
return None

def duff_moisture_code(dmc: float, temperature: float, relative_humidity: float,
precipitation: float, latitude: float = 55, month: int = 7,
Expand Down Expand Up @@ -578,12 +593,24 @@ def duff_moisture_code(dmc: float, temperature: float, relative_humidity: float,
:type latitude_adjust: bool, optional
"""
if dmc is None:
dmc = NULL
logger.error("Failed to calculate DMC; initial DMC is required.")
return None
if temperature is None:
temperature = NULL
if relative_humidity is None:
relative_humidity = NULL
if precipitation is None:
precipitation = NULL
result = CFFDRS.instance().cffdrs._dmcCalc(dmc, temperature, relative_humidity, precipitation,
latitude, month, latitude_adjust)

if len(result) == 0:
logger.error("Failed to calculate DMC")
return None
if isinstance(result[0], float):
return result[0]
raise CFFDRSException("Failed to calculate dmc")
logger.error("Failed to calculate DMC")
return None


def drought_code(dc: float, temperature: float, relative_humidity: float, precipitation: float,
Expand All @@ -610,12 +637,23 @@ def drought_code(dc: float, temperature: float, relative_humidity: float, precip
:return: None
"""
if dc is None:
dc = NULL
logger.error("Failed to calculate DC; initial DC is required.")
return None
if temperature is None:
temperature = NULL
if relative_humidity is None:
relative_humidity = NULL
if precipitation is None:
precipitation = NULL
result = CFFDRS.instance().cffdrs._dcCalc(dc, temperature, relative_humidity, precipitation,
latitude, month, latitude_adjust)
if len(result) == 0:
logger.error("Failed to calculate DC")
return None
if isinstance(result[0], float):
return result[0]
raise CFFDRSException("Failed to calculate dmc")
logger.error("Failed to calculate DC")
return None


def initial_spread_index(ffmc: float, wind_speed: float, fbp_mod: bool = False):
Expand Down
80 changes: 63 additions & 17 deletions api/app/tests/fire_behavior/test_cffdrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import math
from app.schemas.fba_calc import FuelTypeEnum
from app.fire_behaviour import cffdrs
from app.fire_behaviour.cffdrs import (pandas_to_r_converter, hourly_fine_fuel_moisture_code, CFFDRSException)
from app.fire_behaviour.cffdrs import pandas_to_r_converter, hourly_fine_fuel_moisture_code, CFFDRSException


start_date = datetime(2023, 8, 17)
Expand All @@ -33,42 +33,88 @@ def test_pandas_to_r_converter():
def test_hourly_ffmc_calculates_values():
ffmc_old = 80.0
df = hourly_fine_fuel_moisture_code(df_hourly, ffmc_old)

assert not df['hffmc'].isnull().any()


def test_hourly_ffmc_no_temperature():
ffmc_old = 80.0
df_hourly = pd.DataFrame({'datetime': [hourly_datetimes[0], hourly_datetimes[1]], 'celsius': [12, 1], 'precipitation': [0, 1], 'ws': [14, 12], 'rh':[50, 50]})
df_hourly = pd.DataFrame(
{
'datetime': [hourly_datetimes[0], hourly_datetimes[1]],
'celsius': [12, 1],
'precipitation': [0, 1],
'ws': [14, 12],
'rh': [50, 50],
}
)

with pytest.raises(CFFDRSException):
hourly_fine_fuel_moisture_code(df_hourly, ffmc_old)


def test_ros():
""" ROS runs """
ros =cffdrs.rate_of_spread(FuelTypeEnum.C7, 1, 1, 1, 1, pc=100, pdf=None,
cc=None, cbh=10)
"""ROS runs"""
ros = cffdrs.rate_of_spread(FuelTypeEnum.C7, 1, 1, 1, 1, pc=100, pdf=None, cc=None, cbh=10)
assert math.isclose(ros, 1.2966988409822604e-05)


def test_ros_no_isi():
""" ROS fails """
"""ROS fails"""
with pytest.raises(cffdrs.CFFDRSException):
cffdrs.rate_of_spread(FuelTypeEnum.C7, None, 1, 1, 1, pc=100, pdf=None,
cc=None, cbh=10)
cffdrs.rate_of_spread(FuelTypeEnum.C7, None, 1, 1, 1, pc=100, pdf=None, cc=None, cbh=10)


def test_ros_no_bui():
""" ROS fails """
"""ROS fails"""
with pytest.raises(cffdrs.CFFDRSException):
cffdrs.rate_of_spread(FuelTypeEnum.C7, 1, None, 1, 1, pc=100, pdf=None,
cc=None, cbh=10)
cffdrs.rate_of_spread(FuelTypeEnum.C7, 1, None, 1, 1, pc=100, pdf=None, cc=None, cbh=10)


def test_ros_no_params():
""" ROS fails """
"""ROS fails"""
with pytest.raises(cffdrs.CFFDRSException):
cffdrs.rate_of_spread(FuelTypeEnum.C7, None, None, None, None, pc=100, pdf=None,
cc=None, cbh=10)

cffdrs.rate_of_spread(FuelTypeEnum.C7, None, None, None, None, pc=100, pdf=None, cc=None, cbh=10)


@pytest.mark.parametrize(
"ffmc,temperature,precipitation,relative_humidity,wind_speed",
[
(None, 10, 9, 8, 7),
(11, None, 9, 8, 7),
(11, 10, None, 8, 7),
(11, 10, 9, None, 7),
(11, 10, 9, 8, None),
(None, None, None, 8, 7),
(None, None, None, None, None),
],
)
def test_failing_ffmc(ffmc, temperature, precipitation, relative_humidity, wind_speed):
"""Test that we can handle None values when attempting to calculate ffmc"""
res = cffdrs.fine_fuel_moisture_code(
ffmc=ffmc,
temperature=temperature,
precipitation=precipitation,
relative_humidity=relative_humidity,
wind_speed=wind_speed,
)
assert res is None


@pytest.mark.parametrize(
'dmc,temperature,relative_humidity,precipitation',
[(None, 10, 90, 1), (100, None, 90, 1), (100, 10, None, 1), (100, 10, 90, None)],
)
def test_failing_dmc(dmc, temperature, relative_humidity, precipitation):
"""Test that we can handle None values when attempting to calculate dmc"""
res = cffdrs.duff_moisture_code(dmc, temperature, relative_humidity, precipitation)
assert res is None


@pytest.mark.parametrize(
'dc,temperature,relative_humidity,precipitation', [(None, 10, 90, 1), (100, None, 90, 1), (100, 10, 90, None)]
)
def test_failing_dc(dc, temperature, relative_humidity, precipitation):
"""Test that we can handle None values when attempting to calculate dc"""
res = cffdrs.drought_code(dc, temperature, relative_humidity, precipitation)
assert res is None

0 comments on commit 3291788

Please sign in to comment.