Exemple #1
0
async def get_fwi_calc_outputs(request: FWIRequest):
    """ Returns FWI calculations for all inputs """
    try:
        logger.info('/fwi_calc/')
        # we're interested in noon on the given day
        time_of_interest = get_hour_20_from_date(request.date)

        async with ClientSession() as session:
            yesterday_actual, actual = await calculate_actual(session, request, time_of_interest)
            adjusted = await calculate_adjusted(request)

            output = FWIOutput(
                datetime=get_hour_20_from_date(request.date),
                yesterday=yesterday_actual,
                actual=actual,
                adjusted=adjusted
            )

            return FWIOutputResponse(fwi_outputs=[output])
    except Exception as exception:
        logger.critical(exception, exc_info=True)
        raise
Exemple #2
0
async def get_multi_fwi_calc_outputs(request: MultiFWIRequest):
    """ Returns FWI calculations for all inputs """
    try:
        logger.info('/fwi_calc/multi')
        outputs: List[MultiFWIOutput] = []
        async with ClientSession() as session:

            for fwi_input in request.inputs:
                time_of_interest = get_hour_20_from_date(fwi_input.datetime)
                output: MultiFWIOutput = await multi_calculate_actual(
                    session, fwi_input, request.stationCode, time_of_interest)
                outputs.append(output)
            return MultiFWIOutputResponse(multi_fwi_outputs=outputs)
    except Exception as exception:
        logger.critical(exception, exc_info=True)
        raise
Exemple #3
0
def calculate_fire_behaviour_advisory(
        station: FBACalculatorWeatherStation) -> FireBehaviourAdvisory:
    """ Transform from the raw daily json object returned by wf1, to our fba_calc.StationResponse object.
    """
    # pylint: disable=too-many-locals
    # time of interest will be the same for all stations.
    time_of_interest = get_hour_20_from_date(station.time_of_interest)

    fmc = cffdrs.foliar_moisture_content(station.lat, station.long,
                                         station.elevation,
                                         get_julian_date(time_of_interest))
    sfc = cffdrs.surface_fuel_consumption(station.fuel_type, station.bui,
                                          station.ffmc,
                                          station.percentage_conifer)
    lb_ratio = cffdrs.length_to_breadth_ratio(station.fuel_type,
                                              station.wind_speed)
    ros = cffdrs.rate_of_spread(station.fuel_type,
                                isi=station.isi,
                                bui=station.bui,
                                fmc=fmc,
                                sfc=sfc,
                                pc=station.percentage_conifer,
                                cc=station.grass_cure,
                                pdf=station.percentage_dead_balsam_fir,
                                cbh=station.crown_base_height)
    cfb = calculate_cfb(station.fuel_type, fmc, sfc, ros,
                        station.crown_base_height)

    # Calculate rate of spread assuming 60 minutes since ignition.
    ros_t = cffdrs.rate_of_spread_t(fuel_type=station.fuel_type,
                                    ros_eq=ros,
                                    minutes_since_ignition=60,
                                    cfb=cfb)
    cfb_t = calculate_cfb(station.fuel_type, fmc, sfc, ros_t,
                          station.crown_base_height)

    # Get the default crown fuel load, if none specified.
    if station.crown_fuel_load is None:
        cfl = FUEL_TYPE_DEFAULTS[station.fuel_type].get('CFL', None)
    else:
        cfl = station.crown_fuel_load

    hfi = cffdrs.head_fire_intensity(
        fuel_type=station.fuel_type,
        percentage_conifer=station.percentage_conifer,
        percentage_dead_balsam_fir=station.percentage_dead_balsam_fir,
        ros=ros,
        cfb=cfb,
        cfl=cfl,
        sfc=sfc)
    hfi_t = cffdrs.head_fire_intensity(
        fuel_type=station.fuel_type,
        percentage_conifer=station.percentage_conifer,
        percentage_dead_balsam_fir=station.percentage_dead_balsam_fir,
        ros=ros_t,
        cfb=cfb_t,
        cfl=cfl,
        sfc=sfc)
    critical_hours_4000 = get_critical_hours(
        4000, station.fuel_type, station.percentage_conifer,
        station.percentage_dead_balsam_fir, station.bui, station.grass_cure,
        station.crown_base_height, station.ffmc, fmc, cfb, cfl,
        station.wind_speed, station.prev_day_daily_ffmc,
        station.last_observed_morning_rh_values)
    critical_hours_10000 = get_critical_hours(
        10000, station.fuel_type, station.percentage_conifer,
        station.percentage_dead_balsam_fir, station.bui, station.grass_cure,
        station.crown_base_height, station.ffmc, fmc, cfb, cfl,
        station.wind_speed, station.prev_day_daily_ffmc,
        station.last_observed_morning_rh_values)

    fire_type = get_fire_type(fuel_type=station.fuel_type,
                              crown_fraction_burned=cfb)
    flame_length = get_approx_flame_length(hfi)

    wsv = cffdrs.calculate_wind_speed(fuel_type=station.fuel_type,
                                      ffmc=station.ffmc,
                                      bui=station.bui,
                                      ws=station.wind_speed,
                                      fmc=fmc,
                                      sfc=sfc,
                                      pc=station.percentage_conifer,
                                      cc=station.grass_cure,
                                      pdf=station.percentage_dead_balsam_fir,
                                      cbh=station.crown_base_height,
                                      isi=station.isi)

    bros = cffdrs.back_rate_of_spread(fuel_type=station.fuel_type,
                                      ffmc=station.ffmc,
                                      bui=station.bui,
                                      wsv=wsv,
                                      fmc=fmc,
                                      sfc=sfc,
                                      pc=station.percentage_conifer,
                                      cc=station.grass_cure,
                                      pdf=station.percentage_dead_balsam_fir,
                                      cbh=station.crown_base_height)

    sixty_minute_fire_size = get_fire_size(station.fuel_type, ros, bros, 60,
                                           cfb, lb_ratio)
    sixty_minute_fire_size_t = get_fire_size(station.fuel_type, ros_t, bros,
                                             60, cfb_t, lb_ratio)
    thirty_minute_fire_size = get_fire_size(station.fuel_type, ros, bros, 30,
                                            cfb, lb_ratio)

    return FireBehaviourAdvisory(
        hfi=hfi,
        ros=ros,
        fire_type=fire_type,
        cfb=cfb,
        flame_length=flame_length,
        sixty_minute_fire_size=sixty_minute_fire_size,
        thirty_minute_fire_size=thirty_minute_fire_size,
        critical_hours_hfi_4000=critical_hours_4000,
        critical_hours_hfi_10000=critical_hours_10000,
        hfi_t=hfi_t,
        ros_t=ros_t,
        cfb_t=cfb_t,
        sixty_minute_fire_size_t=sixty_minute_fire_size_t)
