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 |
required |
start_date
|
str
|
Inclusive analysis window, |
required |
end_date
|
str
|
Inclusive analysis window, |
required |
monthly_observations
|
optional
|
An already-resolved monthly record -- DataFrame, |
None
|
thresholds
|
('default', 'diagnostic')
|
Cut-off profile. |
"default"
|
feasibility_only
|
bool
|
Run only the fixed-constant recurrent-water screen and return a
:class: |
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. |
True
|
wet_aoi_min_frequency_fraction
|
bool
|
Restrict the read to the wet footprint. |
True
|
wet_aoi_require_year_union
|
bool
|
Restrict the read to the wet footprint. |
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: |
Raises:
| Type | Description |
|---|---|
PreflightProfileUnavailable
|
|
TypeError
|
|
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.