Compare commits

...

5 Commits

Author SHA1 Message Date
Alby96 0d3769db79 Aggiunta download CSV supplementari e restyling impostazioni
- Introdotto supporto per download dati supplementari Calcio (statistiche giocatori, squadre, marcatori, assist, cartellini, rose, allenatori, trasferimenti) con esportazione CSV separati
- Nuova classe SupplementaryDataExporter e client API dedicati per ogni endpoint
- UI impostazioni completamente rinnovata: selezione dinamica endpoint/supplementari, sorgente dati (API/CSV), parametri avanzati
- Migliorata dashboard con stat card, header e sidebar moderni
- Gestione errori globali migliorata
- Refactoring: rimosso codice VirtualFootball/WebView2, unificato caricamento CSV, gestione timezone con ComboBox IANA
- Aggiornato .gitignore e aggiunto template appsettings per sicurezza/configurazione
2026-04-14 18:07:31 +02:00
Alby96 11b299548c Rimuove tutte le configurazioni da appsettings.json
Sono state eliminate le stringhe di connessione ai database e le chiavi di configurazione per l'API di Football dal file appsettings.json.
2026-04-14 18:05:32 +02:00
Alby96 d6db312f73 Aggiunte opzioni avanzate download calcio API-Football
Introdotta la classe FootballDownloadOptions per la gestione centralizzata delle opzioni di download e filtri. L’utente può ora scegliere quali endpoint scaricare (partite, quote, previsioni, classifiche, H2H, eventi, formazioni, statistiche, infortuni) e impostare parametri avanzati (ID bookmaker, pagine quote, leghe, delay API, quota minima residua, ecc.) tramite la nuova sezione UI.

Rifattorizzata la logica di download per supportare il caricamento condizionale e sequenziale degli endpoint, con progress bar proporzionale e callback di stato dettagliate. Aggiunti nuovi client API per endpoint aggiuntivi e metodi di enrichment per arricchire dinamicamente la tabella delle partite.

