diff --git a/apps/scouting/backend/docker-compose.yml b/apps/scouting/backend/docker-compose.yml index 75848956..960d1eae 100644 --- a/apps/scouting/backend/docker-compose.yml +++ b/apps/scouting/backend/docker-compose.yml @@ -1,16 +1,16 @@ -name: gbscout services: backend: + image: greenblitz/gbscout-backend build: context: . dockerfile: Dockerfile networks: - scouting-shared env_file: - - ../../../.public.env - - ../../../.secret.env + - .public.env + - .secret.env ports: - - "${BACKEND_PORT}:4590" # Maps host:${BACKEND_PORT} to container:4590 + - "4590:4590" # Maps host:${BACKEND_PORT} to container:4590 depends_on: - mongodb environment: diff --git a/apps/scouting/backend/src/fuel/calculations/fuel-averaging.ts b/apps/scouting/backend/src/fuel/calculations/fuel-averaging.ts index 3f5f4eac..a03b4559 100644 --- a/apps/scouting/backend/src/fuel/calculations/fuel-averaging.ts +++ b/apps/scouting/backend/src/fuel/calculations/fuel-averaging.ts @@ -17,7 +17,7 @@ import { convertPixelToCentimeters, distanceFromHub } from "@repo/rebuilt_map"; import { interpolateQuadratic } from "./interpolation"; interface ShotStats { - durationMilliseconds: number; + durationSeconds: number; hubDistanceCentimeters: number; } @@ -27,7 +27,7 @@ const NO_FUEL_COLLECTED = 0; const FIRST_SECTION_AMOUNT = 1; const ONE_SECTION_ONLY_LENGTH = 1; -/** +/** * @param sections consists of sections that contains a list of timestamps in ms * @returns mean ball amount * Recursively calculates mean ball amount from timestamp sections. */ @@ -94,6 +94,7 @@ const calculateAccuracies = (sections: BPS["events"], shotDuration: number) => { ), accuracy: section.score.length / section.shoot.length, })); + const sortedAccuracies = accuracies.sort( (accuracy1, accuracy2) => accuracy1.distance - accuracy2.distance, ); @@ -104,9 +105,10 @@ const calculateAccuracies = (sections: BPS["events"], shotDuration: number) => { const compareSections = (section1: number[], section2: number[]) => lastElement(section1) - lastElement(section2); -const correctSectionToTimeFromEnd = (sections: number[]) => { - const endTimestamp = lastElement(sections); - return sections.map((timestamp) => endTimestamp - timestamp).reverse(); +const correctSectionToTimeFromEnd = (section: number[]) => { + const sortedSection = section.sort(); + const endTimestamp = lastElement(sortedSection); + return sortedSection.map((timestamp) => endTimestamp - timestamp).reverse(); }; const formatSections = (sections: BPS["events"]) => @@ -120,8 +122,6 @@ const formatSections = (sections: BPS["events"]) => compareSections(formattedSection1.shoot, formattedSection2.shoot), ); -const MILLISECONDS_IN_SECONDS = 1000; - export const calculateAverageBPS = (bpses: BPS[]) => { const formattedSections = formatSections(bpses.flatMap((bps) => bps.events)); @@ -136,9 +136,11 @@ export const calculateAverageBPS = (bpses: BPS[]) => { longestSectionDuration, ); - return (shotAmount * MILLISECONDS_IN_SECONDS) / longestSectionDuration; + return shotAmount / longestSectionDuration; }; +const MILLISECONDS_IN_SECOND = 1000; + /** Calculates fuel statistics by averaging across multiple matches. */ export const calculateFuelByAveraging = ( shot: ShootEvent, @@ -146,7 +148,8 @@ export const calculateFuelByAveraging = ( sections: BPSEvent[], ): Partial => { const shotStats: ShotStats = { - durationMilliseconds: shot.interval.end - shot.interval.start, + durationSeconds: + (shot.interval.end - shot.interval.start) / MILLISECONDS_IN_SECOND, hubDistanceCentimeters: distanceFromHub( convertPixelToCentimeters(firstElement(shot.positions)), ), @@ -156,7 +159,7 @@ export const calculateFuelByAveraging = ( const shotAmount = calculateBallAmount( formattedSections.map((section) => section.shoot), - shotStats.durationMilliseconds, + shotStats.durationSeconds, ); if (isPass) { @@ -166,14 +169,20 @@ export const calculateFuelByAveraging = ( positions: shot.positions, }; } - const scoredAccuracy = interpolateQuadratic( - shotStats.hubDistanceCentimeters, - calculateAccuracies(formattedSections, shotStats.durationMilliseconds).map( - ({ distance, accuracy }) => ({ x: distance, y: accuracy }), - ), + + const averageAccuracy = calculateAverage( + calculateAccuracies(formattedSections, shotStats.durationSeconds), + ({ accuracy }) => accuracy, ); - const scoredAmount = shotAmount * scoredAccuracy; + // const scoredAccuracy = interpolateQuadratic( + // shotStats.hubDistanceCentimeters, + // calculateAccuracies(formattedSections, shotStats.durationSeconds).map( + // ({ distance, accuracy }) => ({ x: distance, y: accuracy }), + // ), + // ); + + const scoredAmount = shotAmount * averageAccuracy; return { scored: scoredAmount, diff --git a/apps/scouting/backend/src/fuel/fuel-object.ts b/apps/scouting/backend/src/fuel/fuel-object.ts index c5741c87..781d94b3 100644 --- a/apps/scouting/backend/src/fuel/fuel-object.ts +++ b/apps/scouting/backend/src/fuel/fuel-object.ts @@ -29,22 +29,29 @@ const putDefaultsInFuel = (fuel: Partial) => ({ export const createFuelObject = ( shot: ShootEvent, - match: Match, + _match: Match, bpses: BPS[], ): FuelObject => { - const sameMatch = bpses.find( - (value) => - value.match.number === match.number && value.match.type === match.type, - ); + const nonEmptyBPSes = bpses.map((bps) => ({ + ...bps, + events: bps.events.filter((event) => event.shoot.length > 0), + })); + + // const sameMatch = nonEmptyBPSes.find( + // (value) => + // value.match.number === match.number && value.match.type === match.type, + // ); const isPass = isShotPass(shot.positions); - const partialFuel = sameMatch - ? calculateFuelByMatch(shot, isPass, sameMatch) - : calculateFuelByAveraging( - shot, - isPass, - bpses.flatMap((bps) => bps.events), - ); + const partialFuel = + // sameMatch + // ? calculateFuelByMatch(shot, isPass, sameMatch) + // : + calculateFuelByAveraging( + shot, + isPass, + nonEmptyBPSes.flatMap((bps) => bps.events), + ); return putDefaultsInFuel(partialFuel); }; diff --git a/apps/scouting/backend/src/routes/bps-router.ts b/apps/scouting/backend/src/routes/bps-router.ts index fe2c0d2f..79903b7e 100644 --- a/apps/scouting/backend/src/routes/bps-router.ts +++ b/apps/scouting/backend/src/routes/bps-router.ts @@ -114,6 +114,21 @@ bpsRouter.get("/matches", async (req, res) => { )(); }); +bpsRouter.get("/", async (req, res) => { + await pipe( + getBPSCollection(), + flatTryCatch( + (collection) => collection.find().toArray(), + (error) => ({ + status: StatusCodes.INTERNAL_SERVER_ERROR, + reason: `Recieved Error: ${String(error)}`, + }), + ), + bindTo("bpses"), + foldResponse(res), + )(); +}); + bpsRouter.post("/", async (req, res) => { await pipe( rightEither(req), diff --git a/apps/scouting/backend/src/routes/tba-router.ts b/apps/scouting/backend/src/routes/tba-router.ts index f4ec863c..eb6955a0 100644 --- a/apps/scouting/backend/src/routes/tba-router.ts +++ b/apps/scouting/backend/src/routes/tba-router.ts @@ -146,7 +146,6 @@ const getMatches = flow( ), ); - export const fetchCOPRS = (event: string) => pipe( fetchTba(`/event/${event}/coprs`, eventOPRCodec), diff --git a/apps/scouting/backend/src/routes/teams-router.ts b/apps/scouting/backend/src/routes/teams-router.ts index 0eb9bb58..ce96781d 100644 --- a/apps/scouting/backend/src/routes/teams-router.ts +++ b/apps/scouting/backend/src/routes/teams-router.ts @@ -195,11 +195,11 @@ teamsRouter.get("/", async (req, res) => { ), ), flatMap(getTeamBPSes), - flatMap(fetchTeamsCOPRs), + // flatMap(fetchTeamsCOPRs), flatMap(getTeamsEPAs), map((teams) => mapObject(teams, (team) => - processTeam(team.bpses, team.forms, team.coprs, team.epa), + processTeam(team.bpses, team.forms, undefined, undefined), ), ), bindTo("teams"), diff --git a/apps/scouting/frontend/docker-compose.yml b/apps/scouting/frontend/docker-compose.yml index c504343f..afb09760 100644 --- a/apps/scouting/frontend/docker-compose.yml +++ b/apps/scouting/frontend/docker-compose.yml @@ -1,6 +1,7 @@ name: gbscout services: frontend: + image: greenblitz/gbscout-frontend build: context: . dockerfile: Dockerfile diff --git a/apps/scouting/frontend/src/main.tsx b/apps/scouting/frontend/src/main.tsx index 54ad5ea5..d3a27bf7 100644 --- a/apps/scouting/frontend/src/main.tsx +++ b/apps/scouting/frontend/src/main.tsx @@ -5,13 +5,14 @@ import { createRoot } from "react-dom/client"; import "./index.css"; import { BrowserRouter } from "react-router-dom"; import App from "./App"; +import StartMatchLocallyButton from "./scouter/components/StartMatchLocallyButton"; // registerSW({ immediate: true }); createRoot(document.getElementById("root")!).render( - + , ); diff --git a/apps/scouting/frontend/src/scouter/components/StartMatchLocallyButton.tsx b/apps/scouting/frontend/src/scouter/components/StartMatchLocallyButton.tsx new file mode 100644 index 00000000..ec50e592 --- /dev/null +++ b/apps/scouting/frontend/src/scouter/components/StartMatchLocallyButton.tsx @@ -0,0 +1,81 @@ +// בס"ד +import type React from "react"; +import { useMemo } from "react"; +import { useMatchTimer } from "../hooks/useMatchTimer"; // adjust path + +const MILLISECONDS_IN_A_SECOND = 1000; +const SECONDS_IN_A_MINUTE = 60; +const ITERATION_PERIOD_MS = 10; +const DECIMAL_PLACES = 2; +const DECIMAL_PLACES_MILLISECONDS = 3; + +interface StartMatchLocallyButtonProps { + disabled: boolean; +} + +const StartMatchLocallyButton: React.FC = ({ + disabled, +}) => { + const { isRunning, elapsedMs, start, stop, reset } = + useMatchTimer(ITERATION_PERIOD_MS); + + const formattedTime = useMemo(() => { + const minutes = Math.floor( + elapsedMs / MILLISECONDS_IN_A_SECOND / SECONDS_IN_A_MINUTE, + ); + const seconds = + Math.floor(elapsedMs / MILLISECONDS_IN_A_SECOND) % SECONDS_IN_A_MINUTE; + const milliseconds = Math.floor(elapsedMs % MILLISECONDS_IN_A_SECOND); + + return `${String(minutes).padStart(DECIMAL_PLACES, "0")}:${String( + seconds, + ).padStart( + DECIMAL_PLACES, + "0", + )}:${String(milliseconds).padStart(DECIMAL_PLACES_MILLISECONDS, "0")}`; + }, [elapsedMs]); + + const handleClick = () => { + if (disabled) return; + if (isRunning) stop(); + else start(); + }; + + return ( +
+ + + +
+ ); +}; + +export default StartMatchLocallyButton; diff --git a/apps/scouting/frontend/src/scouter/components/bps-components/BpsBase.tsx b/apps/scouting/frontend/src/scouter/components/bps-components/BpsBase.tsx index 1ab43c1b..34cb15eb 100644 --- a/apps/scouting/frontend/src/scouter/components/bps-components/BpsBase.tsx +++ b/apps/scouting/frontend/src/scouter/components/bps-components/BpsBase.tsx @@ -30,6 +30,7 @@ function App() {