From 72a4e75cb2a7b8baceb7ba1d3337b97f2e916f54 Mon Sep 17 00:00:00 2001 From: Icheng Lin Date: Sat, 27 Jan 2024 14:11:27 -0600 Subject: [PATCH] Added Season Events Preprocess --- commands/command_skills.ts | 4 +- events/event_ready.ts | 8 +++ objects/preprocess.ts | 53 +++++++++++++++ objects/robotevent.ts | 130 +++++++++++++++++++++++++++++-------- utilities/async_delay.ts | 7 ++ 5 files changed, 173 insertions(+), 29 deletions(-) create mode 100644 objects/preprocess.ts create mode 100644 utilities/async_delay.ts diff --git a/commands/command_skills.ts b/commands/command_skills.ts index eb54d81..af41faf 100644 --- a/commands/command_skills.ts +++ b/commands/command_skills.ts @@ -1,6 +1,6 @@ import { AttachmentBuilder, ChatInputCommandInteraction, EmbedBuilder, InteractionEditReplyOptions, MessagePayload, SlashCommandBuilder } from "discord.js"; import * as NodeChartJS from "chartjs-node-canvas"; -import RobotEvent, { SeasonData } from "../objects/robotevent"; +import RobotEvent, { SeasonDataSimplified } from "../objects/robotevent"; import VerificationCommand from "../templates/template_command"; import VerificationDisplay from "../utilities/display"; @@ -43,7 +43,7 @@ export default class SkillsCommand extends VerificationCommand { await command_interaction.editReply({embeds: [invalid_embed]}); return; } - const team_season_data: SeasonData[] = []; + const team_season_data: SeasonDataSimplified[] = []; for (let skill_index = 0; skill_index < team_skills.length; skill_index++) { const skill_data = team_skills[skill_index]; // if driver score and programming score is both 0, won't exist in season skill diff --git a/events/event_ready.ts b/events/event_ready.ts index c54690e..5be5d61 100644 --- a/events/event_ready.ts +++ b/events/event_ready.ts @@ -1,6 +1,7 @@ import { Client } from "discord.js"; import Logger from "../objects/logger"; import VerificationEvent from "../templates/template_event"; +import PreProcess from "../objects/preprocess"; export default class ReadyEvent extends VerificationEvent { @@ -12,6 +13,13 @@ export default class ReadyEvent extends VerificationEvent { public async event_trigger(client: Client): Promise { Logger.send_log("Verification bot is now online."); + // repeat preprocess + await this.preprocess_fetch(); + setInterval(this.preprocess_fetch, parseInt(process.env.REDIS_CACHE_LIFESPAN as string) * (1E3)); + } + + public async preprocess_fetch(): Promise { + await PreProcess.preprocess_event_season(); } } \ No newline at end of file diff --git a/objects/preprocess.ts b/objects/preprocess.ts new file mode 100644 index 0000000..a0f2f90 --- /dev/null +++ b/objects/preprocess.ts @@ -0,0 +1,53 @@ +import AsyncDelay from "../utilities/async_delay"; +import VerificationCache from "./cache"; +import Logger from "./logger"; +import RobotEvent, { EventData, SeasonData } from "./robotevent"; + +export default class PreProcess { + + private static processed_season_data: SeasonDataEvents[] = []; + + // only process seasons with the following programs + private static readonly SEASON_PROGRAM_CODES: string[] = ["VRC", "VEXU"]; + + public static get_event_season(event_id: number): any { + + } + + public static async preprocess_event_season(): Promise { + const seasons_list = (await RobotEvent.get_seasons()).filter(season_data => PreProcess.SEASON_PROGRAM_CODES.includes(season_data.season_program.program_code)); + const seasons_events = [] as EventData[][]; + for (const season_data of seasons_list) { + const cache_exist = (await VerificationCache.cache_get(`ROBOTEVENT_SEASONEVENTS_${season_data.season_id}`)) !== undefined; + seasons_events.push(await RobotEvent.get_season_events(season_data.season_id, season_data.season_date.date_end)); + if (!cache_exist) await AsyncDelay.async_delay(10 * 1E3); + } + const seasons_result = seasons_list.map((season_data, season_index) => { + const season_events_sorted = seasons_events[season_index].map(season_data => season_data.event_id).sort(); + return { + season_data: season_data, + season_events: { + id_all: season_events_sorted, + id_min: ((season_events_sorted.length > 0) ? season_events_sorted[0] : null), + id_max: ((season_events_sorted.length > 0) ? season_events_sorted[season_events_sorted.length - 1] : null) + } + } as SeasonDataEvents; + }); + PreProcess.processed_season_data = seasons_result; + // log + Logger.send_log([ + "Completed Event Season Preprocess:", + ...seasons_result.map(result_data => `[${result_data.season_data.season_name}] Events: ${result_data.season_events.id_min}~${result_data.season_events.id_max}`) + ].join("\n")); + } + +} + +interface SeasonDataEvents { + season_data: SeasonData, + season_events: { + id_all: number[], + id_min: number, + id_max: number + } +} \ No newline at end of file diff --git a/objects/robotevent.ts b/objects/robotevent.ts index 103efb3..a70804b 100644 --- a/objects/robotevent.ts +++ b/objects/robotevent.ts @@ -12,7 +12,7 @@ export default class RobotEvent { const api_cache = await VerificationCache.cache_get(`ROBOTEVENT_TEAMBYNUMBER_${team_number}`); if (api_cache !== undefined) return api_cache.cache_data; // cache not exist - const api_response = await this.fetch_retries(`https://www.robotevents.com/api/v2/teams?number=${team_number}&per_page=1000`, 5).then(response => response.json()) as any; + const api_response = await this.fetch_retries(`https://www.robotevents.com/api/v2/teams?number=${team_number}&per_page=250`, 5).then(response => response.json()) as any; if (api_response.data.length <= 0) return undefined; const grade_priority = ["College", "High School", "Middle School", "Elementary School"]; const api_team = api_response.data.sort((team_a: any, team_b: any) => grade_priority.indexOf(team_b.grade) - grade_priority.indexOf(team_a.grade))[api_response.data.length - 1]; @@ -34,7 +34,7 @@ export default class RobotEvent { const api_cache = await VerificationCache.cache_get(`ROBOTEVENT_TEAMAWARDS_${team_id}`); if (api_cache !== undefined) return api_cache.cache_data; // cache not exist - const result = (await this.get_response(`teams/${team_id}/awards?per_page=1000`)).map((award_data: any) => ({ + const result = (await this.get_response(`teams/${team_id}/awards?per_page=250`)).map((award_data: any) => ({ award_id: award_data.id, award_name: award_data.title.match(/^([^\(]+)\s\(/)[1], award_event: { @@ -51,7 +51,7 @@ export default class RobotEvent { const api_cache = await VerificationCache.cache_get(`ROBOTEVENT_TEAMSKILLS_${team_id}`); if (api_cache !== undefined) return api_cache.cache_data; // cache not exist - const result = (await this.get_response(`teams/${team_id}/skills?per_page=1000`)).map((skill_data: any) => ({ + const result = (await this.get_response(`teams/${team_id}/skills?per_page=250`)).map((skill_data: any) => ({ skill_id: skill_data.id, skill_type: skill_data.type, skill_score: skill_data.score, @@ -70,6 +70,65 @@ export default class RobotEvent { return result; } + public static async get_seasons(): Promise { + // load cache + const api_cache = await VerificationCache.cache_get(`ROBOTEVENT_SEASONDATA_ALL`); + if (api_cache !== undefined) return api_cache.cache_data; + // cache not exist + const result = (await this.get_response(`seasons?per_page=250`)).map((season_data: any) => ({ + season_id: season_data.id, + season_name: season_data.name, + season_program: { + program_id: season_data.program.id, + program_name: season_data.program.name, + program_code: season_data.program.code, + }, + season_date: { + date_begin: season_data.start, + date_end: season_data.end + }, + season_year: { + year_begin: season_data.years_start, + year_end: season_data.years_end + } + } as SeasonData)); + await VerificationCache.cache_set(`ROBOTEVENT_SEASONDATA_ALL`, result); + return result; + } + + public static async get_season_events(season_id: number, season_date_end: number): Promise { + // load cache + const api_cache = await VerificationCache.cache_get(`ROBOTEVENT_SEASONEVENTS_${season_id}`); + if (api_cache !== undefined) return api_cache.cache_data; + // cache not exist + const result = (await this.get_response(`seasons/${season_id}/events?per_page=250`)).map((event_data: any) => ({ + event_id: event_data.id, + event_sku: event_data.sku, + event_name: event_data.name, + event_date: { + date_begin: null as unknown, // disabled as unnecessary/not-used + date_end: null as unknown // disabled as unnecessary/not-used + }, + event_program: { + program_id: event_data.program.id, + program_name: event_data.program.name + }, + event_location: { + address_lines: [event_data.location.address_1, event_data.location.address_2].filter(address_line => address_line != null), + address_city: event_data.location.city, + address_state: event_data.location.region, + address_postcode: event_data.location.postcode, + address_country: event_data.location.country, + address_latitude: event_data.location.coordinates.lat, + address_longitude: event_data.location.coordinates.lon + } + } as EventData)); + // cache for 100 years if season ended (older than 6 months) + const season_ended = (Date.now() - season_date_end) > (6 * 2.592E9); + await VerificationCache.cache_set(`ROBOTEVENT_SEASONEVENTS_${season_id}`, result, (season_ended ? (100 * 3.1536E10) : undefined)); + return result; + } + public static async get_season_skills(season_id: number, grade_level: string): Promise { // load cache const api_cache = await VerificationCache.cache_get(`ROBOTEVENT_SEASONSKILLS_${season_id}`); @@ -107,7 +166,7 @@ export default class RobotEvent { const api_cache = await VerificationCache.cache_get(`ROBOTEVENT_EVENTTEAMS_${event_id}`); if (api_cache !== undefined) return api_cache.cache_data; // cache not exist - const result = (await this.get_response(`events/${event_id}/teams?per_page=1000`)).map((team_data: any) => ({ + const result = (await this.get_response(`events/${event_id}/teams?per_page=250`)).map((team_data: any) => ({ team_id: team_data.id, team_number: team_data.number, team_name: team_data.team_name, @@ -125,7 +184,7 @@ export default class RobotEvent { const api_cache = await VerificationCache.cache_get(`ROBOTEVENT_GUILDEVENTS_${guild_id}`); if (api_cache !== undefined) return api_cache.cache_data; // cache not exist - const result = (await this.get_response(`events?${team_ids.map(team_id => `team[]=${team_id}`).join("&")}&start=${event_after.toISOString()}&per_page=1000`)).map((event_data: any) => { + const result = (await this.get_response(`events?${team_ids.map(team_id => `team[]=${team_id}`).join("&")}&start=${event_after.toISOString()}&per_page=250`)).map((event_data: any) => { const event_timezone = VerificationTimezone.timezone_get(event_data.location.coordinates.lat, event_data.location.coordinates.lon); return { event_id: event_data.id, @@ -209,18 +268,45 @@ export interface EventData { date_begin: number, date_end: number }, - event_program: { - program_id: number, - program_name: string - }, + event_program: ProgramData, event_location: LocationData } -export interface SeasonData { +export interface SeasonDataSimplified { season_id: number, season_name: string } +export interface SeasonData { + season_id: number, + season_name: string, + season_program: ProgramData, + season_date: { + date_begin: number, + date_end: number + }, + season_year: { + year_begin: number + year_end: number + } +} + +export interface SeasonSkills { + skills_rank: number, + skills_entries: number, + skills_team: TeamData, + skills_score: { + // driver + driver_score: number, + driver_time_stop: number, + driver_score_date: number, + // programming + programming_score: number, + programming_time_stop: number, + programming_score_date: number + } +} + export interface LocationData { address_lines: string[], address_city: string, @@ -231,6 +317,12 @@ export interface LocationData { address_longitude: number } +export interface ProgramData { + program_id: number, + program_name: string, + program_code: string +} + export interface TeamAward { award_id: number, award_name: string, @@ -244,21 +336,5 @@ export interface TeamSkills { skill_rank: number, skill_attempts: number, skill_event: EventDataSimplified, - skill_season: SeasonData -} - -export interface SeasonSkills { - skills_rank: number, - skills_entries: number, - skills_team: TeamData, - skills_score: { - // driver - driver_score: number, - driver_time_stop: number, - driver_score_date: number, - // programming - programming_score: number, - programming_time_stop: number, - programming_score_date: number - } + skill_season: SeasonDataSimplified } \ No newline at end of file diff --git a/utilities/async_delay.ts b/utilities/async_delay.ts new file mode 100644 index 0000000..c48982a --- /dev/null +++ b/utilities/async_delay.ts @@ -0,0 +1,7 @@ +export default class AsyncDelay { + + public static async async_delay(await_milliseconds: number): Promise { + await new Promise((resolve, reject) => setTimeout(resolve, await_milliseconds)); + } + +} \ No newline at end of file