navigation_safety_core 0.11.11
navigation_safety_core: ^0.11.11 copied to clipboard
Pure Dart core models for navigation safety — alert severity, safety score, threshold config, navigation route, safety scenarios. No Flutter dependency.
navigation_safety_core #
Pure-Dart core models for driving-navigation safety. Threshold
configuration, road-surface vocabulary, alert severity, alert-density
throttle, and an action-coupled alert explainer — tuned per
driver-class, optionally tuned to live driving conditions and live
driver state. Suitable for navigation apps that surface advisory
alerts (winter weather, low visibility, hazardous surface) over a base
map. No Flutter dependency: the Pure-Dart shape lets CLI tools,
server-side logic, test fixtures, and other pure-Dart packages depend
on the safety vocabulary without inheriting Flutter or
flutter_bloc. The companion package
navigation_safety
re-exports everything here and adds a Flutter BLoC layer.
Quick start #
a. Install + import #
Add to pubspec.yaml:
dependencies:
navigation_safety_core: ^0.11.0
Then import:
import 'package:navigation_safety_core/navigation_safety_core.dart';
b. Basic usage — forProfile #
Pick a driver-class profile; receive a config tuned for that class:
final config = NavigationSafetyConfig.forProfile(
DriverProfile.snowZoneExperienced,
);
print(config.warningVisibilityMeters); // 200
print(config.warningTemperatureCelsius); // 0
The six profiles (ageingRural, snowZoneExperienced, noviceUrban,
professional, agriculturalForestry, foreignTouristSnowZone) ship
default thresholds for visibility, temperature, and score floors. The
values are recorded decisions, not population-validated (see
KNOWN_LIMITATIONS.md). Pick the profile
closest to the active driver context; fall back to
snowZoneExperienced (the historical default) when uncertain.
c. Advanced usage — context-aware factories #
When the app has live driving conditions (current speed, humidity,
ambient temperature, time-since-precipitation, the braking deceleration
the vehicle can reach on the current surface), pass a DrivingContext
to forProfileWithContext and the relevant thresholds adjust:
final config = NavigationSafetyConfig.forProfileWithContext(
DriverProfile.snowZoneExperienced,
context: const DrivingContext(
speedMps: 22.2, // ~80 km/h
humidityRH: 0.92,
ambientTempCelsius: 1.0,
timeSincePrecipitation: Duration(minutes: 30),
),
);
When the app additionally has a live driver-state signal (fatigued,
distracted, sensorily-impaired), pair it with the profile in a
DriverContext and pass to forDriverContext:
final config = NavigationSafetyConfig.forDriverContext(
const DriverContext(
profile: DriverProfile.snowZoneExperienced,
state: DriverState.fatigued,
),
environmentalContext: const DrivingContext(speedMps: 22.2),
);
d. Vehicle-class threshold tuning (0.9.0) #
When the app has a vehicle-class signal (e.g. a fleet integrator
serving kei-cars in rural Hokkaido), implement
VehicleClassProvider and pass the token through DrivingContext:
class MyVehicleClassProvider implements VehicleClassProvider {
@override
String? get vehicleClassToken => 'kei-car';
}
final provider = MyVehicleClassProvider();
final config = NavigationSafetyConfig.forProfileWithContext(
DriverProfile.ageingRural,
context: DrivingContext(
speedMps: 18.0,
vehicleClassToken: provider.vehicleClassToken,
),
vehicleOverrides: VehicleThresholdOverrides.withKeiCarDefault(),
);
The built-in withKeiCarDefault() registry adds caution to the
warning visibility floor (+50m) and warning temperature (+1°C) for
the 'kei-car' token; integrators with their own measured
vehicle-class data should build their own registry with
VehicleThresholdOverrides.validated(...). Tokens are advisory
strings, NOT control inputs.
The live driving conditions and the driver state are
conservative-only for thresholds: they can make the thresholds warn
earlier than the per-profile baseline, never later. An earlier threshold
has its own cost: DriverState.impairedVisibility also moves the info
and critical visibility thresholds earlier, and info and critical alerts
take slots in AlertDensityThrottle's rolling window as warnings do, so
a later warning can be dropped. Vehicle-class overrides apply
AFTER the baseline AND AFTER the live-context adjustments. For a
registry built with a VehicleThresholdOverrides constructor, an
override may move only the two warning thresholds, and only earlier;
every other field comes back at its un-overridden value. That is
checked in every build mode, not only in debug builds:
VehicleThresholdOverrides.validated(...) refuses a violating
transform at registration, and on the drive path each refused field
goes back to its un-overridden value and is reported. A registry whose
class implements VehicleThresholdOverrides, or extends it and
replaces applyOverrideForToken, gets these checks only if its method
returns what this package's applyOverrideForToken returns. Otherwise
what it returns is applied unchecked and unreported, in every build
mode, even a warning threshold below the per-profile baseline or a
changed critical threshold or cap.
A runnable end-to-end walkthrough lives in
example/main.dart.
Concepts #
The package separates three orthogonal axes:
| Axis | Type | Question | Lifetime |
|---|---|---|---|
| Trait | DriverProfile |
Who is the driver (class)? | Per-trip / per-session |
| State | DriverState |
What state are they in right now? | Sub-trip; can change mid-trip |
| Live conditions | DrivingContext |
What does the road / weather look like right now? | Real-time; updates on every sample |
The trait + state pairing is DriverContext. The trait/state split is
this package's design. Regan and Strayer 2014 (PMC4001671) list driver
conditions (e.g. young, inexperienced, old) and driver states
(e.g. bored, sleepy, fatigued, drugged, emotional) as factors in
driver inattention. Trait is who the driver is; state is
what state the driver is in right now. The two are independent inputs
to threshold tuning. State adjustments at 0.6.0 are intentionally
small — the API shape is stable; the magnitudes are flagged
UNVERIFIED in KNOWN_LIMITATIONS.md pending
state-axis calibration.
Two runtime helpers ship alongside the threshold config:
AlertDensityThrottle— per-profile alerts/min cap with a rolling 60-second window. Critical alerts always fire (documented invariant); info and warning alerts are gated by the cap, which is meant to limit driver desensitisation; that effect has not been measured (seeKNOWN_LIMITATIONS.md). Per-profile cap defaults are a recorded decision; no source is cited for the values (seeKNOWN_LIMITATIONS.md).AlertExplainer— pre-localised(condition, action, verbosity, locale)tuple for each(RoadSurfaceCondition, DriverProfile)pair. The action wording is the package's own (a recorded decision). Action mood is advisory ("reduce", "avoid", "maintain"), never imperative-on-control.
Calibration formulas #
Three context-dependent calibrations are provided by the standalone
navigation_safety_calibration
package (the single source of truth for the meteorological / kinematic
design-default baseline). Since 0.11.0 core depends on that package
and re-exports it from the barrel, so the three functions are
available directly via
package:navigation_safety_core/navigation_safety_core.dart — you do
not need a separate import:
- Speed-dependent visibility (
computeSpeedAdjustedVisibilityMeters) — the reaction distance (the per-profile reaction-time default times the live speed) plus the braking distance REPLACES the per-profile warning visibility floor when it is longer; it is not added to it. The factories take the deceleration fromDrivingContext.brakingDecelerationMps2. They check it first forNaN, infinite, zero and negative values, which are used as 0.4905 m/s² whatever the readings, and then use values above 5.5 m/s² as 5.5. Without one they use 5.5 m/s², a dry-pavement value, and with the per-profile floors that replaces the floor only above about 134 km/h (agriculturalForestry) to 179 km/h (foreignTouristSnowZone). Where the ambient and humidity readings classify radiative-frost black ice they use 0.981 m/s², or a supplied value when that is lower, and the floor is replaced from about 65 km/h (agriculturalForestry) to 89 km/h (foreignTouristSnowZone). Both figures are recorded decisions; seeKNOWN_LIMITATIONS.md. (forDriverContextadds a further margin of speed times a reaction-time penalty for a fatigued or distracted driver.) - Humidity-dependent effective temperature
(
computeEffectiveTemperatureCelsius) — black ice forms at road-surface temperature ≤ 0 °C. The effective temperature is the dew point (Magnus formula), which sits further below ambient the DRIER the air: at 2 °C ambient it is 1.28 °C at 95 % RH and −7.35 °C at 50 % RH. It therefore covers the dry-to-moderate-humidity radiative-frost case and, as the calibration package states, not saturated freezing fog above about +1 °C. When the effective temperature is at or below the per-profile warning temperature, the warning temperature rises bybaseline − floor(effective), at most 10 °C. Where the readings classify radiative-frost black ice (ambient at or below 3.0 °C and effective temperature at or below 0 °C), the warning temperature is also raised to at least the ambient reading rounded up, so an ambient comparison (<=) warns: at 3.0 °C and 70 % RH the effective temperature is −1.94 °C, and for a profile whose warning temperature is 0 °C the lift gives 2 °C and the classification 3 °C; forageingRuralandforeignTouristSnowZone, whose warning temperature is 2 °C, the lift alone gives 6 °C. Above 3.0 °C ambient, an ambient reading can still stay above the raised warning temperature while the effective temperature is at or below the baseline; the calibration does not classify those readings as black ice. - Time-since-precipitation surface moisture
(
computeSurfaceMoistureFraction) — surface moisture decays exponentially after the last rain or snowfall. The warning visibility floor becomes the per-profile floor plus a margin proportional to how wet the road still is, when that is longer than the floor from speed; the margin is not added to the speed floor.
Each calibration source file's header comment in the
navigation_safety_calibration package names the sources cited for its
formula and states which of its values are recorded decisions.
Determinism note (safety-class). These constants are the
design-default baseline the driver relies on (Magnus black-ice constants; the
surface-moisture half-life; the braking-distance default, 5.5 m/s², a
dry-pavement value and not a worst case for snow or ice; the
per-profile visibility floor). The calibration package is caution-add-only — the per-profile
floor never lowers across its releases. Core pins it at ^0.1.3;
integrators shipping a product SHOULD commit a pubspec.lock so the
exact calibration version is reproducible across builds rather than
floating within the 0.1.x range.
Standards mapping #
This package is intended for SAE J3016 Level 0 and Level 1 supportive use — the driver performs the dynamic driving task at all times; the package's surfaces inform the driver but never actuate the vehicle and never close a control loop.
| Standard | Mapping |
|---|---|
| SAE J3016 | L0 / L1 supportive. No L2+ claim. |
| ISO 26262 | Product-quality scope at the package boundary, not functional-safety scope. The integrator performs the hazard analysis and decides the final ASIL classification for their integration. The package's wording discipline is consistent with QM at the application layer. |
| JIS / JASO (Japanese-domestic automotive standards) | Not mapped at this scope. Consult a qualified Japanese-domestic functional-safety partner before any IVI-vendor or OEM-pilot integration that targets the Japanese-domestic certification surface. |
Full standards-mapping detail and the per-formula UNVERIFIED-magnitude
flags live in
KNOWN_LIMITATIONS.md.
Vehicle Data Integration #
Real driving conditions can come from sensors the host app already
has access to. For embedded Linux IVI integrations that read vehicle
CAN traffic, j1939 and the wider
can_dart family (including
nmea2000, nmea2000_bus, rvc, rvc_bus) ship the bus-level
plumbing — address claiming, multi-packet transport, DM1 diagnostics
— as Pure-Dart packages on pub.dev. navigation_safety_core
composes downstream of those bus events: an integrator decodes the
signals DrivingContext has fields for (vehicle speed, ambient air
temperature, relative humidity, time since precipitation), leaves
null any it does not measure, which keeps the per-profile baseline
for that dimension, and the safety vocabulary handles the rest. Engine
coolant temperature is not ambient air temperature: passed as
ambientTempCelsius, it compares the engine's temperature with the
warning temperature, so once the engine has warmed past it the
temperature warning cannot fire, whatever the air outside. ABS / TCS
engagement and wiper status have no DrivingContext field.
example/can_bus_integration.dart walks through the pattern with one
J1939/71 PGN decoded from the bus (CCVS1 0xFEF1 wheel-based vehicle
speed) as an illustrative anchor. It decodes no ambient-air, humidity
or precipitation signal: the ambient-air reading comes from a
placeholder labelled as one, for the integrator to replace, and
humidity and precipitation history stay null. The composition seam
is the load-bearing part — the same shape applies to other vehicle-bus
signal sources.
What this is NOT #
- Not an L2+ automation or handover-class supervision package. Any deployment where alert acknowledgment is part of an automation-handover safety contract requires the integrator to add their own handover-class driver-attention monitoring, take-over-request signalling, and minimum-risk-manoeuvre fallback.
- Not ASIL-certified. No functional-safety case is asserted at the package boundary; the integrator owns the hazard analysis and the certification path for their integration.
- Not JIS / JASO conformant. Japanese-domestic certification is out of scope at this layer.
- Not a control surface. Action verbs in
AlertExplainerare advisory; speed numbers are advisory reference points chosen by the package, not system-enforced limits. The driver retains full control authority. - Not a routing engine.
NavigationRouteis a typed route representation independent of any specific routing engine; it describes a route, it does not compute one.
Equal-dignity invariant #
Alert visibility, severity ordering, and plane-allocation priority in
the consuming HMI MUST be severity-driven, never profile-driven.
Per-profile differentiation belongs in verbosity, locale, and
density-cap surfaces (provided by AlertExplainer and
AlertDensityThrottle); it MUST NOT enter the visibility or
preemption path. See KNOWN_LIMITATIONS.md
for the full discussion.
Public API surface #
NavigationSafetyConfig— threshold configuration with three factories:forProfile,forProfileWithContext,forDriverContext. As of 0.9.0,forProfileWithContextaccepts an optionalvehicleOverridesparameter for vehicle-class threshold tuning.DriverProfile— six driver-class profiles (trait axis).DriverState— four live-state values (transient state axis).DriverContext— trait + state composite.DrivingContext— live driving-conditions value-object. As of 0.9.0, carries an optionalvehicleClassTokenfield. It also carries an optionalbrakingDecelerationMps2field for the current surface: passnull, not a sentinel, when no value was measured.VehicleClassProvider(0.9.0) — abstract interface returningString? get vehicleClassTokenfor integrator-supplied vehicle-class signals.VehicleThresholdOverrides(0.9.0) — registry of vehicle-class-token → caution-adding-only threshold-transform mappings; ships a built-in'kei-car'default viaVehicleThresholdOverrides.withKeiCarDefault().AlertDensityThrottle— per-profile alerts/min cap with critical-bypass invariant.AlertExplainer— action-coupled per-(condition, profile) text with verbosity + locale.AlertSeverity—info/warning/critical. Declaration order is load-bearing.RoadSurfaceCondition— eight road-surface vocabulary values with a published glossary.SafetyScore— anoverallscore the caller supplies, carried withgripScore,visibilityScoreandfleetConfidenceScore; each is clamped to 0–1, and a non-finite value becomes 0.toAlertSeverityreturns the WORSE of two verdicts:overallagainst the config's score floors, andgripScoreagainstNavigationSafetyConfig.criticalGripScoreFloor. The second exists because callers commonly supply a MEAN of the axes, and a mean cannot say that one axis alone is lethal — black ice under a clear sky. Through 0.11.9toAlertSeveritymappedoverallalone, so on that 50/50 slicecriticalwas unreachable at any grip value once visibility was good. CORRECTED IN 0.11.11 — this paragraph read "becauseoverallis a mean" and "criticalwas unreachable at any grip value once visibility was good", with no slice qualifier, and both were false as written.overallis a third input this package never computes (see the first line of this entry), and on 0.11.9 anoverallbelowwarningScoreFloor(default 0.30) returnedcriticalat ANY grip, including 1.0.SafetyScenario— a named, versioned id for the kind of hazard an alert describes (a class, not an enum);WellKnownScenariosholds ready-made ids in the sensing, routing, signal, dynamics and hmi categories. Nothing in this package's score uses it.NavigationRoute— typed route representation independent of any specific routing engine.
Further reading #
example/main.dart— runnable walkthrough of the threshold factories (profile, live context, driver state),AlertDensityThrottle,AlertExplainerandAlertSeverityordering. It does not coverVehicleThresholdOverrides,VehicleClassProvider, the circadian, session and confidence inputs,SafetyScore,SafetyScenario,NavigationRoute,RoadSurfaceConditionGlossary,LoomFitTelemetry,registerUxDifferentiatoror the calibration functions.example/can_bus_integration.dart—VehicleThresholdOverrides.withKeiCarDefault()on a vehicle-bus frame stream.CHANGELOG.md— per-version behaviour deltas.KNOWN_LIMITATIONS.md— UNVERIFIED-magnitude disclosures, standards-mapping detail, and the full equal-dignity-invariant discussion.LOOMS.md— runtime-loom catalog (theAlertDensityThrottleandAlertExplainerpair).
License #
BSD-3-Clause. See LICENSE.