cyberhybridhub/lib/admin/models/sync_run_event.dart
2026-05-31 11:17:12 -05:00

192 lines
5.4 KiB
Dart

import 'market_history_admin_config.dart';
import 'backfill_sync_item.dart';
enum SyncRunSeverity { ok, warning, error, rateLimit }
enum SyncRunStatus { success, failed, partial, inProgress }
class SyncRunEvent {
const SyncRunEvent({
required this.id,
required this.kind,
required this.startedAt,
required this.finishedAt,
required this.rowsWritten,
required this.rowsRemoved,
required this.error,
required this.severity,
required this.status,
required this.durationMs,
required this.summary,
this.backfillItems = const <BackfillSyncItem>[],
});
final int id;
final String kind;
final DateTime startedAt;
final DateTime? finishedAt;
final int rowsWritten;
final int rowsRemoved;
final String? error;
final SyncRunSeverity severity;
final SyncRunStatus status;
final int? durationMs;
final String summary;
final List<BackfillSyncItem> backfillItems;
String get displayTitle {
switch (kind) {
case 'universe':
return 'Asset universe sync';
case 'backfill':
return 'Market history backfill';
case 'cleanup':
return 'Data retention cleanup';
default:
return kind;
}
}
factory SyncRunEvent.fromJson(Map<String, dynamic> json) {
return SyncRunEvent(
id: (json['id'] as num).toInt(),
kind: json['kind'] as String,
startedAt: DateTime.parse(json['startedAt'] as String).toUtc(),
finishedAt: (json['finishedAt'] as String?) == null
? null
: DateTime.parse(json['finishedAt'] as String).toUtc(),
rowsWritten: (json['rowsWritten'] as num?)?.toInt() ?? 0,
rowsRemoved: (json['rowsRemoved'] as num?)?.toInt() ?? 0,
error: json['error'] as String?,
severity: _parseSeverity(
json['severity'] as String?,
json['error'] as String?,
(json['finishedAt'] as String?) == null,
),
status: _parseStatus(
json['status'] as String?,
json['error'] as String?,
(json['finishedAt'] as String?) == null,
(json['rowsWritten'] as num?)?.toInt() ?? 0,
),
durationMs: (json['durationMs'] as num?)?.toInt(),
summary: (json['summary'] as String?)?.trim().isNotEmpty == true
? (json['summary'] as String)
: _fallbackSummary(
kind: json['kind'] as String,
rowsWritten: (json['rowsWritten'] as num?)?.toInt() ?? 0,
rowsRemoved: (json['rowsRemoved'] as num?)?.toInt() ?? 0,
error: json['error'] as String?,
),
backfillItems: _parseBackfillItems(json['backfillItems']),
);
}
static List<BackfillSyncItem> _parseBackfillItems(dynamic raw) {
if (raw is! List<dynamic>) {
return const <BackfillSyncItem>[];
}
return raw
.whereType<Map<String, dynamic>>()
.map(BackfillSyncItem.fromJson)
.toList(growable: false);
}
static SyncRunSeverity _parseSeverity(
String? wire,
String? error,
bool inProgress,
) {
switch (wire) {
case 'ok':
return SyncRunSeverity.ok;
case 'warning':
return SyncRunSeverity.warning;
case 'error':
return SyncRunSeverity.error;
case 'rate_limit':
return SyncRunSeverity.rateLimit;
default:
final String normalized = (error ?? '').toLowerCase();
if (normalized.contains('429') ||
normalized.contains('rate limit') ||
normalized.contains('rate limited')) {
return SyncRunSeverity.rateLimit;
}
if (error != null && error.trim().isNotEmpty) {
return SyncRunSeverity.error;
}
if (inProgress) {
return SyncRunSeverity.warning;
}
return SyncRunSeverity.ok;
}
}
static SyncRunStatus _parseStatus(
String? wire,
String? error,
bool inProgress,
int rowsWritten,
) {
switch (wire) {
case 'success':
return SyncRunStatus.success;
case 'failed':
return SyncRunStatus.failed;
case 'partial':
return SyncRunStatus.partial;
case 'in_progress':
return SyncRunStatus.inProgress;
default:
if (inProgress) {
return SyncRunStatus.inProgress;
}
if (error != null && error.trim().isNotEmpty) {
return rowsWritten > 0 ? SyncRunStatus.partial : SyncRunStatus.failed;
}
return SyncRunStatus.success;
}
}
static String _fallbackSummary({
required String kind,
required int rowsWritten,
required int rowsRemoved,
required String? error,
}) {
if (error != null && error.trim().isNotEmpty && rowsWritten > 0) {
return 'Partial success: $rowsWritten rows written';
}
if (error != null && error.trim().isNotEmpty) {
return 'Run failed';
}
if (kind == 'cleanup') {
return '$rowsRemoved rows removed';
}
if (kind == 'universe') {
return '$rowsWritten assets refreshed';
}
return '$rowsWritten bar rows written';
}
}
class SyncRunLogPage {
const SyncRunLogPage({
required this.runs,
required this.pinned,
required this.nextBefore,
this.config = const MarketHistoryAdminConfig(
archiveEnabled: false,
windowDays: 7,
retentionDays: 7,
syncEnabled: false,
),
});
final List<SyncRunEvent> runs;
final List<SyncRunEvent> pinned;
final DateTime? nextBefore;
final MarketHistoryAdminConfig config;
}