Skip to content

Alert engine

The backend runs a pipeline of analyzers on each stage. Three severity levels are used:

Level Badge Description
critical critical Blocking issue requiring immediate attention
warning warning Significant issue to watch
nudge nudge Informational suggestion

Every alert carries a stable code (App\Enum\AlertCode) identifying the rule variant that raised it, independent of the message wording. The frontend keys dismissals and deduplication on that code, so rephrasing a message never resurfaces a dismissed alert. One row below per code — AlertDocumentationTest fails if a code is emitted without a row, or if a row documents a code no longer emitted.

Rules are executed in priority order (lower = higher priority):

Rule Code Priority Severity Trigger
Continuity continuity_gap_critical 5 critical Gap > 500 m between consecutive stages
Continuity continuity_gap_warning 5 warning Gap 100-500 m between stages
Elevation elevation_gain 10 warning Elevation gain > 1 200 m on a stage
Steep gradient steep_gradient 20 warning Sustained >= 8 % gradient over >= 500 m
Surface surface_rough 20 warning Rough surface section >= 500 m: unpaved (gravel, dirt, mud, sand...) or rough paved (sett, cobblestone, paving stones); composite values like gravel;dirt count, and tracktype=grade3..5 / smoothness=bad..impassable are used as a fallback when surface is absent
Traffic traffic_main_road 20 critical Primary/trunk road without cycle infrastructure >= 500 m
Traffic traffic_secondary_road_fast 20 warning Secondary road, no cycleway, maxspeed tagged and > 50 km/h
Traffic traffic_secondary_road_slow 20 nudge Secondary road, no cycleway, maxspeed <= 50 km/h or absent/unreadable
E-bike range ebike_range_exceeded 20 warning Day distance > effective range (80 km - elevation / 25); action navigates to the nearest charging station within 2 km, else suggests shortening the stage
Sunset sunset_arrival_after_twilight 20 warning Estimated arrival time exceeds civil twilight end at stage end point (times shown in the stage's local timezone)
Calendar calendar_public_holiday -- nudge Stage falls on a public holiday of a country the route crosses, for any year the trip spans (France as fallback when no boundary resolves). Named and unnamed holidays share this code
Calendar calendar_sunday -- nudge Stage falls on a Sunday (businesses may be closed)
Wind wind_headwind -- warning Headwind >= 25 km/h on >= 60 % of stages with weather data. The threshold stays trip-wide (it describes a trip spent riding into the wind, not one windy day), but the alert is raised on each headwind stage
Wind wind_gusts_strong -- warning Wind gusts >= 50 km/h during the riding window, raised on each affected stage
Comfort comfort_poor_conditions -- warning Poor comfort index (< 40/100), raised on each affected stage
Heat heat_extreme -- warning Feels-like temperature >= 32 °C during the riding window, raised on each affected stage
Cold cold_extreme -- warning Feels-like temperature <= 2 °C during the riding window, raised on each affected stage
Rain rain_heavy -- warning Total precipitation >= 10 mm over the riding window, raised on each affected stage
Bike shops bike_shop_none_nearby -- nudge No repair resource within 2 km of stage midpoint (trips > 5 stages)
Resupply resupply_none_on_stage -- nudge Stage >= 40 km with no food/resupply POI along the route
Resupply resupply_closed_at_passage -- warning All resupply POIs on the stage are known to be closed at estimated passage time (a POI whose OpenStreetMap opening_hours is missing or unparsable is treated as unknown and suppresses the warning)
Accommodation accommodation_seasonal_closure -- warning All detected accommodations on the stage are likely closed due to seasonality
Water points water_point_gap -- nudge Stretch > 30 km without a detected drinking water source
Rest day rest_day_suggested 100 nudge Every N consecutive cycling days without a rest day (default: every 3 days), except on the trip's last day or when the following day is already a rest day
Cultural POI cultural_poi_suggestion -- nudge Museum, monument, castle, church, viewpoint, or attraction within 500 m of route — enriched with opening hours, price and description when sourced from DataTourisme. Named and unnamed POIs share this code
Railway station railway_station_none_nearby -- nudge No train station within 10 km of a stage endpoint (emergency evacuation)
Health services health_service_none_nearby -- nudge No pharmacy, hospital, or clinic within 15 km of a stage
Border crossing border_crossing -- nudge Route crosses an international border (country change detected via the local PostGIS admin-boundary index)
Ferry ferry_crossing -- warning Stage takes a ferry crossing (route runs within 100 m of an osm.ferries line; check schedule/booking)
Ford ford_crossing_dry -- nudge Stage crosses a ford (osm.fords within 25 m of the route), dry weather
Ford ford_crossing_wet -- warning Stage crosses a ford and rain is forecast (precipitation probability >= 50 %): possibly impassable in high water

Terrain rules (Continuity, Elevation, Steep gradient, Surface, Traffic, E-bike range, Sunset, Rest day) implement StageAnalyzerInterface, are tagged app.stage_analyzer and collected via #[AutowireIterator], then sorted by their priority. Rules with -- priority (Calendar, Wind, Comfort, Heat, Cold, Rain, Bike shops, Resupply, Accommodation, Water points, Cultural POI, Railway station, Health services, Border crossing, Ferry, Ford) are raised by separate async Symfony Messenger handlers (MessageHandler/{Check,Analyze,Scan}*Handler.php): the weather-based rules (Wind, Comfort, Heat, Cold, Rain) are the cases of Weather\WeatherStageRule, evaluated by AnalyzeWindHandler, Resupply in ScanPoisHandler, and Accommodation in ScanAccommodationsHandler. Ford runs after the weather computation (its severity depends on the per-stage forecast). At most 3 cultural POI suggestions are raised per stage.

Rest days are skipped by every rule that describes riding (Elevation, Steep gradient, Surface, Traffic, E-bike range, Sunset, Bike shops, Water points, Resupply). Three rules run on rest days on purpose: Continuity (a rest day duplicates the previous stage's end point, so its check is the real gap between the two ridden stages around it), Health services (evaluated at the stage midpoint, i.e. where the rider stays all day) and Calendar (a holiday closes shops whether or not you pedal).