Auger/lib/models/event_signal.dart

120 lines
3.4 KiB
Dart

import "package:capstone_project/utils/agrigator.dart";
// raised when deserializing a signal payload that predates the direction/impact
// rework. the loader catches this and skips the signal.
class LegacySignalException implements Exception {
final String message;
LegacySignalException(this.message);
@override
String toString() => "LegacySignalException: $message";
}
class EventSignal {
final String eventId;
final String eventSummary;
// positive | negative | neutral
final String direction;
// forecasting | reactive
final String nature;
// likelihood the event is real / actually happened as reported (0..1)
final double probability;
// magnitude of expected immediate price reaction, assuming the event is real (0..1)
final double impact;
final String rationale;
final List<FeedItem> articles;
final DateTime createdAt;
EventSignal({
required this.eventId,
required this.eventSummary,
required this.direction,
this.nature = "reactive",
required this.probability,
required this.impact,
required this.rationale,
required this.articles,
DateTime? createdAt,
}) : createdAt = createdAt ?? DateTime.now();
factory EventSignal.fromJson(
Map<String, dynamic> json,
List<FeedItem> articles, {
String? eventIdOverride,
}) {
// legacy schema discriminator — old signals had `signal` + `confidence`
// and no `impact`/`direction`. those numbers meant something different
// (or nothing) so we refuse to load them instead of silently remapping.
final hasImpact = json.containsKey("impact");
final hasDirection = json.containsKey("direction");
if (!hasImpact || !hasDirection) {
throw LegacySignalException(
"payload missing impact/direction — incompatable with current schema",
);
}
return EventSignal(
eventId: eventIdOverride ?? (json["event_id"] ?? "").toString(),
eventSummary: (json["event_summary"] ?? "").toString(),
direction: _normalizeDirection((json["direction"] ?? "").toString()),
nature: _normalizeNature((json["nature"] ?? "").toString()),
probability: _normalizeScore(json["probability"]),
impact: _normalizeScore(json["impact"]),
rationale: (json["rationale"] ?? "").toString(),
articles: articles,
createdAt: DateTime.tryParse((json["created_at"] ?? "").toString()),
);
}
Map<String, dynamic> toJson() {
return {
"event_id": eventId,
"event_summary": eventSummary,
"direction": direction,
"nature": nature,
"probability": probability,
"impact": impact,
"rationale": rationale,
"articles": articles.map((article) => article.toJson()).toList(),
"created_at": createdAt.toIso8601String(),
};
}
static String _normalizeNature(String value) {
return value.trim().toLowerCase() == "forecasting" ? "forecasting" : "reactive";
}
static String _normalizeDirection(String value) {
switch (value.trim().toLowerCase()) {
case "positive":
return "positive";
case "negative":
return "negative";
default:
return "neutral";
}
}
static double _normalizeScore(dynamic value) {
if (value is num) {
return value.toDouble().clamp(0.0, 1.0);
}
if (value is String) {
final parsed = double.tryParse(value);
if (parsed != null) {
return parsed.clamp(0.0, 1.0);
}
}
return 0.0;
}
}