Refactor project structure and enhance stock watchlist functionality
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import "package:capstone_project/models/event_signal.dart";
|
||||
import "package:capstone_project/utils/agrigator.dart";
|
||||
|
||||
class WatchedStock {
|
||||
final String ticker;
|
||||
final String companyName;
|
||||
EventSignal? latestSignal;
|
||||
List<EventSignal> signalHistory;
|
||||
|
||||
WatchedStock({
|
||||
required this.ticker,
|
||||
required this.companyName,
|
||||
this.latestSignal,
|
||||
List<EventSignal>? signalHistory,
|
||||
}) : signalHistory = signalHistory ?? [];
|
||||
|
||||
factory WatchedStock.fromJson(Map<String, dynamic> json) {
|
||||
final signalHistoryJson = (json["signalHistory"] as List? ?? [])
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(_eventSignalFromStoredJson)
|
||||
.whereType<EventSignal>()
|
||||
.toList();
|
||||
|
||||
final latestSignalJson = json["latestSignal"];
|
||||
final latest = latestSignalJson is Map<String, dynamic>
|
||||
? _eventSignalFromStoredJson(latestSignalJson)
|
||||
: null;
|
||||
|
||||
return WatchedStock(
|
||||
ticker: (json["ticker"] ?? "").toString(),
|
||||
companyName: (json["companyName"] ?? "").toString(),
|
||||
latestSignal: latest ?? (signalHistoryJson.isNotEmpty ? signalHistoryJson.first : null),
|
||||
signalHistory: signalHistoryJson,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
"ticker": ticker,
|
||||
"companyName": companyName,
|
||||
"latestSignal": latestSignal?.toJson(),
|
||||
"signalHistory": signalHistory.map((signal) => signal.toJson()).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
// returns null (and logs) when the stored payload is from the pre-rework
|
||||
// schema — we'd rather drop it than invent impact/direction values.
|
||||
static EventSignal? _eventSignalFromStoredJson(Map<String, dynamic> json) {
|
||||
final articles = (json["articles"] as List? ?? [])
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(FeedItem.fromJson)
|
||||
.toList();
|
||||
|
||||
try {
|
||||
return EventSignal.fromJson(json, articles);
|
||||
} on LegacySignalException catch (e) {
|
||||
print("[watched_stock] dropping legacy signal: ${e.message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user