Thresholds are how a stored time series turns
into a warning. Once data is imported and processed, FEWS can watch it and fire
an event the moment a value climbs past “minor flood” or drops below a low-flow
limit — and paint that state onto charts and maps. This is the output side of the
pipeline (hop ⑤): thresholds don’t create data, they
interpret the data you already have.
The one idea to hold on to: define once, assign per location
The single most common confusion here is that thresholds are spread across
separate files that each do one job. Keep them straight and the rest follows:
Warning levels (ThresholdWarningLevels) are the vocabulary — the named
severities like “Minor” and “Major”, each with a colour and icon. Defined once
for the whole system.
Thresholds (Thresholds) are named level slots, grouped, each pointing at
a warning level. Still location- and parameter-independent: this file says
“there is a threshold called MinorFlood and it means the Minor warning
level” — but not what number trips it.
Threshold value sets (ThresholdValueSets) supply the actual numbers per
location and parameter, and bind them to a specific series. This is where
MinorFlood on RiverGauge_01’s discharge becomes 250 m³/s.
Read the arrows bottom-up: a value set says “apply the MinorFlood level to
Q.obs on these gauges at 250”; MinorFlood says “I mean the Minor
warning level”; Minor says “draw me orange.”
The severities, ordered least severe to most severe — the order is the
severity ranking, there’s no numeric rank field. The least-severe level is the
“nothing crossed” state.
color is a hex RGB string like FF0000 (red) or a
named FEWS colour;
the icon filenames point at images in the IconFiles config directory.
The named level slots, grouped. defaultThreshold names the “no threshold
crossed” state; each levelThreshold binds an id to a warning level. No values
here — those are location-specific and live in the next file.
upWarningLevelId is the warning level to raise when the value crosses the
threshold upward; use downWarningLevelId for a level that means trouble when
a value falls (e.g. a low-flow limit). You pick one per levelThreshold.
The numbers, at last — and the binding to a real series. levelThresholdId
refers back to a slot from the Thresholds file; value is the number in the
parameter’s stored unit; timeSeriesSet says which data this set applies to.
Because the timeSeriesSet uses a locationSetId, these two numbers apply to
every gauge in AllDischargeGauges. Values are in m³/s — the stored
unit of Q.obs.
The Thresholds file (and its matching values) supports three kinds of level,
each triggering on a different condition:
Type (in Thresholds)
Fires when…
Value element (in ThresholdValueSets)
levelThreshold
A value crosses a fixed level, up or down.
levelThresholdValue → value
rateThreshold
The rate of change exceeds a limit (e.g. −0.1 m/hour).
rateThresholdValue → value + timeSpan
maxThreshold
A peak above a level occurs within a time window.
maxThresholdValue → value + timeSpan
levelThreshold is by far the most common — it’s the “minor/major flood” case.
The other two exist for flashiness (rate) and peak-detection (max) alerting.
When FEWS evaluates a series against its value set and a value crosses a level,
two things can happen:
A threshold event is logged. Crossings (up and down) are recorded in the
database’s threshold-event table and surface in the Threshold Events and
Threshold Overview displays. These are the raw “gauge X crossed Minor at
09:00” records.
An action / warning level is activated. Via upWarningLevelId, the
crossing raises the associated warning level, which drives the colour and
icon shown for that location. Optionally, upActionLogEventTypeId /
downActionLogEventTypeId on a value emit an action log message (the id must
contain a dot, e.g. Flood.Up) that the master controller can route to
enhanced forecasting, emails, or system alerts.
Thresholds are most useful when you can see them, and that’s handled by the
display side:
On a chart, each levelThresholdValue draws a horizontal line at its
value, labelled with its label and coloured by its warning level. toolTip
adds hover text (it supports HTML via CDATA).
On a spatial map, each location is coloured by the most severe warning
level currently crossed — which is exactly why warning levels must be ordered
by severity in the ThresholdWarningLevels file.
In overview tables, warningArea and the ThresholdOverviewDisplay roll
crossings up into counts per area.
Different numbers per gauge, same levels. Keep one Thresholds definition
and give each gauge its own thresholdValueSet (or use one set over a
location set when they share numbers). This is the whole
reason the definition and the values are split.
Location-attribute-driven values. Instead of value, use valueFunction
with a location attribute tag like @FLOOD_WATCH@ (defined in
locationSets). One value set then produces a different
number at every location, read from its attributes — no per-gauge XML.
Low-flow / falling-level alarms. Use downWarningLevelId on the
levelThreshold so the warning is raised when values fall below the level.
Stage vs discharge with a rating curve. The schema has dedicated stage
and discharge branches that convert threshold levels through a rating curve —
reach for these only when your thresholds are defined in one unit but your
series is in the other. For a plain discharge-on-discharge case, the simple
levelThresholdValue + timeSeriesSet form above is all you need.
Values in the wrong unit.value is interpreted in the parameter’s
stored unit, not its displayUnit. Q.obs stores m³/s, so 250 means
250 m³/s — set flood levels in storage units or add a unit +
configUnitConversionsId. This is silent: the line just sits at the wrong height.
The timeSeriesSet must match the stored header. A value set attaches by
reconstructing the series’ header —
same moduleInstanceId, parameterId, location, timeSeriesType, and
timeStep as the import wrote. Mismatch it and the
threshold silently applies to nothing.
A dangling upWarningLevelId. If it names a warning level that isn’t in
ThresholdWarningLevels, crossings still log as events but have no colour/icon
to display — you get events with no visible warning.
Warning levels out of order. They must run least→most severe. Reverse two
and the “most severe crossed” colour on maps is wrong even though every number
is right.
id collisions across groups. Threshold ids must be unique across the
entireThresholds file — two threshold groups cannot reuse the same
threshold id.
A change has no effect → a higher-versioned file wins. Check for a sibling
like ThresholdValueSets 1.01 default.xml. See how FEWS finds a
file.
Load the config and confirm no threshold-resolution errors in the log —
unresolved levelThresholdId or upWarningLevelId references show up here.
Open the series in the Time Series Display. With a value set matching the
header, you should see horizontal threshold lines at 250 and 400, in the
Minor/Major colours, labelled as configured. No lines usually means the
timeSeriesSet didn’t match — line it up field by field.
Force a crossing. Point the value set at a series you know exceeds a level
(or lower a level temporarily) and confirm the location colours up on the
spatial display and a crossing appears in the
Threshold Events display.
Check severity ordering. Where several levels are crossed, the map should
show the most severe colour — if it shows the wrong one, re-check the order
in ThresholdWarningLevels.