Exemple #4
0
def given_input(fuel_type: str, percentage_conifer: float,
                percentage_dead_balsam_fir: float, grass_cure: float,
                crown_base_height: float, num_iterations: int):
    """ Take input and calculate actual and expected results """

    # get python result:
    # seed = time()
    seed = 43
    logger.info('using random seed: %s', seed)
    random.seed(seed)
    results = []
    for index in range(num_iterations):
        # pylint: disable=invalid-name
        elevation = random.randint(0, 4019)
        latitude = random.uniform(45, 60)
        longitude = random.uniform(-118, -136)
        time_of_interest = _random_date()
        # NOTE: For high wind speeds, the difference between REDapp and FireBAT starts exceeding
        # tolerances. REDapp calculates it's own ISI (doesn't take the one provided by the system),
        # but uses a different formula from CFFDRS, so the results start getting more pronounced
        # with higher wind speeds. For that reason we limit our wind speeds to 40 km/h, since anything
        # above that starts failing the unit tests.
        wind_speed = random.uniform(0, 40)
        wind_direction = random.uniform(0, 360)
        temperature = random.uniform(0, 49.6)  # Lytton, B.C., 2021
        relative_humidity = random.uniform(0, 100)
        precipitation = random.uniform(0, 50)
        dc = random.uniform(0, 600)
        dmc = random.uniform(11, 205)
        bui = bui_calc(dmc, dc)
        ffmc = random.uniform(11, 100)
        isi = initial_spread_index(ffmc, wind_speed)

        message = (
            f"""({index}) elevation:{elevation} ; lat: {latitude} ; lon: {longitude}; """
            f"""toi: {time_of_interest}; ws: {wind_speed}; wd: {wind_direction}; """
            f"""temperature: {temperature}; relative_humidity: {relative_humidity}; """
            f"""precipitation: {precipitation}; dc: {dc}; dmc: {dmc}; bui: {bui}; """
            f"""ffmc: {ffmc}; isi: {isi}""")
        logger.debug(message)

        test_entry = (
            f"""({index}) | {fuel_type} | {elevation} | {latitude} | {longitude} | """
            f"""{time_of_interest} | {wind_speed} | {wind_direction} | {percentage_conifer} | """
            f"""{percentage_dead_balsam_fir} | {grass_cure} | {crown_base_height} | {isi}  | """
            f"""{bui} | {ffmc} | {dmc} | {dc} | 0.01 | 0.01 | 0.01 | 0.01 | None | 0.01 | """
            f"""None | 0.01 | None | 0.01 | |""")
        logger.debug(test_entry)
        python_input = FBACalculatorWeatherStation(
            elevation=elevation,
            fuel_type=FuelTypeEnum[fuel_type],
            time_of_interest=time_of_interest,
            percentage_conifer=percentage_conifer,
            percentage_dead_balsam_fir=percentage_dead_balsam_fir,
            grass_cure=grass_cure,
            crown_base_height=crown_base_height,
            crown_fuel_load=None,
            lat=latitude,
            long=longitude,
            bui=bui,
            ffmc=ffmc,
            isi=isi,
            wind_speed=wind_speed,
            wind_direction=wind_direction,
            temperature=temperature,
            relative_humidity=relative_humidity,
            precipitation=precipitation,
            status='Forecasted',
            prev_day_daily_ffmc=None,
            last_observed_morning_rh_values=None)
        python_fba = calculate_fire_behaviour_advisory(python_input)
        # get REDapp result from java:
        java_fbp = FBPCalculateStatisticsCOM(
            elevation=elevation,
            latitude=latitude,
            longitude=longitude,
            time_of_interest=get_hour_20_from_date(time_of_interest),
            fuel_type=fuel_type,
            ffmc=ffmc,
            dmc=dmc,
            dc=dc,
            bui=bui,
            wind_speed=wind_speed,
            wind_direction=wind_direction,
            percentage_conifer=percentage_conifer,
            percentage_dead_balsam_fir=percentage_dead_balsam_fir,
            grass_cure=grass_cure,
            crown_base_height=crown_base_height)

        error_dict = {'fuel_type': fuel_type}
        results.append({
            'input': {
                'isi': isi,
                'bui': bui,
                'wind_speed': wind_speed,
                'ffmc': ffmc
            },
            'python': python_fba,
            'java': java_fbp,
            'fuel_type': fuel_type,
            'error': error_dict
        })

    return results
