From d219d96540813a4e36d4207beb9f3cf85f348011 Mon Sep 17 00:00:00 2001 From: JackAttack-365 <142643773+jackattack-4@users.noreply.github.com> Date: Mon, 5 Jan 2026 14:44:51 -0800 Subject: [PATCH 1/6] add states for team not existing and team not having data --- .../analysis/teamLookUp/breakdownMetrics.ts | 86 ++++--- .../analysis/teamLookUp/categoryMetrics.ts | 85 ++++--- src/handler/analysis/teamLookUp/getNotes.ts | 214 ++++++++++-------- 3 files changed, 228 insertions(+), 157 deletions(-) diff --git a/src/handler/analysis/teamLookUp/breakdownMetrics.ts b/src/handler/analysis/teamLookUp/breakdownMetrics.ts index ab2a371f..29feeb3c 100644 --- a/src/handler/analysis/teamLookUp/breakdownMetrics.ts +++ b/src/handler/analysis/teamLookUp/breakdownMetrics.ts @@ -1,41 +1,61 @@ import z from "zod"; +import prismaClient from "../../../prismaClient.js"; import { nonEventMetric } from "../coreAnalysis/nonEventMetric.js"; import { - lowercaseToBreakdown, - MetricsBreakdown, + lowercaseToBreakdown, + MetricsBreakdown, } from "../analysisConstants.js"; import { createAnalysisHandler } from "../analysisHandler.js"; + export const breakdownMetrics = createAnalysisHandler({ - params: { - params: z.object({ - team: z.preprocess((x) => Number(x), z.number()), - }), - }, - usesDataSource: true, - shouldCache: true, - createKey: ({ params }) => { - return { - key: ["breakdownMetrics", params.team.toString()], - teamDependencies: [params.team], - tournamentDependencies: [], - }; - }, - calculateAnalysis: async ({ params }, ctx) => { - const result = {}; - for (const [key, metric] of Object.entries(lowercaseToBreakdown)) { - const data = await nonEventMetric(ctx.user, { - team: params.team, - metric: MetricsBreakdown[metric as keyof typeof MetricsBreakdown], - }); - - const valid = Object.values(data).some((val) => Boolean(val)); - - if (valid) { - result[key] = data; - } - } - - return result; - }, + params: { + params: z.object({ + team: z.preprocess((x) => Number(x), z.number()), + }), + }, + usesDataSource: true, + shouldCache: true, + createKey: ({ params }) => { + return { + key: ["breakdownMetrics", params.team.toString()], + teamDependencies: [params.team], + tournamentDependencies: [], + }; + }, + calculateAnalysis: async ({ params }, ctx) => { + const teamRow = await prismaClient.team.findUnique({ + where: { number: params.team }, + select: { number: true }, + }); + + if (!teamRow) { + return { error: "TEAM_DOES_NOT_EXIST" }; + } + + + const result: Record = {}; + for (const [key, metric] of Object.entries(lowercaseToBreakdown)) { + const data = await nonEventMetric(ctx.user, { + team: params.team, + metric: MetricsBreakdown[metric as keyof typeof MetricsBreakdown], + }); + + + const valid = Object.values(data).some((val) => Boolean(val)); + + + if (valid) { + result[key] = data; + } + } + + + if (Object.keys(result).length === 0) { + return { error: "NO_DATA_FOR_TEAM" }; + } + + + return result; + }, }); diff --git a/src/handler/analysis/teamLookUp/categoryMetrics.ts b/src/handler/analysis/teamLookUp/categoryMetrics.ts index 802f9c3b..3dd34f41 100644 --- a/src/handler/analysis/teamLookUp/categoryMetrics.ts +++ b/src/handler/analysis/teamLookUp/categoryMetrics.ts @@ -1,36 +1,63 @@ import z from "zod"; +import prismaClient from "../../../prismaClient.js"; import { metricsCategory, metricToName } from "../analysisConstants.js"; import { averageManyFast } from "../coreAnalysis/averageManyFast.js"; import { createAnalysisHandler } from "../analysisHandler.js"; + export const categoryMetrics = createAnalysisHandler({ - params: { - params: z.object({ - team: z.preprocess((x) => Number(x), z.number()), - }), - }, - usesDataSource: true, - shouldCache: true, - createKey: ({ params }) => { - return { - key: ["categoryMetrics", params.team.toString()], - teamDependencies: [params.team], - tournamentDependencies: [], - }; - }, - calculateAnalysis: async ({ params }, ctx) => { - const result = {}; - - //update if statments in arrayAndAverage if the metric needs to look at scoutReport instead of events table - const data = await averageManyFast(ctx.user, { - teams: [params.team], - metrics: metricsCategory, - }); - - for (const metric of metricsCategory) { - result[metricToName[metric]] = data[metric][params.team]; - } - - return result; - }, + params: { + params: z.object({ + team: z.preprocess((x) => Number(x), z.number()), + }), + }, + + + usesDataSource: true, + shouldCache: true, + createKey: ({ params }) => { + return { + key: ["categoryMetrics", params.team.toString()], + teamDependencies: [params.team], + tournamentDependencies: [], + }; + }, + calculateAnalysis: async ({ params }, ctx) => { + const teamRow = await prismaClient.team.findUnique({ + where: { number: params.team }, + select: { number: true }, + }); + + + if (!teamRow) { + return { error: "TEAM_DOES_NOT_EXIST" }; + } + + + const result: Record = {}; + + + const data = await averageManyFast(ctx.user, { + teams: [params.team], + metrics: metricsCategory, + }); + + + let hasAnyData = false; + for (const metric of metricsCategory) { + const value = data[metric][params.team]; + result[metricToName[metric]] = value; + if (value !== null && value !== undefined && value !== 0) { + hasAnyData = true; + } + } + + + if (!hasAnyData) { + return { error: "NO_DATA_FOR_TEAM" }; + } + + + return result; + }, }); diff --git a/src/handler/analysis/teamLookUp/getNotes.ts b/src/handler/analysis/teamLookUp/getNotes.ts index 02e7ddf8..60257d6f 100644 --- a/src/handler/analysis/teamLookUp/getNotes.ts +++ b/src/handler/analysis/teamLookUp/getNotes.ts @@ -1,103 +1,127 @@ import prismaClient from "../../../prismaClient.js"; import z from "zod"; import { - dataSourceRuleSchema, - dataSourceRuleToPrismaFilter, + dataSourceRuleSchema, + dataSourceRuleToPrismaFilter, } from "../dataSourceRule.js"; import { createAnalysisHandler } from "../analysisHandler.js"; +import { error } from "console"; + export const getNotes = createAnalysisHandler({ - params: { - params: z.object({ - team: z.preprocess((x) => Number(x), z.number()), - }), - }, - usesDataSource: true, - shouldCache: true, - createKey: ({ params }) => { - return { - key: ["getNotes", params.team.toString()], - teamDependencies: [params.team], - tournamentDependencies: [], - }; - }, - calculateAnalysis: async ({ params }, ctx) => { - let notesAndMatches: { - notes: string; - match: string; - tounramentName: string; // Typo for backwards compatibility - sourceTeam: number; - scouterName?: string; - }[]; - - // Set up filters to decrease server load - const sourceTnmtFilter = dataSourceRuleToPrismaFilter( - dataSourceRuleSchema(z.string()).parse(ctx.user.tournamentSourceRule), - ); - const sourceTeamFilter = dataSourceRuleToPrismaFilter( - dataSourceRuleSchema(z.number()).parse(ctx.user.teamSourceRule), - ); - - const noteData = await prismaClient.scoutReport.findMany({ - where: { - teamMatchData: { - teamNumber: params.team, - tournamentKey: sourceTnmtFilter, - }, - scouter: { - sourceTeamNumber: sourceTeamFilter, - }, - notes: { - not: "", - }, - }, - select: { - notes: true, - teamMatchKey: true, - teamMatchData: { - select: { - tournament: { - select: { - name: true, - }, - }, - }, - }, - scouter: { - select: { - sourceTeamNumber: true, - name: Boolean(ctx.user.teamNumber), - }, - }, - }, - orderBy: [ - // Ordered by most recent first - { teamMatchData: { tournament: { date: "desc" } } }, - { teamMatchData: { matchType: "desc" } }, - { teamMatchData: { matchNumber: "desc" } }, - ], - }); - - if (Boolean(ctx.user.teamNumber)) { - notesAndMatches = noteData.map((report) => ({ - notes: report.notes, - match: report.teamMatchKey, - tounramentName: report.teamMatchData.tournament.name, - sourceTeam: report.scouter.sourceTeamNumber, - scouterName: - report.scouter.sourceTeamNumber === ctx.user.teamNumber - ? report.scouter.name - : undefined, - })); - } else { - notesAndMatches = noteData.map((report) => ({ - notes: report.notes, - match: report.teamMatchKey, - tounramentName: report.teamMatchData.tournament.name, - sourceTeam: report.scouter.sourceTeamNumber, - })); - } - - return notesAndMatches; - }, + params: { + params: z.object({ + team: z.preprocess((x) => Number(x), z.number()), + }), + }, + usesDataSource: true, + shouldCache: true, + createKey: ({ params }) => { + return { + key: ["getNotes", params.team.toString()], + teamDependencies: [params.team], + tournamentDependencies: [], + }; + }, + calculateAnalysis: async ({ params }, ctx) => { + const teamRow = await prismaClient.team.findUnique({ + where: { number: params.team }, + select: { number: true }, + }); + + if (!teamRow) { + return { error: "TEAM_DOES_NOT_EXIST" }; + } + + + let notesAndMatches: { + notes: string; + match: string; + tounramentName: string; // Typo for backwards compatibility + sourceTeam: number; + scouterName?: string; + }[]; + + + const sourceTnmtFilter = dataSourceRuleToPrismaFilter( + dataSourceRuleSchema(z.string()).parse(ctx.user.tournamentSourceRule), + ); + const sourceTeamFilter = dataSourceRuleToPrismaFilter( + dataSourceRuleSchema(z.number()).parse(ctx.user.teamSourceRule), + ); + + + const noteData = await prismaClient.scoutReport.findMany({ + where: { + teamMatchData: { + teamNumber: params.team, + tournamentKey: sourceTnmtFilter, + }, + scouter: { + sourceTeamNumber: sourceTeamFilter, + }, + notes: { + not: "", + }, + }, + select: { + notes: true, + teamMatchKey: true, + teamMatchData: { + select: { + tournament: { + select: { + name: true, + }, + }, + }, + }, + scouter: { + select: { + sourceTeamNumber: true, + name: Boolean(ctx.user.teamNumber), + }, + }, + }, + orderBy: [ + { teamMatchData: { tournament: { date: "desc" } } }, + { teamMatchData: { matchType: "desc" } }, + { teamMatchData: { matchNumber: "desc" } }, + ], + }); + + + if (Boolean(ctx.user.teamNumber)) { + notesAndMatches = noteData.map((report) => ({ + notes: report.notes, + match: report.teamMatchKey, + tounramentName: report.teamMatchData.tournament.name, + sourceTeam: report.scouter.sourceTeamNumber, + scouterName: + report.scouter.sourceTeamNumber === ctx.user.teamNumber + ? report.scouter.name + : undefined, + })); + } else { + notesAndMatches = noteData.map((report) => ({ + notes: report.notes, + match: report.teamMatchKey, + tounramentName: report.teamMatchData.tournament.name, + sourceTeam: report.scouter.sourceTeamNumber, + })); + } + + + if (noteData.length === 0) { + return { error: "NO_DATA_FOR_TEAM" }; + } + + + return notesAndMatches; + }, }); + + + + + From da5f87b2656b24a7912900d6c0794f2f350d721f Mon Sep 17 00:00:00 2001 From: JackAttack-365 <142643773+jackattack-4@users.noreply.github.com> Date: Mon, 5 Jan 2026 14:48:37 -0800 Subject: [PATCH 2/6] appease ESLint --- src/handler/analysis/teamLookUp/getNotes.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/handler/analysis/teamLookUp/getNotes.ts b/src/handler/analysis/teamLookUp/getNotes.ts index 60257d6f..bed2c69c 100644 --- a/src/handler/analysis/teamLookUp/getNotes.ts +++ b/src/handler/analysis/teamLookUp/getNotes.ts @@ -5,7 +5,6 @@ import { dataSourceRuleToPrismaFilter, } from "../dataSourceRule.js"; import { createAnalysisHandler } from "../analysisHandler.js"; -import { error } from "console"; export const getNotes = createAnalysisHandler({ @@ -119,9 +118,4 @@ export const getNotes = createAnalysisHandler({ return notesAndMatches; }, -}); - - - - - +}); \ No newline at end of file From 49784c140b9f9b87786342d9b6f86e2f9205cbb4 Mon Sep 17 00:00:00 2001 From: JackAttack-365 <142643773+jackattack-4@users.noreply.github.com> Date: Mon, 5 Jan 2026 14:59:07 -0800 Subject: [PATCH 3/6] check for number of scoutreports instead of looking through the output --- .../analysis/teamLookUp/breakdownMetrics.ts | 10 ++++++-- .../analysis/teamLookUp/categoryMetrics.ts | 25 ++++++++++--------- src/handler/analysis/teamLookUp/getNotes.ts | 9 ++++++- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/handler/analysis/teamLookUp/breakdownMetrics.ts b/src/handler/analysis/teamLookUp/breakdownMetrics.ts index 29feeb3c..0c8a3fc9 100644 --- a/src/handler/analysis/teamLookUp/breakdownMetrics.ts +++ b/src/handler/analysis/teamLookUp/breakdownMetrics.ts @@ -50,8 +50,14 @@ export const breakdownMetrics = createAnalysisHandler({ } } - - if (Object.keys(result).length === 0) { +const reportCount = await prismaClient.scoutReport.count({ + where: { + teamMatchData: { + teamNumber: params.team, + }, + }, + }); + if (reportCount === 0) { return { error: "NO_DATA_FOR_TEAM" }; } diff --git a/src/handler/analysis/teamLookUp/categoryMetrics.ts b/src/handler/analysis/teamLookUp/categoryMetrics.ts index 3dd34f41..415ba64c 100644 --- a/src/handler/analysis/teamLookUp/categoryMetrics.ts +++ b/src/handler/analysis/teamLookUp/categoryMetrics.ts @@ -3,6 +3,7 @@ import prismaClient from "../../../prismaClient.js"; import { metricsCategory, metricToName } from "../analysisConstants.js"; import { averageManyFast } from "../coreAnalysis/averageManyFast.js"; import { createAnalysisHandler } from "../analysisHandler.js"; +import { robotRole } from "../coreAnalysis/robotRole.js"; export const categoryMetrics = createAnalysisHandler({ @@ -42,22 +43,22 @@ export const categoryMetrics = createAnalysisHandler({ metrics: metricsCategory, }); + for (const metric of metricsCategory) { + result[metricToName[metric]] = data[metric][params.team]; + } - let hasAnyData = false; - for (const metric of metricsCategory) { - const value = data[metric][params.team]; - result[metricToName[metric]] = value; - if (value !== null && value !== undefined && value !== 0) { - hasAnyData = true; - } - } - - - if (!hasAnyData) { + const reportCount = await prismaClient.scoutReport.count({ + where: { + teamMatchData: { + teamNumber: params.team, + }, + }, + }); + + if (reportCount === 0) { return { error: "NO_DATA_FOR_TEAM" }; } - return result; }, }); diff --git a/src/handler/analysis/teamLookUp/getNotes.ts b/src/handler/analysis/teamLookUp/getNotes.ts index bed2c69c..b55bc055 100644 --- a/src/handler/analysis/teamLookUp/getNotes.ts +++ b/src/handler/analysis/teamLookUp/getNotes.ts @@ -111,7 +111,14 @@ export const getNotes = createAnalysisHandler({ } - if (noteData.length === 0) { + const reportCount = await prismaClient.scoutReport.count({ + where: { + teamMatchData: { + teamNumber: params.team, + }, + }, + }); + if (reportCount === 0) { return { error: "NO_DATA_FOR_TEAM" }; } From 065d5d98d00eea64f88e69e74da1a3de9903341b Mon Sep 17 00:00:00 2001 From: JackAttack-365 <142643773+jackattack-4@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:00:20 -0800 Subject: [PATCH 4/6] run prettier --- src/handler/analysis/analysisHandler.ts | 4 +- .../analysis/teamLookUp/breakdownMetrics.ts | 111 +++++---- .../analysis/teamLookUp/categoryMetrics.ts | 89 ++++--- src/handler/analysis/teamLookUp/getNotes.ts | 221 +++++++++--------- src/handler/manager/getScouters.ts | 2 +- src/handler/manager/getScoutersOnTeam.ts | 2 +- .../manager/scoutingLeadProgressPage.ts | 4 +- src/handler/slack/addSlackWorkspace.ts | 2 +- src/index.ts | 44 ++-- src/lib/middleware/posthogMiddleware.ts | 2 +- 10 files changed, 232 insertions(+), 249 deletions(-) diff --git a/src/handler/analysis/analysisHandler.ts b/src/handler/analysis/analysisHandler.ts index 6772dc17..44f63999 100644 --- a/src/handler/analysis/analysisHandler.ts +++ b/src/handler/analysis/analysisHandler.ts @@ -120,7 +120,7 @@ export const createAnalysisHandler: < context, ); - res.set('X-Lovat-Cache','miss'); + res.set("X-Lovat-Cache", "miss"); res.status(200).send(calculatedAnalysis.error ?? calculatedAnalysis); try { @@ -143,7 +143,7 @@ export const createAnalysisHandler: < return; } } else { - res.set('X-Lovat-Cache','hit'); + res.set("X-Lovat-Cache", "hit"); res.status(200).send(JSON.parse(cacheRow.toString())); } } catch (error) { diff --git a/src/handler/analysis/teamLookUp/breakdownMetrics.ts b/src/handler/analysis/teamLookUp/breakdownMetrics.ts index 0c8a3fc9..7f993f9e 100644 --- a/src/handler/analysis/teamLookUp/breakdownMetrics.ts +++ b/src/handler/analysis/teamLookUp/breakdownMetrics.ts @@ -2,66 +2,61 @@ import z from "zod"; import prismaClient from "../../../prismaClient.js"; import { nonEventMetric } from "../coreAnalysis/nonEventMetric.js"; import { - lowercaseToBreakdown, - MetricsBreakdown, + lowercaseToBreakdown, + MetricsBreakdown, } from "../analysisConstants.js"; import { createAnalysisHandler } from "../analysisHandler.js"; - export const breakdownMetrics = createAnalysisHandler({ - params: { - params: z.object({ - team: z.preprocess((x) => Number(x), z.number()), - }), - }, - usesDataSource: true, - shouldCache: true, - createKey: ({ params }) => { - return { - key: ["breakdownMetrics", params.team.toString()], - teamDependencies: [params.team], - tournamentDependencies: [], - }; - }, - calculateAnalysis: async ({ params }, ctx) => { - const teamRow = await prismaClient.team.findUnique({ - where: { number: params.team }, - select: { number: true }, - }); - - if (!teamRow) { - return { error: "TEAM_DOES_NOT_EXIST" }; - } - - - const result: Record = {}; - for (const [key, metric] of Object.entries(lowercaseToBreakdown)) { - const data = await nonEventMetric(ctx.user, { - team: params.team, - metric: MetricsBreakdown[metric as keyof typeof MetricsBreakdown], - }); - - - const valid = Object.values(data).some((val) => Boolean(val)); - - - if (valid) { - result[key] = data; - } - } - -const reportCount = await prismaClient.scoutReport.count({ - where: { - teamMatchData: { - teamNumber: params.team, - }, - }, - }); - if (reportCount === 0) { - return { error: "NO_DATA_FOR_TEAM" }; - } - - - return result; - }, + params: { + params: z.object({ + team: z.preprocess((x) => Number(x), z.number()), + }), + }, + usesDataSource: true, + shouldCache: true, + createKey: ({ params }) => { + return { + key: ["breakdownMetrics", params.team.toString()], + teamDependencies: [params.team], + tournamentDependencies: [], + }; + }, + calculateAnalysis: async ({ params }, ctx) => { + const teamRow = await prismaClient.team.findUnique({ + where: { number: params.team }, + select: { number: true }, + }); + + if (!teamRow) { + return { error: "TEAM_DOES_NOT_EXIST" }; + } + + const result: Record = {}; + for (const [key, metric] of Object.entries(lowercaseToBreakdown)) { + const data = await nonEventMetric(ctx.user, { + team: params.team, + metric: MetricsBreakdown[metric as keyof typeof MetricsBreakdown], + }); + + const valid = Object.values(data).some((val) => Boolean(val)); + + if (valid) { + result[key] = data; + } + } + + const reportCount = await prismaClient.scoutReport.count({ + where: { + teamMatchData: { + teamNumber: params.team, + }, + }, + }); + if (reportCount === 0) { + return { error: "NO_DATA_FOR_TEAM" }; + } + + return result; + }, }); diff --git a/src/handler/analysis/teamLookUp/categoryMetrics.ts b/src/handler/analysis/teamLookUp/categoryMetrics.ts index 415ba64c..ba890834 100644 --- a/src/handler/analysis/teamLookUp/categoryMetrics.ts +++ b/src/handler/analysis/teamLookUp/categoryMetrics.ts @@ -5,60 +5,55 @@ import { averageManyFast } from "../coreAnalysis/averageManyFast.js"; import { createAnalysisHandler } from "../analysisHandler.js"; import { robotRole } from "../coreAnalysis/robotRole.js"; - export const categoryMetrics = createAnalysisHandler({ - params: { - params: z.object({ - team: z.preprocess((x) => Number(x), z.number()), - }), - }, - - - usesDataSource: true, - shouldCache: true, - createKey: ({ params }) => { - return { - key: ["categoryMetrics", params.team.toString()], - teamDependencies: [params.team], - tournamentDependencies: [], - }; - }, - calculateAnalysis: async ({ params }, ctx) => { - const teamRow = await prismaClient.team.findUnique({ - where: { number: params.team }, - select: { number: true }, - }); - - - if (!teamRow) { - return { error: "TEAM_DOES_NOT_EXIST" }; - } - - - const result: Record = {}; + params: { + params: z.object({ + team: z.preprocess((x) => Number(x), z.number()), + }), + }, + + usesDataSource: true, + shouldCache: true, + createKey: ({ params }) => { + return { + key: ["categoryMetrics", params.team.toString()], + teamDependencies: [params.team], + tournamentDependencies: [], + }; + }, + calculateAnalysis: async ({ params }, ctx) => { + const teamRow = await prismaClient.team.findUnique({ + where: { number: params.team }, + select: { number: true }, + }); + + if (!teamRow) { + return { error: "TEAM_DOES_NOT_EXIST" }; + } + const result: Record = {}; - const data = await averageManyFast(ctx.user, { - teams: [params.team], - metrics: metricsCategory, - }); + const data = await averageManyFast(ctx.user, { + teams: [params.team], + metrics: metricsCategory, + }); for (const metric of metricsCategory) { result[metricToName[metric]] = data[metric][params.team]; } - const reportCount = await prismaClient.scoutReport.count({ - where: { - teamMatchData: { - teamNumber: params.team, - }, - }, - }); - - if (reportCount === 0) { - return { error: "NO_DATA_FOR_TEAM" }; - } + const reportCount = await prismaClient.scoutReport.count({ + where: { + teamMatchData: { + teamNumber: params.team, + }, + }, + }); + + if (reportCount === 0) { + return { error: "NO_DATA_FOR_TEAM" }; + } - return result; - }, + return result; + }, }); diff --git a/src/handler/analysis/teamLookUp/getNotes.ts b/src/handler/analysis/teamLookUp/getNotes.ts index b55bc055..35cb7788 100644 --- a/src/handler/analysis/teamLookUp/getNotes.ts +++ b/src/handler/analysis/teamLookUp/getNotes.ts @@ -1,128 +1,121 @@ import prismaClient from "../../../prismaClient.js"; import z from "zod"; import { - dataSourceRuleSchema, - dataSourceRuleToPrismaFilter, + dataSourceRuleSchema, + dataSourceRuleToPrismaFilter, } from "../dataSourceRule.js"; import { createAnalysisHandler } from "../analysisHandler.js"; - export const getNotes = createAnalysisHandler({ - params: { - params: z.object({ - team: z.preprocess((x) => Number(x), z.number()), - }), - }, - usesDataSource: true, - shouldCache: true, - createKey: ({ params }) => { - return { - key: ["getNotes", params.team.toString()], - teamDependencies: [params.team], - tournamentDependencies: [], - }; - }, - calculateAnalysis: async ({ params }, ctx) => { - const teamRow = await prismaClient.team.findUnique({ - where: { number: params.team }, - select: { number: true }, - }); - - if (!teamRow) { - return { error: "TEAM_DOES_NOT_EXIST" }; - } - - - let notesAndMatches: { - notes: string; - match: string; - tounramentName: string; // Typo for backwards compatibility - sourceTeam: number; - scouterName?: string; - }[]; - - - const sourceTnmtFilter = dataSourceRuleToPrismaFilter( - dataSourceRuleSchema(z.string()).parse(ctx.user.tournamentSourceRule), - ); - const sourceTeamFilter = dataSourceRuleToPrismaFilter( - dataSourceRuleSchema(z.number()).parse(ctx.user.teamSourceRule), - ); - + params: { + params: z.object({ + team: z.preprocess((x) => Number(x), z.number()), + }), + }, + usesDataSource: true, + shouldCache: true, + createKey: ({ params }) => { + return { + key: ["getNotes", params.team.toString()], + teamDependencies: [params.team], + tournamentDependencies: [], + }; + }, + calculateAnalysis: async ({ params }, ctx) => { + const teamRow = await prismaClient.team.findUnique({ + where: { number: params.team }, + select: { number: true }, + }); - const noteData = await prismaClient.scoutReport.findMany({ - where: { - teamMatchData: { - teamNumber: params.team, - tournamentKey: sourceTnmtFilter, - }, - scouter: { - sourceTeamNumber: sourceTeamFilter, - }, - notes: { - not: "", - }, - }, - select: { - notes: true, - teamMatchKey: true, - teamMatchData: { - select: { - tournament: { - select: { - name: true, - }, - }, - }, - }, - scouter: { - select: { - sourceTeamNumber: true, - name: Boolean(ctx.user.teamNumber), - }, - }, - }, - orderBy: [ - { teamMatchData: { tournament: { date: "desc" } } }, - { teamMatchData: { matchType: "desc" } }, - { teamMatchData: { matchNumber: "desc" } }, - ], - }); + if (!teamRow) { + return { error: "TEAM_DOES_NOT_EXIST" }; + } + let notesAndMatches: { + notes: string; + match: string; + tounramentName: string; // Typo for backwards compatibility + sourceTeam: number; + scouterName?: string; + }[]; - if (Boolean(ctx.user.teamNumber)) { - notesAndMatches = noteData.map((report) => ({ - notes: report.notes, - match: report.teamMatchKey, - tounramentName: report.teamMatchData.tournament.name, - sourceTeam: report.scouter.sourceTeamNumber, - scouterName: - report.scouter.sourceTeamNumber === ctx.user.teamNumber - ? report.scouter.name - : undefined, - })); - } else { - notesAndMatches = noteData.map((report) => ({ - notes: report.notes, - match: report.teamMatchKey, - tounramentName: report.teamMatchData.tournament.name, - sourceTeam: report.scouter.sourceTeamNumber, - })); - } + const sourceTnmtFilter = dataSourceRuleToPrismaFilter( + dataSourceRuleSchema(z.string()).parse(ctx.user.tournamentSourceRule), + ); + const sourceTeamFilter = dataSourceRuleToPrismaFilter( + dataSourceRuleSchema(z.number()).parse(ctx.user.teamSourceRule), + ); + const noteData = await prismaClient.scoutReport.findMany({ + where: { + teamMatchData: { + teamNumber: params.team, + tournamentKey: sourceTnmtFilter, + }, + scouter: { + sourceTeamNumber: sourceTeamFilter, + }, + notes: { + not: "", + }, + }, + select: { + notes: true, + teamMatchKey: true, + teamMatchData: { + select: { + tournament: { + select: { + name: true, + }, + }, + }, + }, + scouter: { + select: { + sourceTeamNumber: true, + name: Boolean(ctx.user.teamNumber), + }, + }, + }, + orderBy: [ + { teamMatchData: { tournament: { date: "desc" } } }, + { teamMatchData: { matchType: "desc" } }, + { teamMatchData: { matchNumber: "desc" } }, + ], + }); - const reportCount = await prismaClient.scoutReport.count({ - where: { - teamMatchData: { - teamNumber: params.team, - }, - }, - }); - if (reportCount === 0) { - return { error: "NO_DATA_FOR_TEAM" }; - } + if (Boolean(ctx.user.teamNumber)) { + notesAndMatches = noteData.map((report) => ({ + notes: report.notes, + match: report.teamMatchKey, + tounramentName: report.teamMatchData.tournament.name, + sourceTeam: report.scouter.sourceTeamNumber, + scouterName: + report.scouter.sourceTeamNumber === ctx.user.teamNumber + ? report.scouter.name + : undefined, + })); + } else { + notesAndMatches = noteData.map((report) => ({ + notes: report.notes, + match: report.teamMatchKey, + tounramentName: report.teamMatchData.tournament.name, + sourceTeam: report.scouter.sourceTeamNumber, + })); + } + const reportCount = await prismaClient.scoutReport.count({ + where: { + teamMatchData: { + teamNumber: params.team, + }, + }, + }); + if (reportCount === 0) { + return { error: "NO_DATA_FOR_TEAM" }; + } - return notesAndMatches; - }, -}); \ No newline at end of file + return notesAndMatches; + }, +}); diff --git a/src/handler/manager/getScouters.ts b/src/handler/manager/getScouters.ts index d86e81a5..6218fae1 100644 --- a/src/handler/manager/getScouters.ts +++ b/src/handler/manager/getScouters.ts @@ -5,7 +5,7 @@ import z from "zod"; export const getScouters = async ( req: AuthenticatedRequest, - res: Response + res: Response, ): Promise => { try { const params = z diff --git a/src/handler/manager/getScoutersOnTeam.ts b/src/handler/manager/getScoutersOnTeam.ts index a3933c71..83967451 100644 --- a/src/handler/manager/getScoutersOnTeam.ts +++ b/src/handler/manager/getScoutersOnTeam.ts @@ -4,7 +4,7 @@ import z from "zod"; export const getScoutersOnTeam = async ( req: Request, - res: Response + res: Response, ): Promise => { try { console.log(req.headers); diff --git a/src/handler/manager/scoutingLeadProgressPage.ts b/src/handler/manager/scoutingLeadProgressPage.ts index 5f48ec45..c4deebed 100644 --- a/src/handler/manager/scoutingLeadProgressPage.ts +++ b/src/handler/manager/scoutingLeadProgressPage.ts @@ -5,7 +5,7 @@ import { AuthenticatedRequest } from "../../lib/middleware/requireAuth.js"; export const scoutingLeadProgressPage = async ( req: AuthenticatedRequest, - res: Response + res: Response, ): Promise => { try { const params = z @@ -147,7 +147,7 @@ export const scoutingLeadProgressPage = async ( matchesScouted: matchesScoutedAtTournament.length, missedMatches: Math.max( 0, - totalAssignedScouterMatches - matchesScoutedAtTournament.length + totalAssignedScouterMatches - matchesScoutedAtTournament.length, ), }; result.push(currData); diff --git a/src/handler/slack/addSlackWorkspace.ts b/src/handler/slack/addSlackWorkspace.ts index a2b1252f..a17b5603 100644 --- a/src/handler/slack/addSlackWorkspace.ts +++ b/src/handler/slack/addSlackWorkspace.ts @@ -48,7 +48,7 @@ export const addSlackWorkspace = async ( if (!teamRow) { res.status(404).send("Team not found"); - return + return; } await prismaClient.slackWorkspace.upsert({ diff --git a/src/index.ts b/src/index.ts index d813e07b..49021ff7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -140,7 +140,7 @@ app.post( "/v1/slack/command", express.urlencoded({ extended: true }), requireSlackToken, - processCommand + processCommand, ); app.post("/v1/slack/event", requireSlackToken, processEvent); @@ -148,7 +148,7 @@ app.post("/v1/slack/event", requireSlackToken, processEvent); app.post( "/v1/manager/onboarding/verifyemail", requireLovatSignature, - approveTeamEmail + approveTeamEmail, ); //tested // Log requests @@ -158,12 +158,12 @@ app.use(posthogReporter); app.get( "/v1/manager/tournament/:tournament/teams", requireAuth, - getTeamsInTournament + getTeamsInTournament, ); app.get( "/v1/manager/tournament/:tournament/rankedTeams", requireAuth, - getTeamRankings + getTeamRankings, ); app.get("/v1/manager/teams", requireAuth, getTeams); //tested app.get("/v1/manager/tournaments", requireAuth, getTournaments); //tested @@ -181,7 +181,7 @@ app.get("/v1/manager/scoutreports/:uuid", getScoutReport); //tested app.post( "/v1/manager/tournament/:tournament/scoutershifts", requireAuth, - addScouterShift + addScouterShift, ); //tested , expecting only 1 at a time // app.get('/manager/tournament/:tournament/scoutershifts',requireAuth, getScouterSchedule) //tested app.post("/v1/manager/scoutershifts/:uuid", requireAuth, updateScouterShift); //tested @@ -200,18 +200,18 @@ app.post("/v1/manager/mutablepicklists", requireAuth, addMutablePicklist); // te app.delete( "/v1/manager/mutablepicklists/:uuid", requireAuth, - deleteMutablePicklist + deleteMutablePicklist, ); //tested app.get("/v1/manager/mutablepicklists", requireAuth, getMutablePicklists); //tested app.get( "/v1/manager/mutablepicklists/:uuid", requireAuth, - getSingleMutablePicklist + getSingleMutablePicklist, ); //tested app.put( "/v1/manager/mutablepicklists/:uuid", requireAuth, - updateMutablePicklist + updateMutablePicklist, ); //tested // Also it would be nice to have an endpoint to subscribe to a mutable picklist, so that the client can get updates when it changes @@ -221,7 +221,7 @@ app.put( app.get( "/v1/manager/registeredteams/:team/registrationstatus", requireAuth, - checkRegisteredTeam + checkRegisteredTeam, ); //tested app.post("/v1/manager/onboarding/username", requireAuth, addUsername); //tested app.post("/v1/manager/onboarding/teamcode", requireAuth, checkCode); //tested @@ -229,18 +229,18 @@ app.post("/v1/manager/settings/teamsource", requireAuth, addTeamSource); //teste app.post( "/v1/manager/settings/tournamentsource", requireAuth, - addTournamentSource + addTournamentSource, ); app.post("/v1/manager/onboarding/team", requireAuth, addRegisteredTeam); //tested, is the link correct? app.post( "/v1/manager/registeredteams/:team/approve", requireLovatSignature, - approveRegisteredTeam + approveRegisteredTeam, ); //tested waiting for new middle ware app.post( "/v1/manager/registeredteams/:team/reject", requireLovatSignature, - rejectRegisteredTeam + rejectRegisteredTeam, ); // tested, waiting for new middle ware app.post("/v1/manager/onboarding/teamwebsite", requireAuth, addWebsite); //tested @@ -248,7 +248,7 @@ app.post( "/v1/manager/onboarding/resendverificationemail", resendEmailLimiter, requireAuth, - resendEmail + resendEmail, ); //tested app.get("/v1/manager/profile", requireAuth, getProfile); //tested app.get("/v1/manager/users", requireAuth, getUsers); //tested @@ -263,13 +263,13 @@ app.get("/v1/manager/settings/teamsource", requireAuth, getTeamSource); app.get( "/v1/manager/settings/tournamentsource", requireAuth, - getTournamentSource + getTournamentSource, ); app.put( "/v1/manager/settings/teamemail", updateTeamEmails, requireAuth, - updateTeamEmail + updateTeamEmail, ); //scouting lead information/QR codes @@ -277,13 +277,13 @@ app.get("/v1/manager/code", requireAuth, getTeamCode); app.get( "/v1/manager/tournament/:tournament/scoutershifts", requireAuth, - getScouterSchedule + getScouterSchedule, ); //tested app.post( "/v1/manager/dashboard/scoutreport", requireAuth, - addScoutReportDashboard + addScoutReportDashboard, ); //scouter onboarding @@ -310,7 +310,7 @@ app.get("/v1/analysis/breakdown/team/:team", requireAuth, breakdownMetrics); //t app.get( "/v1/analysis/breakdown/team/:team/:breakdown", requireAuth, - breakdownDetails + breakdownDetails, ); app.get("/v1/analysis/notes/team/:team", requireAuth, getNotes); //tested app.get("/v1/analysis/flag/team/:team", requireAuth, multipleFlags); //tested @@ -334,17 +334,17 @@ app.get("/v1/manager/scouterreports", requireAuth, scouterScoutReports); app.get( "/v1/analysis/metrics/scoutreport/:uuid", requireAuth, - matchPageSpecificScouter + matchPageSpecificScouter, ); app.get( "/v1/analysis/scoutreports/match/:match", requireAuth, - scoutReportForMatch + scoutReportForMatch, ); app.get( "/v1/analysis/timeline/scoutreport/:uuid", requireAuth, - timelineForScoutReport + timelineForScoutReport, ); //pit scouting @@ -361,7 +361,7 @@ app.get("/v1/analysis/reportcsv", requireAuth, getReportCSV); app.get( "/v1/manager/team-tournament-status", requireAuth, - getTeamTournamentStatus + getTeamTournamentStatus, ); // match results from scouting reports diff --git a/src/lib/middleware/posthogMiddleware.ts b/src/lib/middleware/posthogMiddleware.ts index 6f6c59f1..38dcc42e 100644 --- a/src/lib/middleware/posthogMiddleware.ts +++ b/src/lib/middleware/posthogMiddleware.ts @@ -61,7 +61,7 @@ const posthogReporter = async ( $set: userProps, $pathname: req.route?.path, method: req.method, - cache: res.getHeader('X-Lovat-Cache'), + cache: res.getHeader("X-Lovat-Cache"), path: req.path, query: req.query, reqBody: req.body, From 608c4b82c924526d519808815b2353e665d06d4d Mon Sep 17 00:00:00 2001 From: JackAttack-365 <142643773+jackattack-4@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:02:33 -0800 Subject: [PATCH 5/6] Update categoryMetrics.ts --- src/handler/analysis/teamLookUp/categoryMetrics.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/handler/analysis/teamLookUp/categoryMetrics.ts b/src/handler/analysis/teamLookUp/categoryMetrics.ts index ba890834..e680eeaa 100644 --- a/src/handler/analysis/teamLookUp/categoryMetrics.ts +++ b/src/handler/analysis/teamLookUp/categoryMetrics.ts @@ -3,7 +3,6 @@ import prismaClient from "../../../prismaClient.js"; import { metricsCategory, metricToName } from "../analysisConstants.js"; import { averageManyFast } from "../coreAnalysis/averageManyFast.js"; import { createAnalysisHandler } from "../analysisHandler.js"; -import { robotRole } from "../coreAnalysis/robotRole.js"; export const categoryMetrics = createAnalysisHandler({ params: { From 8341aea2a31711762628f0c3d8f82bbe49340a5b Mon Sep 17 00:00:00 2001 From: JackAttack-365 <142643773+jackattack-4@users.noreply.github.com> Date: Thu, 8 Jan 2026 16:11:55 -0800 Subject: [PATCH 6/6] implemet suggestions --- .../analysis/teamLookUp/breakdownMetrics.ts | 22 +++++++++---------- .../analysis/teamLookUp/categoryMetrics.ts | 22 +++++++++---------- src/handler/analysis/teamLookUp/getNotes.ts | 22 +++++++++---------- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/handler/analysis/teamLookUp/breakdownMetrics.ts b/src/handler/analysis/teamLookUp/breakdownMetrics.ts index 7f993f9e..a43e67a9 100644 --- a/src/handler/analysis/teamLookUp/breakdownMetrics.ts +++ b/src/handler/analysis/teamLookUp/breakdownMetrics.ts @@ -32,6 +32,17 @@ export const breakdownMetrics = createAnalysisHandler({ return { error: "TEAM_DOES_NOT_EXIST" }; } + const reportCount = await prismaClient.scoutReport.count({ + where: { + teamMatchData: { + teamNumber: params.team, + }, + }, + }); + if (reportCount === 0) { + return { error: "NO_DATA_FOR_TEAM" }; + } + const result: Record = {}; for (const [key, metric] of Object.entries(lowercaseToBreakdown)) { const data = await nonEventMetric(ctx.user, { @@ -46,17 +57,6 @@ export const breakdownMetrics = createAnalysisHandler({ } } - const reportCount = await prismaClient.scoutReport.count({ - where: { - teamMatchData: { - teamNumber: params.team, - }, - }, - }); - if (reportCount === 0) { - return { error: "NO_DATA_FOR_TEAM" }; - } - return result; }, }); diff --git a/src/handler/analysis/teamLookUp/categoryMetrics.ts b/src/handler/analysis/teamLookUp/categoryMetrics.ts index e680eeaa..aad18aef 100644 --- a/src/handler/analysis/teamLookUp/categoryMetrics.ts +++ b/src/handler/analysis/teamLookUp/categoryMetrics.ts @@ -30,17 +30,6 @@ export const categoryMetrics = createAnalysisHandler({ return { error: "TEAM_DOES_NOT_EXIST" }; } - const result: Record = {}; - - const data = await averageManyFast(ctx.user, { - teams: [params.team], - metrics: metricsCategory, - }); - - for (const metric of metricsCategory) { - result[metricToName[metric]] = data[metric][params.team]; - } - const reportCount = await prismaClient.scoutReport.count({ where: { teamMatchData: { @@ -53,6 +42,17 @@ export const categoryMetrics = createAnalysisHandler({ return { error: "NO_DATA_FOR_TEAM" }; } + const result: Record = {}; + + const data = await averageManyFast(ctx.user, { + teams: [params.team], + metrics: metricsCategory, + }); + + for (const metric of metricsCategory) { + result[metricToName[metric]] = data[metric][params.team]; + } + return result; }, }); diff --git a/src/handler/analysis/teamLookUp/getNotes.ts b/src/handler/analysis/teamLookUp/getNotes.ts index 35cb7788..6f76fe29 100644 --- a/src/handler/analysis/teamLookUp/getNotes.ts +++ b/src/handler/analysis/teamLookUp/getNotes.ts @@ -31,6 +31,17 @@ export const getNotes = createAnalysisHandler({ return { error: "TEAM_DOES_NOT_EXIST" }; } + const reportCount = await prismaClient.scoutReport.count({ + where: { + teamMatchData: { + teamNumber: params.team, + }, + }, + }); + if (reportCount === 0) { + return { error: "NO_DATA_FOR_TEAM" }; + } + let notesAndMatches: { notes: string; match: string; @@ -105,17 +116,6 @@ export const getNotes = createAnalysisHandler({ })); } - const reportCount = await prismaClient.scoutReport.count({ - where: { - teamMatchData: { - teamNumber: params.team, - }, - }, - }); - if (reportCount === 0) { - return { error: "NO_DATA_FOR_TEAM" }; - } - return notesAndMatches; }, });