Add centralized retry/fallback policy with hard-coded endpoints

Extract duplicated retry logic from OverpassService and RoutingService
into a shared resilience framework in service_policy.dart:

- ResiliencePolicy: configurable retries, backoff, and HTTP timeout
- executeWithFallback: retry loop with primary→fallback endpoint chain
- ErrorDisposition enum: abort / fallback / retry classification
- ServicePolicy + ServicePolicyResolver: per-service compliance rules
  (rate limits, caching, concurrency) for OSMF and third-party services
- ServiceRateLimiter: async semaphore-based concurrency and rate control

OverpassService now hits overpass.deflock.org first, falls back to
overpass-api.de. RoutingService hits api.dontgetflocked.com first,
falls back to alprwatch.org. Both use per-service error classifiers
to determine retry vs fallback vs abort behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Doug Borg
2026-03-11 23:13:52 -06:00
co-authored by Claude Opus 4.6
parent 4d1032e56d
commit 2833906c68
8 changed files with 968 additions and 270 deletions
+3 -3
View File
@@ -250,11 +250,11 @@ class NavigationState extends ChangeNotifier {
_calculateRoute();
}
/// Calculate route using alprwatch
/// Calculate route via RoutingService (primary + fallback endpoints).
void _calculateRoute() {
if (_routeStart == null || _routeEnd == null) return;
debugPrint('[NavigationState] Calculating route with alprwatch...');
debugPrint('[NavigationState] Calculating route...');
_isCalculating = true;
_routingError = null;
notifyListeners();
@@ -271,7 +271,7 @@ class NavigationState extends ChangeNotifier {
_showingOverview = true;
_provisionalPinLocation = null; // Hide provisional pin
debugPrint('[NavigationState] alprwatch route calculated: ${routeResult.toString()}');
debugPrint('[NavigationState] Route calculated: ${routeResult.toString()}');
notifyListeners();
}).catchError((error) {