120 lines
3.4 KiB
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;
|
|
}
|
|
}
|