Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
b8762ad
Caching for run parameters, provincial summary and fire shape areas
dgboss Aug 21, 2025
6fe321c
Progress
dgboss Aug 25, 2025
9df8c04
Hook and slice clean up
dgboss Aug 25, 2025
fd004e8
Re-enable auth
dgboss Aug 25, 2025
72d577f
Fix unit tests due to new hooks
dgboss Aug 26, 2025
33fb086
Allow viewing of offline data on launch
dgboss Aug 26, 2025
88308da
Download TDY and TMR hfi pmtiles on start
dgboss Aug 27, 2025
ff3cdc2
Merge branch 'main' into task/offline-data/4741
dgboss Aug 27, 2025
ad70cb8
Remove dupes
dgboss Aug 27, 2025
9851f46
Typing change
dgboss Aug 27, 2025
83b7498
fba endpoint tests
dgboss Aug 27, 2025
59407fc
Fix test
dgboss Aug 27, 2025
fe78941
Mock db call
dgboss Aug 28, 2025
54ae667
run parameters slice tests
dgboss Sep 23, 2025
b391bec
Cahcing tests
dgboss Nov 17, 2025
efbf12d
Merge branch 'main' into task/offline-data/4741
dgboss Nov 18, 2025
a4ddd16
Dedupe in fba router
dgboss Nov 18, 2025
ad9710b
Code clean up
dgboss Nov 18, 2025
15c7039
test fix
dgboss Nov 18, 2025
9b08f35
Fix test
dgboss Nov 19, 2025
f3008c3
Remove test
dgboss Nov 20, 2025
7d111f3
fire centre slice tests
dgboss Nov 20, 2025
18011a0
data slice utils tests
dgboss Nov 20, 2025
3d10af2
fix
dgboss Nov 20, 2025
2f7b7e6
More fba endpoint tests
dgboss Nov 20, 2025
53d2e8b
Remove useless test
dgboss Nov 20, 2025
ab0986c
Fix logic error
dgboss Nov 20, 2025
d33cf9d
fixes
dgboss Nov 20, 2025
21e0674
dataHook tests
dgboss Nov 24, 2025
f3bbcca
Null checks
dgboss Nov 24, 2025
bf85c1f
Merge branch 'main' into task/offline-data/4741
dgboss Nov 24, 2025
86e065a
Simplify app startup date
dgboss Nov 24, 2025
1bb76b7
Formatting
dgboss Nov 24, 2025
1057de2
Fix tests
dgboss Nov 24, 2025
8fdd027
PR feedback
dgboss Nov 27, 2025
a331dac
Use cached fire centers if offline.
dgboss Nov 27, 2025
44be9c1
Add missing dependency to zoneStatus hook.
dgboss Nov 27, 2025
1710904
Add return type to readFromFilesystem
dgboss Nov 27, 2025
f1e9ba9
Typings
dgboss Nov 27, 2025
326599f
test fixes
dgboss Nov 28, 2025
896d4b5
Don't set zone on today
dgboss Nov 28, 2025
ccaa277
Better fix
dgboss Nov 28, 2025
86d85f6
Another test fix
dgboss Nov 28, 2025
f2be8a4
Merge branch 'main' into task/offline-data/4741
dgboss Nov 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
323 changes: 222 additions & 101 deletions api/app/routers/fba.py

Large diffs are not rendered by default.

160 changes: 153 additions & 7 deletions api/app/tests/fba/test_fba_endpoint.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio
import json
import math
from collections import namedtuple
Expand All @@ -18,7 +19,14 @@
TPIClassEnum,
)
from wps_shared.db.models.fuel_type_raster import FuelTypeRaster
from wps_shared.schemas.fba import HfiThreshold
from wps_shared.schemas.auto_spatial_advisory import SFMSRunType
from wps_shared.schemas.fba import (
FireZoneHFIStats,
HFIStatsResponse,
HfiThreshold,
LatestSFMSRunParameterRangeResponse,
SFMSRunParameter,
)
from wps_shared.tests.common import default_mock_client_get

