Skip to content

Preflight

Whether an AOI's record can support the analysis, decided before any monthly acquisition. See Preflight: what the record can support for narrative context and the current calibration status of the threshold profiles.

Entry point

hydroseason.preflight

Decide whether an AOI's satellite record can support the analysis at all.

Two questions live here, deliberately kept apart because they have different answer shapes and different maturity:

Is there any recurrent surface water? :func:run_regular_preflight (and :func:preflight with feasibility_only=True) answers this from one all-time DEA WOfS Statistics read using fixed constants -- recurrent water at >=10% frequency, subject to a contiguous-cluster rule -- and returns a :class:~hydroseason._preflight_feasibility.FeasibilityResult. This is the screen run_hydroseason applies automatically before monthly acquisition; a rejection surfaces as :class:~hydroseason.workflow.HydroSeasonPreflightError. A Statistics outage never converts into a "no water" answer: the workflow warns and continues, and the standalone feasibility path re-raises.

Is the record dense and long enough for per-year detection? :func:preflight answers this and returns a :class:~hydroseason._preflight_types.PreflightResult carrying separate candidate, monthly, and timing decisions. Its threshold profile is not yet calibrated: thresholds="default" raises :class:PreflightProfileUnavailable rather than guessing. Until that profile is frozen, use thresholds="diagnostic" to obtain the measured metrics without any pass/fail gating, or supply your own :class:~hydroseason._preflight_types.PreflightThresholds.

PreflightProfileUnavailable

Bases: RuntimeError

The reviewed preflight profile has not been installed yet.

preflight

preflight(aoi: Any, start_date: Any, end_date: Any, *, monthly_observations: Any | None = None, thresholds: ThresholdMode | PreflightThresholds = 'default', stac_url: str = DEFAULT_WO_STATISTICS_STAC_URL, statistics_product: str = DEA_STATS_ANNUAL_COLLECTION, resolution: float = 30.0, feasibility_only: bool = False, crs: str = 'EPSG:3577', chunks: Mapping[str, int] | None = None, cache_dir: str | Path | None = None, prune_to_wet_aoi: bool = True, wet_aoi_min_frequency_fraction: float | None = None, wet_aoi_require_year_union: bool = False, max_invalid_pct: float = 20.0, quality_policy: QualityPolicy = 'flag', allow_unknown_quality: bool = False) -> PreflightResult | FeasibilityResult

Report what an AOI's satellite record can support, before analysing it.

Reads DEA WOfS Statistics for aoi over start_date..end_date and reports whether the record can support annual detection -- without running the analysis and without ever answering "no water" from a data outage.

Parameters:

Name Type Description Default
aoi (str, Path or GeoDataFrame)

Area of interest, in any CRS; reprojected to crs internally.

required
start_date str

Inclusive analysis window, "YYYY-MM-DD".

required
end_date str

Inclusive analysis window, "YYYY-MM-DD".

required
monthly_observations optional

An already-resolved monthly record -- DataFrame, xarray DataArray/Dataset, WOfSCacheHandle, or a .zarr path -- used for the monthly and timing decisions. Without it those decisions are "not_assessed"; the candidate decision still runs.

None
thresholds ('default', 'diagnostic')

Cut-off profile. "default" raises :class:PreflightProfileUnavailable because the reviewed profile is not installed yet; "diagnostic" measures every metric with all cut-offs at zero, so nothing is gated and the numbers can be inspected on their own; a :class:~hydroseason._preflight_types.PreflightThresholds instance applies your own declared cut-offs.

"default"
feasibility_only bool

Run only the fixed-constant recurrent-water screen and return a :class:~hydroseason._preflight_feasibility.FeasibilityResult. This path ignores thresholds entirely, so it works today, and it re-raises a Statistics outage rather than reporting no water.

False
stac_url str

DEA Statistics access: endpoint, collection, target resolution in metres, working CRS, dask chunking, and an optional cache directory.

DEFAULT_WO_STATISTICS_STAC_URL
statistics_product str

DEA Statistics access: endpoint, collection, target resolution in metres, working CRS, dask chunking, and an optional cache directory.

