Skip to content

Commit

Permalink
Merge pull request #27 from AlecM33/revert-13-savant-cache
Browse files Browse the repository at this point in the history
Revert "Savant metrics cache"
  • Loading branch information
AlecM33 authored Jul 29, 2024
2 parents 781f2f8 + b9f24ff commit cd10400
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 180 deletions.
1 change: 0 additions & 1 deletion config/globals.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ module.exports = {
'other_out'
],
SAVANT_POLLING_INTERVAL: 15000,
SAVANT_MAX_ATTEMPTS: 12,
SLOW_POLL_INTERVAL: 300000,
GAMEDAY_PING_INTERVAL: 10000,
HIGHLIGHTS_PER_MESSAGE: 8,
Expand Down
84 changes: 1 addition & 83 deletions modules/command-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,86 +288,6 @@ module.exports = {
home: (game.teams?.home?.team?.abbreviation || game.teams?.home?.abbreviation || game.gameData?.teams?.home?.abbreviation),
away: (game.teams?.away?.team?.abbreviation || game.teams?.away?.abbreviation || game.gameData?.teams?.away?.abbreviation)
};
},

getWeatherEmoji: (condition) => {
switch (condition) {
case 'Clear':
case 'Sunny':
return '\u2600';
case 'Cloudy':
return '\u2601';
case 'Partly Cloudy':
return '\uD83C\uDF24';
case 'Dome':
case 'Roof Closed':
return '';
case 'Drizzle':
case 'Rain':
return '\uD83C\uDF27';
case 'Snow':
return '\u2744';
case 'Overcast':
return '\uD83C\uDF2B';
default:
return '';
}
},

getScoreString: (liveFeed, currentPlayJSON) => {
const homeScore = currentPlayJSON.result.homeScore;
const awayScore = currentPlayJSON.result.awayScore;
return (currentPlayJSON.about.halfInning === 'top'
? '**' + liveFeed.gameData.teams.away.abbreviation + ' ' + awayScore + '**, ' +
liveFeed.gameData.teams.home.abbreviation + ' ' + homeScore
: liveFeed.gameData.teams.away.abbreviation + ' ' + awayScore + ', **' +
liveFeed.gameData.teams.home.abbreviation + ' ' + homeScore + '**');
},

buildPitchingStatsMarkdown: (pitchingStats, pitchMix, includeExtra = false) => {
let reply = '\n';
if (!pitchingStats) {
reply += 'W-L: -\n' +
'ERA: -.--\n' +
'WHIP: -.--' +
(includeExtra
? '\nK/9: -.--\n' +
'BB/9: -.--\n' +
'H/9: -.--\n' +
'HR/9: -.--\n' +
'Saves/Opps: -/-'
: '');
} else {
reply += 'W-L: ' + pitchingStats.wins + '-' + pitchingStats.losses + '\n' +
'ERA: ' + pitchingStats.era + '\n' +
'WHIP: ' + pitchingStats.whip +
(includeExtra
? '\nK/9: ' + pitchingStats.strikeoutsPer9Inn + '\n' +
'BB/9: ' + pitchingStats.walksPer9Inn + '\n' +
'H/9: ' + pitchingStats.hitsPer9Inn + '\n' +
'HR/9: ' + pitchingStats.homeRunsPer9 + '\n' +
'Saves/Opps: ' + pitchingStats.saves + '/' + pitchingStats.saveOpportunities
: '');
}
reply += '\n**Arsenal:**' + '\n';
if (pitchMix instanceof Error) {
reply += pitchMix.message;
return reply;
}
if (pitchMix && pitchMix.length > 0 && pitchMix[0].length > 0) {
reply += (() => {
let arsenal = '';
for (let i = 0; i < pitchMix[0].length; i ++) {
arsenal += pitchMix[0][i] + ' (' + pitchMix[1][i] + '%)' +
': ' + pitchMix[2][i] + ' mph, ' + pitchMix[3][i] + ' BAA' + '\n';
}
return arsenal;
})();
} else {
reply += 'No data!';
}

return reply;
}
};

Expand Down Expand Up @@ -418,11 +338,9 @@ function parsePitchingStats (people) {
return people?.people[0]?.stats?.find(stat => stat?.group?.displayName === 'pitching')?.splits[0]?.stat;
}

