190 lines
6.0 KiB
Dart
190 lines
6.0 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import 'road_alert_models.dart';
|
|
|
|
@immutable
|
|
class SwarmReport {
|
|
final String id;
|
|
final RoadAlertCategory category;
|
|
final String? subtype;
|
|
final double latitude;
|
|
final double longitude;
|
|
final double? heading;
|
|
final String? roadName;
|
|
final String description;
|
|
final String reporterUid;
|
|
final DateTime reportedAt;
|
|
final DateTime expiresAt;
|
|
final int confirmations;
|
|
final int dismissals;
|
|
final String? groupRideId;
|
|
final String? source;
|
|
final RoadAlertStatus status;
|
|
|
|
const SwarmReport({
|
|
required this.id,
|
|
required this.category,
|
|
this.subtype,
|
|
required this.latitude,
|
|
required this.longitude,
|
|
this.heading,
|
|
this.roadName,
|
|
required this.description,
|
|
required this.reporterUid,
|
|
required this.reportedAt,
|
|
required this.expiresAt,
|
|
this.confirmations = 1,
|
|
this.dismissals = 0,
|
|
this.groupRideId,
|
|
this.source,
|
|
this.status = RoadAlertStatus.active,
|
|
});
|
|
|
|
double get confidence {
|
|
final total = confirmations + dismissals;
|
|
if (total == 0) return 0.8;
|
|
return (confirmations / total).clamp(0.1, 1.0);
|
|
}
|
|
|
|
bool get isExpired => DateTime.now().isAfter(expiresAt);
|
|
|
|
bool get isCommunityConfirmed => confirmations >= 2;
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'id': id,
|
|
'category': category.name,
|
|
if (subtype != null) 'subtype': subtype,
|
|
'latitude': latitude,
|
|
'longitude': longitude,
|
|
if (heading != null) 'heading': heading,
|
|
if (roadName != null) 'roadName': roadName,
|
|
'description': description,
|
|
'reporterUid': reporterUid,
|
|
'reportedAt': reportedAt.millisecondsSinceEpoch,
|
|
'expiresAt': expiresAt.millisecondsSinceEpoch,
|
|
'confirmations': confirmations,
|
|
'dismissals': dismissals,
|
|
if (groupRideId != null) 'groupRideId': groupRideId,
|
|
if (source != null) 'source': source,
|
|
'status': status.name,
|
|
};
|
|
|
|
factory SwarmReport.fromJson(Map<String, dynamic> json) {
|
|
final catName = json['category']?.toString() ?? 'other';
|
|
final category = RoadAlertCategory.values.firstWhere(
|
|
(c) => c.name == catName,
|
|
orElse: () => RoadAlertCategory.other,
|
|
);
|
|
|
|
final rawReportedAt = json['reportedAt'];
|
|
final reportedAt = rawReportedAt is num
|
|
? DateTime.fromMillisecondsSinceEpoch(rawReportedAt.toInt())
|
|
: (DateTime.tryParse(rawReportedAt?.toString() ?? '') ?? DateTime.now());
|
|
|
|
final rawExpiresAt = json['expiresAt'];
|
|
final expiresAt = rawExpiresAt is num
|
|
? DateTime.fromMillisecondsSinceEpoch(rawExpiresAt.toInt())
|
|
: (DateTime.tryParse(rawExpiresAt?.toString() ?? '') ??
|
|
reportedAt.add(const Duration(hours: 1)));
|
|
|
|
final confirmations = (json['confirmations'] as num?)?.toInt() ?? 1;
|
|
final dismissals = (json['dismissals'] as num?)?.toInt() ?? 0;
|
|
|
|
final rawStatus = json['status']?.toString();
|
|
final status = RoadAlertStatus.values.firstWhere(
|
|
(s) => s.name == rawStatus,
|
|
orElse: () => confirmations >= 2
|
|
? RoadAlertStatus.confirmed
|
|
: RoadAlertStatus.active,
|
|
);
|
|
|
|
return SwarmReport(
|
|
id: json['id']?.toString() ?? '',
|
|
category: category,
|
|
subtype: json['subtype']?.toString(),
|
|
latitude: (json['latitude'] as num).toDouble(),
|
|
longitude: (json['longitude'] as num).toDouble(),
|
|
heading: json['heading'] != null ? (json['heading'] as num).toDouble() : null,
|
|
roadName: json['roadName']?.toString(),
|
|
description: json['description']?.toString() ?? 'Swarm Report',
|
|
reporterUid: json['reporterUid']?.toString() ?? 'anonymous',
|
|
reportedAt: reportedAt,
|
|
expiresAt: expiresAt,
|
|
confirmations: confirmations,
|
|
dismissals: dismissals,
|
|
groupRideId: json['groupRideId']?.toString(),
|
|
source: json['source']?.toString(),
|
|
status: status,
|
|
);
|
|
}
|
|
|
|
RoadAlert toRoadAlert({double? distanceFromUser}) {
|
|
final isHighSeverity = category == RoadAlertCategory.police ||
|
|
category == RoadAlertCategory.accident ||
|
|
category == RoadAlertCategory.weatherHazard;
|
|
final sourceName = source ??
|
|
(groupRideId != null ? 'Pack Rider Report' : 'MotorSignal Swarm');
|
|
|
|
return RoadAlert(
|
|
id: id,
|
|
providerId: 'swarm_alerts',
|
|
category: category,
|
|
subtype: subtype,
|
|
latitude: latitude,
|
|
longitude: longitude,
|
|
heading: heading,
|
|
roadName: roadName,
|
|
description: description,
|
|
reportedAt: reportedAt,
|
|
expiresAt: expiresAt,
|
|
lastUpdated: DateTime.now(),
|
|
confidence: confidence,
|
|
severity: isHighSeverity ? RoadAlertSeverity.high : RoadAlertSeverity.medium,
|
|
source: sourceName,
|
|
sourceType: RoadAlertSourceType.userReported,
|
|
status: status,
|
|
confirmations: confirmations,
|
|
dismissals: dismissals,
|
|
reporterUid: reporterUid,
|
|
distanceFromUser: distanceFromUser,
|
|
);
|
|
}
|
|
|
|
SwarmReport copyWith({
|
|
String? id,
|
|
RoadAlertCategory? category,
|
|
String? subtype,
|
|
double? latitude,
|
|
double? longitude,
|
|
double? heading,
|
|
String? roadName,
|
|
String? description,
|
|
String? reporterUid,
|
|
DateTime? reportedAt,
|
|
DateTime? expiresAt,
|
|
int? confirmations,
|
|
int? dismissals,
|
|
String? groupRideId,
|
|
String? source,
|
|
RoadAlertStatus? status,
|
|
}) {
|
|
return SwarmReport(
|
|
id: id ?? this.id,
|
|
category: category ?? this.category,
|
|
subtype: subtype ?? this.subtype,
|
|
latitude: latitude ?? this.latitude,
|
|
longitude: longitude ?? this.longitude,
|
|
heading: heading ?? this.heading,
|
|
roadName: roadName ?? this.roadName,
|
|
description: description ?? this.description,
|
|
reporterUid: reporterUid ?? this.reporterUid,
|
|
reportedAt: reportedAt ?? this.reportedAt,
|
|
expiresAt: expiresAt ?? this.expiresAt,
|
|
confirmations: confirmations ?? this.confirmations,
|
|
dismissals: dismissals ?? this.dismissals,
|
|
groupRideId: groupRideId ?? this.groupRideId,
|
|
source: source ?? this.source,
|
|
status: status ?? this.status,
|
|
);
|
|
}
|
|
}
|