mock_fire_centre_name = "PGFireCentre"
Expand All @@ -33,11 +41,11 @@
)
get_sfms_run_datetimes_url = "/api/fba/sfms-run-datetimes/forecast/2022-09-27"
get_sfms_run_bounds_url = "/api/fba/sfms-run-bounds"
get_tpi_stats_url = "api/fba/tpi-stats/forecast/2022-09-27/2022-09-27"

decode_fn = "jwt.decode"
mock_tpi_stats = AdvisoryTPIStats(
id=1, advisory_shape_id=1, valley_bottom=1, mid_slope=2, upper_slope=3, pixel_size_metres=50
)

mock_tpi_stats_empty = []

mock_fire_centre_info = [(9.0, 11.0, 1, 1, 50, 100, 1)]
mock_fire_centre_info_with_grass = [(9.0, 11.0, 12, 1, 50, 100, None)]
Expand Down Expand Up @@ -73,7 +81,6 @@
],
)


def create_mock_centre_tpi_stats(
advisory_shape_id, source_identifier, valley_bottom, mid_slope, upper_slope, pixel_size_metres
):
Expand All @@ -90,6 +97,74 @@
mock_centre_tpi_stats_1 = create_mock_centre_tpi_stats(1, 1, 1, 2, 3, 2)
mock_centre_tpi_stats_2 = create_mock_centre_tpi_stats(2, 2, 1, 2, 3, 2)

TPIFuelAreasResponse = namedtuple(
"TPIFuelAreasResponse",
[
"tpi_class",
"fuel_area",
"source_identifier",
"id",
"name",
],
)


def mock_create_tpi_fuel_area(tpi_class, fuel_area, source_identifier, id, name):
return TPIFuelAreasResponse(
tpi_class=tpi_class,
fuel_area=fuel_area,
source_identifier=source_identifier,
id=id,
name=name,
)


mock_tpi_fuel_area_1 = mock_create_tpi_fuel_area(
TPIClassEnum.valley_bottom, 100, "20", 2, "Coastal"
)
mock_tpi_fuel_area_2 = mock_create_tpi_fuel_area(TPIClassEnum.mid_slope, 200, "20", 2, "Coastal")
mock_tpi_fuel_area_3 = mock_create_tpi_fuel_area(TPIClassEnum.upper_slope, 300, "20", 2, "Coastal")

TPIStatsResponse = namedtuple(
"TPIStatsResponse",
[
"advisory_shape_id",
"source_identifier",
"valley_bottom",
"mid_slope",
"upper_slope",
"pixel_size_metres",
"fire_centre_id",
"fire_centre_name",
],
)


def create_mock_tpi_stats(
advisory_shape_id,
source_identifier,
valley_bottom,
mid_slope,
upper_slope,
pixel_size_metres,
fire_centre_id,
fire_centre_name,
):
return TPIStatsResponse(
advisory_shape_id=advisory_shape_id,
source_identifier=source_identifier,
valley_bottom=valley_bottom,
mid_slope=mid_slope,
upper_slope=upper_slope,
pixel_size_metres=pixel_size_metres,
fire_centre_id=fire_centre_id,
fire_centre_name=fire_centre_name,
)


mock_tpi_stats_1 = create_mock_tpi_stats(1, 1, 1, 2, 3, 2, 1, "foo")
mock_tpi_stats_2 = create_mock_tpi_stats(2, 2, 1, 2, 3, 2, 2, "bar")


