Skip to content

Thresholds & warnings

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

Section titled “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.
ThresholdWarningLevels ← the SEVERITY vocabulary (colour + icon)
└─ thresholdWarningLevel id="Minor" (ordered least → most severe)
thresholdWarningLevel id="Major"
│ upWarningLevelId points here
Thresholds ← named LEVEL SLOTS (no numbers yet)
└─ thresholdGroup id="Public"
├─ defaultThreshold shortName="Normal" (the "nothing crossed" state)
├─ levelThreshold id="MinorFlood" → upWarningLevelId=Minor
└─ levelThreshold id="MajorFlood" → upWarningLevelId=Major
│ levelThresholdId points here
ThresholdValueSets ← the NUMBERS, per location/parameter
└─ thresholdValueSet id="Flood_Discharge"
├─ levelThresholdValue: levelThresholdId=MinorFlood value=250
├─ levelThresholdValue: levelThresholdId=MajorFlood value=400
└─ timeSeriesSet: parameterId=Q.obs · locationSetId=AllDischargeGauges

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 three files for the running example — Minor/Major flood warnings on observed discharge (Q.obs), applied to the AllDischargeGauges set.

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.

Config/RegionConfigFiles/ThresholdWarningLevels 1.00 default.xml
<?xml version="1.0" encoding="UTF-8"?>
<thresholdWarningLevels xmlns="http://www.wldelft.nl/fews">
<thresholdWarningLevel id="Normal" name="No warning">
<color>00FF00</color>
<iconName>normal.png</iconName>
<historicOverlayIconName>normal_hist.png</historicOverlayIconName>
<forecastOverlayIconName>normal_fc.png</forecastOverlayIconName>
</thresholdWarningLevel>
<thresholdWarningLevel id="Minor" name="Minor flood">
<color>FFA500</color>
<iconName>minor.png</iconName>
<historicOverlayIconName>minor_hist.png</historicOverlayIconName>
<forecastOverlayIconName>minor_fc.png</forecastOverlayIconName>
</thresholdWarningLevel>
<thresholdWarningLevel id="Major" name="Major flood">
<color>FF0000</color>
<iconName>major.png</iconName>
<historicOverlayIconName>major_hist.png</historicOverlayIconName>
<forecastOverlayIconName>major_fc.png</forecastOverlayIconName>
</thresholdWarningLevel>
</thresholdWarningLevels>

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 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)
levelThresholdA value crosses a fixed level, up or down.levelThresholdValuevalue
rateThresholdThe rate of change exceeds a limit (e.g. −0.1 m/hour).rateThresholdValuevalue + timeSpan
maxThresholdA peak above a level occurs within a time window.maxThresholdValuevalue + 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.

The full element lists are in the schemas linked under See also.

thresholdWarningLevel (the severity vocabulary)

FieldMeaning
id (attribute)Warning-level id referenced by upWarningLevelId.
name (attribute)Display name, e.g. “Flood Warning”.
colorHex RGB (FF0000) or named FEWS colour (required).
iconName, historicOverlayIconName, forecastOverlayIconNameIcon files in IconFiles (all required).
(file order)Levels must be listed least→most severe.

levelThreshold (a named slot, in Thresholds)

FieldMeaning
id (attribute)Threshold id referenced by levelThresholdId; unique across the whole file.
upWarningLevelId / downWarningLevelIdWhich warning level to raise (pick one).
seasonOptional — restrict the threshold to part of the year.

levelThresholdValue (the number, in ThresholdValueSets)

FieldMeaning
levelThresholdIdThe slot this value fills (must match a Thresholds id).
value / valueFunctionThe number, or a location-attribute expression.
label, toolTipChart label and hover text.
warningAreaArea name(s) for overview-display roll-ups.
upActionLogEventTypeId / downActionLogEventTypeIdAction message on crossing (id must contain a dot).

thresholdValueSet (the binding)

FieldMeaning
id (attribute)Identifier of the value set.
timeSeriesSet / timeSeriesWhich series the values apply to (location + parameter).
unitOnly if the values differ from the parameter’s stored unit (needs a configUnitConversionsId).
  • 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 entire Thresholds 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.
  1. Load the config and confirm no threshold-resolution errors in the log — unresolved levelThresholdId or upWarningLevelId references show up here.
  2. 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.
  3. 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.
  4. 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.