Skip to content
2 changes: 1 addition & 1 deletion apps/scouting/backend/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { build, context } from "esbuild";
import { spawn } from "child_process";

const isDev = process.env.NODE_ENV === "DEV";
const isDev = process.env.NODE_ENV !== "DEV";
Copy link
Contributor

Choose a reason for hiding this comment

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

remove before merging


const bundlePath = "dist/bundle.js";

Expand Down
163 changes: 163 additions & 0 deletions apps/scouting/backend/src/routes/compare-router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
//בס"ד

import type { ScoutingForm, TeleClimbLevel } from "@repo/scouting_types";
import { Router } from "express";
import { getFormsCollection } from "./forms-router";
import { pipe } from "fp-ts/lib/function";
import {
flatMap,
left,
map,
right,
tryCatch,
fold,
} from "fp-ts/lib/TaskEither";
import { mongofyQuery } from "../middleware/query";
import { StatusCodes } from "http-status-codes";
import { firstElement, isEmpty } from "@repo/array-functions";
import { calcAverageGeneralFuelData } from "./general-router";
import { generalCalculateFuel } from "../fuel/fuel-general";
import { getBPSes } from "./teams-router";

export const compareRouter = Router();

type GamePeriod = "auto" | "fullGame";
Copy link
Contributor

Choose a reason for hiding this comment

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

What about autonomous? Consider adding it

Copy link
Contributor

Choose a reason for hiding this comment

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

What about teleop? Consider adding it


const DIGITS_AFTER_DOT = 2;
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider renaming it to 'DIFITS_AFTER_DECIMAL_DOT'

const INITIAL_COUNTER_VALUE = 0;
const INCREMENT = 1;

const calculateAverageScoredFuel = (
forms: ScoutingForm[],
gamePeriod: GamePeriod,
) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider declaring a type 'L1 | L2 | L3 | L4' that the function will return

const generalFuelData = forms.map((form) =>
generalCalculateFuel(form, getBPSes()),
);
const averagedFuelData = calcAverageGeneralFuelData(generalFuelData);

return parseFloat(
averagedFuelData[gamePeriod].scored.toFixed(DIGITS_AFTER_DOT),
);
};

const findMaxClimbLevel = (forms: ScoutingForm[]) => {
const fullGameClimbedLevels = forms
.map((form) => form.tele.climb.level)
.concat(forms.map((form) => form.auto.climb.level));
return fullGameClimbedLevels.includes("L3")
? "L3"
: fullGameClimbedLevels.includes("L2")
? "L2"
: fullGameClimbedLevels.includes("L1")
? "L1"
: "L0";
};

const findTimesClimbedToMax = (
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider changing it to 'findTimesClimbedToMax'

forms: ScoutingForm[],
maxLevel: TeleClimbLevel,
) => {
return forms.reduce(
(counter, form) =>
form.tele.climb.level === maxLevel ? counter + INCREMENT : counter,
INITIAL_COUNTER_VALUE,
);
};

const findTimesClimbedInAuto = (forms: ScoutingForm[]) => {
return forms.reduce(
(counter, form) =>
form.auto.climb.level === "L1" ? counter + INCREMENT : counter,
INITIAL_COUNTER_VALUE,
);
};

const timesClimedToLevel = (
level: TeleClimbLevel,
climbedLevels: TeleClimbLevel[],
) => {
return climbedLevels.filter((currentLevel) => currentLevel === level).length;
};

const findTimesClimbedToLevels = (forms: ScoutingForm[]) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider declaring an interface that the function will return

const climbedLevels = forms.map((form) => form.tele.climb.level);
return {
L1: timesClimedToLevel("L1", climbedLevels),
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, consider using a type consisting the possible climbing levels

L2: timesClimedToLevel("L2", climbedLevels),
L3: timesClimedToLevel("L3", climbedLevels),
};
};

compareRouter.get("/teams", async (req, res) => {
await pipe(
getFormsCollection(),
flatMap((collection) =>
tryCatch(
() =>
collection
.find(mongofyQuery({ "match.type": "qualification" }))
.toArray(),
(error) => ({
status: StatusCodes.INTERNAL_SERVER_ERROR,
reason: `DB Error: ${error}`,
}),
),
),
map((forms) => forms.map((form) => form.teamNumber)),
fold(
(error) => () =>
Promise.resolve(res.status(error.status).send(error.reason)),
(teamNumbers) => () =>
Promise.resolve(res.status(StatusCodes.OK).json({ teamNumbers })),
),
)();
});
Comment on lines +92 to +115
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider putting it in a function


