How Swim Hastings decides what to tell you
This document describes every rule the service uses, with the exact numbers.
It exists so that anyone can check our reasoning, disagree with it, and tell us
why. The rules are implemented in src/swimhastings/rules.py and the numbers
live in config/rules_config.yaml; if the code and this document ever
disagree, that is a bug and we want to hear about it. This file is published
as is at swimhastings.uk/heuristics/.
What this service is, and is not
Swim Hastings estimates when swimming at each beach along the Hastings and St Leonards frontage is more likely than usual to mean swimming in diluted sewage. It works entirely from public data: Southern Water's published storm overflow activity, Environment Agency rainfall and bathing-water data, tide predictions, and the rain forecast.
We do not test water quality. Nobody involved takes samples. A green verdict means "none of our data sources shows a problem", not "the water is clean". A red verdict means "the public data shows something we think you would want to know about", not "the water is proven contaminated". The Environment Agency's official classifications remain the only regulated assessment of these waters. Swim at your own judgment.
The numbers below are published, versioned guesses. They are informed by the general literature on bacterial die-off and by local knowledge of the frontage, but they have not been validated against water samples, because we have none. During the shadow period we compare our verdicts against Southern Water's beach warnings, the EA's forecasts, and reports from swimmers, and we tune the numbers in public, in this file's changelog.
The verdicts
- 🟢 no known issues — no rule fired.
- 🟡 caution — at least one rule fired at amber. Something in the data suggests an elevated risk.
- 🔴 avoid — at least one rule fired at red. There is an active or recent discharge affecting this zone.
When several rules fire, the worst one sets the colour and every reason is shown. Every alert and every status page entry lists the rules that fired, so you are never asked to trust a bare colour.
The zones and the outfall map
We divide the frontage into five zones, west to east: Glyne Gap, Bulverhythe, St Leonards (Marina to Warrior Square), Hastings Pelham (pier to harbour arm), and Rock-a-Nore with the Fairlight end. For each storm overflow outfall we recorded, by hand, which zones it discharges into directly and which zones a plume could plausibly reach while the tide is flooding (running broadly eastward along this coast) or ebbing (westward).
This mapping is local judgment, and we say so plainly: it is not a
hydrodynamic model. It is written down in config/beaches.yaml, one entry per
outfall, so anyone who knows this coast can challenge a specific line. The
outfall positions and identifiers come from Southern Water's published data;
the names are our own labels, because the public feed does not include names.
The feed does say which watercourse each outfall discharges into, and we
show that, verbatim and attributed, next to every outfall on the status page,
so that a local reader can test our mapping against it. Most overflows on
this coast are inland: they spill into a stream or drain, and reach a beach
only if that watercourse does. Where a watercourse drains is checked against
Ordnance Survey's flow-directed Open Rivers network
(scripts/trace_outfall_drainage.py, run over every outfall on 2026-09-05);
an overflow whose watercourse leaves the frontage, like the Pett Level marsh
sewers that reach the sea at Rye, carries no zones, and an inland overflow
carries the map of the sea outlet its water reaches, not of the beach
nearest its own position. That network knows seven outlets on this coast:
two Pevensey Levels sluices at Norman's Bay, the Combe Haven sluice at
Bulverhythe, the culverted town streams at Hastings, and the Ecclesbourne
and Fairlight glens.
The rules
R1 — active discharge into a zone → red
If Southern Water's data shows an outfall releasing right now, every zone that outfall discharges into directly goes red, and stays red until the release stops. This is the simplest and least arguable rule.
R2 — recent discharge → red, then amber, decaying over time
Contamination does not vanish when a release stops, so an ended release keeps a zone red for a while and then amber for a while longer. The windows are:
| Release length | Red after it ends | Amber until |
|---|---|---|
| longer than 1 hour | 24 hours | 48 hours after the end |
| 1 hour or shorter | 24 hours | 24 hours after the end |
(For a short release the red window covers the whole decay period, so in practice a short release means red for 24 hours and no amber tail.)
Tides change how quickly the sea disperses a plume, so the windows are scaled by the tidal phase at evaluation time: on spring tides (stronger streams, faster dispersion) every window is multiplied by 0.75, and on neap tides by 1.25. Between the two, the windows apply as written.
These decay times are the most uncertain numbers in the system. Real die-off depends on sunlight, temperature, and sea state, none of which we currently model. We chose 24 and 48 hours because they bracket the advice most UK bathing-water bodies give after a discharge, and we expect to tune them.
R3 — active discharge nearby, carried by the tide → amber, or red if long
If an outfall is releasing now and the current tide direction can carry the plume into a zone (per the hand-set map above), that zone goes amber. If the release has already been running for more than 3 hours, the zone goes red instead, on the reasoning that a long release puts enough into the water that adjacency stops being a comfort.
This rule needs tide data. When we have none, R3 does not fire at all, which is the less cautious direction; the status page says when we are running without tide data.
Travel time for inland overflows
Most overflows on this coast are not on the beach. They spill into a stream
some way inland, and the stream takes time to reach the sea: the Powdermill
Stream CSO at Battle is about 11 km of river from the Bulverhythe sluice,
the Cole Stream CSO at Cooden 2.5 km from the sluice its water reaches.
So for an inland outfall, rules R1 to R3 do not judge the release as the
feed reports it; they judge it shifted later by the time the water takes
to arrive. That time is the outfall's river distance (travel_km in
config/beaches.yaml, measured on Ordnance Survey's Open Rivers network)
divided by an assumed stream speed (travel_delay.stream_speed_km_h in
config/rules_config.yaml, 2 km/h). Until the first of the water is due,
the beach keeps whatever colour its other inputs give it and carries an
advisory note saying the release has happened, how far upstream, and when
to expect it. From then on R1 applies while water is still arriving, R2's
decay runs from when the last of it arrived, and R3's 3-hour escalation
clock starts when the water reaches the sea.
The speed is a published guess and deliberately a fast one: a small Wealden stream in spate runs at roughly half a metre a second. If the real water is slower than that, the beach goes red a little before it needs to, which is the right way to be wrong. Sea outfalls, and the one 300 m beach ditch at Bexhill, have no delay.
R4 — heavy rain but no reported discharge → amber
If any of the rain gauges in the catchments that drive local overflows records more than 10 mm in 6 hours, we treat the silence of the discharge feed as suspicious rather than reassuring, and every zone with no discharge signal of its own goes amber. Storm overflows exist to spill in exactly this weather, event monitors are not perfectly reliable, and reporting can lag. This rule is our answer to the known weaknesses of EDM (event duration monitoring) data.
R4 only fires when no discharge rule (R1, R2, R3) has already fired for the zone, because in that case the discharge data is evidently flowing and the rain adds nothing.
R5 — heavy rain forecast → advisory note only
If more than 15 mm of rain is forecast in the next 24 hours, every verdict carries a note saying overflows are likely and it is worth checking again before swimming. R5 never changes a colour; forecast rain is a reason to re-check, not evidence of contamination.
R6 — Environment Agency warnings → at least amber
Two EA signals each set a floor of amber for a designated bathing water: an active pollution risk forecast for the site, or a latest in-season sample exceeding our screening thresholds (E. coli above 500 or intestinal enterococci above 200 cfu/100 ml, which are the boundaries of the "good" class in the Bathing Water Regulations' coastal standards, used here as single-sample screening levels; the EA itself classifies annually, not per sample).
Only Hastings Pelham Beach, St Leonards and Bexhill are designated bathing waters, so only they can receive R6 input. Glyne Gap, Bulverhythe, and Rock-a-Nore are not sampled by the EA at all, which is itself worth knowing.
The verdict is decided by the two boolean signals alone, but the displayed reason carries the detail behind them: for a pollution risk warning, the EA's risk level and the forecast's expiry time, plus a reminder that this is the EA's daily rainfall/tide/wind prediction of reduced water quality and not a recorded storm overflow release (so no outfall will show a release when only R6 is active); for a poor sample, the sample date and the raw E. coli / intestinal enterococci counts. Each beach panel also links to the EA's own profile page for the site.
R7 — manual override
We can set a zone's verdict by hand, in either direction, and the verdict says a person set it and why. This exists for situations the rules cannot see, such as a pollution incident reported by another route. An override beats every other rule while it is active.
Staleness
The most dangerous failure for a service like this is a dead data feed silently showing a fresh-looking green. Freshness is checked per source, not just overall: each source that can raise a verdict has its own age limit (Southern Water discharge data 45 minutes, EA rainfall 45 minutes), and if any of them is over its limit — or has never delivered data at all, as on a fresh install — every verdict is flagged as delayed and the status page shows a warning banner naming the delayed source. A recent weather poll can never mask a dead discharge feed.
The rainfall feed reads three gauges in one poll, and the EA's server answers slowly and unevenly. A poll counts as fresh when at least one gauge answers: a gauge that times out or errors is skipped for that poll and named in the poll log, and R4 reads whichever gauges did answer. Only a poll in which no gauge answers is a failed poll — that one still flags every verdict and wakes the operator.
EA bathing-water status is judged differently, because we cannot fetch it ourselves: the EA's bathing-water service refuses our server's requests, so that one source is relayed to us hourly from a machine on a home connection. How long ago we fetched it therefore measures that machine's uptime, not whether what we are showing is current. The EA's pollution risk forecast carries its own expiry — typically the following morning — so we treat the source as current while the forecast we hold is still inside it, and fall back to a one-day limit when we hold no usable expiry. A relay that stops for good is still caught: once the forecast we hold expires, the verdicts are flagged and the operator is alarmed as before. Our own monitoring separately wakes the operator, and the status page also checks its own age in your browser, so a page our systems stopped regenerating warns you about itself.
The under-cliff walk verdict (cliff_walk)
A separate verdict, same discipline, different question: is there enough tide window to walk the foreshore under the cliffs from Rock-a-Nore past Fairlight Glen to Pett Level? People are cut off on this route every season and there are no exits between Ecclesbourne Glen and Pett Level.
The rule (src/swimhastings/cliffwalk.py, constants under cliff_walk in
config/rules_config.yaml): the route is treated as passable while the
predicted tide height at the Hastings station is below 3.0 m. From the
predicted high and low waters we compute the passable windows, then:
- 🔴 avoid — the predicted height is at or above the threshold now; the route is cut off.
- 🟡 caution — passable now, but the window closes in less than the 3 h we allow for the one-way walk plus a 1 h margin. Fine for the beach near an exit point; do not set out for Pett Level.
- 🟢 — the window outlasts the walk plus the margin.
Heights between high and low water are the same sinusoidal interpolation the status page uses — an approximation, and labelled as one. All three numbers are published guesses to be tuned against ground truth (the 3.0 m threshold in particular), and this verdict is about tide only: swell, surge and weather can close the route much earlier than the tide alone suggests. The verdict says so on every surface, and no verdict is ever shown from tide data more than 12 hours old — a missing verdict is shown as missing, never defaulted to green.
About Southern Water's own classifications
Southern Water publishes its own view of whether a release is affecting bathing waters. Where we have that classification we store it and can show it, always attributed. It never contributes to a verdict. Our reasons are structural: their classification methodology is not published in a form we can check, and an assessment of impact made by the party doing the discharging should be corroborated rather than inherited. Where we criticise their data we will do it specifically and with citations, not by assertion.
The release data itself (which outfall, when it started, when it stopped) is theirs, published under CC BY 4.0, and we depend on it gratefully. Two known limits of that feed: it reports only the most recent event per outfall, so two short releases in quick succession can appear to us as one; and while a release is in progress the feed's end-time field still shows the previous event, which we detect and treat as "still discharging".
Data sources and attribution
- Southern Water Storm Overflow Activity, © Southern Water, licensed under CC BY 4.0, via the Stream water data portal. Polled every 10 minutes.
- Environment Agency Bathing Water Quality API and flood-monitoring API (rainfall), Open Government Licence v3. Polled hourly and every 15 minutes respectively.
- Tide predictions from the UK Tidal API (Discovery tier). Contains ADMIRALTY ® tidal data: © Crown copyright and database right. The rules use only direction and phase; the status page additionally shows the next high or low water time and an estimated current height (a sinusoidal interpolation between low and high water — an approximation, and labelled as one). The licence permits showing this to you; it forbids us offering the data for bulk copying or re-publication, which we don't.
- Rain forecast from Open-Meteo. Polled every 6 hours.
- Sea and shore conditions (air and sea temperature, wind, modelled wave height) from Open-Meteo, polled hourly. These appear on the status page as comfort information only. They are not inputs to any rule: a flat, warm sea can still be contaminated, and we do not blur the two ideas.
Changelog
- 2026-09-05 (travel time). Inland overflows now carry their river
distance to the sea, and rules R1 to R3 see their releases shifted later
by that distance at an assumed 2 km/h (new "Travel time for inland
overflows" section above;
travel_kmper outfall inconfig/beaches.yaml,travel_delay.stream_speed_km_hinconfig/rules_config.yaml). Before this, a release at Battle turned Bulverhythe red the moment the feed reported it, though the water was five or six hours from the beach; the audit below made that gap explicit for eight outfalls, and it goes both ways, since the decay after a release also started too early. While the water is on its way the beach now carries an advisory note with the expected arrival instead of a colour. Sea outfalls are unchanged. - 2026-09-05 (outfall map audit). After the Pett Level correction we
traced every one of the 47 outfalls in the feed through OS Open Rivers,
not just the six on that marsh, asking of each inland overflow: where
does its watercourse reach the sea? Three lines of the map were wrong.
Two Powdermill Stream CSOs (SWS00031 at Battle, SWS00490 at Crowhurst)
had been filed under "Brede catchment, drains to Rye"; that is the
Sedlescombe stream of the same name. Battle's Powdermill Stream is Combe
Haven water and reaches the sea at Bulverhythe, so both now carry the
same map as the Watermill Stream CSO beside them (local to Bulverhythe,
Glyne Gap on the ebb). This is the opposite error to Pett Level: a
release there would have produced no verdict at all. The Cole Stream CSO
at Cooden (SWS00546) was mapped as discharging directly into Cooden; the
stream in fact runs west behind the beach into the East Stream and
reaches the sea through the Pevensey Levels sluice 1.5 km west, the
same outlet as the two Levels CSOs, so it now carries their flood-only
map. And the lower Combe Haven valley CSO (SWS00978) was mapped as local
to Glyne Gap as well as Bulverhythe; there is no outlet at Glyne Gap,
the river reaches the sea at the Bulverhythe sluice, so Glyne Gap moves
to the ebb adjacency, the map the Bulverhythe beach outfall at that
sluice already had. The remaining 44 lines held, including every
drains-to-Rye entry. One line OS could not settle either way: the
"Pigeon Cove Gill" outfall on the ridge above Ore (SWS00713), where the
network has no gill by that name; it stays local to Fairlight on
Southern Water's word that it discharges to a coastal gill. None of the
three corrected outfalls has released since June 2026, so no published
verdict was affected. Every outfall now carries Southern Water's
receiving-water field in
config/beaches.yaml, and the trace script covers the whole map. No rule thresholds changed. - 2026-09-05 (rainfall poll). A rainfall poll now succeeds when at least one of the three
EA gauges answers (
sources/ea_rainfall.py). Before, one gauge past its 90-second timeout failed the whole poll, and three misses in a row — the EA host is routinely slow — flagged every verdict as delayed while the other gauges' fresh readings went unread. On the morning of 5 September the page carried the delayed banner over nine green beaches for the rainfall feed alone. A poll in which no gauge answers still fails, still alarms the operator and still flags the verdicts; a gauge that is skipped is named in the poll log with its error. No thresholds changed. - 2026-09-04 (labels, receiving water). Follow-up to the entry below. The same Pett Level resident pointed out that no sewage outfall exists at Cliff End, and they were right about our label too: the outfall we had called "Cliff End outfall" (SWS00659) sits 1.7 km inland at Pett village. Southern Water's receiving-water field, which the poller had been discarding, says it discharges to the English Channel, so it reaches the sea by pipe rather than at Cliff End. It is renamed accordingly. That field is now stored with every release and shown, attributed, next to every outfall on the status page, so a local reader can test any line of our map against it. No mapping or verdict logic changed.
- 2026-09-02 (outfall map). Pett Level lost five outfalls. The Pannel
Sewer CSO and the four Marsham Sewer tributary CSOs on the Pett Level
marsh had been mapped as discharging "directly" into Pett Level, on the
cautious assumption that the marsh drains to the sea at Cliff End. A
local swimmer challenged that, and Ordnance Survey's Open Rivers network
(a flow-directed map of every watercourse) confirms it: both sewers run
east into the Royal Military Canal and reach the sea through the River
Brede at Rye, about 7 km from Cliff End, with no watercourse outlet to
the sea within 3 km of Pett Level. Those five CSOs now have no zones, so
a release there produces no verdict on our frontage (
config/beaches.yaml; the check isscripts/trace_outfall_drainage.py). Only the Cliff End sea outfall (SWS00659) still drives Pett Level. In late August the old mapping had turned Pett Level red four times for releases that went to Rye; those were false alarms and we said so to the person who asked. No rule thresholds changed. - 2026-08-31. EA bathing-water freshness now follows the forecast's own
expiry rather than a 3-hour fetch limit (
stalenessinconfig/rules_config.yaml; fallback raised to 24 hours for when no expiry is on file). That source reaches us by an hourly relay from a home machine, so the old limit measured that machine's uptime: when it slept overnight, the morning's verdicts were all flagged as delayed even though the bathing-water forecast on the page was the current one, valid until 08:29 that morning. Nothing about the verdicts themselves changed — the same inputs still produce the same colours — but a warning that fired every night for a feed that was not actually out of date was training readers to ignore the one channel that has to stay trustworthy. A genuinely dead relay still flags the verdicts and wakes the operator, once what we hold expires. - 2026-08-19 (display fix). Release start/end times in R1/R2 reasons were printed in UTC, reading an hour early during British Summer Time (the status page's events table was already UK-local, so the same event showed two different times). Reason times are now UK local everywhere. No verdict logic or thresholds changed.
- 2026-08-19. R6's displayed reasons now carry the evidence behind the booleans: risk level and expiry for a pollution risk warning (with an explicit note that it is a forecast, not a recorded release), sample date and bacterial counts for a poor sample. Both reasons can now appear together when both signals are active (previously the warning suppressed the sample text). No thresholds or verdict logic changed — the same inputs still produce the same colours.
- v1.1 (August 2026). Staleness tightened after an external code audit:
freshness is now judged per source rather than by the newest poll from any
source, so a dead Southern Water feed flags every verdict as delayed even
while weather polls succeed (new constants:
staleness.verdict_sources_max_age_min). "No data yet" now counts as stale rather than fresh. No thresholds for the discharge/rain/decay rules changed. - 2026-08-01. Added the under-cliff walk verdict (
cliff_walk: passable below 3.0 m, 3 h walk + 1 h margin, 12 h data-age limit). New surface, no change to the swim rules R1–R7. - v1 (July 2026). Initial public rule set, R1 to R7 as above. All constants are first guesses awaiting the two-week shadow period; expect this file to change and to say so.
Contains data from Southern Water Storm Overflow Activity, © Southern Water, licensed under CC BY 4.0; Environment Agency data under the Open Government Licence v3; forecast and conditions by Open-Meteo. Contains ADMIRALTY ® tidal data: © Crown copyright and database right. This service reports and interprets that public data; it does not measure water quality. Privacy.