136 lines
5.1 KiB
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());
|
|
}
|
|
}
|