compareRouter.get("/", async (req, res) => {
await pipe(
getFormsCollection(),
flatMap((collection) =>
tryCatch(
() => collection.find(mongofyQuery(req.query)).toArray(),
(error) => ({
status: StatusCodes.INTERNAL_SERVER_ERROR,
reason: `DB Error: ${error}`,
}),
),
),
flatMap((forms) => {
if (isEmpty(forms)) return right(forms);

const firstTeam = firstElement(forms).teamNumber;
const isSameTeam = forms.every((f) => f.teamNumber === firstTeam);

return isSameTeam
? right(forms)
: left({
status: StatusCodes.BAD_REQUEST,
reason:
"Compare Two Validation Error: Forms contain data from multiple different teams.",
});
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

is this really needed? find out if this is redundant and if not, consider type checking the query instead or maybe even moving to a body.

map((teamForms) => ({
teamNumber: firstElement(teamForms).teamNumber,
averageFuelInGame: calculateAverageScoredFuel(teamForms, "fullGame"),
averageFuelInAuto: calculateAverageScoredFuel(teamForms, "auto"),
maxClimbLevel: findMaxClimbLevel(teamForms),
timesClimbedToMax: findTimesClimbedToMax(
teamForms,
findMaxClimbLevel(teamForms),
),
timesClimbedInAuto: findTimesClimbedInAuto(teamForms),
timesClimbedToLevels: findTimesClimbedToLevels(teamForms),
})),

fold(
(error) => () =>
Promise.resolve(res.status(error.status).send(error.reason)),
(teamCompareData) => () =>
Promise.resolve(res.status(StatusCodes.OK).json({ teamCompareData })),
),
)();
});
13 changes: 4 additions & 9 deletions apps/scouting/backend/src/routes/general-router.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//בס"ד
/* eslint-disable @typescript-eslint/no-magic-numbers */ //for the example bps

import { Router } from "express";
import { getFormsCollection } from "./forms-router";
Expand All @@ -8,10 +7,10 @@ import { flatMap, fold, map, tryCatch } from "fp-ts/lib/TaskEither";
import { mongofyQuery } from "../middleware/query";
import { generalCalculateFuel } from "../fuel/fuel-general";
import { StatusCodes } from "http-status-codes";

import type { BPS, FuelObject, GeneralFuelData } from "@repo/scouting_types";
import { averageFuel } from "../fuel/distance-split";
import { firstElement, isEmpty } from "@repo/array-functions";
import { getBPSes } from "./teams-router";

export const generalRouter = Router();

Expand All @@ -21,13 +20,9 @@ interface AccumulatedFuelData {
tele: FuelObject[];
}

const getBPS = () => {
return [];
};

const ONE_ITEM_ARRAY = 1;

const calcAverageGeneralFuelData = (fuelData: GeneralFuelData[]) => {
export const calcAverageGeneralFuelData = (fuelData: GeneralFuelData[]) => {
if (fuelData.length === ONE_ITEM_ARRAY || isEmpty(fuelData)) {
return firstElement(fuelData);
}
Expand Down Expand Up @@ -70,7 +65,7 @@ generalRouter.get("/", async (req, res) => {
map((forms) =>
forms.map((form) => ({
teamNumber: form.teamNumber,
generalFuelData: generalCalculateFuel(form, getBPS()),
generalFuelData: generalCalculateFuel(form, getBPSes()),
})),
),

Expand All @@ -79,7 +74,7 @@ generalRouter.get("/", async (req, res) => {
(accumulatorRecord, fuelData) => ({
...accumulatorRecord,
[fuelData.teamNumber]: [
...accumulatorRecord[fuelData.teamNumber],
...(accumulatorRecord[fuelData.teamNumber] ?? []),
fuelData.generalFuelData,
],
}),
Expand Down
2 changes: 2 additions & 0 deletions apps/scouting/backend/src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { gameRouter } from "./game-router";
import { formsRouter } from "./forms-router";
import { teamsRouter } from "./teams-router";
import { generalRouter } from "./general-router";
import { compareRouter } from "./compare-router";

export const apiRouter = Router();

Expand All @@ -14,6 +15,7 @@ apiRouter.use("/tba", tbaRouter);
apiRouter.use("/game", gameRouter);
apiRouter.use("/team",teamsRouter)
apiRouter.use("/general", generalRouter);
apiRouter.use("/compare", compareRouter);

apiRouter.get("/health", (req, res) => {
res.status(StatusCodes.OK).send({ message: "Healthy!" });
Expand Down
2 changes: 1 addition & 1 deletion apps/scouting/backend/src/routes/teams-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ const processTeam = (bpses: BPS[], forms: ScoutingForm[]): TeamData => {
return { tele, auto, fullGame, metrics: { epa: 0, bps: 0 } };
};

const getBPSes = (): BPS[] => [
export const getBPSes = (): BPS[] => [
{
events: [
{
Expand Down
9 changes: 9 additions & 0 deletions apps/scouting/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,21 @@ import type { FC } from "react";
import { ScoutMatch } from "./scouter/pages/ScoutMatch";
import { Route, Routes } from "react-router-dom";
import { ScoutedMatches } from "./scouter/pages/ScoutedMatches";
import { CompareTwo } from "./scouter/components/CompareTwo";
import { GeneralDataTable } from "./scouter/components/GeneralDataTable";

const App: FC = () => {
return (
<Routes>
<Route path="*" element={<ScoutedMatches />} />
<Route path="/scout" element={<ScoutMatch />} />
<Route path="/compare" element={<CompareTwo />} />
<Route
path="/table"
element={
<GeneralDataTable filters={{ "match.type": "qualification" }} />
}
/>
</Routes>
);
};
Expand Down
Loading