Preflight: what the record can support¶
Preflight asks whether an AOI's satellite record can carry the analysis at all — before any monthly acquisition is paid for. Two questions live here, deliberately kept apart because they have different answer shapes and different maturity.
Is there any recurrent surface water?¶
run_hydroseason applies this screen automatically on every regular DEA
run. One all-time WOfS Statistics read decides it, using fixed constants:
pixels wet at or above 10% recurrence must form a contiguous cluster of at
least the minimum pixel count for the run's resolution.
Nothing is configurable here, and nothing needs to be. If the AOI passes, the
same Statistics read is handed straight to monthly acquisition as the
count_wet > 0 AND AOI maximum-water mask — so the screen costs no extra
query. If it fails, the run stops:
from hydroseason import run_hydroseason, HydroSeasonPreflightError
try:
result = run_hydroseason(
None,
aoi="catchment.geojson",
start_date="2005-01-01",
end_date="2025-12-01",
output_dir="output/report",
)
except HydroSeasonPreflightError as exc:
print(exc.result.reason)
print(exc.result.core_pixel_count, exc.result.largest_cluster_pixels)
HydroSeasonPreflightError.result is a FeasibilityResult carrying the
measured quantities behind the rejection, so the decision can be audited
rather than taken on trust. On a successful run the same object is available
as result.preflight_result.
An outage is not an answer
If DEA Statistics cannot be reached, run_hydroseason warns and
continues into monthly acquisition. Converting a service outage into a
"no water here" result would be scientifically wrong, so it never
happens.
Run the same screen on its own, without committing to an analysis:
from hydroseason import preflight
feasibility = preflight(
"catchment.geojson", "2005-01-01", "2025-12-01",
feasibility_only=True,
)
print(feasibility.feasible, feasibility.reason)
Called standalone this path re-raises a Statistics outage instead of warning: you asked the question directly, so you get the failure directly.
Is the record dense enough for per-year detection?¶
The second question is broader, and its answer is a PreflightResult
carrying three independent decisions — each "pass", "fail",
"indeterminate", or "not_assessed":
| Decision | Question |
|---|---|
candidate_decision |
Does the Statistics grid hold 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? |
The monthly and timing decisions need a monthly record. Without one they
report "not_assessed", which means the question was not answered — not that
it was answered negatively.
result = preflight(
"catchment.geojson", "2005-01-01", "2025-12-01",
monthly_observations="cache/catchment.zarr",
thresholds="diagnostic",
)
print(result.summary())
print(result.reasons)
payload = result.to_dict()
Thresholds are not calibrated yet¶
thresholds="default" raises
The reviewed threshold profile has not been installed in 0.2.0.
Calling preflight(...) without a thresholds argument raises
PreflightProfileUnavailable rather than guessing at cut-offs that were
never reviewed.
Two modes work today:
thresholds="diagnostic"measures every metric with all cut-offs at zero. Nothing is gated, so the decisions carry no scientific authority — what you get is the measurement, to inspect on its own terms.- A
PreflightThresholdsinstance applies cut-offs you declare, under aprofile_nameandprofile_versionyou choose, so results stay attributable to the profile that produced them.
feasibility_only=True ignores thresholds entirely and is unaffected by
any of this.
Cost¶
Outside feasibility_only, preflight reads annual Statistics rather than the
all-time grid, so it is the more expensive of the two questions.
wet_aoi_require_year_union defaults to False here — the opposite of
monthly acquisition, which keeps that per-year mask-union safety net. On a
large catchment the union accounts for roughly 92% of preflight runtime, and
a preflight decision is a routing call, not a forensic record.
What each result carries¶
FeasibilityResult—feasible,reason, and the measuredcore_pixel_count,cluster_count,largest_cluster_pixels,minimum_cluster_pixels.to_dict()returns a JSON-ready payload.PreflightResult— the three decisions and their reason codes,candidate_metrics/monthly_metrics, thethresholdsprofile that was applied,capabilities,warnings, andprovenancerecording the Statistics request and monthly source identity.summary()renders one line;to_dict()returns a JSON-ready payload (flat=Falsefor the nested form).
Full signatures: API Reference — Preflight.