CentreTPIFuelAreasResponse = namedtuple(
"CentreTPIFuelAreasResponse", ["tpi_class", "fuel_area", "source_identifier"]
Expand Down Expand Up @@ -125,8 +200,8 @@
return {}


async def mock_get_tpi_stats(*_, **__):
return mock_tpi_stats
async def mock_get_tpi_stats_empty(*_, **__):

Check warning on line 203 in api/app/tests/fba/test_fba_endpoint.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use asynchronous features in this function or remove the `async` keyword.

See more on https://sonarcloud.io/project/issues?id=bcgov_wps&issues=AZrHMljtOFXmxVAYLwCD&open=AZrHMljtOFXmxVAYLwCD&pullRequest=4902
return mock_tpi_stats_empty


async def mock_get_tpi_stats_none(*_, **__):
Expand All @@ -149,6 +224,10 @@
return [mock_centre_tpi_stats_1, mock_centre_tpi_stats_2]


async def mock_get_tpi_stats(*_, **__):
return [mock_tpi_stats_1, mock_tpi_stats_2]


async def mock_get_fire_centre_tpi_fuel_areas(*_, **__):
return [mock_centre_tpi_fuel_area_1, mock_centre_tpi_fuel_area_2, mock_centre_tpi_fuel_area_3]

Expand All @@ -168,6 +247,32 @@
return []


async def mock_get_most_recent_run_datetime_for_date_range(*_, **__):

Check warning on line 250 in api/app/tests/fba/test_fba_endpoint.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use asynchronous features in this function or remove the `async` keyword.

See more on https://sonarcloud.io/project/issues?id=bcgov_wps&issues=AZrHMljtOFXmxVAYLwCE&open=AZrHMljtOFXmxVAYLwCE&pullRequest=4902
for_date_1 = date(2025, 8, 25)
for_date_2 = date(2025, 8, 26)
run_datetime = datetime(2025, 8, 25)
run_parameter_1 = SFMSRunParameter(
for_date=for_date_1, run_datetime=run_datetime, run_type=SFMSRunType.FORECAST
)
run_parameter_2 = SFMSRunParameter(
for_date=for_date_2, run_datetime=run_datetime, run_type=SFMSRunType.FORECAST
)
return [run_parameter_1, run_parameter_2]


async def mock_get_all_zone_source_ids(*_, **__):

Check warning on line 263 in api/app/tests/fba/test_fba_endpoint.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use asynchronous features in this function or remove the `async` keyword.

See more on https://sonarcloud.io/project/issues?id=bcgov_wps&issues=AZrHMljtOFXmxVAYLwCF&open=AZrHMljtOFXmxVAYLwCF&pullRequest=4902
return [1, 2, 3]


async def mock_get_tpi_fuel_areas(*_, **__):

Check warning on line 267 in api/app/tests/fba/test_fba_endpoint.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use asynchronous features in this function or remove the `async` keyword.

See more on https://sonarcloud.io/project/issues?id=bcgov_wps&issues=AZrHMljtOFXmxVAYLwCG&open=AZrHMljtOFXmxVAYLwCG&pullRequest=4902
return [mock_tpi_fuel_area_1, mock_centre_tpi_fuel_area_2, mock_tpi_fuel_area_3]


async def mock_get_hfi_fuels_data_for_run_parameter(*_, **__):

Check warning on line 271 in api/app/tests/fba/test_fba_endpoint.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use asynchronous features in this function or remove the `async` keyword.

See more on https://sonarcloud.io/project/issues?id=bcgov_wps&issues=AZrHMljtOFXmxVAYLwCH&open=AZrHMljtOFXmxVAYLwCH&pullRequest=4902
mock_fire_zone_hfi_stats = FireZoneHFIStats(min_wind_stats=[], fuel_area_stats=[])
return HFIStatsResponse(zone_data={1: mock_fire_zone_hfi_stats})


@pytest.fixture()
def client():
from app.main import app as test_app
Expand Down Expand Up @@ -205,6 +310,7 @@
get_fire_centre_info_url,
get_sfms_run_datetimes_url,
get_sfms_run_bounds_url,
get_tpi_stats_url,
],
)
def test_get_endpoints_unauthorized(client: TestClient, endpoint: str):
Expand Down Expand Up @@ -406,6 +512,32 @@
assert json_response["firezone_tpi_stats"][1]["mid_slope_tpi"] is None
assert json_response["firezone_tpi_stats"][1]["upper_slope_tpi"] is None