DEFAULT_WO_STATISTICS_STAC_URL
resolution str

DEA Statistics access: endpoint, collection, target resolution in metres, working CRS, dask chunking, and an optional cache directory.

DEFAULT_WO_STATISTICS_STAC_URL
crs str

DEA Statistics access: endpoint, collection, target resolution in metres, working CRS, dask chunking, and an optional cache directory.

DEFAULT_WO_STATISTICS_STAC_URL
chunks str

DEA Statistics access: endpoint, collection, target resolution in metres, working CRS, dask chunking, and an optional cache directory.

DEFAULT_WO_STATISTICS_STAC_URL
cache_dir str

DEA Statistics access: endpoint, collection, target resolution in metres, working CRS, dask chunking, and an optional cache directory.

DEFAULT_WO_STATISTICS_STAC_URL
prune_to_wet_aoi bool

Restrict the read to the wet footprint. wet_aoi_require_year_union defaults to False here -- unlike monthly acquisition, which keeps the per-year mask-union safety net -- because it costs the bulk of a large catchment's preflight runtime and a preflight decision is not a forensic record.

True
wet_aoi_min_frequency_fraction bool

Restrict the read to the wet footprint. wet_aoi_require_year_union defaults to False here -- unlike monthly acquisition, which keeps the per-year mask-union safety net -- because it costs the bulk of a large catchment's preflight runtime and a preflight decision is not a forensic record.

True
wet_aoi_require_year_union bool

Restrict the read to the wet footprint. wet_aoi_require_year_union defaults to False here -- unlike monthly acquisition, which keeps the per-year mask-union safety net -- because it costs the bulk of a large catchment's preflight runtime and a preflight decision is not a forensic record.

True
max_invalid_pct float

How the monthly record's per-month quality is screened before the monthly and timing decisions are taken.

20.0
quality_policy float

How the monthly record's per-month quality is screened before the monthly and timing decisions are taken.

20.0
allow_unknown_quality float

How the monthly record's per-month quality is screened before the monthly and timing decisions are taken.

20.0

Returns:

Type Description
PreflightResult or FeasibilityResult

A :class:~hydroseason._preflight_feasibility.FeasibilityResult when feasibility_only=True, otherwise a :class:~hydroseason._preflight_types.PreflightResult.

Raises:

Type Description
PreflightProfileUnavailable

thresholds="default" was requested before the reviewed profile was installed.

TypeError

thresholds was neither a recognised mode nor a :class:~hydroseason._preflight_types.PreflightThresholds.

Notes

Outside feasibility_only, an unreachable or unusable Statistics source never becomes a negative answer: the affected decisions become "not_assessed" and the cause is recorded in result.warnings.

Examples:

Screen an AOI for recurrent water before committing to acquisition::

from hydroseason import preflight

feasibility = preflight(
    "catchment.geojson", "2005-01-01", "2025-12-01",
    feasibility_only=True,
)
print(feasibility.feasible, feasibility.reason)

Measure detection support without gating it::

result = preflight(
    "catchment.geojson", "2005-01-01", "2025-12-01",
    thresholds="diagnostic",
)
print(result.summary())

Results

hydroseason._preflight_feasibility

Cheap feasibility filter: is there any recurrent surface water here?

This module deliberately answers ONE question and refuses to answer more. Session testing against 18 real AOIs established that every richer criterion encodes a hidden assumption about which water regime counts as "valid", and each produced wrong answers on real data: absolute pixel-count thresholds passed a desert dunefield and a snow/shadow alpine ridge; a core-to-ever-wet concentration ratio inverted (reservoirs 66-93%, real rivers 2.8-4.8%); DEA's seasonal products are northern-monsoon- windowed and score southern catchments backwards; cluster shape metrics were noise at catchment scale.

Frequency-distribution metrics DO separate regime types cleanly, but regime is not suitability -- a reservoir, a wetland, a salt lake and a before/after-dam comparison are all legitimate subjects with opposite signatures. So this filter rejects only AOIs with no recurrent water at all, and everything else goes to the real workflow.

