Flood
The tide is rising. Water moves shoreward, into estuaries and harbors. This is the period after a low tide and before the next high tide. Current flows inward.
The tidal_phase tool does not just look up a value from NOAA. It runs a classification algorithm locally, using NOAA’s high/low predictions as input. This page explains how that algorithm works, why it exists, and what its outputs mean.
All of this logic lives in tidal.py — a pure-Python module with no I/O, no async, and no FastMCP dependencies. It takes datetimes and numbers in, and returns classification results out.
Tides follow a roughly sinusoidal cycle. At any given moment, the water is doing one of four things:
Flood
The tide is rising. Water moves shoreward, into estuaries and harbors. This is the period after a low tide and before the next high tide. Current flows inward.
Ebb
The tide is falling. Water moves seaward, draining out of estuaries and harbors. This is the period after a high tide and before the next low tide. Current flows outward.
Slack High
The tide has just reached its peak or is about to. Current is minimal — the water is essentially pausing before reversing direction. This is the transition from flood to ebb.
Slack Low
The tide has just reached its trough or is about to. Current is minimal. This is the transition from ebb to flood.
The classification algorithm maps any timestamp to one of these four phases, plus an "unknown" fallback when data is insufficient.
The algorithm takes two inputs: a timestamp (now) and a list of high/low events from NOAA’s hilo predictions. It produces a phase label, human-readable description, timing details, and a progress percentage.
Parse hilo predictions — parse_hilo_predictions() converts raw NOAA response dicts (with string timestamps and string values) into typed records with Python datetime objects and float values. It filters out any entries missing a "type" field and sorts by time.
# Input from NOAA{"t": "2026-02-21 04:30", "v": "4.521", "type": "H"}
# After parsing{"dt": datetime(2026, 2, 21, 4, 30), "v": 4.521, "type": "H"}Find bracketing events — The algorithm scans the sorted event list to find two events: the most recent one before now (the previous event) and the first one after now (the next event). These two events bracket the current moment in the tidal cycle.
Check slack windows — Before classifying ebb or flood, the algorithm checks whether now falls within a slack window around any high or low event. This check takes priority because slack periods are short and operationally important.
Classify by bracketing event types — If not in a slack window, the phase is determined by what kind of events bracket the current time:
Calculate progress — Linear interpolation between the previous and next events gives a percentage indicating how far through the current phase the tide has progressed.
The slack window is defined by a single constant:
SLACK_WINDOW_MIN = 30 # minutesIf the current time is within 30 minutes of a high tide event (either just past it or approaching it), the phase is slack_high. Within 30 minutes of a low tide event, the phase is slack_low.
The slack check runs before the ebb/flood classification. This means a timestamp that is 20 minutes after a high tide will be classified as slack_high, not ebb, even though the water has technically started falling. This is intentional — from a practical standpoint (navigation, fishing, crabbing), the current near a tidal turning point is negligible regardless of which side of the peak you are on.
The algorithm checks slack conditions in this order:
slack_highslack_highslack_lowslack_lowIf none of these match, classification falls through to the ebb/flood logic.
When both a previous and next event are known, the algorithm computes a progress percentage through the current phase using linear interpolation:
progress_pct = (elapsed / total) * 100Where elapsed is the time since the previous event and total is the full duration between the previous and next events.
Example: High tide occurred at 06:00, next low tide is at 12:12 (6 hours 12 minutes apart). At 09:00, 3 hours have elapsed out of 6.2 total hours.
progress_pct = (3.0 / 6.2) * 100 = 48.4%This tells you the tide is about halfway through its ebb phase. A progress of 0% means the phase just started; 100% means the next turning point is imminent.
The interpolate_predictions() function serves a different purpose from phase classification. It takes 6-minute interval predictions (not hilo events) and linearly interpolates the expected water level at any arbitrary timestamp.
The water_level_anomaly tool uses this to compare what the water level should be (predicted) against what it actually is (observed). The difference is the anomaly — caused by wind, barometric pressure, storm surge, or other non-tidal forces.
def interpolate_predictions(obs_dt, pred_times, pred_values) -> float | None:The function finds the two prediction timestamps that bracket obs_dt, then does a linear interpolation between their values. If obs_dt falls outside the prediction window entirely, it returns None.
NOAA provides 6-minute predictions. Over a 6-minute interval, the tidal curve is nearly linear — the error from linear interpolation versus a true sinusoidal fit is on the order of millimeters. Since the anomaly detection threshold defaults to 0.5 feet, this precision is more than adequate.
The algorithm handles several boundary conditions:
| Situation | Behavior |
|---|---|
| No hilo data at all | Returns "unknown" phase with null timing fields |
| Only a previous event (no future event in data) | Infers phase from the last event type: after a high -> "ebb", after a low -> "flood" |
| Only a future event (no past event in data) | Infers from the upcoming event type: before a high -> "flood", before a low -> "ebb" |
| Neither previous nor future event | Returns "unknown" |
| Exactly on a hilo event timestamp | The event is classified as prev_event (the <= comparison), placing the timestamp within the slack window |
The single-event fallback cases use qualifiers like “likely ebbing” or “likely flooding” in the description to signal reduced confidence. These situations arise when the prediction window is too narrow — for instance, fetching only 2 hours of hilo data when tidal cycles run 6+ hours.
The classify_tidal_phase() function returns a dict with these keys:
{ "phase": "ebb", # ebb | flood | slack_high | slack_low | unknown "description": "Tide is falling ...", # Human-readable explanation "previous": { # Most recent past hilo event "type": "high", "time": "2026-02-21 06:00", "level_ft": 4.521, }, "next": { # Next upcoming hilo event "type": "low", "time": "2026-02-21 12:12", "level_ft": -0.134, }, "minutes_since_previous": 180, # Minutes since previous event "minutes_to_next": 192, # Minutes until next event "progress_pct": 48.4, # Percentage through current phase}The SmartPot tidal_phase tool wraps this output with station info, the current UTC timestamp, and the latest observed water level reading, giving a complete picture of tidal conditions at a location.