Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
5c61040
Ran - please work
RJ0907 Feb 27, 2026
4146470
Ran - needs a bit of work before finishing
RJ0907 Feb 27, 2026
a21535c
Ran - the basic version works
RJ0907 Mar 1, 2026
adb5711
Ran - no linting errots
RJ0907 Mar 1, 2026
63fbb24
Ran - styling
RJ0907 Mar 1, 2026
0773f77
Ran - done styling hopefully
RJ0907 Mar 1, 2026
1c13396
Ran - about to integrate the button
RJ0907 Mar 1, 2026
0f9e0fe
Ran - starting to integrate
RJ0907 Mar 1, 2026
9013613
Ran - integrated, but should be static
RJ0907 Mar 1, 2026
5c54191
Ran - works. needs styling and to support minuts
RJ0907 Mar 1, 2026
4853af0
Ran - added minutes measurement
RJ0907 Mar 2, 2026
41fde2c
Ran - now the time is globa;
RJ0907 Mar 3, 2026
532d192
Ran - this shit worksssssssssssssssssssssssss uga buga
RJ0907 Mar 3, 2026
fb02493
Ran - uga buga it worksssssssssssssssss
RJ0907 Mar 3, 2026
0b63e3a
Works however must be caliberated
RJ0907 Mar 3, 2026
8774412
Ran - linting shit
RJ0907 Mar 3, 2026
510e808
Ran - works=
RJ0907 Mar 3, 2026
0c9162e
Ran - linting
RJ0907 Mar 3, 2026
4eef889
Linting
RJ0907 Mar 3, 2026
6daa191
Ran - kept on linting
RJ0907 Mar 3, 2026
4d27a2b
Ran - almost doen
RJ0907 Mar 3, 2026
7b45a3c
Ran -almost done
RJ0907 Mar 3, 2026
a635d6d
Ran - everrything works. However, must be linted
RJ0907 Mar 3, 2026
7cc9337
Ran - works
RJ0907 Mar 9, 2026
0a2beba
Ran - workssssss
RJ0907 Mar 9, 2026
022b2dc
Ran - avoids crashing
RJ0907 Mar 9, 2026
69d954b
Ran - works and prettier
RJ0907 Mar 9, 2026
6254c5a
Ran - works
RJ0907 Mar 9, 2026
9ddb9ca
Ran - added a child fucntion for time and stuff
RJ0907 Mar 9, 2026
22f71c6
Ran - background
RJ0907 Mar 9, 2026
420e9cf
Ran - changing background
RJ0907 Mar 9, 2026
674cf2d
Ran - styling
RJ0907 Mar 9, 2026
74ed11e
Ran - changed the alert color
RJ0907 Mar 9, 2026
12fcc6f
Ran - fixed the game schedule constants
RJ0907 Mar 9, 2026
bbc40d5
Ran - changed a name to be more informative according to Yoni's CR
RJ0907 Mar 11, 2026
bfe74d8
Ran - added auto trasition from first tab
RJ0907 Mar 13, 2026
a8a26da
Resolved
RJ0907 Mar 13, 2026
b1a159c
Ran - changed a name to be more informative according to Yoni
RJ0907 Mar 13, 2026
1835cfa
Ran - mapping match's schedule according to shifts' names and not number
RJ0907 Mar 13, 2026
3a91248
Ran - scheduling each tab in the interface Tab
RJ0907 Mar 29, 2026
85de34e
Ran - using names
RJ0907 Mar 29, 2026
a39d82c
Ran - now works
RJ0907 Mar 29, 2026
55d5128
Yoni - added reset time to post match
YoniKiriaty Apr 3, 2026
ad4f260
Yoni - fixed conflicts
YoniKiriaty Apr 3, 2026
9d62e16
Yoni - working test matches
YoniKiriaty Apr 3, 2026
446274a
Yoni - added submit to bps
YoniKiriaty Apr 3, 2026
94de596
Yoni - fixed type imports
YoniKiriaty Apr 3, 2026
2c75148
Yoni - fixed some issues
YoniKiriaty Apr 3, 2026
3df248a
Yoni - fixed scored amount (interpolation)
YoniKiriaty Apr 3, 2026
def4cce
Yoni - filtered empty bpses
YoniKiriaty Apr 3, 2026
8f73ef7
Yoni - added data scouted
YoniKiriaty Apr 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions apps/scouting/backend/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
41 changes: 25 additions & 16 deletions apps/scouting/backend/src/fuel/calculations/fuel-averaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { convertPixelToCentimeters, distanceFromHub } from "@repo/rebuilt_map";
import { interpolateQuadratic } from "./interpolation";

