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 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 json, List 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 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; } }