import 'package:postgres/postgres.dart'; import '../alpaca/alpaca_assets_client.dart'; import '../alpaca/alpaca_models.dart'; import 'sync_run_recorder.dart'; import 'tradable_assets_db.dart'; /// Outcome of a single [TradableAssetsSync.runOnce] invocation. /// /// On the happy path [error] is `null` and [rowsWritten] is the number of /// asset rows upserted. On a failure path [error] holds the upstream /// message and [rowsWritten] is `0` — the exception itself is never /// rethrown to callers (the scheduler in §5 must keep going). class TradableAssetsSyncResult { TradableAssetsSyncResult({ required this.rowsWritten, required this.startedAt, required this.finishedAt, this.error, }); final int rowsWritten; final DateTime startedAt; final DateTime finishedAt; final String? error; bool get succeeded => error == null; } /// Daily refresh of the `tradable_assets` cache from Alpaca. /// /// One pass: /// 1. [SyncRunRecorder] inserts a `market_data_sync_runs` row with /// `kind='universe'`. /// 2. Pull `/v2/assets?status=active&asset_class=us_equity`. /// 3. Upsert into `tradable_assets`, marking missing symbols inactive. /// 4. Recorder closes the row with `finished_at` + `rows_written` (or /// `error` on failure). /// /// The scheduler in §5 runs this once per `MARKET_UNIVERSE_REFRESH_HOURS`. class TradableAssetsSync { TradableAssetsSync({ required AlpacaAssetsClient assetsClient, required TradableAssetsDb assetsDb, required Connection connection, }) : _assetsClient = assetsClient, _assetsDb = assetsDb, _recorder = SyncRunRecorder(connection); final AlpacaAssetsClient _assetsClient; final TradableAssetsDb _assetsDb; final SyncRunRecorder _recorder; static const String kind = 'universe'; Future runOnce({DateTime? now}) async { final DateTime started = (now ?? DateTime.now()).toUtc(); final SyncRunOutcome outcome = await _recorder.record( kind, () async { final List assets = await _assetsClient.listActiveTradable(); await _assetsDb.upsertAll(assets, now: started); return SyncRunCounts(rowsWritten: assets.length); }, now: started, ); return TradableAssetsSyncResult( rowsWritten: outcome.rowsWritten, startedAt: outcome.startedAt, finishedAt: outcome.finishedAt, error: outcome.error, ); } }