Aggiornate le impostazioni utente e la UI per supportare tutte le nuove opzioni. Aggiunto il controllo della quota API residua prima del download. Inseriti nuovi file sorgente per i client API mancanti e la gestione delle opzioni.
2026-04-07 18:18:06 +02:00
Alby96 51568df264 Eliminazione file release notes 2026-04-07 15:07:58 +02:00
Alby96 89138e2b8f Nessuna modifica ai file di codice
Non sono state apportate modifiche al codice; il diff riguarda solo il file RELEASE_NOTES.md senza alcuna aggiunta o rimozione di righe.
2026-04-07 15:06:59 +02:00
29 changed files with 3758 additions and 773 deletions
+6
View File
@@ -414,3 +414,9 @@ FodyWeavers.xsd
# Built Visual Studio Code Extensions
*.vsix
# Secrets / credentials - never commit
appsettings.json
appsettings.*.json
!appsettings.template.json
settings.ini
@@ -1,8 +1,38 @@
using System;
using System.Windows;
using System.Windows.Threading;
namespace HorseRacingPredictor
{
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
DispatcherUnhandledException += App_DispatcherUnhandledException;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
base.OnStartup(e);
}
private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
MessageBox.Show(
$"Errore non gestito:\n\n{e.Exception.GetType().Name}: {e.Exception.Message}\n\n{e.Exception.StackTrace}",
"Errore applicazione",
MessageBoxButton.OK,
MessageBoxImage.Error);
e.Handled = true;
}
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
if (e.ExceptionObject is Exception ex)
{
MessageBox.Show(
$"Errore fatale:\n\n{ex.GetType().Name}: {ex.Message}\n\n{ex.StackTrace}",
"Errore fatale",
MessageBoxButton.OK,
MessageBoxImage.Error);
}
}
}
}
@@ -27,7 +27,6 @@
<PackageReference Include="Microsoft.ML.CpuMath" Version="6.0.0-preview.26160.2" />
<PackageReference Include="Microsoft.ML.DataView" Version="6.0.0-preview.26160.2" />
<PackageReference Include="Microsoft.ML.FastTree" Version="6.0.0-preview.26160.2" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3908-prerelease" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.5-beta1" />
<PackageReference Include="RestSharp" Version="114.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.5" />
@@ -54,4 +53,9 @@
<ItemGroup Label="EmbeddedResource items now included by globbing that were not in the original project file">
<EmbeddedResource Remove="Main.resx" />
</ItemGroup>
<ItemGroup>
<Content Include="HorseRacingPredictor\appsettings.json" Link="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
@@ -0,0 +1,26 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "coachs" dell'API-Football.
/// Restituisce informazioni sugli allenatori.
/// </summary>
internal class Coaches : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "coachs";
public RestResponse GetByTeam(int teamId)
{
try
{
return ExecuteRequest($"{Endpoint}?team={teamId}", ApiTypes.Teams);
}
catch (Exception ex)
{
throw new Exception($"Errore recupero allenatore team {teamId}: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,29 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "fixtures/events" dell'API-Football.
/// Restituisce gli eventi di una partita (gol, cartellini, sostituzioni, VAR).
/// </summary>
internal class Events : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "fixtures/events";
/// <summary>
/// Ottiene tutti gli eventi per una partita
/// </summary>
public RestResponse GetEventsByFixture(int fixtureId)
{
try
{
return ExecuteRequest($"{Endpoint}?fixture={fixtureId}", ApiTypes.Fixtures);
}
catch (Exception ex)
{
throw new Exception($"Errore durante il recupero degli eventi per la partita {fixtureId}: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,29 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "fixtures/statistics" dell'API-Football.
/// Restituisce le statistiche di una partita (possesso, tiri, falli, ecc.).
/// </summary>
internal class FixtureStatistics : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "fixtures/statistics";
/// <summary>
/// Ottiene le statistiche per una partita
/// </summary>
public RestResponse GetStatisticsByFixture(int fixtureId)
{
try
{
return ExecuteRequest($"{Endpoint}?fixture={fixtureId}", ApiTypes.Fixtures);
}
catch (Exception ex)
{
throw new Exception($"Errore durante il recupero delle statistiche per la partita {fixtureId}: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,32 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "fixtures/headtohead" dell'API-Football.
/// Restituisce lo storico degli scontri diretti tra due squadre.
/// </summary>
internal class HeadToHead : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "fixtures/headtohead";
/// <summary>
/// Ottiene gli scontri diretti tra due squadre
/// </summary>
/// <param name="teamId1">ID della prima squadra</param>
/// <param name="teamId2">ID della seconda squadra</param>
/// <param name="last">Numero di ultimi scontri da recuperare (default 5)</param>
public RestResponse GetH2H(int teamId1, int teamId2, int last = 5)
{
try
{
return ExecuteRequest($"{Endpoint}?h2h={teamId1}-{teamId2}&last={last}", ApiTypes.Fixtures);
}
catch (Exception ex)
{
throw new Exception($"Errore durante il recupero H2H per {teamId1} vs {teamId2}: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,60 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "injuries" dell'API-Football.
/// Restituisce la lista di giocatori infortunati o squalificati.
/// </summary>
internal class Injuries : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "injuries";
/// <summary>
/// Ottiene gli infortunati per una data specifica
/// </summary>
public RestResponse GetInjuriesByDate(DateTime date)
{
try
{
string dateStr = date.ToString("yyyy-MM-dd");
return ExecuteRequest($"{Endpoint}?date={dateStr}", ApiTypes.Fixtures);
}
catch (Exception ex)
{
throw new Exception($"Errore durante il recupero degli infortunati per la data {date.ToShortDateString()}: {ex.Message}", ex);
}
}
/// <summary>
/// Ottiene gli infortunati per una partita specifica
/// </summary>
public RestResponse GetInjuriesByFixture(int fixtureId)
{
try
{
return ExecuteRequest($"{Endpoint}?fixture={fixtureId}", ApiTypes.Fixtures);
}
catch (Exception ex)
{
throw new Exception($"Errore durante il recupero degli infortunati per la partita {fixtureId}: {ex.Message}", ex);
}
}
/// <summary>
/// Ottiene gli infortunati per una lega e stagione
/// </summary>
public RestResponse GetInjuriesByLeague(int leagueId, int season)
{
try
{
return ExecuteRequest($"{Endpoint}?league={leagueId}&season={season}", ApiTypes.Fixtures);
}
catch (Exception ex)
{
throw new Exception($"Errore durante il recupero degli infortunati per lega {leagueId}, stagione {season}: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,29 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "fixtures/lineups" dell'API-Football.
/// Restituisce formazioni, modulo e titolari di una partita.
/// </summary>
internal class Lineups : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "fixtures/lineups";
/// <summary>
/// Ottiene le formazioni per una partita
/// </summary>
public RestResponse GetLineupsByFixture(int fixtureId)
{
try
{
return ExecuteRequest($"{Endpoint}?fixture={fixtureId}", ApiTypes.Fixtures);
}
catch (Exception ex)
{
throw new Exception($"Errore durante il recupero delle formazioni per la partita {fixtureId}: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,59 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "players" dell'API-Football.
/// Restituisce statistiche dettagliate dei giocatori per squadra/lega/stagione.
/// </summary>
internal class PlayerStatistics : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "players";
/// <summary>
/// Ottiene le statistiche dei giocatori per squadra e stagione (paginato, 20 per pagina).
/// </summary>
public RestResponse GetByTeamAndSeason(int teamId, int season, int page = 1)
{
try
{
return ExecuteRequest($"{Endpoint}?team={teamId}&season={season}&page={page}", ApiTypes.Teams);
}
catch (Exception ex)
{
throw new Exception($"Errore recupero statistiche giocatori team {teamId}, stagione {season}, pagina {page}: {ex.Message}", ex);
}
}
/// <summary>
/// Ottiene le statistiche di un singolo giocatore per stagione.
/// </summary>
public RestResponse GetByPlayerAndSeason(int playerId, int season)
{
try
{
return ExecuteRequest($"{Endpoint}?id={playerId}&season={season}", ApiTypes.Teams);
}
catch (Exception ex)
{
throw new Exception($"Errore recupero statistiche giocatore {playerId}, stagione {season}: {ex.Message}", ex);
}
}
/// <summary>
/// Ottiene le statistiche dei giocatori per lega e stagione (paginato, 20 per pagina).
/// </summary>
public RestResponse GetByLeagueAndSeason(int leagueId, int season, int page = 1)
{
try
{
return ExecuteRequest($"{Endpoint}?league={leagueId}&season={season}&page={page}", ApiTypes.Leagues);
}
catch (Exception ex)
{
throw new Exception($"Errore recupero statistiche giocatori lega {leagueId}, stagione {season}, pagina {page}: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,38 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "sidelined" dell'API-Football.
/// Restituisce lo storico di indisponibilità/squalifiche di un giocatore o allenatore.
/// </summary>
internal class Sidelined : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "sidelined";
public RestResponse GetByPlayer(int playerId)
{
try
{
return ExecuteRequest($"{Endpoint}?player={playerId}", ApiTypes.Teams);
}
catch (Exception ex)
{
throw new Exception($"Errore recupero sidelined giocatore {playerId}: {ex.Message}", ex);
}
}
public RestResponse GetByCoach(int coachId)
{
try
{
return ExecuteRequest($"{Endpoint}?coach={coachId}", ApiTypes.Teams);
}
catch (Exception ex)
{
throw new Exception($"Errore recupero sidelined allenatore {coachId}: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,26 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "players/squads" dell'API-Football.
/// Restituisce la rosa corrente di una squadra.
/// </summary>
internal class Squads : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "players/squads";
public RestResponse GetSquad(int teamId)
{
try
{
return ExecuteRequest($"{Endpoint}?team={teamId}", ApiTypes.Teams);
}
catch (Exception ex)
{
throw new Exception($"Errore recupero rosa team {teamId}: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,44 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "standings" dell'API-Football.
/// Restituisce la classifica di un campionato per una stagione.
/// </summary>
internal class Standings : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "standings";
/// <summary>
/// Ottiene la classifica per una lega e una stagione
/// </summary>
public RestResponse GetStandings(int leagueId, int season)
{
try
{
return ExecuteRequest($"{Endpoint}?league={leagueId}&season={season}", ApiTypes.Leagues);
}
catch (Exception ex)
{
throw new Exception($"Errore durante il recupero della classifica per lega {leagueId}, stagione {season}: {ex.Message}", ex);
}
}
/// <summary>
/// Ottiene la classifica per un team e una stagione
/// </summary>
public RestResponse GetStandingsByTeam(int teamId, int season)
{
try
{
return ExecuteRequest($"{Endpoint}?team={teamId}&season={season}", ApiTypes.Teams);
}
catch (Exception ex)
{
throw new Exception($"Errore durante il recupero della classifica per team {teamId}, stagione {season}: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,30 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "status" dell'API-Football.
/// Restituisce la quota residua giornaliera e le informazioni sull'account.
/// Non conta come chiamata nella quota giornaliera.
/// </summary>
internal class Status : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "status";
/// <summary>
/// Ottiene lo stato dell'account e la quota residua
/// </summary>
public RestResponse GetStatus()
{
try
{
return ExecuteRequest(Endpoint);
}
catch (Exception ex)
{
throw new Exception($"Errore durante il recupero dello stato API: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,33 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "teams/statistics" dell'API-Football.
/// Restituisce statistiche aggregate di una squadra per campionato e stagione
/// (forma, gol, clean sheet, penalty, cartellini, formazioni usate, ecc.).
/// </summary>
internal class TeamStatistics : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "teams/statistics";
/// <summary>
/// Ottiene le statistiche di una squadra per lega, stagione e opzionalmente fino a una data.
/// </summary>
public RestResponse GetTeamStatistics(int teamId, int leagueId, int season, string dateTo = null)
{
try
{
string query = $"{Endpoint}?team={teamId}&league={leagueId}&season={season}";
if (!string.IsNullOrEmpty(dateTo))
query += $"&date={dateTo}";
return ExecuteRequest(query, ApiTypes.Teams);
}
catch (Exception ex)
{
throw new Exception($"Errore recupero statistiche team {teamId}, lega {leagueId}, stagione {season}: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,26 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "players/topassists" dell'API-Football.
/// Restituisce i 20 migliori assistman di una lega/stagione.
/// </summary>
internal class TopAssists : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "players/topassists";
public RestResponse GetTopAssists(int leagueId, int season)
{
try
{
return ExecuteRequest($"{Endpoint}?league={leagueId}&season={season}", ApiTypes.Leagues);
}
catch (Exception ex)
{
throw new Exception($"Errore recupero top assistman lega {leagueId}, stagione {season}: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,39 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per gli endpoint "players/topyellowcards" e "players/topredcards" dell'API-Football.
/// Restituisce i 20 giocatori con più cartellini per lega/stagione.
/// </summary>
internal class TopCards : HorseRacingPredictor.Football.Manager.API
{
private const string YellowEndpoint = "players/topyellowcards";
private const string RedEndpoint = "players/topredcards";
public RestResponse GetTopYellowCards(int leagueId, int season)
{
try
{
return ExecuteRequest($"{YellowEndpoint}?league={leagueId}&season={season}", ApiTypes.Leagues);
}
catch (Exception ex)
{
throw new Exception($"Errore recupero top cartellini gialli lega {leagueId}, stagione {season}: {ex.Message}", ex);
}
}
public RestResponse GetTopRedCards(int leagueId, int season)
{
try
{
return ExecuteRequest($"{RedEndpoint}?league={leagueId}&season={season}", ApiTypes.Leagues);
}
catch (Exception ex)
{
throw new Exception($"Errore recupero top cartellini rossi lega {leagueId}, stagione {season}: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,26 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "players/topscorers" dell'API-Football.
/// Restituisce i 20 migliori marcatori di una lega/stagione.
/// </summary>
internal class TopScorers : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "players/topscorers";
public RestResponse GetTopScorers(int leagueId, int season)
{
try
{
return ExecuteRequest($"{Endpoint}?league={leagueId}&season={season}", ApiTypes.Leagues);
}
catch (Exception ex)
{
throw new Exception($"Errore recupero capocannonieri lega {leagueId}, stagione {season}: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,38 @@
using System;
using RestSharp;
namespace HorseRacingPredictor.Football.API
{
/// <summary>
/// Client per l'endpoint "transfers" dell'API-Football.
/// Restituisce la cronologia trasferimenti di un giocatore o di una squadra.
/// </summary>
internal class Transfers : HorseRacingPredictor.Football.Manager.API
{
private const string Endpoint = "transfers";
public RestResponse GetByTeam(int teamId)
{
try
{
return ExecuteRequest($"{Endpoint}?team={teamId}", ApiTypes.Teams);
}
catch (Exception ex)
{
throw new Exception($"Errore recupero trasferimenti team {teamId}: {ex.Message}", ex);
}
}
public RestResponse GetByPlayer(int playerId)
{
try
{
return ExecuteRequest($"{Endpoint}?player={playerId}", ApiTypes.Teams);
}
catch (Exception ex)
{
throw new Exception($"Errore recupero trasferimenti giocatore {playerId}: {ex.Message}", ex);
}
}
}
}
@@ -0,0 +1,148 @@
using System.Collections.Generic;
namespace HorseRacingPredictor.Football
{
/// <summary>
/// Parametri configurabili per controllare quali endpoint API-Football scaricare
/// e come filtrare i dati risultanti.
/// </summary>
public class FootballDownloadOptions
{
// ?? Endpoint da scaricare ?????????????????????????????????
public bool DownloadFixtures { get; set; } = true;
public bool DownloadOdds { get; set; } = true;
public bool DownloadPredictions { get; set; } = true;
public bool DownloadStandings { get; set; } = false;
public bool DownloadH2H { get; set; } = false;
public bool DownloadEvents { get; set; } = false;
public bool DownloadLineups { get; set; } = false;
public bool DownloadStatistics { get; set; } = false;
public bool DownloadInjuries { get; set; } = false;
// ?? Endpoint supplementari (CSV separati) ?????????????????
/// <summary>Scarica statistiche giocatori per squadra/stagione.</summary>
public bool DownloadPlayerStats { get; set; } = false;
/// <summary>Scarica statistiche aggregate delle squadre per lega/stagione.</summary>
public bool DownloadTeamStats { get; set; } = false;
/// <summary>Scarica classifica marcatori della lega.</summary>
public bool DownloadTopScorers { get; set; } = false;
/// <summary>Scarica classifica assistman della lega.</summary>
public bool DownloadTopAssists { get; set; } = false;
/// <summary>Scarica classifica cartellini (gialli e rossi) della lega.</summary>
public bool DownloadTopCards { get; set; } = false;
/// <summary>Scarica le rose attuali delle squadre.</summary>
public bool DownloadSquads { get; set; } = false;
/// <summary>Scarica informazioni sugli allenatori delle squadre.</summary>
public bool DownloadCoaches { get; set; } = false;
/// <summary>Scarica lo storico trasferimenti delle squadre.</summary>
public bool DownloadTransfers { get; set; } = false;
/// <summary>
/// Indica se almeno un endpoint supplementare (CSV separato) è selezionato.
/// </summary>
public bool AnySupplementarySelected =>
DownloadPlayerStats || DownloadTeamStats || DownloadTopScorers ||
DownloadTopAssists || DownloadTopCards || DownloadSquads ||
DownloadCoaches || DownloadTransfers;
// ?? Filtri ????????????????????????????????????????????????
/// <summary>
/// Se non vuoto, scarica fixture solo per queste leghe (league IDs).
/// Lista vuota = tutte le leghe.
/// </summary>
public List<int> LeagueIds { get; set; } = new();
/// <summary>
/// ID del bookmaker per le quote (default 8 = Bet365).
/// </summary>
public int BookmakerId { get; set; } = 8;
/// <summary>
/// Numero massimo di pagine da scaricare per le quote.
/// </summary>
public int OddsMaxPages { get; set; } = 3;
/// <summary>
/// Timezone IANA per le date delle fixture (es. "Europe/Rome").
/// </summary>
public string Timezone { get; set; } = "Europe/Rome";
/// <summary>
/// Stagione corrente per le classifiche (calcolata automaticamente se 0).
/// </summary>
public int Season { get; set; } = 0;
/// <summary>
/// Numero massimo di fixture per cui scaricare dati aggiuntivi
/// (H2H, events, lineups, statistics) per evitare troppe chiamate.
/// 0 = nessun limite.
/// </summary>
public int MaxFixturesForDetails { get; set; } = 50;
/// <summary>
/// Ritardo in millisecondi tra una chiamata API e l'altra per rispettare il rate limit.
/// </summary>
public int ApiDelayMs { get; set; } = 300;
/// <summary>
/// Se true, controlla la quota residua prima di iniziare e si ferma quando insufficiente.
/// </summary>
public bool CheckQuota { get; set; } = true;
/// <summary>
/// Numero minimo di richieste residue prima di fermare il download.
/// </summary>
public int MinRemainingQuota { get; set; } = 10;
/// <summary>
/// Restituisce la stagione corrente calcolata (anno corrente o anno-1 se prima di luglio).
/// </summary>
public int GetEffectiveSeason()
{
if (Season > 0) return Season;
var now = System.DateTime.Now;
return now.Month >= 7 ? now.Year : now.Year - 1;
}
/// <summary>
/// Conta il numero approssimativo di chiamate API previste per una data.
/// Utile per mostrare all'utente una stima.
/// </summary>
public int EstimateApiCalls(int fixtureCount)
{
int calls = 0;
if (DownloadFixtures) calls += 1;
if (DownloadOdds) calls += OddsMaxPages;
if (DownloadPredictions) calls += fixtureCount;
if (DownloadStandings && LeagueIds.Count > 0) calls += LeagueIds.Count;
else if (DownloadStandings) calls += 5; // stima
if (DownloadH2H) calls += fixtureCount;
if (DownloadEvents) calls += fixtureCount;
if (DownloadLineups) calls += fixtureCount;
if (DownloadStatistics) calls += fixtureCount;
if (DownloadInjuries) calls += 1;
if (CheckQuota) calls += 1; // status endpoint
// Supplementari (una chiamata per squadra coinvolta ? fixtureCount*2 unici, stima fixtureCount)
int teamEstimate = System.Math.Max(fixtureCount, 1);
int leagueEstimate = LeagueIds.Count > 0 ? LeagueIds.Count : 5;
if (DownloadPlayerStats) calls += teamEstimate; // 1 pagina per squadra (minimo)
if (DownloadTeamStats) calls += teamEstimate;
if (DownloadTopScorers) calls += leagueEstimate;
if (DownloadTopAssists) calls += leagueEstimate;
if (DownloadTopCards) calls += leagueEstimate * 2; // gialli + rossi
if (DownloadSquads) calls += teamEstimate;
if (DownloadCoaches) calls += teamEstimate;
if (DownloadTransfers) calls += teamEstimate;
return calls;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,733 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
namespace HorseRacingPredictor.Football
{
/// <summary>
/// Scarica dati supplementari dall'API-Football e li esporta come CSV separati.
/// Ogni tipo di dato produce un file CSV indipendente che l'IA potrà poi unire
/// in fase di elaborazione usando le chiavi comuni (team_id, league_id, player_id).
/// </summary>
public class SupplementaryDataExporter
{
private readonly API.PlayerStatistics _playerStatsAPI = new();
private readonly API.TeamStatistics _teamStatsAPI = new();
private readonly API.TopScorers _topScorersAPI = new();
private readonly API.TopAssists _topAssistsAPI = new();
private readonly API.TopCards _topCardsAPI = new();
private readonly API.Squads _squadsAPI = new();
private readonly API.Coaches _coachesAPI = new();
private readonly API.Transfers _transfersAPI = new();
private readonly API.Status _statusAPI = new();
private readonly Manager.Database _database = new();
/// <summary>
/// Scarica tutti i dati supplementari selezionati e li salva come CSV nella cartella specificata.
/// Restituisce la lista di file CSV generati.
/// </summary>
public List<string> DownloadAndExport(
DateTime date,
string exportFolder,
FootballDownloadOptions options,
HashSet<int> teamIds,
HashSet<int> leagueIds,
IProgress<int> progressCallback = null,
IProgress<string> statusCallback = null)
{
var generatedFiles = new List<string>();
int season = options.GetEffectiveSeason();
string dateTag = date.ToString("yyyy-MM-dd");
// Calcola step totali per progress
int totalSteps = CountSteps(options, teamIds.Count, leagueIds.Count);
int currentStep = 0;
void Report(string msg)
{
currentStep++;
int pct = totalSteps > 0 ? (int)((double)currentStep / totalSteps * 100) : 0;
progressCallback?.Report(Math.Min(pct, 100));
statusCallback?.Report(msg);
}
// ?? Top Scorers ??
if (options.DownloadTopScorers)
{
Report("Scaricamento capocannonieri...");
var table = DownloadTopScorersTable(leagueIds, season, options.ApiDelayMs);
if (table.Rows.Count > 0)
{
string path = SaveCsv(table, exportFolder, $"TopScorers_{dateTag}.csv");
generatedFiles.Add(path);
}
}
// ?? Top Assists ??
if (options.DownloadTopAssists)
{
Report("Scaricamento top assistman...");
var table = DownloadTopAssistsTable(leagueIds, season, options.ApiDelayMs);
if (table.Rows.Count > 0)
{
string path = SaveCsv(table, exportFolder, $"TopAssists_{dateTag}.csv");
generatedFiles.Add(path);
}
}
// ?? Top Cards ??
if (options.DownloadTopCards)
{
Report("Scaricamento top cartellini...");
var table = DownloadTopCardsTable(leagueIds, season, options.ApiDelayMs);
if (table.Rows.Count > 0)
{
string path = SaveCsv(table, exportFolder, $"TopCards_{dateTag}.csv");
generatedFiles.Add(path);
}
}
// ?? Team Stats ??
if (options.DownloadTeamStats)
{
var table = new DataTable();
table.Columns.Add("TeamId", typeof(int));
table.Columns.Add("LeagueId", typeof(int));
table.Columns.Add("Season", typeof(int));
table.Columns.Add("Forma", typeof(string));
table.Columns.Add("Partite Totali", typeof(int));
table.Columns.Add("Vittorie Casa", typeof(int));
table.Columns.Add("Vittorie Trasf.", typeof(int));
table.Columns.Add("Pareggi Casa", typeof(int));
table.Columns.Add("Pareggi Trasf.", typeof(int));
table.Columns.Add("Sconfitte Casa", typeof(int));
table.Columns.Add("Sconfitte Trasf.", typeof(int));
table.Columns.Add("Gol Fatti Casa", typeof(int));
table.Columns.Add("Gol Fatti Trasf.", typeof(int));
table.Columns.Add("Gol Subiti Casa", typeof(int));
table.Columns.Add("Gol Subiti Trasf.", typeof(int));
table.Columns.Add("Clean Sheet Tot.", typeof(int));
table.Columns.Add("Penalty Segnati", typeof(int));
table.Columns.Add("Penalty Falliti", typeof(int));
foreach (int teamId in teamIds)
{
foreach (int leagueId in leagueIds)
{
Report($"Statistiche team {teamId} in lega {leagueId}...");
try
{
var resp = _teamStatsAPI.GetTeamStatistics(teamId, leagueId, season, dateTag);
if (resp?.IsSuccessful == true && !string.IsNullOrEmpty(resp.Content))
ParseTeamStatsRow(table, resp.Content, teamId, leagueId, season);
}
catch { /* continua */ }
if (options.ApiDelayMs > 0) System.Threading.Thread.Sleep(options.ApiDelayMs);
}
}
if (table.Rows.Count > 0)
{
string path = SaveCsv(table, exportFolder, $"TeamStats_{dateTag}.csv");
generatedFiles.Add(path);
}
}
// ?? Player Stats ??
if (options.DownloadPlayerStats)
{
Report("Scaricamento statistiche giocatori...");
var table = DownloadPlayerStatsTable(teamIds, season, options.ApiDelayMs, statusCallback);
if (table.Rows.Count > 0)
{
string path = SaveCsv(table, exportFolder, $"PlayerStats_{dateTag}.csv");
generatedFiles.Add(path);
}
}
// ?? Squads ??
if (options.DownloadSquads)
{
Report("Scaricamento rose squadre...");
var table = DownloadSquadsTable(teamIds, options.ApiDelayMs, statusCallback);
if (table.Rows.Count > 0)
{
string path = SaveCsv(table, exportFolder, $"Squads_{dateTag}.csv");
generatedFiles.Add(path);
}
}
// ?? Coaches ??
if (options.DownloadCoaches)
{
Report("Scaricamento allenatori...");
var table = DownloadCoachesTable(teamIds, options.ApiDelayMs, statusCallback);
if (table.Rows.Count > 0)
{
string path = SaveCsv(table, exportFolder, $"Coaches_{dateTag}.csv");
generatedFiles.Add(path);
}
}
// ?? Transfers ??
if (options.DownloadTransfers)
{
Report("Scaricamento trasferimenti...");
var table = DownloadTransfersTable(teamIds, options.ApiDelayMs, statusCallback);
if (table.Rows.Count > 0)
{
string path = SaveCsv(table, exportFolder, $"Transfers_{dateTag}.csv");
generatedFiles.Add(path);
}
}
progressCallback?.Report(100);
statusCallback?.Report($"Generati {generatedFiles.Count} CSV supplementari");
return generatedFiles;
}
#region Download & Parse Methods
private DataTable DownloadTopScorersTable(HashSet<int> leagueIds, int season, int delayMs)
{
var table = CreateTopPlayersTable("Gol");
foreach (int leagueId in leagueIds)
{
try
{
var resp = _topScorersAPI.GetTopScorers(leagueId, season);
if (resp?.IsSuccessful == true && !string.IsNullOrEmpty(resp.Content))
ParseTopPlayersResponse(table, resp.Content, leagueId, season, "goals", "total");
}
catch { }
if (delayMs > 0) System.Threading.Thread.Sleep(delayMs);
}
return table;
}
private DataTable DownloadTopAssistsTable(HashSet<int> leagueIds, int season, int delayMs)
{
var table = CreateTopPlayersTable("Assist");
foreach (int leagueId in leagueIds)
{
try
{
var resp = _topAssistsAPI.GetTopAssists(leagueId, season);
if (resp?.IsSuccessful == true && !string.IsNullOrEmpty(resp.Content))
ParseTopPlayersResponse(table, resp.Content, leagueId, season, "goals", "assists");
}
catch { }
if (delayMs > 0) System.Threading.Thread.Sleep(delayMs);
}
return table;
}
private DataTable DownloadTopCardsTable(HashSet<int> leagueIds, int season, int delayMs)
{
var table = new DataTable();
table.Columns.Add("LeagueId", typeof(int));
table.Columns.Add("Season", typeof(int));
table.Columns.Add("Posizione", typeof(int));
table.Columns.Add("PlayerId", typeof(int));
table.Columns.Add("Giocatore", typeof(string));
table.Columns.Add("TeamId", typeof(int));
table.Columns.Add("Squadra", typeof(string));
table.Columns.Add("Tipo", typeof(string));
table.Columns.Add("Totale", typeof(int));
foreach (int leagueId in leagueIds)
{
try
{
var respY = _topCardsAPI.GetTopYellowCards(leagueId, season);
if (respY?.IsSuccessful == true && !string.IsNullOrEmpty(respY.Content))
ParseTopCardsResponse(table, respY.Content, leagueId, season, "Yellow");
}
catch { }
if (delayMs > 0) System.Threading.Thread.Sleep(delayMs);
try
{
var respR = _topCardsAPI.GetTopRedCards(leagueId, season);
if (respR?.IsSuccessful == true && !string.IsNullOrEmpty(respR.Content))
ParseTopCardsResponse(table, respR.Content, leagueId, season, "Red");
}
catch { }
if (delayMs > 0) System.Threading.Thread.Sleep(delayMs);
}
return table;
}
private DataTable DownloadPlayerStatsTable(HashSet<int> teamIds, int season, int delayMs, IProgress<string> statusCallback)
{
var table = new DataTable();
table.Columns.Add("PlayerId", typeof(int));
table.Columns.Add("Giocatore", typeof(string));
table.Columns.Add("Età", typeof(int));
table.Columns.Add("Nazionalità", typeof(string));
table.Columns.Add("TeamId", typeof(int));
table.Columns.Add("Squadra", typeof(string));
table.Columns.Add("LeagueId", typeof(int));
table.Columns.Add("Campionato", typeof(string));
table.Columns.Add("Presenze", typeof(int));
table.Columns.Add("Minuti", typeof(int));
table.Columns.Add("Rating", typeof(string));
table.Columns.Add("Gol", typeof(int));
table.Columns.Add("Assist", typeof(int));
table.Columns.Add("Gialli", typeof(int));
table.Columns.Add("Rossi", typeof(int));
table.Columns.Add("Tiri Totali", typeof(int));
table.Columns.Add("Tiri in Porta", typeof(int));
table.Columns.Add("Passaggi Chiave", typeof(int));
table.Columns.Add("Dribbling Riusciti", typeof(int));
table.Columns.Add("Falli Commessi", typeof(int));
table.Columns.Add("Falli Subiti", typeof(int));
table.Columns.Add("Ruolo", typeof(string));
int count = 0;
foreach (int teamId in teamIds)
{
count++;
statusCallback?.Report($"Statistiche giocatori team {count}/{teamIds.Count}...");
try
{
var resp = _playerStatsAPI.GetByTeamAndSeason(teamId, season);
if (resp?.IsSuccessful == true && !string.IsNullOrEmpty(resp.Content))
ParsePlayerStatsResponse(table, resp.Content);
}
catch { }
if (delayMs > 0) System.Threading.Thread.Sleep(delayMs);
}
return table;
}
private DataTable DownloadSquadsTable(HashSet<int> teamIds, int delayMs, IProgress<string> statusCallback)
{
var table = new DataTable();
table.Columns.Add("TeamId", typeof(int));
table.Columns.Add("Squadra", typeof(string));
table.Columns.Add("PlayerId", typeof(int));
table.Columns.Add("Giocatore", typeof(string));
table.Columns.Add("Età", typeof(int));
table.Columns.Add("Numero", typeof(int));
table.Columns.Add("Ruolo", typeof(string));
int count = 0;
foreach (int teamId in teamIds)
{
count++;
statusCallback?.Report($"Rosa team {count}/{teamIds.Count}...");
try
{
var resp = _squadsAPI.GetSquad(teamId);
if (resp?.IsSuccessful == true && !string.IsNullOrEmpty(resp.Content))
ParseSquadsResponse(table, resp.Content);
}
catch { }
if (delayMs > 0) System.Threading.Thread.Sleep(delayMs);
}
return table;
}
private DataTable DownloadCoachesTable(HashSet<int> teamIds, int delayMs, IProgress<string> statusCallback)
{
var table = new DataTable();
table.Columns.Add("CoachId", typeof(int));
table.Columns.Add("Nome", typeof(string));
table.Columns.Add("Età", typeof(int));
table.Columns.Add("Nazionalità", typeof(string));
table.Columns.Add("TeamId", typeof(int));
table.Columns.Add("Squadra", typeof(string));
int count = 0;
foreach (int teamId in teamIds)
{
count++;
statusCallback?.Report($"Allenatore team {count}/{teamIds.Count}...");
try
{
var resp = _coachesAPI.GetByTeam(teamId);
if (resp?.IsSuccessful == true && !string.IsNullOrEmpty(resp.Content))
ParseCoachesResponse(table, resp.Content, teamId);
}
catch { }
if (delayMs > 0) System.Threading.Thread.Sleep(delayMs);
}
return table;
}
private DataTable DownloadTransfersTable(HashSet<int> teamIds, int delayMs, IProgress<string> statusCallback)
{
var table = new DataTable();
table.Columns.Add("PlayerId", typeof(int));
table.Columns.Add("Giocatore", typeof(string));
table.Columns.Add("Data", typeof(string));
table.Columns.Add("Tipo", typeof(string));
table.Columns.Add("Da TeamId", typeof(int));
table.Columns.Add("Da Squadra", typeof(string));
table.Columns.Add("A TeamId", typeof(int));
table.Columns.Add("A Squadra", typeof(string));
int count = 0;
foreach (int teamId in teamIds)
{
count++;
statusCallback?.Report($"Trasferimenti team {count}/{teamIds.Count}...");
try
{
var resp = _transfersAPI.GetByTeam(teamId);
if (resp?.IsSuccessful == true && !string.IsNullOrEmpty(resp.Content))
ParseTransfersResponse(table, resp.Content);
}
catch { }
if (delayMs > 0) System.Threading.Thread.Sleep(delayMs);
}
return table;
}
#endregion
#region JSON Parsing Helpers
private static DataTable CreateTopPlayersTable(string statName)
{
var table = new DataTable();
table.Columns.Add("LeagueId", typeof(int));
table.Columns.Add("Season", typeof(int));
table.Columns.Add("Posizione", typeof(int));
table.Columns.Add("PlayerId", typeof(int));
table.Columns.Add("Giocatore", typeof(string));
table.Columns.Add("TeamId", typeof(int));
table.Columns.Add("Squadra", typeof(string));
table.Columns.Add(statName, typeof(int));
table.Columns.Add("Presenze", typeof(int));
table.Columns.Add("Rating", typeof(string));
return table;
}
private static void ParseTopPlayersResponse(DataTable table, string json, int leagueId, int season, string statSection, string statField)
{
var doc = JsonDocument.Parse(json).RootElement;
if (!doc.TryGetProperty("response", out var resp)) return;
int pos = 0;
foreach (var item in resp.EnumerateArray())
{
pos++;
var row = table.NewRow();
row["LeagueId"] = leagueId;
row["Season"] = season;
row["Posizione"] = pos;
row["PlayerId"] = GetInt(item, "player", "id");
row["Giocatore"] = GetStr(item, "player", "name");
// statistics[0]
if (item.TryGetProperty("statistics", out var stats))
{
foreach (var s in stats.EnumerateArray())
{
row["TeamId"] = GetInt(s, "team", "id");
row["Squadra"] = GetStr(s, "team", "name");
row[table.Columns[7].ColumnName] = GetIntNested(s, statSection, statField);
row["Presenze"] = GetIntNested(s, "games", "appearences");
row["Rating"] = GetStrNested(s, "games", "rating");
break;
}
}
table.Rows.Add(row);
}
}
private static void ParseTopCardsResponse(DataTable table, string json, int leagueId, int season, string cardType)
{
var doc = JsonDocument.Parse(json).RootElement;
if (!doc.TryGetProperty("response", out var resp)) return;
int pos = 0;
foreach (var item in resp.EnumerateArray())
{
pos++;
var row = table.NewRow();
row["LeagueId"] = leagueId;
row["Season"] = season;
row["Posizione"] = pos;
row["PlayerId"] = GetInt(item, "player", "id");
row["Giocatore"] = GetStr(item, "player", "name");
row["Tipo"] = cardType;
if (item.TryGetProperty("statistics", out var stats))
{
foreach (var s in stats.EnumerateArray())
{
row["TeamId"] = GetInt(s, "team", "id");
row["Squadra"] = GetStr(s, "team", "name");
string section = cardType == "Yellow" ? "yellow" : "red";
row["Totale"] = GetIntNested(s, "cards", section);
break;
}
}
table.Rows.Add(row);
}
}
private static void ParseTeamStatsRow(DataTable table, string json, int teamId, int leagueId, int season)
{
var doc = JsonDocument.Parse(json).RootElement;
if (!doc.TryGetProperty("response", out var resp)) return;
var row = table.NewRow();
row["TeamId"] = teamId;
row["LeagueId"] = leagueId;
row["Season"] = season;
row["Forma"] = resp.TryGetProperty("form", out var f) ? f.GetString() ?? "" : "";
row["Partite Totali"] = GetIntNested(resp, "fixtures", "played", "total");
row["Vittorie Casa"] = GetIntNested(resp, "fixtures", "wins", "home");
row["Vittorie Trasf."] = GetIntNested(resp, "fixtures", "wins", "away");
row["Pareggi Casa"] = GetIntNested(resp, "fixtures", "draws", "home");
row["Pareggi Trasf."] = GetIntNested(resp, "fixtures", "draws", "away");
row["Sconfitte Casa"] = GetIntNested(resp, "fixtures", "loses", "home");
row["Sconfitte Trasf."] = GetIntNested(resp, "fixtures", "loses", "away");
row["Gol Fatti Casa"] = GetIntNested(resp, "goals", "for", "total", "home");
row["Gol Fatti Trasf."] = GetIntNested(resp, "goals", "for", "total", "away");
row["Gol Subiti Casa"] = GetIntNested(resp, "goals", "against", "total", "home");
row["Gol Subiti Trasf."] = GetIntNested(resp, "goals", "against", "total", "away");
row["Clean Sheet Tot."] = GetIntNested(resp, "clean_sheet", "total");
row["Penalty Segnati"] = GetIntNested(resp, "penalty", "scored", "total");
row["Penalty Falliti"] = GetIntNested(resp, "penalty", "missed", "total");
table.Rows.Add(row);
}
private static void ParsePlayerStatsResponse(DataTable table, string json)
{
var doc = JsonDocument.Parse(json).RootElement;
if (!doc.TryGetProperty("response", out var resp)) return;
foreach (var item in resp.EnumerateArray())
{
int playerId = GetInt(item, "player", "id");
string name = GetStr(item, "player", "name");
int age = GetInt(item, "player", "age");
string nationality = GetStr(item, "player", "nationality");
if (!item.TryGetProperty("statistics", out var stats)) continue;
foreach (var s in stats.EnumerateArray())
{
var row = table.NewRow();
row["PlayerId"] = playerId;
row["Giocatore"] = name;
row["Età"] = age;
row["Nazionalità"] = nationality;
row["TeamId"] = GetInt(s, "team", "id");
row["Squadra"] = GetStr(s, "team", "name");
row["LeagueId"] = GetInt(s, "league", "id");
row["Campionato"] = GetStr(s, "league", "name");
row["Presenze"] = GetIntNested(s, "games", "appearences");
row["Minuti"] = GetIntNested(s, "games", "minutes");
row["Rating"] = GetStrNested(s, "games", "rating");
row["Gol"] = GetIntNested(s, "goals", "total");
row["Assist"] = GetIntNested(s, "goals", "assists");
row["Gialli"] = GetIntNested(s, "cards", "yellow");
row["Rossi"] = GetIntNested(s, "cards", "red");
row["Tiri Totali"] = GetIntNested(s, "shots", "total");
row["Tiri in Porta"] = GetIntNested(s, "shots", "on");
row["Passaggi Chiave"] = GetIntNested(s, "passes", "key");
row["Dribbling Riusciti"] = GetIntNested(s, "dribbles", "success");
row["Falli Commessi"] = GetIntNested(s, "fouls", "committed");
row["Falli Subiti"] = GetIntNested(s, "fouls", "drawn");
row["Ruolo"] = GetStrNested(s, "games", "position");
table.Rows.Add(row);
}
}
}
private static void ParseSquadsResponse(DataTable table, string json)
{
var doc = JsonDocument.Parse(json).RootElement;
if (!doc.TryGetProperty("response", out var resp)) return;
foreach (var teamEntry in resp.EnumerateArray())
{
int teamId = GetInt(teamEntry, "team", "id");
string teamName = GetStr(teamEntry, "team", "name");
if (!teamEntry.TryGetProperty("players", out var players)) continue;
foreach (var p in players.EnumerateArray())
{
var row = table.NewRow();
row["TeamId"] = teamId;
row["Squadra"] = teamName;
row["PlayerId"] = p.TryGetProperty("id", out var id) && id.ValueKind == JsonValueKind.Number ? id.GetInt32() : 0;
row["Giocatore"] = p.TryGetProperty("name", out var n) ? n.GetString() ?? "" : "";
row["Età"] = p.TryGetProperty("age", out var a) && a.ValueKind == JsonValueKind.Number ? a.GetInt32() : 0;
row["Numero"] = p.TryGetProperty("number", out var num) && num.ValueKind == JsonValueKind.Number ? num.GetInt32() : 0;
row["Ruolo"] = p.TryGetProperty("position", out var pos) ? pos.GetString() ?? "" : "";
table.Rows.Add(row);
}
}
}
private static void ParseCoachesResponse(DataTable table, string json, int teamId)
{
var doc = JsonDocument.Parse(json).RootElement;
if (!doc.TryGetProperty("response", out var resp)) return;
foreach (var c in resp.EnumerateArray())
{
var row = table.NewRow();
row["CoachId"] = c.TryGetProperty("id", out var id) && id.ValueKind == JsonValueKind.Number ? id.GetInt32() : 0;
row["Nome"] = c.TryGetProperty("name", out var n) ? n.GetString() ?? "" : "";
row["Età"] = c.TryGetProperty("age", out var a) && a.ValueKind == JsonValueKind.Number ? a.GetInt32() : 0;
row["Nazionalità"] = c.TryGetProperty("nationality", out var nat) ? nat.GetString() ?? "" : "";
row["TeamId"] = teamId;
row["Squadra"] = c.TryGetProperty("team", out var t) && t.TryGetProperty("name", out var tn) ? tn.GetString() ?? "" : "";
table.Rows.Add(row);
}
}
private static void ParseTransfersResponse(DataTable table, string json)
{
var doc = JsonDocument.Parse(json).RootElement;
if (!doc.TryGetProperty("response", out var resp)) return;
foreach (var entry in resp.EnumerateArray())
{
int playerId = GetInt(entry, "player", "id");
string playerName = GetStr(entry, "player", "name");
if (!entry.TryGetProperty("transfers", out var transfers)) continue;
foreach (var t in transfers.EnumerateArray())
{
var row = table.NewRow();
row["PlayerId"] = playerId;
row["Giocatore"] = playerName;
row["Data"] = t.TryGetProperty("date", out var d) ? d.GetString() ?? "" : "";
row["Tipo"] = t.TryGetProperty("type", out var tp) ? tp.GetString() ?? "" : "";
row["Da TeamId"] = GetInt(t, "teams", "out", "id");
row["Da Squadra"] = GetStr(t, "teams", "out", "name");
row["A TeamId"] = GetInt(t, "teams", "in", "id");
row["A Squadra"] = GetStr(t, "teams", "in", "name");
table.Rows.Add(row);
}
}
}
#endregion
#region JSON Utility Helpers
private static int GetInt(JsonElement el, string prop1, string prop2)
{
if (el.TryGetProperty(prop1, out var p1) && p1.TryGetProperty(prop2, out var p2) && p2.ValueKind == JsonValueKind.Number)
return p2.GetInt32();
return 0;
}
private static int GetInt(JsonElement el, string prop1, string prop2, string prop3)
{
if (el.TryGetProperty(prop1, out var p1) && p1.TryGetProperty(prop2, out var p2) && p2.TryGetProperty(prop3, out var p3) && p3.ValueKind == JsonValueKind.Number)
return p3.GetInt32();
return 0;
}
private static string GetStr(JsonElement el, string prop1, string prop2)
{
if (el.TryGetProperty(prop1, out var p1) && p1.TryGetProperty(prop2, out var p2))
return p2.GetString() ?? "";
return "";
}
private static string GetStr(JsonElement el, string prop1, string prop2, string prop3)
{
if (el.TryGetProperty(prop1, out var p1) && p1.TryGetProperty(prop2, out var p2) && p2.TryGetProperty(prop3, out var p3))
return p3.GetString() ?? "";
return "";
}
private static int GetIntNested(JsonElement el, string p1, string p2)
{
if (el.TryGetProperty(p1, out var v1) && v1.TryGetProperty(p2, out var v2) && v2.ValueKind == JsonValueKind.Number)
return v2.GetInt32();
return 0;
}
private static int GetIntNested(JsonElement el, string p1, string p2, string p3)
{
if (el.TryGetProperty(p1, out var v1) && v1.TryGetProperty(p2, out var v2) && v2.TryGetProperty(p3, out var v3) && v3.ValueKind == JsonValueKind.Number)
return v3.GetInt32();
return 0;
}
private static int GetIntNested(JsonElement el, string p1, string p2, string p3, string p4)
{
if (el.TryGetProperty(p1, out var v1) && v1.TryGetProperty(p2, out var v2) && v2.TryGetProperty(p3, out var v3) && v3.TryGetProperty(p4, out var v4) && v4.ValueKind == JsonValueKind.Number)
return v4.GetInt32();
return 0;
}
private static string GetStrNested(JsonElement el, string p1, string p2)
{
if (el.TryGetProperty(p1, out var v1) && v1.TryGetProperty(p2, out var v2))
return v2.GetString() ?? "";
return "";
}
#endregion
#region CSV & Utility
private static string SaveCsv(DataTable table, string folder, string fileName)
{
if (!Directory.Exists(folder))
Directory.CreateDirectory(folder);
string path = Path.Combine(folder, fileName);
var sb = new StringBuilder();
// Header
for (int i = 0; i < table.Columns.Count; i++)
{
if (i > 0) sb.Append(',');
sb.Append(EscapeCsvField(table.Columns[i].ColumnName));
}
sb.AppendLine();
// Rows
foreach (DataRow row in table.Rows)
{
for (int i = 0; i < table.Columns.Count; i++)
{
if (i > 0) sb.Append(',');
sb.Append(EscapeCsvField(row[i]?.ToString() ?? ""));
}
sb.AppendLine();
}
File.WriteAllText(path, sb.ToString(), Encoding.UTF8);
return path;
}
private static string EscapeCsvField(string field)
{
if (field.Contains(',') || field.Contains('"') || field.Contains('\n'))
return "\"" + field.Replace("\"", "\"\"") + "\"";
return field;
}
private static int CountSteps(FootballDownloadOptions options, int teamCount, int leagueCount)
{
int steps = 0;
if (options.DownloadTopScorers) steps += leagueCount;
if (options.DownloadTopAssists) steps += leagueCount;
if (options.DownloadTopCards) steps += leagueCount * 2;
if (options.DownloadTeamStats) steps += teamCount * leagueCount;
if (options.DownloadPlayerStats) steps += teamCount;
if (options.DownloadSquads) steps += teamCount;
if (options.DownloadCoaches) steps += teamCount;
if (options.DownloadTransfers) steps += teamCount;
return Math.Max(steps, 1);
}
#endregion
}
}
@@ -21,8 +21,9 @@ namespace HorseRacingPredictor
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
// ?? Football ????????????????????????????????????????????
// ?? Football ??????????????????????????????????????????
public string ApiKey { get; set; } = string.Empty;
public string FbDataSource { get; set; } = "API - API-Football";
public string FbExportPath { get; set; } = string.Empty;
public string FbPrefix { get; set; } = string.Empty;
public string FbSuffix { get; set; } = string.Empty;
@@ -30,8 +31,39 @@ namespace HorseRacingPredictor
public string FbDateFormat { get; set; } = "yyyy-MM-dd";
public string FbFormat { get; set; } = "CSV";
// ?? Racing ??????????????????????????????????????????????
// ?? Football Download Options ????????????????????????????
public bool FbDownloadFixtures { get; set; } = true;
public bool FbDownloadOdds { get; set; } = true;
public bool FbDownloadPredictions { get; set; } = true;
public bool FbDownloadStandings { get; set; } = false;
public bool FbDownloadH2H { get; set; } = false;
public bool FbDownloadEvents { get; set; } = false;
public bool FbDownloadLineups { get; set; } = false;
public bool FbDownloadStatistics { get; set; } = false;
public bool FbDownloadInjuries { get; set; } = false;
public List<int> FbLeagueIds { get; set; } = new();
public int FbBookmakerId { get; set; } = 8;
public int FbOddsMaxPages { get; set; } = 3;
public string FbTimezone { get; set; } = "Europe/Rome";
public int FbSeason { get; set; } = 0;
public int FbMaxFixturesForDetails { get; set; } = 50;
public int FbApiDelayMs { get; set; } = 300;
public bool FbCheckQuota { get; set; } = true;
public int FbMinRemainingQuota { get; set; } = 10;
// ?? Football Supplementary Downloads (CSV separati) ??????
public bool FbDownloadPlayerStats { get; set; } = false;
public bool FbDownloadTeamStats { get; set; } = false;
public bool FbDownloadTopScorers { get; set; } = false;
public bool FbDownloadTopAssists { get; set; } = false;
public bool FbDownloadTopCards { get; set; } = false;
public bool FbDownloadSquads { get; set; } = false;
public bool FbDownloadCoaches { get; set; } = false;
public bool FbDownloadTransfers { get; set; } = false;
// ?? Racing ???????????????????????????????????????????????
public string RacingApiKey { get; set; } = string.Empty;
public string RcDataSource { get; set; } = "API - FormFav";
public string RcExportPath { get; set; } = string.Empty;
public string RcPrefix { get; set; } = string.Empty;
public string RcSuffix { get; set; } = string.Empty;
@@ -41,7 +73,44 @@ namespace HorseRacingPredictor
public string RcTimezone { get; set; } = "Australia/Sydney";
public List<string> RcCountries { get; set; } = new() { "au", "nz" };
// ?? Persistence ?????????????????????????????????????????
// ?? Persistence ??????????????????????????????????????
/// <summary>
/// Costruisce un FootballDownloadOptions a partire dalle impostazioni salvate.
/// </summary>
public Football.FootballDownloadOptions ToFootballDownloadOptions()
{
return new Football.FootballDownloadOptions
{
DownloadFixtures = FbDownloadFixtures,
DownloadOdds = FbDownloadOdds,
DownloadPredictions = FbDownloadPredictions,
DownloadStandings = FbDownloadStandings,
DownloadH2H = FbDownloadH2H,
DownloadEvents = FbDownloadEvents,
DownloadLineups = FbDownloadLineups,
DownloadStatistics = FbDownloadStatistics,
DownloadInjuries = FbDownloadInjuries,
LeagueIds = new List<int>(FbLeagueIds),
BookmakerId = FbBookmakerId,
OddsMaxPages = FbOddsMaxPages,
Timezone = FbTimezone,
Season = FbSeason,
MaxFixturesForDetails = FbMaxFixturesForDetails,
ApiDelayMs = FbApiDelayMs,
CheckQuota = FbCheckQuota,
MinRemainingQuota = FbMinRemainingQuota,
// Supplementari
DownloadPlayerStats = FbDownloadPlayerStats,
DownloadTeamStats = FbDownloadTeamStats,
DownloadTopScorers = FbDownloadTopScorers,
DownloadTopAssists = FbDownloadTopAssists,
DownloadTopCards = FbDownloadTopCards,
DownloadSquads = FbDownloadSquads,
DownloadCoaches = FbDownloadCoaches,
DownloadTransfers = FbDownloadTransfers
};
}
public static UserSettings Load()
{
@@ -1,11 +0,0 @@
{
"ConnectionStrings": {
"Football": "Server=DESKTOP-9O9JHFS;Database=TestBS_Football;User Id=sa;Password=Asti2019;TrustServerCertificate=True",
"Horses": "Server=DESKTOP-9O9JHFS;Database=TestBS_Horses;User Id=sa;Password=Asti2019;TrustServerCertificate=True"
},
"Api": {
"FootballApiKey": "f3795ccef056c5478d316162517d9970",
"FootballApiKeyHeader": "x-rapidapi-key",
"FootballApiHost": "v3.football.api-sports.io"
}
}
@@ -0,0 +1,11 @@
{
"ConnectionStrings": {
"Football": "Server=YOUR_SERVER;Database=YOUR_DB;User Id=YOUR_USER;Password=YOUR_PASSWORD;TrustServerCertificate=True",
"Horses": "Server=YOUR_SERVER;Database=YOUR_DB;User Id=YOUR_USER;Password=YOUR_PASSWORD;TrustServerCertificate=True"
},
"Api": {
"FootballApiKey": "",
"FootballApiKeyHeader": "x-rapidapi-key",
"FootballApiHost": "v3.football.api-sports.io"
}
}
@@ -1,4 +1,4 @@
using BettingPredictor.UI;
using BettingPredictor.UI;
using System;
using System.Data;
using System.IO;
@@ -16,8 +16,8 @@ namespace BettingPredictor
private DataTable racingData;
// Credenziali predefinite Racing API
private const string DefaultRacingUser = "qi1mHOHPquDY9KNDASAeGipy";
private const string DefaultRacingPass = "RXNFU1YX27R9rTnk8Vop8ZfH";
private const string DefaultRacingUser = "";
private const string DefaultRacingPass = "";
// Pagine e nav gestiti come array per semplificare la navigazione
private Panel[] pages;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,30 +0,0 @@
namespace HorseRacingPredictor.VirtualFootball
{
/// <summary>
/// Represents a single virtual football match result displayed in the results panel.
/// </summary>
public class VirtualMatch
{
public string Time { get; set; }
public string Home { get; set; }
public int HomeGoals { get; set; }
public int AwayGoals { get; set; }
public string Away { get; set; }
public string Score => $"{HomeGoals} - {AwayGoals}";
/// <summary>1, X, or 2</summary>
public string Outcome
{
get
{
if (HomeGoals > AwayGoals) return "1";
if (HomeGoals < AwayGoals) return "2";
return "X";
}
}
/// <summary>Row background colour: green for draw, red for 1/2.</summary>
public string RowColor => Outcome == "X" ? "#2A4A3A" : "#4A2A2A";
}
}