Exemple #5
0
def get_prep_day_dailies(dailies_date: date, area_dailies: List[StationDaily]) -> List[StationDaily]:
    """ Return all the dailies (that's noon, or 20 hours UTC) for a given date """
    dailies_date_time = get_hour_20_from_date(dailies_date)
    return list(filter(lambda daily: (daily.date == dailies_date_time), area_dailies))
Exemple #6
0
def given_red_app_input(elevation: float,  # pylint: disable=too-many-arguments, invalid-name, too-many-locals
                        latitude: float, longitude: float, time_of_interest: date,
                        wind_speed: float, wind_direction: float,
                        percentage_conifer: float,
                        percentage_dead_balsam_fir: float,
                        grass_cure: float,
                        crown_base_height: float,
                        isi: float, bui: float, ffmc: float, dmc: float, dc: float, fuel_type: str):
    """ Take input and calculate actual and expected results """
    # get python result:
    python_input = FBACalculatorWeatherStation(elevation=elevation,
                                               fuel_type=FuelTypeEnum[fuel_type],
                                               time_of_interest=time_of_interest,
                                               percentage_conifer=percentage_conifer,
                                               percentage_dead_balsam_fir=percentage_dead_balsam_fir,
                                               grass_cure=grass_cure,
                                               crown_base_height=crown_base_height,
                                               crown_fuel_load=None,
                                               lat=latitude, long=longitude, bui=bui,
                                               ffmc=ffmc, isi=isi, wind_speed=wind_speed,
                                               wind_direction=wind_direction,
                                               temperature=20.0,  # temporary fix so tests don't break
                                               relative_humidity=20.0,
                                               precipitation=2.0,
                                               status='Forecasted',
                                               prev_day_daily_ffmc=90.0,
                                               last_observed_morning_rh_values={
                                                   7.0: 61.0, 8.0: 54.0, 9.0: 43.0, 10.0: 38.0,
                                                   11.0: 34.0, 12.0: 23.0})
    python_fba = calculate_fire_behaviour_advisory(python_input)
    # get REDapp result from java:
    java_fbp = FBPCalculateStatisticsCOM(elevation=elevation,
                                         latitude=latitude,
                                         longitude=longitude,
                                         time_of_interest=get_hour_20_from_date(time_of_interest),
                                         fuel_type=fuel_type,
                                         ffmc=ffmc,
                                         dmc=dmc,
                                         dc=dc,
                                         bui=bui,
                                         wind_speed=wind_speed,
                                         wind_direction=wind_direction,
                                         percentage_conifer=percentage_conifer,
                                         percentage_dead_balsam_fir=percentage_dead_balsam_fir,
                                         grass_cure=grass_cure,
                                         crown_base_height=crown_base_height)

    # NOTE: REDapp has a ros_eq and a ros_t ;
    # assumptions:
    # ros_eq == ROScalc
    # ros_t  == ROStcalc
    expected = {
        'ros': java_fbp.ros_eq,
        'ros_t': java_fbp.ros_t,
        'cfb': java_fbp.cfb / 100.0,  # CFFDRS gives cfb as a fraction
        'hfi': java_fbp.hfi,
        'area': java_fbp.area
    }

    error_dict = {
        'fuel_type': fuel_type
    }

    return {
        'python': python_fba,
        'expected': expected,
        'fuel_type': fuel_type,
        'error': error_dict
    }