interface ShotStats {
durationMilliseconds: number;
durationSeconds: number;
hubDistanceCentimeters: number;
}

Expand All @@ -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. */
Expand Down Expand Up @@ -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,
);
Expand All @@ -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"]) =>
Expand All @@ -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));

Expand All @@ -136,17 +136,20 @@ 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,
isPass: boolean,
sections: BPSEvent[],
): Partial<FuelObject> => {
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)),
),
Expand All @@ -156,7 +159,7 @@ export const calculateFuelByAveraging = (

const shotAmount = calculateBallAmount(
formattedSections.map((section) => section.shoot),
shotStats.durationMilliseconds,
shotStats.durationSeconds,
);

if (isPass) {
Expand All @@ -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,
Expand Down
31 changes: 19 additions & 12 deletions apps/scouting/backend/src/fuel/fuel-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,29 @@ const putDefaultsInFuel = (fuel: Partial<FuelObject>) => ({

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);
};
15 changes: 15 additions & 0 deletions apps/scouting/backend/src/routes/bps-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
1 change: 0 additions & 1 deletion apps/scouting/backend/src/routes/tba-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,6 @@ const getMatches = flow(
),
);


export const fetchCOPRS = (event: string) =>
pipe(
fetchTba(`/event/${event}/coprs`, eventOPRCodec),
Expand Down
4 changes: 2 additions & 2 deletions apps/scouting/backend/src/routes/teams-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
1 change: 1 addition & 0 deletions apps/scouting/frontend/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: gbscout
services:
frontend:
image: greenblitz/gbscout-frontend
build:
context: .
dockerfile: Dockerfile
Expand Down
3 changes: 2 additions & 1 deletion apps/scouting/frontend/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<StrictMode>
<BrowserRouter>
<App />
<App></App>
</BrowserRouter>
</StrictMode>,
);
Original file line number Diff line number Diff line change
@@ -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<StartMatchLocallyButtonProps> = ({
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 (
<div className="flex flex-col items-center gap-2">
<button
onClick={handleClick}
disabled={disabled}
className={`px-10 py-5 text-2xl rounded-full font-bold tracking-wider
transition-all duration-200 border-2
${
disabled
? "bg-gray-700 border-gray-600 text-gray-400 cursor-not-allowed"
: isRunning
? "bg-gradient-to-br from-green-900 to-black border-green-700 text-green-200 shadow-[0_0_25px_rgba(34,197,94,0.35)]"
: "bg-gradient-to-br from-black via-green-950 to-black border-green-800 text-green-300 hover:scale-105 hover:border-green-500 hover:shadow-[0_0_30px_rgba(34,197,94,0.45)] active:scale-95"
}
`}
>
{formattedTime}
</button>

<button
onClick={reset}
disabled={disabled}
className={`px-8 py-4 text-lg rounded-full font-semibold tracking-wide
transition-all duration-200 border
${
disabled
? "bg-gray-700 border-gray-600 text-gray-400 cursor-not-allowed"
: "bg-black border-green-900 text-green-300 hover:border-green-600 hover:bg-green-950 hover:scale-105 active:scale-95"
}
`}
>
Reset
</button>
</div>
);
};

export default StartMatchLocallyButton;
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ function App() {
<Video source={videoSource} playerRef={playerRef} />
</div>
<Counter playerRef={playerRef} />

</div>
</div>
);
Expand Down
Loading