@pytest.mark.usefixtures("mock_jwt_decode")
@patch("app.routers.fba.get_auth_header", mock_get_auth_header)
@patch("app.routers.fba.get_tpi_stats", mock_get_tpi_stats)
@patch("app.routers.fba.get_fuel_type_raster_by_year", mock_get_fuel_type_raster_by_year)
@patch("app.routers.fba.get_tpi_fuel_areas", mock_get_tpi_fuel_areas)
def test_get_tpi_stats_authorized(client: TestClient):
"""Allowed to get tpi stats for run parameters when authorized"""
response = client.get(get_tpi_stats_url)

json_response = response.json()
assert response.status_code == 200
assert json_response["firezone_tpi_stats"][0]["fire_zone_id"] == 1
assert json_response["firezone_tpi_stats"][0]["valley_bottom_hfi"] == 4
assert json_response["firezone_tpi_stats"][0]["valley_bottom_tpi"] is None
assert json_response["firezone_tpi_stats"][0]["mid_slope_hfi"] == 8
assert math.isclose(json_response["firezone_tpi_stats"][0]["mid_slope_tpi"], 2.0)
assert json_response["firezone_tpi_stats"][0]["upper_slope_hfi"] == 12
assert json_response["firezone_tpi_stats"][0]["upper_slope_tpi"] is None
assert json_response["firezone_tpi_stats"][1]["fire_zone_id"] == 2
assert json_response["firezone_tpi_stats"][1]["valley_bottom_hfi"] == 4
assert json_response["firezone_tpi_stats"][1]["valley_bottom_tpi"] is None
assert json_response["firezone_tpi_stats"][1]["mid_slope_hfi"] == 8
assert json_response["firezone_tpi_stats"][1]["mid_slope_tpi"] is None
assert json_response["firezone_tpi_stats"][1]["upper_slope_hfi"] == 12
assert json_response["firezone_tpi_stats"][1]["upper_slope_tpi"] is None


@pytest.mark.usefixtures("mock_jwt_decode")
@patch("app.routers.fba.get_auth_header", mock_get_auth_header)
Expand Down Expand Up @@ -440,6 +572,9 @@
"/api/fba/fire-centre-tpi-stats/forecast/2024-08-10/2024-08-10/PGFireCentre",
"/api/fba/sfms-run-datetimes/forecast/2022-09-27",
"/api/fba/sfms-run-bounds",
"/api/fba/latest-sfms-run-parameters/2025-08-25/2025-08-26",
"/api/fba/hfi-stats/forecast/2025-08-25T15:01:47.340947Z/2025-08-26",
"/api/fba/tpi-stats/forecast/2025-08-25T15:01:47.340947Z/2025-08-26",
]


Expand All @@ -456,8 +591,19 @@
@patch("app.routers.fba.get_fuel_type_raster_by_year", mock_get_fuel_type_raster_by_year)
@patch("app.routers.fba.get_fire_centre_tpi_fuel_areas", mock_get_fire_centre_tpi_fuel_areas)
@patch("app.routers.fba.get_centre_tpi_stats", mock_get_centre_tpi_stats)
@patch("app.routers.fba.get_tpi_stats", mock_get_tpi_stats)
@patch("app.routers.fba.get_run_datetimes", mock_get_sfms_run_datetimes)
@patch("app.routers.fba.get_sfms_bounds", mock_get_sfms_bounds)
@patch(
"app.routers.fba.get_most_recent_run_datetime_for_date_range",
mock_get_most_recent_run_datetime_for_date_range,
)
@patch(
"app.routers.fba.get_all_zone_source_ids",
mock_get_all_zone_source_ids,
)
@patch("app.routers.fba.get_tpi_fuel_areas", mock_get_tpi_fuel_areas)
@patch("app.routers.fba.get_tpi_stats", mock_get_tpi_stats)
def test_fba_endpoints_allowed_for_test_idir(client, endpoint):
headers = {"Authorization": "Bearer token"}
response = client.get(endpoint, headers=headers)
Expand Down
Loading
Loading