Exemple #7
0
async def get_stations_data(  # pylint:disable=too-many-locals
    request: StationListRequest,
    _=Depends(authentication_required)):
    """ Returns per-station data for a list of requested stations """
    logger.info('/fba-calc/stations')
    try:
        # build a list of station codes
        station_codes = [station.station_code for station in request.stations]
        # remove any duplicate station codes
        unique_station_codes = list(dict.fromkeys(station_codes))

        # we're interested in noon on the given day
        time_of_interest = get_hour_20_from_date(request.date)

        async with ClientSession() as session:
            # authenticate against wfwx api
            header = await get_auth_header(session)
            # get station information from the wfwx api
            wfwx_stations = await get_wfwx_stations_from_station_codes(
                session, header, unique_station_codes)
            # get the dailies for all the stations
            dailies = await get_dailies(session, header, wfwx_stations,
                                        time_of_interest)
            # turn it into a dictionary so we can easily get at data using a station id
            dailies_by_station_id = {
                raw_daily.get('stationId'): raw_daily
                async for raw_daily in dailies
            }
            # must retrieve the previous day's observed/forecasted FFMC value from WFWX
            prev_day = time_of_interest - timedelta(days=1)
            # get the "daily" data for the station for the previous day
            yesterday_response = await get_dailies(session, header,
                                                   wfwx_stations, prev_day)
            # turn it into a dictionary so we can easily get at data
            yesterday_dailies_by_station_id = {
                raw_daily.get('stationId'): raw_daily
                async for raw_daily in yesterday_response
            }
            # get hourly observation history from our API (used for calculating morning diurnal FFMC)
            hourly_observations = await get_hourly_readings_in_time_interval(
                unique_station_codes, time_of_interest - timedelta(days=4),
                time_of_interest)
            # also turn hourly obs data into a dict indexed by station id
            hourly_obs_by_station_code = \
                {raw_hourly.station.code: raw_hourly for raw_hourly in hourly_observations}

        # we need a lookup from station code to station id
        # TODO: this is a bit silly, the call to get_wfwx_stations_from_station_codes repeats a lot of this!
        wfwx_station_lookup = {
            wfwx_station.code: wfwx_station
            for wfwx_station in wfwx_stations
        }

        stations_response = []
        # for each station code in our station list.
        for requested_station in request.stations:
            # get the wfwx station
            wfwx_station = wfwx_station_lookup[requested_station.station_code]
            # get the raw daily response from wf1.
            if wfwx_station.wfwx_id in dailies_by_station_id and\
                    requested_station.station_code in hourly_obs_by_station_code:
                try:
                    station_response = await process_request(
                        dailies_by_station_id, yesterday_dailies_by_station_id,
                        hourly_obs_by_station_code, wfwx_station,
                        requested_station, time_of_interest)
                except Exception as exception:  # pylint: disable=broad-except
                    # If something goes wrong processing the request, then we return this station
                    # with an error response.
                    logger.error('request object: %s', request.__str__())
                    logger.critical(exception, exc_info=True)
                    station_response = process_request_without_observation(
                        requested_station, wfwx_station, request.date, 'ERROR')

            else:
                # if we can't get the daily (no forecast, or no observation)
                station_response = process_request_without_observation(
                    requested_station, wfwx_station, request.date, 'N/A')

            # Add the response to our list of responses
            stations_response.append(station_response)

        return StationsListResponse(date=request.date,
                                    stations=stations_response)
    except Exception as exception:
        logger.error('request object: %s', request.__str__())
        logger.critical(exception, exc_info=True)
        raise