|
27 | 27 | import { format } from 'date-fns'; |
28 | 28 | import { tweened } from 'svelte/motion'; |
29 | 29 | import { cubicOut } from 'svelte/easing'; |
30 | | - import { TrendingUp, Users, Target, Zap } from 'lucide-svelte'; |
| 30 | + import { TrendingUp, Users, Target, Zap, Shield } from 'lucide-svelte'; |
31 | 31 | import { supabase, hasValidSupabaseConfig, getRecentMatches } from '../lib/supabase'; |
32 | 32 | import { apiService } from '../services/apiService'; |
33 | 33 | import EnhancedVisualizations from './EnhancedVisualizations.svelte'; |
|
61 | 61 | let averageGoals = tweened(0, { duration: 1200, easing: cubicOut }); |
62 | 62 |
|
63 | 63 | // Statistics |
64 | | - let homeWinPercentage = 0; |
65 | | - let mostCommonScore = '0-0'; |
| 64 | + let bestDefenseTeam = ''; |
| 65 | + let bestDefenseGoalsConceded = 0; |
66 | 66 | let topScoringTeam = ''; |
67 | 67 | let totalTeams = 0; |
68 | 68 | let highestScoringMatch = { goals: 0, score: '0-0', homeTeam: '', awayTeam: '', date: '' }; |
|
101 | 101 | totalMatches.set(dashboardData.stats.total_matches); |
102 | 102 | totalGoals.set(dashboardData.stats.total_goals); |
103 | 103 | averageGoals.set(dashboardData.stats.avg_goals_per_match); |
104 | | - homeWinPercentage = dashboardData.stats.home_win_percentage; |
105 | 104 | totalTeams = dashboardData.stats.total_teams; |
106 | 105 |
|
107 | 106 | // Set matches for visualizations |
|
140 | 139 | awayTeam: highestMatchAwayTeam, |
141 | 140 | date: highestMatchDate |
142 | 141 | }; |
| 142 | +
|
| 143 | + // Calculate best defense (team with fewest goals conceded) |
| 144 | + const teamDefenseStats: Record<string, {conceded: number, matches: number}> = {}; |
| 145 | + dashboardData.recent_matches.forEach(m => { |
| 146 | + const homeTeam = m.home_team; |
| 147 | + const awayTeam = m.away_team; |
| 148 | +
|
| 149 | + if (!teamDefenseStats[homeTeam]) teamDefenseStats[homeTeam] = {conceded: 0, matches: 0}; |
| 150 | + if (!teamDefenseStats[awayTeam]) teamDefenseStats[awayTeam] = {conceded: 0, matches: 0}; |
| 151 | +
|
| 152 | + teamDefenseStats[homeTeam].conceded += (m.away_score || 0); |
| 153 | + teamDefenseStats[homeTeam].matches += 1; |
| 154 | + teamDefenseStats[awayTeam].conceded += (m.home_score || 0); |
| 155 | + teamDefenseStats[awayTeam].matches += 1; |
| 156 | + }); |
| 157 | +
|
| 158 | + let minGoalsConceded = Infinity; |
| 159 | + Object.entries(teamDefenseStats).forEach(([team, stats]) => { |
| 160 | + if (stats.matches >= 5 && stats.conceded < minGoalsConceded) { |
| 161 | + minGoalsConceded = stats.conceded; |
| 162 | + bestDefenseTeam = team; |
| 163 | + bestDefenseGoalsConceded = stats.conceded; |
| 164 | + } |
| 165 | + }); |
143 | 166 | } |
144 | 167 |
|
145 | 168 | console.log('Dashboard loaded from API:', { |
|
229 | 252 | const avgGoals = validMatches.length > 0 ? sampleGoals / validMatches.length : 0; |
230 | 253 | averageGoals.set(avgGoals); |
231 | 254 |
|
232 | | - // Home win percentage from European data |
233 | | - const homeWins = validMatches.filter(m => m.result === 'H').length; |
234 | | - homeWinPercentage = validMatches.length > 0 |
235 | | - ? (homeWins / validMatches.length) * 100 |
236 | | - : 0; |
237 | | -
|
238 | | - // Most common score from European leagues |
239 | | - const scoreMap = new Map<string, number>(); |
| 255 | + // Calculate best defense (team with fewest goals conceded) |
| 256 | + const teamDefenseStats: Record<string, {conceded: number, matches: number}> = {}; |
240 | 257 | validMatches.forEach(m => { |
241 | | - const score = `${m.home_score}-${m.away_score}`; |
242 | | - scoreMap.set(score, (scoreMap.get(score) || 0) + 1); |
| 258 | + const homeTeam = m.home_team; |
| 259 | + const awayTeam = m.away_team; |
| 260 | +
|
| 261 | + if (!teamDefenseStats[homeTeam]) teamDefenseStats[homeTeam] = {conceded: 0, matches: 0}; |
| 262 | + if (!teamDefenseStats[awayTeam]) teamDefenseStats[awayTeam] = {conceded: 0, matches: 0}; |
| 263 | +
|
| 264 | + teamDefenseStats[homeTeam].conceded += (m.away_score || 0); |
| 265 | + teamDefenseStats[homeTeam].matches += 1; |
| 266 | + teamDefenseStats[awayTeam].conceded += (m.home_score || 0); |
| 267 | + teamDefenseStats[awayTeam].matches += 1; |
243 | 268 | }); |
244 | 269 |
|
245 | | - let maxCount = 0; |
246 | | - scoreMap.forEach((count, score) => { |
247 | | - if (count > maxCount) { |
248 | | - maxCount = count; |
249 | | - mostCommonScore = score; |
| 270 | + let minGoalsConceded = Infinity; |
| 271 | + Object.entries(teamDefenseStats).forEach(([team, stats]) => { |
| 272 | + if (stats.matches >= 5 && stats.conceded < minGoalsConceded) { |
| 273 | + minGoalsConceded = stats.conceded; |
| 274 | + bestDefenseTeam = team; |
| 275 | + bestDefenseGoalsConceded = stats.conceded; |
250 | 276 | } |
251 | 277 | }); |
252 | 278 |
|
|
338 | 364 | totalMatches.set(freshData.stats.total_matches); |
339 | 365 | totalGoals.set(freshData.stats.total_goals); |
340 | 366 | averageGoals.set(freshData.stats.avg_goals_per_match); |
341 | | - homeWinPercentage = freshData.stats.home_win_percentage; |
342 | 367 | totalTeams = freshData.stats.total_teams; |
343 | 368 |
|
344 | 369 | // Update goalsChart with new league data |
|
372 | 397 | date: highestMatchDate |
373 | 398 | }; |
374 | 399 |
|
| 400 | + // Recalculate best defense for the selected league |
| 401 | + const teamDefenseStats: Record<string, {conceded: number, matches: number}> = {}; |
| 402 | + freshData.recent_matches.forEach(m => { |
| 403 | + const homeTeam = m.home_team; |
| 404 | + const awayTeam = m.away_team; |
| 405 | +
|
| 406 | + if (!teamDefenseStats[homeTeam]) teamDefenseStats[homeTeam] = {conceded: 0, matches: 0}; |
| 407 | + if (!teamDefenseStats[awayTeam]) teamDefenseStats[awayTeam] = {conceded: 0, matches: 0}; |
| 408 | +
|
| 409 | + teamDefenseStats[homeTeam].conceded += (m.away_score || 0); |
| 410 | + teamDefenseStats[homeTeam].matches += 1; |
| 411 | + teamDefenseStats[awayTeam].conceded += (m.home_score || 0); |
| 412 | + teamDefenseStats[awayTeam].matches += 1; |
| 413 | + }); |
| 414 | +
|
| 415 | + let minGoalsConceded = Infinity; |
| 416 | + Object.entries(teamDefenseStats).forEach(([team, stats]) => { |
| 417 | + if (stats.matches >= 5 && stats.conceded < minGoalsConceded) { |
| 418 | + minGoalsConceded = stats.conceded; |
| 419 | + bestDefenseTeam = team; |
| 420 | + bestDefenseGoalsConceded = stats.conceded; |
| 421 | + } |
| 422 | + }); |
| 423 | +
|
375 | 424 | console.log('Dashboard refreshed with league data:', { |
376 | 425 | league: league || 'all', |
377 | 426 | matches: freshData.recent_matches.length, |
|
524 | 573 | </div> |
525 | 574 | </div> |
526 | 575 |
|
527 | | - <!-- How We Analyse --> |
| 576 | + <!-- How This Was All Put Together --> |
528 | 577 | <div class="bg-white dark:bg-slate-900 rounded-xl p-6 shadow-lg"> |
529 | 578 | <h3 class="text-lg font-semibold text-slate-800 dark:text-slate-200 mb-4 flex items-center gap-2"> |
530 | 579 | <Target class="w-5 h-5 text-blue-600 dark:text-green-500" /> |
531 | | - How We Analyse |
| 580 | + How This Was All Put Together |
532 | 581 | </h3> |
533 | 582 | <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"> |
534 | 583 | <div class="text-center p-4 bg-slate-50 dark:bg-slate-800/50 rounded-lg"> |
535 | 584 | <div class="w-10 h-10 bg-blue-100 dark:bg-blue-900/30 rounded-lg mx-auto mb-3 flex items-center justify-center"> |
536 | 585 | <Zap class="w-5 h-5 text-blue-600" /> |
537 | 586 | </div> |
538 | 587 | <h4 class="font-semibold text-sm mb-1">RAG System</h4> |
539 | | - <p class="text-xs text-slate-600 dark:text-slate-400">Natural language to SQL</p> |
| 588 | + <p class="text-xs text-slate-600 dark:text-slate-400">Natural language to SQL using OpenAI API with GPT-4, custom backend APIs using FastAPI with Python scripts</p> |
540 | 589 | </div> |
541 | 590 | <div class="text-center p-4 bg-slate-50 dark:bg-slate-800/50 rounded-lg"> |
542 | 591 | <div class="w-10 h-10 bg-indigo-100 dark:bg-green-900/30 rounded-lg mx-auto mb-3 flex items-center justify-center"> |
543 | 592 | <TrendingUp class="w-5 h-5 text-indigo-600 dark:text-green-600" /> |
544 | 593 | </div> |
545 | | - <h4 class="font-semibold text-sm mb-1">Pattern Discovery</h4> |
546 | | - <p class="text-xs text-slate-600 dark:text-slate-400">Automatic anomaly detection</p> |
| 594 | + <h4 class="font-semibold text-sm mb-1">Data Source</h4> |
| 595 | + <p class="text-xs text-slate-600 dark:text-slate-400">Supabase Vector Embeddings, custom database configurations created using open source data from football-data.co.uk</p> |
547 | 596 | </div> |
548 | 597 | <div class="text-center p-4 bg-slate-50 dark:bg-slate-800/50 rounded-lg"> |
549 | 598 | <div class="w-10 h-10 bg-purple-100 dark:bg-purple-900/30 rounded-lg mx-auto mb-3 flex items-center justify-center"> |
550 | 599 | <Users class="w-5 h-5 text-purple-600" /> |
551 | 600 | </div> |
552 | | - <h4 class="font-semibold text-sm mb-1">Vector Embeddings</h4> |
553 | | - <p class="text-xs text-slate-600 dark:text-slate-400">Semantic database search</p> |
| 601 | + <h4 class="font-semibold text-sm mb-1">Pattern Discovery</h4> |
| 602 | + <p class="text-xs text-slate-600 dark:text-slate-400">Using smart Python visualisations and charts (Chart.js, Plotly, Matplotlib)</p> |
554 | 603 | </div> |
555 | 604 | <div class="text-center p-4 bg-slate-50 dark:bg-slate-800/50 rounded-lg"> |
556 | 605 | <div class="w-10 h-10 bg-amber-100 dark:bg-amber-900/30 rounded-lg mx-auto mb-3 flex items-center justify-center"> |
557 | 606 | <Target class="w-5 h-5 text-amber-600" /> |
558 | 607 | </div> |
559 | | - <h4 class="font-semibold text-sm mb-1">Statistical Analysis</h4> |
560 | | - <p class="text-xs text-slate-600 dark:text-slate-400">Historical trend analysis</p> |
| 608 | + <h4 class="font-semibold text-sm mb-1">Tech Stack</h4> |
| 609 | + <p class="text-xs text-slate-600 dark:text-slate-400">React, Svelte, Docker, Supabase, Render, Node.js</p> |
561 | 610 | </div> |
562 | 611 | </div> |
563 | 612 | </div> |
|
636 | 685 |
|
637 | 686 | <div class="bg-white dark:bg-slate-900 rounded-xl p-3 sm:p-6 shadow-lg hover:shadow-xl transition-shadow"> |
638 | 687 | <div class="flex items-start justify-between mb-2 sm:mb-4"> |
639 | | - <div class="bg-sky-500/10 dark:bg-sky-500/20 p-2 sm:p-3 rounded-lg"> |
640 | | - <Users class="w-4 h-4 sm:w-6 sm:h-6 text-sky-600 dark:text-sky-400" /> |
| 688 | + <div class="bg-purple-500/10 dark:bg-purple-500/20 p-2 sm:p-3 rounded-lg"> |
| 689 | + <Shield class="w-4 h-4 sm:w-6 sm:h-6 text-purple-600 dark:text-purple-400" /> |
641 | 690 | </div> |
642 | 691 | </div> |
643 | 692 | <div class="text-xs sm:text-sm font-medium text-slate-600 dark:text-slate-400 mb-1"> |
644 | | - Home Win % |
| 693 | + Best Defense |
645 | 694 | </div> |
646 | 695 | <div class="text-lg sm:text-2xl font-bold text-slate-900 dark:text-white mb-1"> |
647 | | - {homeWinPercentage.toFixed(1)}% |
| 696 | + {bestDefenseTeam || 'N/A'} |
648 | 697 | </div> |
649 | 698 | <div class="text-xs text-slate-500 dark:text-slate-500 leading-tight"> |
650 | | - {mostCommonScore} most common |
| 699 | + {bestDefenseGoalsConceded} goals conceded |
651 | 700 | </div> |
652 | 701 | </div> |
653 | 702 |
|
|
0 commit comments