/* This is very hacky. We are building an HTML version of the table in a headless browser, styling
/* This is not the best solution, admittedly. We are building an HTML version of the table in a headless browser, styling
it how we want, and taking a screenshot of that, attaching it to the reply as a .png. Why? Trying to simply reply with ASCII
is subject to formatting issues on phone screens, which rudely break up the characters and make the tables look like gibberish.
TODO: this solution works well, but adds bloat to the codebase by introducing puppeteer as a dependency. I am very much open to better solutions.
*/
async function getScreenshotOfHTMLTables (tables) {
const browser = await puppeteer.launch({
Expand Down
27 changes: 0 additions & 27 deletions modules/gameday-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,32 +38,5 @@ module.exports = {
const linescore = feed.linescore();

return '\n\n**Due up**: ' + linescore.offense?.batter?.fullName + ', ' + linescore.offense?.onDeck?.fullName + ', ' + linescore.offense?.inHole?.fullName;
},

/*
Earlier messages not sent on a delay may have already polled baseball savant and obtained the advanced metrics.
If that's the case, we can avoid making a call to them again.
*/
checkForCachedSavantMetrics: (embed, play) => {
if (play.metricsAvailable && play.isInPlay && play.playId) {
const cachedPlay = globalCache.values.game.savantMetricsCache[play.playId];
if (cachedPlay) {
let description = embed.data?.description;
if (cachedPlay.xba) {
description = description.replaceAll('xBA: Pending...', 'xBA: ' + cachedPlay.xba +
(cachedPlay.is_barrel === 1 ? ' \uD83D\uDFE2 (Barreled)' : ''));
embed.setDescription(description);
}
if (cachedPlay.homeRunBallparks !== undefined) {
description = description.replaceAll('HR/Park: Pending...', 'HR/Park: ' +
cachedPlay.homeRunBallparks + '/30' +
(cachedPlay.homeRunBallparks === 30 ? '\u203C\uFE0F' : ''));
embed.setDescription(description);
}
return true;
}
return false;
}
return false;
}
};
19 changes: 6 additions & 13 deletions modules/gameday.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ function subscribe (bot, liveGame, games) {
globalCache.values.game.lastSocketMessageLength = e.data.length;
if (eventJSON.gameEvents.includes('game_finished') && !globalCache.values.game.finished) {
globalCache.values.game.finished = true;
globalCache.values.game.startReported = false;
LOGGER.info('NOTIFIED OF GAME CONCLUSION: CLOSING...');
ws.close();
await statusPoll(bot, games);
Expand Down Expand Up @@ -186,16 +187,14 @@ async function sendMessage (returnedChannel, embed, messages) {

async function sendDelayedMessage (play, gamePk, channelSubscription, returnedChannel, embed) {
setTimeout(async () => {
const cacheHit = gamedayUtil.checkForCachedSavantMetrics(embed, play);
LOGGER.debug('Sending!');
const message = await returnedChannel.send({
embeds: [embed]
});
if (!cacheHit) {
await maybePopulateAdvancedStatcastMetrics(play, [message], gamePk);
} else {
LOGGER.trace('Savant cache hit for play: ' + play.playId);
}
/* TODO: savant polling will be done for each delayed message individually. Not ideal, but shouldn't be too bad.
In any case, there's an opportunity for non-delayed messages to cache the info for delayed messages.
*/
await maybePopulateAdvancedStatcastMetrics(play, [message], gamePk);
}, channelSubscription.delay * 1000);
}

Expand Down Expand Up @@ -239,7 +238,7 @@ async function pollForSavantData (gamePk, playId, messages, hitDistance) {
LOGGER.debug('Savant: all messages done.');
return;
}
if (attempts < globals.SAVANT_MAX_ATTEMPTS) {
if (attempts < 10) {
LOGGER.trace('Savant: polling for ' + playId + '...');
const gameFeed = await mlbAPIUtil.savantGameFeed(gamePk);
const matchingPlay = gameFeed?.team_away?.find(play => play?.play_id === playId)
Expand Down Expand Up @@ -271,7 +270,6 @@ function processMatchingPlay (matchingPlay, messages, messageTrackers, playId, h
}).then((m) => LOGGER.trace('Edited: ' + m.id)).catch((e) => console.error(e));
if (hitDistance && hitDistance < 300) {
LOGGER.debug('Found xba, done polling for: ' + playId);
globalCache.values.game.savantMetricsCache[playId] = { xba: matchingPlay.xba, is_barrel: matchingPlay.is_barrel };
messageTrackers.find(tracker => tracker.id === messages[i].id).done = true;
}
}
Expand All @@ -288,11 +286,6 @@ function processMatchingPlay (matchingPlay, messages, messageTrackers, playId, h
}).then((m) => LOGGER.trace('Edited: ' + m.id)).catch((e) => console.error(e));
if (matchingPlay.xba) {
LOGGER.debug('Found all metrics: done polling for: ' + playId);
globalCache.values.game.savantMetricsCache[playId] = {
xba: matchingPlay.xba,
homeRunBallparks: matchingPlay.contextMetrics.homeRunBallparks,
is_barrel: matchingPlay.is_barrel
};
messageTrackers.find(tracker => tracker.id === messages[i].id).done = true;
}
}
Expand Down
2 changes: 0 additions & 2 deletions modules/global-cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const values = {
lastReportedPlayDescription: null,
startReported: false,
reportedDescriptions: [],
savantMetricsCache: {},
homeTeamColor: null,
awayTeamColor: null,
finished: false,
Expand All @@ -25,7 +24,6 @@ function resetGameCache () {
values.game.lastReportedPlayDescription = null;
values.game.startReported = false;
values.game.reportedDescriptions = [];
values.game.savantMetricsCache = {};
values.game.homeTeamColor = null;
values.game.awayTeamColor = null;
values.game.finished = false;
Expand Down
92 changes: 87 additions & 5 deletions modules/interaction-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ module.exports = {
name: (hydratedHomeProbable.handedness
? hydratedHomeProbable.handedness + 'HP **'
: '**') + (probables.homeProbableLastName || 'TBD') + '** (' + probables.homeAbbreviation + ')',
value: commandUtil.buildPitchingStatsMarkdown(hydratedHomeProbable.pitchingStats, hydratedHomeProbable.pitchMix),
value: buildPitchingStatsMarkdown(hydratedHomeProbable.pitchingStats, hydratedHomeProbable.pitchMix),
inline: true
})
.addFields({
name: (hydratedAwayProbable.handedness
? hydratedAwayProbable.handedness + 'HP **'
: '**') + (probables.awayProbableLastName || 'TBD') + '** (' + probables.awayAbbreviation + ')',
value: commandUtil.buildPitchingStatsMarkdown(hydratedAwayProbable.pitchingStats, hydratedAwayProbable.pitchMix),
value: buildPitchingStatsMarkdown(hydratedAwayProbable.pitchingStats, hydratedAwayProbable.pitchMix),
inline: true
});
await interaction.followUp({
Expand Down Expand Up @@ -390,7 +390,7 @@ module.exports = {
'## ' + (pitcherInfo.handedness
? pitcherInfo.handedness + 'HP **'
: '**') + (pitcher.fullName || 'TBD') + '** (' + abbreviation + ')' +
commandUtil.buildPitchingStatsMarkdown(pitcherInfo.pitchingStats, pitcherInfo.pitchMix, true))
buildPitchingStatsMarkdown(pitcherInfo.pitchingStats, pitcherInfo.pitchMix, true))
.setColor((halfInning === 'top'
? globalCache.values.game.homeTeamColor
: globalCache.values.game.awayTeamColor)
Expand Down Expand Up @@ -467,7 +467,7 @@ module.exports = {
liveFeed.gameData.teams.home.teamName.toLowerCase().replaceAll(' ', '-') + '/' +
liveFeed.gameData.datetime.officialDate.replaceAll('-', '/') +
'/' + game.gamePk + '/play/' + scoringPlayIndex;
links.push(commandUtil.getScoreString(liveFeed, play) + ' [' + play.result.description.trim() + '](<' + link + '>)\n');
links.push(getScoreString(liveFeed, play) + ' [' + play.result.description.trim() + '](<' + link + '>)\n');
});
// discord limits messages to 2,000 characters. We very well might need a couple messages to link everything.
const messagesNeeded = Math.ceil(liveFeed.liveData.plays.scoringPlays.length / globals.SCORING_PLAYS_PER_MESSAGE);
Expand Down Expand Up @@ -552,11 +552,93 @@ module.exports = {
components: [],
content: weather && Object.keys(weather).length > 0
? 'Weather at ' + currentLiveFeed.gameData.venue.name + ':\n' +
commandUtil.getWeatherEmoji(weather.condition) + ' ' + weather.condition + '\n' +
getWeatherEmoji(weather.condition) + ' ' + weather.condition + '\n' +
'\uD83C\uDF21 ' + weather.temp + '°\n' +
'\uD83C\uDF43 ' + weather.wind
: 'Not available yet - check back an hour or two before game time.'
});
}
}
};

function getWeatherEmoji (condition) {
switch (condition) {
case 'Clear':
case 'Sunny':
return '\u2600';
case 'Cloudy':
return '\u2601';
case 'Partly Cloudy':
return '\uD83C\uDF24';
case 'Dome':
case 'Roof Closed':
return '';
case 'Drizzle':
case 'Rain':
return '\uD83C\uDF27';
case 'Snow':
return '\u2744';
case 'Overcast':
return '\uD83C\uDF2B';
default:
return '';
}
}

function getScoreString (liveFeed, currentPlayJSON) {
const homeScore = currentPlayJSON.result.homeScore;
const awayScore = currentPlayJSON.result.awayScore;
return (currentPlayJSON.about.halfInning === 'top'
? '**' + liveFeed.gameData.teams.away.abbreviation + ' ' + awayScore + '**, ' +
liveFeed.gameData.teams.home.abbreviation + ' ' + homeScore
: liveFeed.gameData.teams.away.abbreviation + ' ' + awayScore + ', **' +
liveFeed.gameData.teams.home.abbreviation + ' ' + homeScore + '**');
}

function buildPitchingStatsMarkdown (pitchingStats, pitchMix, includeExtra = false) {
let reply = '\n';
if (!pitchingStats) {
reply += 'G: -\n' +
'W-L: -\n' +
'ERA: -.--\n' +
'WHIP: -.--' +
(includeExtra
? '\nK/9: -.--\n' +
'BB/9: -.--\n' +
'H/9: -.--\n' +
'HR/9: -.--\n' +
'Saves/Opps: -/-'
: '');
} else {
reply += 'G: ' + pitchingStats.gamesPlayed + '\n' +
'W-L: ' + pitchingStats.wins + '-' + pitchingStats.losses + '\n' +
'ERA: ' + pitchingStats.era + '\n' +
'WHIP: ' + pitchingStats.whip +
(includeExtra
? '\nK/9: ' + pitchingStats.strikeoutsPer9Inn + '\n' +
'BB/9: ' + pitchingStats.walksPer9Inn + '\n' +
'H/9: ' + pitchingStats.hitsPer9Inn + '\n' +
'HR/9: ' + pitchingStats.homeRunsPer9 + '\n' +
'Saves/Opps: ' + pitchingStats.saves + '/' + pitchingStats.saveOpportunities
: '');
}
reply += '\n**Arsenal:**' + '\n';
if (pitchMix instanceof Error) {
reply += pitchMix.message;
return reply;
}
if (pitchMix && pitchMix.length > 0 && pitchMix[0].length > 0) {
reply += (() => {
let arsenal = '';
for (let i = 0; i < pitchMix[0].length; i ++) {
arsenal += pitchMix[0][i] + ' (' + pitchMix[1][i] + '%)' +
': ' + pitchMix[2][i] + ' mph, ' + pitchMix[3][i] + ' BAA' + '\n';
}
return arsenal;
})();
} else {
reply += 'No data!';
}

return reply;
}
2 changes: 1 addition & 1 deletion spec/gameday-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ describe('gameday', () => {
});

describe('#processMatchingPlay', () => {
it('should edit all messages with xBA and HR/Park and mark them as done', async () => {
it('should edit all messages with xBA and mark them as done', async () => {
const mockSetDescription = (description) => {};
const mockEmbed = {
description: 'xBA: Pending...\nHR/Park: Pending...',
Expand Down
Loading

0 comments on commit cd10400

Please sign in to comment.