FeasibilityResult dataclass

Whether an AOI holds any recurrent surface water worth analysing.

The cheap screen that guards regular DEA acquisition. feasible is True when pixels wet at or above the fixed recurrence frequency form a contiguous cluster of at least minimum_cluster_pixels at the run's resolution; reason states in words which condition decided it. core_pixel_count, cluster_count and largest_cluster_pixels are the measured quantities behind that call, so a rejection can be audited rather than taken on trust. to_dict() returns a JSON-ready payload.

A rejection reached through :func:hydroseason.run_hydroseason is raised as :class:hydroseason.workflow.HydroSeasonPreflightError, which carries this object as .result.

hydroseason._preflight_types

PreflightResult dataclass

What an AOI's record can and cannot support, decided in three parts.

Each part answers a different question and carries its own decision -- "pass", "fail", "indeterminate", or "not_assessed":

candidate_decision Does the all-time Statistics grid show enough reliably-observed, recurrently-wet pixels for this AOI to be a candidate at all? monthly_decision Is the monthly record dense enough -- usable months, months per year, supported years -- for per-year detection? timing_decision Is calendar-month coverage even enough to trust seasonal timing?

candidate_eligible/monthly_eligible/timing_eligible reduce each decision to True/False/None, where None means the question was not answered rather than answered negatively. reasons merges the three reason-code tuples in order, summary() renders them as one line, and to_dict() returns a JSON-ready payload (flat=False for the nested form). provenance records the Statistics request and monthly source identity behind the decisions; warnings records anything degraded -- a Statistics outage included -- that did not stop the run.

PreflightThresholds dataclass

One named, versioned cut-off profile applied by :func:hydroseason.preflight.

profile_status states how far the profile has been taken: "testing" (fixtures only), "provisional" (usable, not frozen), or "frozen" (reviewed and immutable). The reviewed "default" profile is not installed yet -- see :class:hydroseason.preflight.PreflightProfileUnavailable. Until it is, pass thresholds="diagnostic" for ungated measurement, or construct this class directly to declare your own cut-offs, stating a profile_name/profile_version so results stay attributable.

Workflow integration

run_hydroseason runs the recurrent-water screen on every regular DEA run. A rejection is raised as HydroSeasonPreflightError, carrying the FeasibilityResult as .result; a successful run exposes the same object as HydroSeasonRunResult.preflight_result.

hydroseason.workflow

Public orchestrator: preview an AOI, resolve water input, then add rainfall.

run_hydroseason ties together the modules that were designed to stay decoupled from each other: it resolves whatever water source the caller supplied (:mod:hydroseason._workflow_input), runs :func:analyze_catchment exactly once -- the sole routing authority, rainfall-blind -- and only then looks at rainfall at all. Rainfall is a strictly separate, best-effort branch: loading a supplied CSV, fetching from SILO, and comparing against the already-computed regime can each fail independently without taking the water analysis or the report down with them. Every rainfall failure is recorded on the result and surfaced as a UserWarning; only water input, analysis, and report-writing failures are fatal and propagate as exceptions. A supplied AOI is loaded once before acquisition so its optional display context can be previewed and reused for rainfall; context and preview failures only warn, but an unloadable supplied AOI is fatal.

Rainfall is off by default (fetch_rainfall=False). A supplied rainfall_csv_path always takes precedence over fetch_rainfall=True: if both are given, SILO is never called.

A fetch_rainfall=True run whose environment cannot import the SILO dependencies warns before the water step rather than after it, giving the caller a chance to abort before spending hours on water acquisition. The warning does not make ancillary rainfall fatal and does not change the water analysis.

progress is off by default. progress=True writes five numbered step lines to standard error and switches on the per-calendar-year bar that load_wofs_monthly_extent already provides; passing a callable instead delivers :class:hydroseason._progress.ProgressEvent objects and leaves the nested bar off. Progress reporting never changes what a run computes.

HydroSeasonPreflightError

Bases: RuntimeError

The regular DEA workflow found no usable recurrent surface water.