Codex commited on
Commit ·
b548c5a
1
Parent(s): c93121f
Trigger morning report from Circa release
Browse files- src/config.js +2 -0
- src/market-scanner.js +13 -3
- test/market-scanner.test.js +84 -0
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 |
-
|
|
|
|
| 4187 |
return null;
|
| 4188 |
}
|
| 4189 |
|
| 4190 |
-
const
|
| 4191 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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: {},
|