Skip to content

SFMS: FMC - #5734

Open
brettedw wants to merge 7 commits into
mainfrom
task/sfmsng-fmc
Open

SFMS: FMC#5734
brettedw wants to merge 7 commits into
mainfrom
task/sfmsng-fmc

Conversation

@brettedw

@brettedw brettedw commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Adds daily Foliar Moisture Content (FMC) raster generation to the SFMS pipeline.
FMC is calculated once per calendar day using elevation, latitude, longitude, and Julian date, then shared between actual and forecast processing.

  • Adds a vectorized FMC processor using the CFFDRS implementation
  • Stores FMC outputs under:
    sfms_ng/static/fmc/YYYY/MM/DD/fmc_YYYYMMDD.tif
  • Ensures actual jobs have FMC for the processing date
  • Ensures forecast jobs have FMC for each of the three forecast dates
  • Reuses existing FMC outputs when both files exist and the raster matches the selected fuel grid
  • Validates the static FMC inputs against the fuel grid
  • Updates the FBP TODO documentation and centralizes shared FMC/SFC raster test setup

Addresses the backend piece of #5656

Test Links:

Landing Page
MoreCast
Percentile Calculator
FireCalc
FireCalc bookmark
Auto Spatial Advisory (ASA)
HFI Calculator
SFMS Insights
Fire Watch
Weather Toolkit

@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 67.81%. Comparing base (5ad0d01) to head (c49338a).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #5734      +/-   ##
==========================================
+ Coverage   67.74%   67.81%   +0.07%     
==========================================
  Files         386      386              
  Lines       22432    22436       +4     
  Branches     3105     3105              
==========================================
+ Hits        15196    15216      +20     
+ Misses       6079     6060      -19     
- Partials     1157     1160       +3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sonarqubecloud

Copy link
Copy Markdown

@brettedw
brettedw requested a review from conbrad August 20, 2026 22:12
raster_addresser: SFMSNGRasterAddresser,
s3_client: S3Client,
) -> None:
"""Validate complete FMC rasters and publish dates without exisiting FMC rasters."""

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"""Validate complete FMC rasters and publish dates without exisiting FMC rasters."""
"""Validate complete FMC rasters and publish dates without existing FMC rasters."""


for target_date, output_key in inputs.output_keys.items():
result = calculate_foliar_moisture_content(datasets, target_date)
with create_masked_output_dataset(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This opens the BC mask from S3 once per output date, I think we create a function that opens the BC mask once and yields a ContextManager generator for other datasets, then we can hoist this above the loop.

return FoliarMoistureContentResult(output)


class FoliarMoistureContentProcessor:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FoliarMoistureContentProcessor and SurfaceFuelConsumptionProcessor have near-identical dependency-exists, open-datasets and grid validation logic. We could pull the shared behavior into a GriddedRasterDependencies composition object that both processors hold as self._deps.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E.g.

class GriddedRasterDependencies:
    """Loads and validates raster dependencies against a reference grid."""

    @staticmethod
    async def assert_keys_exist(
        s3_client: S3Client,
        keys: Iterable[GDALPath],
        error_label: str,
    ) -> None:
        keys = tuple(keys)
        if not await s3_client.all_objects_exist(*keys):
            details = ", ".join(str(key) for key in keys)
            raise RuntimeError(f"Missing {error_label}: {details}")

    @staticmethod
    def index_by_key(datasets: list[WPSDataset]) -> dict[GDALPath, WPSDataset]:
        return {dataset.ds_path: dataset for dataset in datasets}

    @staticmethod
    def validate_grids(
        reference: WPSDataset,
        reference_key: GDALPath,
        candidates: Iterable[Tuple[str, GDALPath, WPSDataset]],
    ) -> None:
        reference_ds = reference.as_gdal_ds()
        for label, key, dataset in candidates:
            if not rasters_match(dataset.as_gdal_ds(), reference_ds):
                raise ValueError(
                    f"{label} raster does not match the fuel grid: {key} vs {reference_key}"
                )

)


async def ensure_fmc_rasters(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Callers don't need to know about ensuring FMC rasters, they should just be able to rely on FoliarMoistureContentProcessor.process() to guarantee it. If these functions live in the FoliarMoistureContentProcessor, then FoliarMoistureContentProcessor().process(...) becomes the same shape as the SFC call site, caller doesn't know or care that anything gets skipped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants