Skip to content

(feat) LVT-197: Add practice matches#50

Draft
jackattack-4 wants to merge 13 commits intomainfrom
feature/practice-matches
Draft

(feat) LVT-197: Add practice matches#50
jackattack-4 wants to merge 13 commits intomainfrom
feature/practice-matches

Conversation

@jackattack-4
Copy link
Copy Markdown
Contributor

  • adds PRACTICE match type
  • adds new setting includePracticeMatches and related endpoints
  • uses includePracticeMatches as a filter in analysis
  • updates match importer to pull practice matches from Nexus

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for importing and optionally including PRACTICE matches in analysis by introducing a new user setting (includePracticeMatches) plus endpoints to read/update it, and applying the filter across analysis queries.

Changes:

  • Add PRACTICE to MatchType, add User.includePracticeMatches with default false.
  • Add manager settings endpoints + settings update support for includePracticeMatches.
  • Update analysis and CSV/picklist queries to optionally include/exclude practice matches; update match importer to pull practice matches from Nexus.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/routes/manager/settings.routes.ts Adds OpenAPI + routing for /practicesource endpoints (but PUT /settings spec now out of sync with handler).
src/routes/manager/scoutershifts.routes.ts Moves /generate route registration (currently makes it unauthenticated).
src/handler/manager/settings/updateSettings.ts Requires and persists includePracticeMatches during settings updates.
src/handler/manager/settings/getPracticeSource.ts New handler to return includePracticeMatches.
src/handler/manager/settings/addPracticeSource.ts New handler to update includePracticeMatches.
src/handler/manager/scoutershifts/generateSchedule.ts Adds “no schedule” 404 handling; widens request type to non-authenticated Request.
src/handler/manager/getTeams.ts Removes a stray console.log.
src/handler/manager/addTournamentMatches.ts Imports practice matches from Nexus and writes them to teamMatchData (currently includes debug logs, early-return behavior, and an import-time invocation).
src/handler/analysis/teamLookUp/getNotes.ts Applies practice-match filtering based on user setting.
src/handler/analysis/teamLookUp/categoryMetrics.ts Applies practice-match filtering based on user setting.
src/handler/analysis/teamLookUp/breakdownMetrics.ts Applies practice-match filtering based on user setting.
src/handler/analysis/picklist/endgamePicklistTeamFast.ts Adds practice-match filtering parameter.
src/handler/analysis/csv/getTeamCSV.ts Applies practice-match filtering based on user setting.
src/handler/analysis/csv/getReportCSV.ts Applies practice-match filtering based on user setting.
src/handler/analysis/coreAnalysis/nonEventMetric.ts Adds SQL condition to exclude PRACTICE matches unless enabled.
src/handler/analysis/coreAnalysis/averageManyFast.ts Applies practice-match filtering based on user setting.
src/handler/analysis/coreAnalysis/arrayAndAverageTeams.ts Removes debug logging; applies practice-match filtering based on user setting.
src/handler/analysis/autoPaths/autoPathsTeam.ts Applies practice-match filtering based on user setting.
prisma/schema.prisma Adds User.includePracticeMatches and MatchType.PRACTICE.
Comments suppressed due to low confidence (1)

src/handler/manager/addTournamentMatches.ts:318

  • This module calls addTournamentMatches("2026cancmp") at import time. That will trigger external API calls and DB writes whenever the module is loaded (including in prod startup/tests), which is a serious side effect. Remove the invocation or move it behind an explicit CLI/script entrypoint.

addTournamentMatches("2026cancmp");


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/routes/manager/scoutershifts.routes.ts
Comment thread src/handler/manager/settings/getPracticeSource.ts Outdated
Comment thread src/handler/manager/settings/getPracticeSource.ts Outdated
Comment thread src/handler/manager/settings/addPracticeSource.ts Outdated
Comment thread src/handler/manager/addTournamentMatches.ts Outdated
Comment thread src/handler/manager/addTournamentMatches.ts Outdated
Comment thread src/handler/manager/addTournamentMatches.ts Outdated
Comment thread src/routes/manager/settings.routes.ts
Comment thread src/handler/manager/settings/updateSettings.ts Outdated
jackattack-4 and others added 6 commits April 11, 2026 11:26
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 77 to +81
};
const ReverseMatchTypeMap: Record<MatchType, number> = {
[MatchType.QUALIFICATION]: 0,
[MatchType.ELIMINATION]: 1,
[MatchType.PRACTICE]: 2,
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReverseMatchTypeMap now supports MatchType.PRACTICE, but the forward MatchTypeMap still only maps 0 and 1. Any code that converts a numeric matchType (e.g. request params) to the Prisma enum will not be able to resolve practice matches (value 2) correctly. Add 2: MatchType.PRACTICE to MatchTypeMap to keep the mappings consistent.

Copilot uses AI. Check for mistakes.
Comment on lines 25 to 29
.object({
teamSource: z.array(z.number()),
tournamentSource: z.array(z.string()),
includePracticeMatches: z.boolean().optional().default(false),
})
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

includePracticeMatches is optional in the API schema, but the Zod parser sets a default of false. If a client updates settings without sending this field, the user’s existing includePracticeMatches value will be overwritten to false unintentionally. Remove the .default(false) and only include includePracticeMatches in the Prisma update when it is provided (or explicitly keep the existing DB value).

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +102
const nexusResponse = await fetch(
`https://frc.nexus/api/v1/event/${tournamentKey}`,
{
method: "GET",
headers: {
"Nexus-Api-Key": process.env.NEXUS_KEY ?? "",
},
},
);

if (!nexusResponse.ok) {
const errorMessage = await nexusResponse.text();
console.error("Error getting live event status:", errorMessage);
} else {
const data = await nexusResponse.json();

for (const match of data.matches) {
if (match.label.startsWith("Practice")) {
const practiceMatchNumber = parseInt(match.label.split(" ")[1]);
if (isNaN(practiceMatchNumber)) {
continue;
}

const matchKey = `${tournamentKey}_pr${practiceMatchNumber}`;
for (let i = 0; i < match.redTeams.length; i++) {
const teamNumber = Number(match.redTeams[i]);
const currMatchKey = `${matchKey}_${i}`;
await prismaClient.teamMatchData.upsert({
where: {
key: currMatchKey,
},
update: {
tournamentKey: tournamentKey,
matchNumber: practiceMatchNumber,
teamNumber: teamNumber,
matchType: "PRACTICE",
},
create: {
key: currMatchKey,
tournamentKey: tournamentKey,
matchNumber: practiceMatchNumber,
teamNumber: teamNumber,
matchType: "PRACTICE",
},
});
}
for (let i = 0; i < match.blueTeams.length; i++) {
const teamNumber = Number(match.blueTeams[i]);
const currMatchKey = `${matchKey}_${i + 3}`;

await prismaClient.teamMatchData.upsert({
where: {
key: currMatchKey,
},
update: {
tournamentKey: tournamentKey,
matchNumber: practiceMatchNumber,
teamNumber: teamNumber,
matchType: "PRACTICE",
},
create: {
key: currMatchKey,
tournamentKey: tournamentKey,
matchNumber: practiceMatchNumber,
teamNumber: teamNumber,
matchType: "PRACTICE",
},
});
}
}
}
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Nexus practice-match import is inside the same outer try as the existing TBA import. If nexusResponse.json() throws or data.matches is missing/unexpected, the function will jump to the catch and skip importing qualification/elimination matches entirely. Wrap the Nexus logic in its own try/catch (and/or validate data before iterating) so failures in Nexus don’t prevent the main TBA match import.

Suggested change
const nexusResponse = await fetch(
`https://frc.nexus/api/v1/event/${tournamentKey}`,
{
method: "GET",
headers: {
"Nexus-Api-Key": process.env.NEXUS_KEY ?? "",
},
},
);
if (!nexusResponse.ok) {
const errorMessage = await nexusResponse.text();
console.error("Error getting live event status:", errorMessage);
} else {
const data = await nexusResponse.json();
for (const match of data.matches) {
if (match.label.startsWith("Practice")) {
const practiceMatchNumber = parseInt(match.label.split(" ")[1]);
if (isNaN(practiceMatchNumber)) {
continue;
}
const matchKey = `${tournamentKey}_pr${practiceMatchNumber}`;
for (let i = 0; i < match.redTeams.length; i++) {
const teamNumber = Number(match.redTeams[i]);
const currMatchKey = `${matchKey}_${i}`;
await prismaClient.teamMatchData.upsert({
where: {
key: currMatchKey,
},
update: {
tournamentKey: tournamentKey,
matchNumber: practiceMatchNumber,
teamNumber: teamNumber,
matchType: "PRACTICE",
},
create: {
key: currMatchKey,
tournamentKey: tournamentKey,
matchNumber: practiceMatchNumber,
teamNumber: teamNumber,
matchType: "PRACTICE",
},
});
}
for (let i = 0; i < match.blueTeams.length; i++) {
const teamNumber = Number(match.blueTeams[i]);
const currMatchKey = `${matchKey}_${i + 3}`;
await prismaClient.teamMatchData.upsert({
where: {
key: currMatchKey,
},
update: {
tournamentKey: tournamentKey,
matchNumber: practiceMatchNumber,
teamNumber: teamNumber,
matchType: "PRACTICE",
},
create: {
key: currMatchKey,
tournamentKey: tournamentKey,
matchNumber: practiceMatchNumber,
teamNumber: teamNumber,
matchType: "PRACTICE",
},
});
}
}
}
try {
const nexusResponse = await fetch(
`https://frc.nexus/api/v1/event/${tournamentKey}`,
{
method: "GET",
headers: {
"Nexus-Api-Key": process.env.NEXUS_KEY ?? "",
},
},
);
if (!nexusResponse.ok) {
const errorMessage = await nexusResponse.text();
console.error("Error getting live event status:", errorMessage);
} else {
const data = await nexusResponse.json();
if (
typeof data !== "object" ||
data === null ||
!("matches" in data) ||
!Array.isArray(data.matches)
) {
console.error("Invalid Nexus event payload:", data);
} else {
for (const match of data.matches) {
if (
typeof match !== "object" ||
match === null ||
typeof match.label !== "string" ||
!Array.isArray(match.redTeams) ||
!Array.isArray(match.blueTeams)
) {
continue;
}
if (match.label.startsWith("Practice")) {
const practiceMatchNumber = parseInt(match.label.split(" ")[1]);
if (isNaN(practiceMatchNumber)) {
continue;
}
const matchKey = `${tournamentKey}_pr${practiceMatchNumber}`;
for (let i = 0; i < match.redTeams.length; i++) {
const teamNumber = Number(match.redTeams[i]);
const currMatchKey = `${matchKey}_${i}`;
await prismaClient.teamMatchData.upsert({
where: {
key: currMatchKey,
},
update: {
tournamentKey: tournamentKey,
matchNumber: practiceMatchNumber,
teamNumber: teamNumber,
matchType: "PRACTICE",
},
create: {
key: currMatchKey,
tournamentKey: tournamentKey,
matchNumber: practiceMatchNumber,
teamNumber: teamNumber,
matchType: "PRACTICE",
},
});
}
for (let i = 0; i < match.blueTeams.length; i++) {
const teamNumber = Number(match.blueTeams[i]);
const currMatchKey = `${matchKey}_${i + 3}`;
await prismaClient.teamMatchData.upsert({
where: {
key: currMatchKey,
},
update: {
tournamentKey: tournamentKey,
matchNumber: practiceMatchNumber,
teamNumber: teamNumber,
matchType: "PRACTICE",
},
create: {
key: currMatchKey,
tournamentKey: tournamentKey,
matchNumber: practiceMatchNumber,
teamNumber: teamNumber,
matchType: "PRACTICE",
},
});
}
}
}
}
}
} catch (error) {
console.error("Error importing Nexus practice matches:", error);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants