MotorSignal-App/lib/services/alerts/community_traffic_parser.dart

136 lines
5.1 KiB
Dart

import 'road_alert_models.dart';
class CommunityTrafficParser {
static List<RoadAlert> parseAlertsJson(
Map<String, dynamic> json, {
required String providerId,
required String providerName,
}) {
final List<RoadAlert> alerts = [];
final rawAlerts = json['alerts'] ?? json['incidents'] ?? [];
if (rawAlerts is! List) return alerts;
for (var item in rawAlerts) {
if (item is! Map<String, dynamic>) continue;
final id = item['id']?.toString() ?? item['uuid']?.toString() ?? '${item['latitude']}_${item['longitude']}';
final typeStr = (item['type'] ?? item['category'] ?? item['typeGroup'] ?? '').toString().toUpperCase();
final subTypeStr = (item['subtype'] ?? item['subCategory'] ?? '').toString().toUpperCase();
final category = mapTypeToCategory(typeStr, subTypeStr);
final double? lat = _toDouble(item['latitude'] ?? item['lat'] ?? (item['location'] != null ? item['location']['y'] : null));
final double? lng = _toDouble(item['longitude'] ?? item['lon'] ?? item['lng'] ?? (item['location'] != null ? item['location']['x'] : null));
if (lat == null || lng == null) continue;
final double? heading = _toDouble(item['heading'] ?? item['bearing'] ?? item['direction']);
final String? street = item['street']?.toString() ?? item['road']?.toString() ?? item['locationName']?.toString();
final String description = _buildDescription(typeStr, subTypeStr, item['reportDescription'] ?? item['description']);
final now = DateTime.now();
final int nMinutesOld = (item['pubMillis'] != null)
? ((now.millisecondsSinceEpoch - (item['pubMillis'] as num).toInt()) / 60000).clamp(0, 120).toInt()
: 5;
final reportedAt = now.subtract(Duration(minutes: nMinutesOld));
final expiresAt = now.add(const Duration(minutes: 30));
final double? confidence = _toDouble(item['confidence'] ?? item['reliability']);
final severity = _mapSeverity(item['severity'] ?? item['reliability']);
alerts.add(RoadAlert(
id: id,
providerId: providerId,
category: category,
latitude: lat,
longitude: lng,
heading: heading,
roadName: street,
description: description,
reportedAt: reportedAt,
expiresAt: expiresAt,
lastUpdated: now,
confidence: confidence != null ? (confidence > 1.0 ? confidence / 10.0 : confidence) : 0.8,
severity: severity,
source: providerName,
sourceType: RoadAlertSourceType.userReported,
));
}
return alerts;
}
static RoadAlertCategory mapTypeToCategory(String type, String subtype) {
final combined = '${type}_$subtype';
if (combined.contains('POLICE') ||
type == 'POLICE' ||
subtype.contains('POLICE_VISIBLE') ||
subtype.contains('POLICE_HIDDEN') ||
subtype.contains('POLICE_TRAP')) {
return RoadAlertCategory.police;
}
if (combined.contains('SPEED_CAM') ||
combined.contains('CAMERA') ||
subtype.contains('SPEED_CAMERA') ||
subtype.contains('RED_LIGHT_CAMERA')) {
return RoadAlertCategory.speedCamera;
}
if (combined.contains('ACCIDENT') || type == 'ACCIDENT') {
return RoadAlertCategory.accident;
}
if (combined.contains('HAZARD') || type == 'HAZARD' || subtype.contains('DEBRIS') || subtype.contains('WEATHER')) {
return RoadAlertCategory.hazard;
}
if (combined.contains('ROAD_CLOSED') || combined.contains('CLOSURE') || type == 'ROAD_CLOSED') {
return RoadAlertCategory.roadClosure;
}
if (combined.contains('CONSTRUCTION') || combined.contains('ROAD_WORKS') || type == 'CONSTRUCTION') {
return RoadAlertCategory.construction;
}
if (combined.contains('JAM') || combined.contains('TRAFFIC') || type == 'JAM') {
return RoadAlertCategory.traffic;
}
return RoadAlertCategory.other;
}
static String _buildDescription(String type, String subtype, dynamic rawDesc) {
if (rawDesc != null && rawDesc.toString().trim().isNotEmpty) {
return rawDesc.toString();
}
if (subtype.contains('POLICE_HIDDEN')) return 'Police reported hidden on shoulder';
if (subtype.contains('POLICE_VISIBLE')) return 'Police reported visible ahead';
if (type == 'POLICE') return 'Police report in area';
if (subtype.contains('SPEED_CAMERA')) return 'Speed enforcement camera reported';
if (type == 'ACCIDENT') return 'Traffic accident reported';
if (type == 'HAZARD') return 'Road hazard reported';
if (type == 'ROAD_CLOSED') return 'Road closed';
if (type == 'CONSTRUCTION') return 'Construction zone';
return 'Community traffic report';
}
static RoadAlertSeverity _mapSeverity(dynamic val) {
if (val == null) return RoadAlertSeverity.medium;
final num n = (val is num) ? val : num.tryParse(val.toString()) ?? 5;
if (n >= 8) return RoadAlertSeverity.critical;
if (n >= 5) return RoadAlertSeverity.high;
if (n >= 3) return RoadAlertSeverity.medium;
return RoadAlertSeverity.low;
}
static double? _toDouble(dynamic val) {
if (val == null) return null;
if (val is num) return val.toDouble();
return double.tryParse(val.toString());
}
}