Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 12 additions & 1 deletion fastfuels_sdk/v2/grids.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
LandfireFbfm13Version,
LandfireFbfm40Version,
LandfireFccsVersion,
LandfireSeason,
LandfireTopographyVersion,
LeafluxBand,
ListGridsResponse,
Expand Down Expand Up @@ -1774,6 +1775,7 @@ def create_fuel_model_grid_from_landfire_fbfm13(
def create_fuel_model_grid_from_landfire_fbfm40(
domain,
version: Optional[str] = None,
season: Optional[str] = None,
remove_non_burnable: Optional[list] = None,
output_resolution_m: Optional[float] = None,
align_to=None,
Expand All @@ -1793,7 +1795,15 @@ def create_fuel_model_grid_from_landfire_fbfm40(
The domain (or its id) to create the grid in.
version : str, optional
LANDFIRE version (see ``LandfireFbfm40Version``). Defaults to the API's
current version.
current version. Seasonal versions (see ``season``) are only available
for the LANDFIRE Seasonal Fuels vintages (e.g. "2025").
season : str, optional
LANDFIRE Seasonal Fuels window (see ``LandfireSeason``): "ES" (early
spring), "SP" (spring), "SU" (summer), or "FA" (fall). When set, the
grid is fetched from the LANDFIRE Product Service for that season rather
than the staged annual release, and ``version`` must be a seasonal
vintage (e.g. "2025"). When omitted, ``version`` must be an annual
vintage. ``represented_year`` reflects the projected season year.
remove_non_burnable : list, optional
Non-burnable fuel models to drop (``NonBurnableFuelModel`` members or
their string keys, e.g. "NB1", "NB2").
Expand Down Expand Up @@ -1821,6 +1831,7 @@ def create_fuel_model_grid_from_landfire_fbfm40(
"""
request_body = CreateLandfireFbfm40Request(
version=LandfireFbfm40Version(version) if version is not None else UNSET,
season=LandfireSeason(season) if season is not None else UNSET,
remove_non_burnable=_enum_list(remove_non_burnable, NonBurnableFuelModel),
alignment=_build_alignment(output_resolution_m, align_to, align, resampling),
extent_buffer_cells=extent_buffer_cells,
Expand Down
104 changes: 104 additions & 0 deletions tests/v2/test_grids.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
from fastfuels_sdk.v2.client_library.types import UNSET, Response
from fastfuels_sdk.v2.exceptions import (
NotFoundException,
UnprocessableEntityException,
expect,
)
from fastfuels_sdk.v2.modifications import mask
Expand Down Expand Up @@ -1348,6 +1349,109 @@ def test_rejects_unknown_version(self):
version="1999",
)

def test_season_passed_through(self, monkeypatch):
# A seasonal request forwards both the seasonal version and the
# season window to the request body.
created = Grid(
id="fbfm40-grid-id",
domain_id="domain-id",
status=JobStatus.PENDING,
source=GridSource(),
bands=[],
)
captured = {}

def fake_create(domain_id, *, client, body):
captured.update(body=body)
return Response(
status_code=HTTPStatus.CREATED,
content=b"",
headers={},
parsed=created,
)

client = object()
monkeypatch.setattr(grids, "ensure_client", lambda: client)
monkeypatch.setattr(grids.create_landfire_fbfm40, "sync_detailed", fake_create)

create_fuel_model_grid_from_landfire_fbfm40(
SimpleNamespace(id="domain-id"),
version="2025",
season="SP",
)

assert captured["body"].version.value == "2025"
assert captured["body"].season.value == "SP"

def test_rejects_unknown_season(self):
with pytest.raises(ValueError):
create_fuel_model_grid_from_landfire_fbfm40(
SimpleNamespace(id="domain-id"),
version="2025",
season="WINTER",
)

def test_create_seasonal_reports_projected_year(self):
# LANDFIRE Seasonal Fuels (version 2025 + SP is spring 2026). The
# projected season year is resolved from the live LFPS catalog at
# request time and surfaces as represented_year on the pending grid
# -- so the wiring is confirmed without waiting for the grid to build
# (the build submits a real, up-to-20-minute on-demand LFPS job).
#
# Seasonal Fuels is a geographically limited pilot served live by the
# LANDFIRE Product Service, so this needs a domain inside the current
# spring coverage region (a small box in northern Arizona). Coverage is
# a moving boundary; if LFPS isn't serving this domain/season when the
# test runs, skip rather than fail -- the request wiring itself is
# covered by test_season_passed_through.
domain = Domain.from_geojson(
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[-110.276, 35.749],
[-110.252, 35.749],
[-110.252, 35.769],
[-110.276, 35.769],
[-110.276, 35.749],
]
],
},
}
],
},
name="throwaway_seasonal_domain",
tags=["sdk-test"],
)
try:
try:
grid = create_fuel_model_grid_from_landfire_fbfm40(
domain,
version="2025",
season="SP",
output_resolution_m=30,
name="throwaway_fbfm40_seasonal",
tags=["test"],
)
except UnprocessableEntityException as exc:
pytest.skip(
f"LANDFIRE Seasonal Fuels not serving this domain/season: {exc}"
)

# Do NOT wait(): completion submits a live 20-minute LFPS job.
assert grid.status in (JobStatus.PENDING, JobStatus.RUNNING)
# version 2025 + SP projects to spring 2026, read from the catalog.
assert grid.represented_year == 2026
grid.delete()
finally:
domain.delete(force=True)


class TestRepresentedYear:
"""Unit tests for the Grid.represented_year accessor (no API)."""
Expand Down
Loading