Codex commited on
Commit
b548c5a
·
1 Parent(s): c93121f

Trigger morning report from Circa release

Browse files
src/config.js CHANGED
@@ -23,6 +23,7 @@ export function getConfig() {
23
  const scanReportChannelId = process.env.SCAN_REPORT_CHANNEL_ID?.trim() || null;
24
  const scanAlertChannelId = process.env.SCAN_ALERT_CHANNEL_ID?.trim() || null;
25
  const scanMorningTime = process.env.SCAN_MORNING_TIME?.trim() || '08:00';
 
26
  const scanTimeZone = process.env.SCAN_TIMEZONE?.trim() || 'America/Chicago';
27
  const scanMinBooks = Number(process.env.SCAN_MIN_BOOKS || 3);
28
  const scanDisagreementThreshold = Number(process.env.SCAN_DISAGREEMENT_THRESHOLD || 0.08);
@@ -104,6 +105,7 @@ export function getConfig() {
104
  scanReportChannelId,
105
  scanAlertChannelId,
106
  scanMorningTime,
 
107
  scanTimeZone,
108
  scanMinBooks,
109
  scanDisagreementThreshold,
 
23
  const scanReportChannelId = process.env.SCAN_REPORT_CHANNEL_ID?.trim() || null;
24
  const scanAlertChannelId = process.env.SCAN_ALERT_CHANNEL_ID?.trim() || null;
25
  const scanMorningTime = process.env.SCAN_MORNING_TIME?.trim() || '08:00';
26
+ const scanMorningFallbackTime = process.env.SCAN_MORNING_FALLBACK_TIME?.trim() || '09:45';
27
  const scanTimeZone = process.env.SCAN_TIMEZONE?.trim() || 'America/Chicago';
28
  const scanMinBooks = Number(process.env.SCAN_MIN_BOOKS || 3);
29
  const scanDisagreementThreshold = Number(process.env.SCAN_DISAGREEMENT_THRESHOLD || 0.08);
 
105
  scanReportChannelId,
106
  scanAlertChannelId,
107
  scanMorningTime,
108
+ scanMorningFallbackTime,
109
  scanTimeZone,
110
  scanMinBooks,
111
  scanDisagreementThreshold,
src/market-scanner.js CHANGED
@@ -3912,6 +3912,7 @@ export class MarketScanner {
3912
  reportChannelId: this.config.scanReportChannelId,
3913
  alertChannelId: this.config.scanAlertChannelId,
3914
  morningTime: this.config.scanMorningTime,
 
3915
  timeZone: this.config.scanTimeZone,
3916
  frequencyMinutes: this.config.scanFrequencyMinutes,
3917
  circaChannelId: this.config.circaChannelId,
@@ -4183,12 +4184,18 @@ export class MarketScanner {
4183
  }
4184
 
4185
  const dateKey = getZonedDateKey(now, this.config.scanTimeZone);
4186
- if (getZonedTime(now, this.config.scanTimeZone) !== this.config.scanMorningTime) {
 
4187
  return null;
4188
  }
4189
 
4190
- const hasReport = await this.store.hasScanReport(dateKey, 'morning');
4191
- if (hasReport) {
 
 
 
 
 
4192
  return null;
4193
  }
4194
 
@@ -4530,6 +4537,9 @@ export class MarketScanner {
4530
 
4531
  await this.store.recordCircaDailyPost(dateKey, snapshot.id, this.config.circaChannelId);
4532
  this.status.lastCircaBoardAt = new Date().toISOString();
 
 
 
4533
  return groupedMarkets;
4534
  } finally {
4535
  this.circaBoardRunning = false;
 
3912
  reportChannelId: this.config.scanReportChannelId,
3913
  alertChannelId: this.config.scanAlertChannelId,
3914
  morningTime: this.config.scanMorningTime,
3915
+ morningFallbackTime: this.config.scanMorningFallbackTime,
3916
  timeZone: this.config.scanTimeZone,
3917
  frequencyMinutes: this.config.scanFrequencyMinutes,
3918
  circaChannelId: this.config.circaChannelId,
 
4184
  }
4185
 
4186
  const dateKey = getZonedDateKey(now, this.config.scanTimeZone);
4187
+ const hasReport = await this.store.hasScanReport(dateKey, 'morning');
4188
+ if (hasReport) {
4189
  return null;
4190
  }
4191
 
4192
+ const currentTime = getZonedTime(now, this.config.scanTimeZone);
4193
+ const fallbackTime = this.config.scanMorningFallbackTime ?? '09:45';
4194
+ const fallbackReached = currentTime >= fallbackTime;
4195
+ const circaPosted = this.config.circaWorkflowEnabled
4196
+ ? Boolean(await this.store.getCircaDailyPost(dateKey))
4197
+ : false;
4198
+ if (!circaPosted && !fallbackReached) {
4199
  return null;
4200
  }
4201
 
 
4537
 
4538
  await this.store.recordCircaDailyPost(dateKey, snapshot.id, this.config.circaChannelId);
4539
  this.status.lastCircaBoardAt = new Date().toISOString();
4540
+ if (this.config.oddsWorkflowEnabled) {
4541
+ await this.maybeRunMorningReport(new Date());
4542
+ }
4543
  return groupedMarkets;
4544
  } finally {
4545
  this.circaBoardRunning = false;
test/market-scanner.test.js CHANGED
@@ -1434,6 +1434,90 @@ test('home run odds lookup accepts expanded sportsbook books', async () => {
1434
  }
1435
  });
1436
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1437
  test('direct player odds lookups reuse cached Odds API entries within the ttl window', async () => {
1438
  const scanner = new MarketScanner({
1439
  client: {},
 
1434
  }
1435
  });
1436
 
1437
+ test('morning report waits for Circa post before fallback time', async () => {
1438
+ const scanner = new MarketScanner({
1439
+ client: {},
1440
+ store: {
1441
+ hasScanReport: async () => false,
1442
+ getCircaDailyPost: async () => null,
1443
+ },
1444
+ embeds: {},
1445
+ config: {
1446
+ enabled: true,
1447
+ oddsWorkflowEnabled: true,
1448
+ circaWorkflowEnabled: true,
1449
+ scanTimeZone: 'America/Chicago',
1450
+ scanMorningFallbackTime: '09:45',
1451
+ },
1452
+ });
1453
+
1454
+ let called = false;
1455
+ scanner.runMorningReport = async () => {
1456
+ called = true;
1457
+ return { ok: true };
1458
+ };
1459
+
1460
+ const result = await scanner.maybeRunMorningReport(new Date('2026-04-07T14:30:00Z'));
1461
+ assert.equal(result, null);
1462
+ assert.equal(called, false);
1463
+ });
1464
+
1465
+ test('morning report runs immediately after same-day Circa post', async () => {
1466
+ const scanner = new MarketScanner({
1467
+ client: {},
1468
+ store: {
1469
+ hasScanReport: async () => false,
1470
+ getCircaDailyPost: async () => ({ postDate: '2026-04-07', snapshotId: 123 }),
1471
+ },
1472
+ embeds: {},
1473
+ config: {
1474
+ enabled: true,
1475
+ oddsWorkflowEnabled: true,
1476
+ circaWorkflowEnabled: true,
1477
+ scanTimeZone: 'America/Chicago',
1478
+ scanMorningFallbackTime: '09:45',
1479
+ },
1480
+ });
1481
+
1482
+ let invokedDateKey = null;
1483
+ scanner.runMorningReport = async (dateKey) => {
1484
+ invokedDateKey = dateKey;
1485
+ return { ok: true };
1486
+ };
1487
+
1488
+ const result = await scanner.maybeRunMorningReport(new Date('2026-04-07T14:30:00Z'));
1489
+ assert.deepEqual(result, { ok: true });
1490
+ assert.equal(invokedDateKey, '2026-04-07');
1491
+ });
1492
+
1493
+ test('morning report falls back at 9:45 America/Chicago without Circa post', async () => {
1494
+ const scanner = new MarketScanner({
1495
+ client: {},
1496
+ store: {
1497
+ hasScanReport: async () => false,
1498
+ getCircaDailyPost: async () => null,
1499
+ },
1500
+ embeds: {},
1501
+ config: {
1502
+ enabled: true,
1503
+ oddsWorkflowEnabled: true,
1504
+ circaWorkflowEnabled: true,
1505
+ scanTimeZone: 'America/Chicago',
1506
+ scanMorningFallbackTime: '09:45',
1507
+ },
1508
+ });
1509
+
1510
+ let invokedDateKey = null;
1511
+ scanner.runMorningReport = async (dateKey) => {
1512
+ invokedDateKey = dateKey;
1513
+ return { ok: true };
1514
+ };
1515
+
1516
+ const result = await scanner.maybeRunMorningReport(new Date('2026-04-07T14:45:00Z'));
1517
+ assert.deepEqual(result, { ok: true });
1518
+ assert.equal(invokedDateKey, '2026-04-07');
1519
+ });
1520
+
1521
  test('direct player odds lookups reuse cached Odds API entries within the ttl window', async () => {
1522
  const scanner = new MarketScanner({
1523
  client: {},