Skip to content

Commit fe899bf

Browse files
committed
beta test
1 parent 13a7f68 commit fe899bf

12 files changed

Lines changed: 247 additions & 88 deletions

File tree

api/averageMatchScores.tsx

Lines changed: 87 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -210,45 +210,105 @@ export async function attachHourlyAverages(
210210
penalty: allValidMatches.length > 0 ? Number((allValidMatches.reduce((sum, row) => sum + (row.penalty ?? 0), 0) / allValidMatches.length).toFixed(2)) : 5,
211211
};
212212

213-
// Return matches with average data attached (but keep original match data intact)
214-
return matches.map((match) => {
215-
const window = getHourWindow(match.date);
216-
let avg = hourlyAverages.get(`${window.start}-${window.end}`);
213+
// Helper function to get smooth average using expanding time window
214+
const getSmoothAverage = (matchDate: string, previousAverage?: { points: number; tele: number; penalty: number }) => {
215+
const matchTime = new Date(matchDate).getTime();
217216

218-
// If no exact hour match, find the closest hour with actual data
219-
if (!avg) {
220-
const matchTime = new Date(match.date).getTime();
221-
let closestAvg = null;
222-
let closestDistance = Infinity;
217+
// Try expanding time windows until we find enough data
218+
const windowSizes = [1, 2, 4, 8, 12, 24]; // hours
219+
220+
for (const windowSize of windowSizes) {
221+
const startTime = new Date(matchTime - (windowSize * 60 * 60 * 1000));
222+
const endTime = new Date(matchTime + (windowSize * 60 * 60 * 1000));
223+
224+
const relevantMatches = data.filter(row => {
225+
const rowTime = new Date(row.date).getTime();
226+
return rowTime >= startTime.getTime() &&
227+
rowTime <= endTime.getTime() &&
228+
(row.totalPoints ?? 0) > 0;
229+
});
230+
231+
// Reduce minimum matches required for smaller windows to make it more responsive
232+
const minMatches = windowSize <= 2 ? 3 : windowSize <= 8 ? 5 : 8;
223233

224-
// Find the closest hour with actual data
225-
for (const [windowKey, avgData] of hourlyAverages) {
226-
const windowStart = windowKey.split('-')[0];
227-
try {
228-
const windowTime = new Date(windowStart).getTime();
229-
const distance = Math.abs(windowTime - matchTime);
230-
231-
if (distance < closestDistance) {
232-
closestDistance = distance;
233-
closestAvg = avgData;
234-
}
235-
} catch (e) {
236-
continue; // Skip invalid dates
234+
if (relevantMatches.length >= minMatches) {
235+
let totalPoints = 0;
236+
let totalTele = 0;
237+
let totalPenalty = 0;
238+
239+
for (const row of relevantMatches) {
240+
totalPoints += row.totalPoints ?? 0;
241+
totalTele += row.tele ?? 0;
242+
totalPenalty += row.penalty ?? 0;
237243
}
244+
245+
const newAverage = {
246+
points: Number((totalPoints / relevantMatches.length).toFixed(2)),
247+
tele: Number((totalTele / relevantMatches.length).toFixed(2)),
248+
penalty: Number((totalPenalty / relevantMatches.length).toFixed(2)),
249+
};
250+
251+
// If we have a previous average, smooth the transition (blend 70% new, 30% previous)
252+
if (previousAverage) {
253+
return {
254+
points: Number((0.7 * newAverage.points + 0.3 * previousAverage.points).toFixed(2)),
255+
tele: Number((0.7 * newAverage.tele + 0.3 * previousAverage.tele).toFixed(2)),
256+
penalty: Number((0.7 * newAverage.penalty + 0.3 * previousAverage.penalty).toFixed(2)),
257+
};
258+
}
259+
260+
return newAverage;
238261
}
239-
240-
// Use closest if found, otherwise use overall fallback
241-
avg = closestAvg || overallFallback;
242262
}
243263

264+
// If no good window found, use overall average (possibly blended with previous)
265+
if (previousAverage) {
266+
return {
267+
points: Number((0.8 * overallFallback.points + 0.2 * previousAverage.points).toFixed(2)),
268+
tele: Number((0.8 * overallFallback.tele + 0.2 * previousAverage.tele).toFixed(2)),
269+
penalty: Number((0.8 * overallFallback.penalty + 0.2 * previousAverage.penalty).toFixed(2)),
270+
};
271+
}
272+
273+
return overallFallback;
274+
};
275+
276+
// Create a smoothed average cache for better interpolation
277+
const sortedMatches = matches.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
278+
const smoothedAverages = new Map<string, { points: number; tele: number; penalty: number }>();
279+
280+
// Pre-calculate smooth averages for each unique time point with progressive smoothing
281+
const uniqueTimes = [...new Set(sortedMatches.map(m => m.date))];
282+
let previousAverage: { points: number; tele: number; penalty: number } | undefined;
283+
284+
for (const time of uniqueTimes) {
285+
const window = getHourWindow(time);
286+
const windowKey = `${window.start}-${window.end}`;
287+
288+
// First try exact hour match
289+
let avg = hourlyAverages.get(windowKey);
290+
291+
// If no exact match, use smooth averaging with previous context
292+
if (!avg) {
293+
avg = getSmoothAverage(time, previousAverage);
294+
}
295+
296+
smoothedAverages.set(time, avg);
297+
previousAverage = avg; // Store for next iteration
298+
}
299+
300+
// Return matches with smoothed average data
301+
return matches.map((match) => {
302+
const avg = smoothedAverages.get(match.date) || overallFallback;
303+
244304
// Return the original match data PLUS the average data for comparison
245305
return {
246306
...match,
247307
// Keep original values
248308
totalPoints: match.totalPoints ?? 0,
249309
tele: match.tele ?? 0,
250310
penalty: match.penalty ?? 0,
251-
// Add average data for graph comparison
311+
// Add smoothed average data for graph comparison
252312
averagePoints: avg.points,
253313
averageTele: avg.tele,
254314
averagePenalty: avg.penalty,
@@ -272,7 +332,7 @@ export function getAverageByMatchType(matches: AllianceInfo[]): MatchTypeAverage
272332
if (m.matchType === 'QUALIFICATION') {
273333
qualTotal += pts;
274334
qualCount++;
275-
} else if (m.matchType === 'PLAYOFF') {
335+
} else if (m.matchType === 'PLAYOFF' || (typeof m.matchType === 'string' && (m.matchType as string).includes('FINAL'))) {
276336
finalsTotal += pts;
277337
finalsCount++;
278338
}

api/dashboardInfo.tsx

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,38 +30,63 @@ function getCacheKey(year: SupportedYear, teamNumber: number): string {
3030
}
3131

3232
// ------------------- TEAMS -------------------
33-
export async function getAllTeams(year: SupportedYear): Promise<TeamInfo[] | null> {
33+
export async function getAllTeams(year: SupportedYear, retryCount = 0): Promise<TeamInfo[] | null> {
3434
if (allTeamsCache.has(year) && allTeamsCache.get(year)) {
35-
return allTeamsCache.get(year)!;
35+
const cachedData = allTeamsCache.get(year)!;
36+
if (cachedData.length > 0) {
37+
return cachedData;
38+
}
3639
}
3740

38-
const { data } = await supabase
39-
.from(getSeasonTable(year))
40-
.select('teamName, teamNumber, overallOPR, overallRank, location, autoOPR, teleOPR, endgameOPR, autoRank, teleRank, endgameRank')
41-
.order('overallRank', { ascending: true })
42-
.throwOnError();
41+
try {
42+
const { data, error } = await supabase
43+
.from(getSeasonTable(year))
44+
.select('teamName, teamNumber, overallOPR, overallRank, location, autoOPR, teleOPR, endgameOPR, autoRank, teleRank, endgameRank')
45+
.order('overallRank', { ascending: true });
4346

44-
const result = data.map(row => ({
45-
teamName: row.teamName || `Team ${row.teamNumber}`,
46-
teamNumber: row.teamNumber,
47-
overallOPR: row.overallOPR || 0,
48-
overallRank: row.overallRank || 0,
49-
location: row.location || 'N/A',
50-
autoOPR: row.autoOPR || 0,
51-
teleOPR: row.teleOPR || 0,
52-
endgameOPR: row.endgameOPR || 0,
53-
autoRank: row.autoRank || 0,
54-
teleRank: row.teleRank || 0,
55-
endgameRank: row.endgameRank || 0,
56-
founded: 'N/A',
57-
highestScore: '',
58-
website: 'None',
59-
sponsors: '',
60-
achievements: 'None This Season',
61-
}));
47+
if (error) {
48+
throw error;
49+
}
6250

63-
allTeamsCache.set(year, result);
64-
return result;
51+
if (!data || data.length === 0) {
52+
console.warn(`No teams data found for year ${year}`);
53+
return [];
54+
}
55+
56+
const result = data.map(row => ({
57+
teamName: row.teamName || `Team ${row.teamNumber}`,
58+
teamNumber: row.teamNumber,
59+
overallOPR: row.overallOPR || 0,
60+
overallRank: row.overallRank || 0,
61+
location: row.location || 'N/A',
62+
autoOPR: row.autoOPR || 0,
63+
teleOPR: row.teleOPR || 0,
64+
endgameOPR: row.endgameOPR || 0,
65+
autoRank: row.autoRank || 0,
66+
teleRank: row.teleRank || 0,
67+
endgameRank: row.endgameRank || 0,
68+
founded: 'N/A',
69+
highestScore: '',
70+
website: 'None',
71+
sponsors: '',
72+
achievements: 'None This Season',
73+
}));
74+
75+
allTeamsCache.set(year, result);
76+
return result;
77+
} catch (error) {
78+
console.error(`Error fetching teams for year ${year}:`, error);
79+
80+
// Retry up to 2 times with exponential backoff
81+
if (retryCount < 2) {
82+
const delay = Math.pow(2, retryCount) * 1000; // 1s, 2s delays
83+
await new Promise(resolve => setTimeout(resolve, delay));
84+
return getAllTeams(year, retryCount + 1);
85+
}
86+
87+
// Return empty array instead of null to prevent UI issues
88+
return [];
89+
}
6590
}
6691

6792
export async function getTeamInfo(teamNumber: number, year: SupportedYear): Promise<TeamInfo | null> {

app/dashboards/age.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ useEffect(() => {
351351
{matchTypeAverages && <EventPerformance matchType={matchTypeAverages}/>}
352352
{teamInfo && <EventScores teamInfo={teamInfo} />}
353353
{teamInfo && (
354-
<View style={{ minWidth: 550, flexShrink: 0 }}>
354+
<View style={{ minWidth: 550, flexShrink: 0, alignSelf: 'stretch' }}>
355355
<InfoBlock screenWidth={containerWidth} teamInfo={teamInfo} highScore={highestScore}/>
356356
</View>
357357
)}
@@ -458,6 +458,7 @@ const styles = StyleSheet.create({
458458
gap: 16,
459459
marginBottom: 20,
460460
flexDirection: 'row',
461+
alignItems: 'stretch', // This makes all child components have equal height
461462
},
462463
eventContainer: {
463464
marginBottom: -20,

app/dashboards/energize.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,6 @@ useEffect(() => {
179179
};
180180

181181
const getSeasonDisplayText = () => {
182-
// Map years to game names if you have them, otherwise just show the year
183182
const gameNames: Record<SupportedYear, string> = {
184183
2019: '2019 - Skystone',
185184
2020: '2020 - Ultimate Goal',
@@ -353,7 +352,7 @@ useEffect(() => {
353352
{matchTypeAverages && <EventPerformance matchType={matchTypeAverages}/>}
354353
{teamInfo && <EventScores teamInfo={teamInfo} />}
355354
{teamInfo && (
356-
<View style={{ minWidth: 550, flexShrink: 0 }}>
355+
<View style={{ minWidth: 550, flexShrink: 0, alignSelf: 'stretch' }}>
357356
<InfoBlock screenWidth={containerWidth} teamInfo={teamInfo} highScore={highestScore}/>
358357
</View>
359358
)}
@@ -460,6 +459,7 @@ const styles = StyleSheet.create({
460459
gap: 16,
461460
marginBottom: 20,
462461
flexDirection: 'row',
462+
alignItems: 'stretch', // This makes all child components have equal height
463463
},
464464
eventContainer: {
465465
marginBottom: -20,

app/dashboards/forward.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ useEffect(() => {
351351
{matchTypeAverages && <EventPerformance matchType={matchTypeAverages}/>}
352352
{teamInfo && <EventScores teamInfo={teamInfo} />}
353353
{teamInfo && (
354-
<View style={{ minWidth: 550, flexShrink: 0 }}>
354+
<View style={{ minWidth: 550, flexShrink: 0, alignSelf: 'stretch' }}>
355355
<InfoBlock screenWidth={containerWidth} teamInfo={teamInfo} highScore={highestScore}/>
356356
</View>
357357
)}
@@ -458,6 +458,7 @@ const styles = StyleSheet.create({
458458
gap: 16,
459459
marginBottom: 20,
460460
flexDirection: 'row',
461+
alignItems: 'stretch', // This makes all child components have equal height
461462
},
462463
eventContainer: {
463464
marginBottom: -20,

app/dashboards/gameChangers.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ useEffect(() => {
351351
{matchTypeAverages && <EventPerformance matchType={matchTypeAverages}/>}
352352
{teamInfo && <EventScores teamInfo={teamInfo} />}
353353
{teamInfo && (
354-
<View style={{ minWidth: 550, flexShrink: 0 }}>
354+
<View style={{ minWidth: 550, flexShrink: 0, alignSelf: 'stretch' }}>
355355
<InfoBlock screenWidth={containerWidth} teamInfo={teamInfo} highScore={highestScore}/>
356356
</View>
357357
)}
@@ -458,6 +458,7 @@ const styles = StyleSheet.create({
458458
gap: 16,
459459
marginBottom: 20,
460460
flexDirection: 'row',
461+
alignItems: 'stretch', // This makes all child components have equal height
461462
},
462463
eventContainer: {
463464
marginBottom: -20,

app/dashboards/inShow.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ useEffect(() => {
351351
{matchTypeAverages && <EventPerformance matchType={matchTypeAverages}/>}
352352
{teamInfo && <EventScores teamInfo={teamInfo} />}
353353
{teamInfo && (
354-
<View style={{ minWidth: 550, flexShrink: 0 }}>
354+
<View style={{ minWidth: 550, flexShrink: 0, alignSelf: 'stretch' }}>
355355
<InfoBlock screenWidth={containerWidth} teamInfo={teamInfo} highScore={highestScore}/>
356356
</View>
357357
)}
@@ -458,6 +458,7 @@ const styles = StyleSheet.create({
458458
gap: 16,
459459
marginBottom: 20,
460460
flexDirection: 'row',
461+
alignItems: 'stretch', // This makes all child components have equal height
461462
},
462463
eventContainer: {
463464
marginBottom: -20,

app/dashboards/intothedeep.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ useEffect(() => {
351351
{matchTypeAverages && <EventPerformance matchType={matchTypeAverages}/>}
352352
{teamInfo && <EventScores teamInfo={teamInfo} />}
353353
{teamInfo && (
354-
<View style={{ minWidth: 550, flexShrink: 0 }}>
354+
<View style={{ minWidth: 550, flexShrink: 0, alignSelf: 'stretch' }}>
355355
<InfoBlock screenWidth={containerWidth} teamInfo={teamInfo} highScore={highestScore}/>
356356
</View>
357357
)}
@@ -458,6 +458,7 @@ const styles = StyleSheet.create({
458458
gap: 16,
459459
marginBottom: 20,
460460
flexDirection: 'row',
461+
alignItems: 'stretch', // This makes all child components have equal height
461462
},
462463
eventContainer: {
463464
marginBottom: -20,

app/dashboards/rise.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ useEffect(() => {
351351
{matchTypeAverages && <EventPerformance matchType={matchTypeAverages}/>}
352352
{teamInfo && <EventScores teamInfo={teamInfo} />}
353353
{teamInfo && (
354-
<View style={{ minWidth: 550, flexShrink: 0 }}>
354+
<View style={{ minWidth: 550, flexShrink: 0, alignSelf: 'stretch' }}>
355355
<InfoBlock screenWidth={containerWidth} teamInfo={teamInfo} highScore={highestScore}/>
356356
</View>
357357
)}
@@ -458,6 +458,7 @@ const styles = StyleSheet.create({
458458
gap: 16,
459459
marginBottom: 20,
460460
flexDirection: 'row',
461+
alignItems: 'stretch', // This makes all child components have equal height
461462
},
462463
eventContainer: {
463464
marginBottom: -20,

components/graphs/overtimeGraph.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const UserGraphSection = ({ screenWidth, teamInfo, matches, averages, wins }: Us
5353

5454
const matchData = matches
5555
?.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime())
56-
.map((match, index) => {
56+
.map((match, index, sortedMatches) => {
5757
const activeKey = tabKeyMap[activeTab];
5858

5959
// Get the corresponding average key
@@ -62,10 +62,21 @@ const UserGraphSection = ({ screenWidth, teamInfo, matches, averages, wins }: Us
6262
activeKey === 'penalty' ? 'averagePenalty' :
6363
'averagePoints';
6464

65+
let averageValue = Number(match[averageKey as keyof AllianceInfo] ?? 0);
66+
67+
// Apply smoothing to make the average line more fluid
68+
if (index > 0 && index < sortedMatches.length - 1) {
69+
const prevAvg = Number(sortedMatches[index - 1][averageKey as keyof AllianceInfo] ?? averageValue);
70+
const nextAvg = Number(sortedMatches[index + 1][averageKey as keyof AllianceInfo] ?? averageValue);
71+
72+
// Simple moving average smoothing (30% of neighboring values)
73+
averageValue = Number((0.4 * averageValue + 0.3 * prevAvg + 0.3 * nextAvg).toFixed(2));
74+
}
75+
6576
return {
6677
name: `M${index + 1}`,
6778
current: match[activeKey] ?? 0,
68-
average: match[averageKey as keyof AllianceInfo] ?? 0,
79+
average: averageValue,
6980
};
7081
}) ?? [];
7182

0 commit comments

Comments
 (0)