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
This commit is contained in:
@@ -414,3 +414,9 @@ FodyWeavers.xsd
|
|||||||
# Built Visual Studio Code Extensions
|
# Built Visual Studio Code Extensions
|
||||||
*.vsix
|
*.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;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
namespace HorseRacingPredictor
|
namespace HorseRacingPredictor
|
||||||
{
|
{
|
||||||
public partial class App : Application
|
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.CpuMath" Version="6.0.0-preview.26160.2" />
|
||||||
<PackageReference Include="Microsoft.ML.DataView" 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.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="Newtonsoft.Json" Version="13.0.5-beta1" />
|
||||||
<PackageReference Include="RestSharp" Version="114.0.0" />
|
<PackageReference Include="RestSharp" Version="114.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.5" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.5" />
|
||||||
|
|||||||
@@ -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,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,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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,39 @@ namespace HorseRacingPredictor.Football
|
|||||||
public bool DownloadStatistics { get; set; } = false;
|
public bool DownloadStatistics { get; set; } = false;
|
||||||
public bool DownloadInjuries { 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 ????????????????????????????????????????????????
|
// ?? Filtri ????????????????????????????????????????????????
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Se non vuoto, scarica fixture solo per queste leghe (league IDs).
|
/// Se non vuoto, scarica fixture solo per queste leghe (league IDs).
|
||||||
@@ -96,6 +129,19 @@ namespace HorseRacingPredictor.Football
|
|||||||
if (DownloadStatistics) calls += fixtureCount;
|
if (DownloadStatistics) calls += fixtureCount;
|
||||||
if (DownloadInjuries) calls += 1;
|
if (DownloadInjuries) calls += 1;
|
||||||
if (CheckQuota) calls += 1; // status endpoint
|
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;
|
return calls;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1878,5 +1878,40 @@ namespace HorseRacingPredictor.Football
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Supplementary Data
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scarica i dati supplementari selezionati (statistiche giocatori, capocannonieri, ecc.)
|
||||||
|
/// e li esporta come CSV separati nella cartella specificata.
|
||||||
|
/// Richiede un DataTable di fixture già scaricato per estrarre teamId e leagueId coinvolti.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> DownloadSupplementaryData(
|
||||||
|
DataTable fixturesTable,
|
||||||
|
DateTime date,
|
||||||
|
string exportFolder,
|
||||||
|
FootballDownloadOptions options,
|
||||||
|
IProgress<int> progressCallback = null,
|
||||||
|
IProgress<string> statusCallback = null)
|
||||||
|
{
|
||||||
|
if (fixturesTable == null || fixturesTable.Rows.Count == 0 || !options.AnySupplementarySelected)
|
||||||
|
return new List<string>();
|
||||||
|
|
||||||
|
// Estrai team IDs e league IDs unici dal DataTable fixture
|
||||||
|
var teamIds = new HashSet<int>();
|
||||||
|
var leagueIds = new HashSet<int>();
|
||||||
|
|
||||||
|
foreach (DataRow row in fixturesTable.Rows)
|
||||||
|
{
|
||||||
|
if (row["HomeTeamId"] != DBNull.Value) teamIds.Add(Convert.ToInt32(row["HomeTeamId"]));
|
||||||
|
if (row["AwayTeamId"] != DBNull.Value) teamIds.Add(Convert.ToInt32(row["AwayTeamId"]));
|
||||||
|
if (row["LeagueId"] != DBNull.Value) leagueIds.Add(Convert.ToInt32(row["LeagueId"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
var exporter = new SupplementaryDataExporter();
|
||||||
|
return exporter.DownloadAndExport(date, exportFolder, options, teamIds, leagueIds, progressCallback, statusCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ namespace HorseRacingPredictor
|
|||||||
|
|
||||||
// ?? Football ??????????????????????????????????????????
|
// ?? Football ??????????????????????????????????????????
|
||||||
public string ApiKey { get; set; } = string.Empty;
|
public string ApiKey { get; set; } = string.Empty;
|
||||||
|
public string FbDataSource { get; set; } = "API - API-Football";
|
||||||
public string FbExportPath { get; set; } = string.Empty;
|
public string FbExportPath { get; set; } = string.Empty;
|
||||||
public string FbPrefix { get; set; } = string.Empty;
|
public string FbPrefix { get; set; } = string.Empty;
|
||||||
public string FbSuffix { get; set; } = string.Empty;
|
public string FbSuffix { get; set; } = string.Empty;
|
||||||
@@ -50,8 +51,19 @@ namespace HorseRacingPredictor
|
|||||||
public bool FbCheckQuota { get; set; } = true;
|
public bool FbCheckQuota { get; set; } = true;
|
||||||
public int FbMinRemainingQuota { get; set; } = 10;
|
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 ???????????????????????????????????????????????
|
// ?? Racing ???????????????????????????????????????????????
|
||||||
public string RacingApiKey { get; set; } = string.Empty;
|
public string RacingApiKey { get; set; } = string.Empty;
|
||||||
|
public string RcDataSource { get; set; } = "API - FormFav";
|
||||||
public string RcExportPath { get; set; } = string.Empty;
|
public string RcExportPath { get; set; } = string.Empty;
|
||||||
public string RcPrefix { get; set; } = string.Empty;
|
public string RcPrefix { get; set; } = string.Empty;
|
||||||
public string RcSuffix { get; set; } = string.Empty;
|
public string RcSuffix { get; set; } = string.Empty;
|
||||||
@@ -87,7 +99,16 @@ namespace HorseRacingPredictor
|
|||||||
MaxFixturesForDetails = FbMaxFixturesForDetails,
|
MaxFixturesForDetails = FbMaxFixturesForDetails,
|
||||||
ApiDelayMs = FbApiDelayMs,
|
ApiDelayMs = FbApiDelayMs,
|
||||||
CheckQuota = FbCheckQuota,
|
CheckQuota = FbCheckQuota,
|
||||||
MinRemainingQuota = FbMinRemainingQuota
|
MinRemainingQuota = FbMinRemainingQuota,
|
||||||
|
// Supplementari
|
||||||
|
DownloadPlayerStats = FbDownloadPlayerStats,
|
||||||
|
DownloadTeamStats = FbDownloadTeamStats,
|
||||||
|
DownloadTopScorers = FbDownloadTopScorers,
|
||||||
|
DownloadTopAssists = FbDownloadTopAssists,
|
||||||
|
DownloadTopCards = FbDownloadTopCards,
|
||||||
|
DownloadSquads = FbDownloadSquads,
|
||||||
|
DownloadCoaches = FbDownloadCoaches,
|
||||||
|
DownloadTransfers = FbDownloadTransfers
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
@@ -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;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -16,8 +16,8 @@ namespace BettingPredictor
|
|||||||
private DataTable racingData;
|
private DataTable racingData;
|
||||||
|
|
||||||
// Credenziali predefinite Racing API
|
// Credenziali predefinite Racing API
|
||||||
private const string DefaultRacingUser = "qi1mHOHPquDY9KNDASAeGipy";
|
private const string DefaultRacingUser = "";
|
||||||
private const string DefaultRacingPass = "RXNFU1YX27R9rTnk8Vop8ZfH";
|
private const string DefaultRacingPass = "";
|
||||||
|
|
||||||
// Pagine e nav gestiti come array per semplificare la navigazione
|
// Pagine e nav gestiti come array per semplificare la navigazione
|
||||||
private Panel[] pages;
|
private Panel[] pages;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -8,9 +8,7 @@ using System.Text.RegularExpressions;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using Microsoft.Web.WebView2.Core;
|
|
||||||
|
|
||||||
namespace HorseRacingPredictor
|
namespace HorseRacingPredictor
|
||||||
{
|
{
|
||||||
@@ -22,17 +20,21 @@ namespace HorseRacingPredictor
|
|||||||
private DataTable _racingData;
|
private DataTable _racingData;
|
||||||
private CancellationTokenSource _racingCts;
|
private CancellationTokenSource _racingCts;
|
||||||
|
|
||||||
// Virtual Football
|
|
||||||
private readonly ObservableCollection<VirtualFootball.VirtualMatch> _vfbResults = new ObservableCollection<VirtualFootball.VirtualMatch>();
|
|
||||||
|
|
||||||
private const string DefaultRacingApiKey = "";
|
private const string DefaultRacingApiKey = "";
|
||||||
|
|
||||||
|
// Dynamic checkbox dictionaries for football endpoint/supplementary popups
|
||||||
|
private readonly Dictionary<string, CheckBox> _fbEndpointCheckboxes = new();
|
||||||
|
private readonly Dictionary<string, CheckBox> _fbSupplementaryCheckboxes = new();
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_footballManager = new Football.Main();
|
_footballManager = new Football.Main();
|
||||||
_racingManager = new HorseRacing.Main(DefaultRacingApiKey);
|
_racingManager = new HorseRacing.Main(DefaultRacingApiKey);
|
||||||
BuildCountryCheckboxes();
|
BuildCountryCheckboxes();
|
||||||
|
BuildFbEndpointCheckboxes();
|
||||||
|
BuildFbSupplementaryCheckboxes();
|
||||||
|
PopulateTimezoneComboBoxes();
|
||||||
// Wire preview update events
|
// Wire preview update events
|
||||||
txtFbPrefix.TextChanged += (s, e) => UpdateFbPreview();
|
txtFbPrefix.TextChanged += (s, e) => UpdateFbPreview();
|
||||||
txtFbSuffix.TextChanged += (s, e) => UpdateFbPreview();
|
txtFbSuffix.TextChanged += (s, e) => UpdateFbPreview();
|
||||||
@@ -168,6 +170,44 @@ namespace HorseRacingPredictor
|
|||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ———————————— TIMEZONE COMBOS ————————————
|
||||||
|
|
||||||
|
private List<string> _ianaTimezones;
|
||||||
|
|
||||||
|
private void PopulateTimezoneComboBoxes()
|
||||||
|
{
|
||||||
|
_ianaTimezones = BuildIanaTimezoneList();
|
||||||
|
cmbFbTimezone.ItemsSource = _ianaTimezones;
|
||||||
|
cmbRcTimezone.ItemsSource = _ianaTimezones;
|
||||||
|
SetTimezoneSelection(cmbFbTimezone, "Europe/Rome");
|
||||||
|
SetTimezoneSelection(cmbRcTimezone, "Australia/Sydney");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<string> BuildIanaTimezoneList()
|
||||||
|
{
|
||||||
|
var ids = new SortedSet<string>(StringComparer.Ordinal);
|
||||||
|
foreach (var tz in TimeZoneInfo.GetSystemTimeZones())
|
||||||
|
{
|
||||||
|
if (TimeZoneInfo.TryConvertWindowsIdToIanaId(tz.Id, out var ianaId))
|
||||||
|
ids.Add(ianaId);
|
||||||
|
}
|
||||||
|
return [.. ids];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetTimezoneSelection(ComboBox combo, string ianaId)
|
||||||
|
{
|
||||||
|
if (combo == null || string.IsNullOrWhiteSpace(ianaId)) return;
|
||||||
|
// Add to list if not present (e.g. user saved a custom value)
|
||||||
|
if (_ianaTimezones != null && !_ianaTimezones.Contains(ianaId))
|
||||||
|
{
|
||||||
|
_ianaTimezones.Add(ianaId);
|
||||||
|
_ianaTimezones.Sort(StringComparer.Ordinal);
|
||||||
|
combo.ItemsSource = null;
|
||||||
|
combo.ItemsSource = _ianaTimezones;
|
||||||
|
}
|
||||||
|
combo.SelectedItem = ianaId;
|
||||||
|
}
|
||||||
|
|
||||||
// ???????????????????? LIFECYCLE ????????????????????
|
// ???????????????????? LIFECYCLE ????????????????????
|
||||||
|
|
||||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||||
@@ -185,17 +225,24 @@ namespace HorseRacingPredictor
|
|||||||
if (pageFootball != null) pageFootball.Visibility = name == "football" ? Visibility.Visible : Visibility.Collapsed;
|
if (pageFootball != null) pageFootball.Visibility = name == "football" ? Visibility.Visible : Visibility.Collapsed;
|
||||||
if (pageRacing != null) pageRacing.Visibility = name == "racing" ? Visibility.Visible : Visibility.Collapsed;
|
if (pageRacing != null) pageRacing.Visibility = name == "racing" ? Visibility.Visible : Visibility.Collapsed;
|
||||||
if (pageSettings != null) pageSettings.Visibility = name == "settings" ? Visibility.Visible : Visibility.Collapsed;
|
if (pageSettings != null) pageSettings.Visibility = name == "settings" ? Visibility.Visible : Visibility.Collapsed;
|
||||||
if (pageVirtualFb != null) pageVirtualFb.Visibility = name == "virtualfb" ? Visibility.Visible : Visibility.Collapsed;
|
|
||||||
|
|
||||||
// Update title if available
|
// Update title and subtitle
|
||||||
if (lblTitle != null)
|
if (lblTitle != null)
|
||||||
{
|
{
|
||||||
switch (name)
|
switch (name)
|
||||||
{
|
{
|
||||||
case "football": lblTitle.Text = "Calcio"; break;
|
case "football":
|
||||||
case "racing": lblTitle.Text = "Corse Cavalli"; break;
|
lblTitle.Text = "Calcio";
|
||||||
case "settings": lblTitle.Text = "Impostazioni"; break;
|
lblSubtitle.Text = "Dashboard eventi calcistici";
|
||||||
case "virtualfb": lblTitle.Text = "Calcio Virtuale"; break;
|
break;
|
||||||
|
case "racing":
|
||||||
|
lblTitle.Text = "Corse Cavalli";
|
||||||
|
lblSubtitle.Text = "Dashboard corse ippiche";
|
||||||
|
break;
|
||||||
|
case "settings":
|
||||||
|
lblTitle.Text = "Impostazioni";
|
||||||
|
lblSubtitle.Text = "Configurazione API, esportazione e parametri";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,55 +250,6 @@ namespace HorseRacingPredictor
|
|||||||
private void navFootball_Checked(object sender, RoutedEventArgs e) => ShowPage("football");
|
private void navFootball_Checked(object sender, RoutedEventArgs e) => ShowPage("football");
|
||||||
private void navRacing_Checked(object sender, RoutedEventArgs e) => ShowPage("racing");
|
private void navRacing_Checked(object sender, RoutedEventArgs e) => ShowPage("racing");
|
||||||
private void navSettings_Checked(object sender, RoutedEventArgs e) => ShowPage("settings");
|
private void navSettings_Checked(object sender, RoutedEventArgs e) => ShowPage("settings");
|
||||||
private bool _vfbInitialized;
|
|
||||||
private async void navVirtualFb_Checked(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
ShowPage("virtualfb");
|
|
||||||
// Bind results list once
|
|
||||||
if (lbVfbResults.ItemsSource == null)
|
|
||||||
lbVfbResults.ItemsSource = _vfbResults;
|
|
||||||
|
|
||||||
if (!_vfbInitialized)
|
|
||||||
{
|
|
||||||
_vfbInitialized = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Persistent user-data folder so cookies, localStorage and session survive
|
|
||||||
var userDataFolder = Path.Combine(
|
|
||||||
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
|
||||||
"BettingPredictor", "WebView2Data");
|
|
||||||
|
|
||||||
var env = await CoreWebView2Environment.CreateAsync(
|
|
||||||
browserExecutableFolder: null,
|
|
||||||
userDataFolder: userDataFolder,
|
|
||||||
options: new CoreWebView2EnvironmentOptions());
|
|
||||||
|
|
||||||
await wbVirtualFb.EnsureCoreWebView2Async(env);
|
|
||||||
|
|
||||||
// Match the real Chrome User-Agent from the HAR capture
|
|
||||||
wbVirtualFb.CoreWebView2.Settings.UserAgent =
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
|
|
||||||
"(KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36";
|
|
||||||
|
|
||||||
// Allow everything the SPA needs
|
|
||||||
wbVirtualFb.CoreWebView2.Settings.IsScriptEnabled = true;
|
|
||||||
wbVirtualFb.CoreWebView2.Settings.IsWebMessageEnabled = true;
|
|
||||||
wbVirtualFb.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = true;
|
|
||||||
wbVirtualFb.CoreWebView2.Settings.IsStatusBarEnabled = false;
|
|
||||||
|
|
||||||
wbVirtualFb.CoreWebView2.Navigate(txtVfbUrl.Text);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
System.Diagnostics.Debug.WriteLine($"[VFB] WebView2 init error: {ex.Message}");
|
|
||||||
MessageBox.Show(
|
|
||||||
$"Impossibile inizializzare WebView2.\n\n" +
|
|
||||||
$"Assicurati che il Microsoft Edge WebView2 Runtime sia installato.\n\n{ex.Message}",
|
|
||||||
"Errore WebView2", MessageBoxButton.OK, MessageBoxImage.Warning);
|
|
||||||
_vfbInitialized = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ???????????????????? FOOTBALL ????????????????????
|
// ???????????????????? FOOTBALL ????????????????????
|
||||||
|
|
||||||
@@ -261,6 +259,61 @@ namespace HorseRacingPredictor
|
|||||||
await DownloadFootballAsync(date);
|
await DownloadFootballAsync(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void btnBrowseCsvFb_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
using (var dlg = new System.Windows.Forms.FolderBrowserDialog())
|
||||||
|
{
|
||||||
|
dlg.Description = "Seleziona la cartella con i file CSV calcio";
|
||||||
|
if (dlg.ShowDialog() != System.Windows.Forms.DialogResult.OK) return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lblStatusFb.Text = "Caricamento file CSV…";
|
||||||
|
pbFootball.Value = 0;
|
||||||
|
btnExportFbCsv.IsEnabled = false;
|
||||||
|
btnBrowseCsvFb.IsEnabled = false;
|
||||||
|
|
||||||
|
var selectedPath = dlg.SelectedPath;
|
||||||
|
var progress = new Progress<int>(v => pbFootball.Value = v);
|
||||||
|
var status = new Progress<string>(s => lblStatusFb.Text = s);
|
||||||
|
|
||||||
|
var result = await Task.Run(() => LoadCsvFiles(selectedPath, progress, status));
|
||||||
|
|
||||||
|
if (result.table == null)
|
||||||
|
{
|
||||||
|
lblStatusFb.Text = result.message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
InjectRowNumbers(result.table);
|
||||||
|
_footballData = result.table;
|
||||||
|
dgFootball.ItemsSource = _footballData?.DefaultView;
|
||||||
|
UpdateFootballStatCards();
|
||||||
|
|
||||||
|
if (_footballData.Rows.Count > 0)
|
||||||
|
{
|
||||||
|
btnExportFbCsv.IsEnabled = true;
|
||||||
|
lblStatusFb.Text = $"Caricati {_footballData.Rows.Count} righe da {result.fileCount} file CSV";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lblStatusFb.Text = "Nessun dato trovato nei file CSV";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Errore durante il caricamento CSV:\n{ex.Message}",
|
||||||
|
"Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
lblStatusFb.Text = "Errore nel caricamento CSV";
|
||||||
|
pbFootball.Value = 0;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
btnBrowseCsvFb.IsEnabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task DownloadFootballAsync(DateTime date)
|
private async Task DownloadFootballAsync(DateTime date)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -293,10 +346,31 @@ namespace HorseRacingPredictor
|
|||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Update stat cards
|
||||||
|
UpdateFootballStatCards();
|
||||||
|
|
||||||
if (_footballData != null && _footballData.Rows.Count > 0)
|
if (_footballData != null && _footballData.Rows.Count > 0)
|
||||||
{
|
{
|
||||||
btnExportFbCsv.IsEnabled = true;
|
btnExportFbCsv.IsEnabled = true;
|
||||||
lblStatusFb.Text = $"Scaricate {_footballData.Rows.Count} partite";
|
lblStatusFb.Text = $"Scaricate {_footballData.Rows.Count} partite";
|
||||||
|
|
||||||
|
// Scarica dati supplementari (CSV separati) se selezionati
|
||||||
|
if (options.AnySupplementarySelected)
|
||||||
|
{
|
||||||
|
string exportFolder = !string.IsNullOrWhiteSpace(txtFbExportPath.Text)
|
||||||
|
? txtFbExportPath.Text.Trim()
|
||||||
|
: Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
||||||
|
|
||||||
|
lblStatusFb.Text = "Scaricamento dati supplementari…";
|
||||||
|
var suppFiles = await Task.Run(() =>
|
||||||
|
_footballManager.DownloadSupplementaryData(
|
||||||
|
_footballData, date, exportFolder, options, progress, status));
|
||||||
|
|
||||||
|
if (suppFiles.Count > 0)
|
||||||
|
lblStatusFb.Text = $"Scaricate {_footballData.Rows.Count} partite + {suppFiles.Count} CSV supplementari";
|
||||||
|
else
|
||||||
|
lblStatusFb.Text = $"Scaricate {_footballData.Rows.Count} partite (nessun dato supplementare trovato)";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -443,14 +517,156 @@ namespace HorseRacingPredictor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rbRcSource_Checked(object sender, RoutedEventArgs e)
|
private void ApplyDataSourceVisibility()
|
||||||
{
|
{
|
||||||
// Toggle visibility of API vs CSV controls
|
// Football: toggle API vs CSV controls
|
||||||
if (dpRacing == null || btnDownloadRc == null || btnBrowseCsvRc == null) return;
|
if (btnDownloadFb != null && btnBrowseCsvFb != null)
|
||||||
bool isApi = rbRcApi.IsChecked == true;
|
{
|
||||||
dpRacing.Visibility = isApi ? Visibility.Visible : Visibility.Collapsed;
|
bool fbIsApi = IsFbApiSource();
|
||||||
btnDownloadRc.Visibility = isApi ? Visibility.Visible : Visibility.Collapsed;
|
dpFootball.Visibility = fbIsApi ? Visibility.Visible : Visibility.Collapsed;
|
||||||
btnBrowseCsvRc.Visibility = isApi ? Visibility.Collapsed : Visibility.Visible;
|
btnDownloadFb.Visibility = fbIsApi ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
btnBrowseCsvFb.Visibility = fbIsApi ? Visibility.Collapsed : Visibility.Visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Football: show/hide API-only settings (Endpoint, Supplementary, Advanced)
|
||||||
|
if (pnlFbApiOptions != null)
|
||||||
|
pnlFbApiOptions.Visibility = IsFbApiSource() ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
|
||||||
|
// Racing: toggle API vs CSV controls
|
||||||
|
if (btnDownloadRc != null && btnBrowseCsvRc != null)
|
||||||
|
{
|
||||||
|
bool rcIsApi = IsRcApiSource();
|
||||||
|
dpRacing.Visibility = rcIsApi ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
btnDownloadRc.Visibility = rcIsApi ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
btnBrowseCsvRc.Visibility = rcIsApi ? Visibility.Collapsed : Visibility.Visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsFbApiSource()
|
||||||
|
{
|
||||||
|
var src = (cmbFbDataSource?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "";
|
||||||
|
return src.StartsWith("API", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsRcApiSource()
|
||||||
|
{
|
||||||
|
var src = (cmbRcDataSource?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "";
|
||||||
|
return src.StartsWith("API", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ———————————— FOOTBALL ENDPOINT / SUPPLEMENTARY POPUPS ————————————
|
||||||
|
|
||||||
|
private static readonly (string key, string label, bool defaultChecked)[] FbEndpointItems =
|
||||||
|
[
|
||||||
|
("Fixtures", "Partite", true),
|
||||||
|
("Odds", "Quote", true),
|
||||||
|
("Predictions","Previsioni", true),
|
||||||
|
("Standings", "Classifiche", false),
|
||||||
|
("H2H", "H2H", false),
|
||||||
|
("Events", "Eventi", false),
|
||||||
|
("Lineups", "Formazioni", false),
|
||||||
|
("Statistics", "Statistiche", false),
|
||||||
|
("Injuries", "Infortuni", false),
|
||||||
|
];
|
||||||
|
|
||||||
|
private static readonly (string key, string label)[] FbSupplementaryItems =
|
||||||
|
[
|
||||||
|
("PlayerStats", "Giocatori"),
|
||||||
|
("TeamStats", "Squadre"),
|
||||||
|
("TopScorers", "Marcatori"),
|
||||||
|
("TopAssists", "Assist"),
|
||||||
|
("TopCards", "Cartellini"),
|
||||||
|
("Squads", "Rose"),
|
||||||
|
("Coaches", "Allenatori"),
|
||||||
|
("Transfers", "Trasferimenti"),
|
||||||
|
];
|
||||||
|
|
||||||
|
private void BuildFbEndpointCheckboxes()
|
||||||
|
{
|
||||||
|
if (pnlFbEndpoints == null) return;
|
||||||
|
pnlFbEndpoints.Children.Clear();
|
||||||
|
_fbEndpointCheckboxes.Clear();
|
||||||
|
|
||||||
|
foreach (var (key, label, defaultChecked) in FbEndpointItems)
|
||||||
|
{
|
||||||
|
var cb = new CheckBox
|
||||||
|
{
|
||||||
|
Content = label,
|
||||||
|
Tag = key,
|
||||||
|
IsChecked = defaultChecked,
|
||||||
|
Margin = new Thickness(4, 2, 4, 2),
|
||||||
|
FontSize = 12,
|
||||||
|
Foreground = FindResource("BrText") as System.Windows.Media.Brush,
|
||||||
|
};
|
||||||
|
cb.Checked += (s, e) => UpdateFbEndpointsSummary();
|
||||||
|
cb.Unchecked += (s, e) => UpdateFbEndpointsSummary();
|
||||||
|
_fbEndpointCheckboxes[key] = cb;
|
||||||
|
pnlFbEndpoints.Children.Add(cb);
|
||||||
|
}
|
||||||
|
UpdateFbEndpointsSummary();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildFbSupplementaryCheckboxes()
|
||||||
|
{
|
||||||
|
if (pnlFbSupplementary == null) return;
|
||||||
|
pnlFbSupplementary.Children.Clear();
|
||||||
|
_fbSupplementaryCheckboxes.Clear();
|
||||||
|
|
||||||
|
foreach (var (key, label) in FbSupplementaryItems)
|
||||||
|
{
|
||||||
|
var cb = new CheckBox
|
||||||
|
{
|
||||||
|
Content = label,
|
||||||
|
Tag = key,
|
||||||
|
IsChecked = false,
|
||||||
|
Margin = new Thickness(4, 2, 4, 2),
|
||||||
|
FontSize = 12,
|
||||||
|
Foreground = FindResource("BrText") as System.Windows.Media.Brush,
|
||||||
|
};
|
||||||
|
cb.Checked += (s, e) => UpdateFbSupplementarySummary();
|
||||||
|
cb.Unchecked += (s, e) => UpdateFbSupplementarySummary();
|
||||||
|
_fbSupplementaryCheckboxes[key] = cb;
|
||||||
|
pnlFbSupplementary.Children.Add(cb);
|
||||||
|
}
|
||||||
|
UpdateFbSupplementarySummary();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateFbEndpointsSummary()
|
||||||
|
{
|
||||||
|
var selected = _fbEndpointCheckboxes
|
||||||
|
.Where(kv => kv.Value.IsChecked == true)
|
||||||
|
.Select(kv => kv.Value.Content.ToString())
|
||||||
|
.ToList();
|
||||||
|
if (lblFbEndpointsSummary != null)
|
||||||
|
lblFbEndpointsSummary.Text = selected.Count > 0
|
||||||
|
? string.Join(", ", selected) : "Nessuno";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateFbSupplementarySummary()
|
||||||
|
{
|
||||||
|
var selected = _fbSupplementaryCheckboxes
|
||||||
|
.Where(kv => kv.Value.IsChecked == true)
|
||||||
|
.Select(kv => kv.Value.Content.ToString())
|
||||||
|
.ToList();
|
||||||
|
if (lblFbSupplementarySummary != null)
|
||||||
|
lblFbSupplementarySummary.Text = selected.Count > 0
|
||||||
|
? string.Join(", ", selected) : "Nessuno";
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool GetFbEndpoint(string key) =>
|
||||||
|
_fbEndpointCheckboxes.TryGetValue(key, out var cb) && cb.IsChecked == true;
|
||||||
|
|
||||||
|
private void SetFbEndpoint(string key, bool value)
|
||||||
|
{
|
||||||
|
if (_fbEndpointCheckboxes.TryGetValue(key, out var cb)) cb.IsChecked = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool GetFbSupplementary(string key) =>
|
||||||
|
_fbSupplementaryCheckboxes.TryGetValue(key, out var cb) && cb.IsChecked == true;
|
||||||
|
|
||||||
|
private void SetFbSupplementary(string key, bool value)
|
||||||
|
{
|
||||||
|
if (_fbSupplementaryCheckboxes.TryGetValue(key, out var cb)) cb.IsChecked = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void btnDownloadRc_Click(object sender, RoutedEventArgs e)
|
private async void btnDownloadRc_Click(object sender, RoutedEventArgs e)
|
||||||
@@ -458,7 +674,7 @@ namespace HorseRacingPredictor
|
|||||||
await DownloadRacecardsAsync();
|
await DownloadRacecardsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnBrowseCsvRc_Click(object sender, RoutedEventArgs e)
|
private async void btnBrowseCsvRc_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
using (var dlg = new System.Windows.Forms.FolderBrowserDialog())
|
using (var dlg = new System.Windows.Forms.FolderBrowserDialog())
|
||||||
{
|
{
|
||||||
@@ -470,90 +686,31 @@ namespace HorseRacingPredictor
|
|||||||
lblStatusRc.Text = "Caricamento file CSV…";
|
lblStatusRc.Text = "Caricamento file CSV…";
|
||||||
pbRacing.Value = 0;
|
pbRacing.Value = 0;
|
||||||
btnExportRcCsv.IsEnabled = false;
|
btnExportRcCsv.IsEnabled = false;
|
||||||
|
btnBrowseCsvRc.IsEnabled = false;
|
||||||
|
|
||||||
var csvFiles = Directory.GetFiles(dlg.SelectedPath, "*.csv", SearchOption.AllDirectories)
|
var selectedPath = dlg.SelectedPath;
|
||||||
.OrderBy(f => f)
|
var progress = new Progress<int>(v => pbRacing.Value = v);
|
||||||
.ToList();
|
var status = new Progress<string>(s => lblStatusRc.Text = s);
|
||||||
|
|
||||||
if (csvFiles.Count == 0)
|
var result = await Task.Run(() => LoadCsvFiles(selectedPath, progress, status));
|
||||||
|
|
||||||
|
if (result.table == null)
|
||||||
{
|
{
|
||||||
lblStatusRc.Text = "Nessun file CSV trovato nella cartella selezionata";
|
lblStatusRc.Text = result.message;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge all CSV files into a single DataTable preserving all original columns
|
|
||||||
var table = new DataTable();
|
|
||||||
table.Columns.Add("Meeting", typeof(string));
|
|
||||||
table.Columns.Add("Race", typeof(int));
|
|
||||||
|
|
||||||
int processed = 0;
|
|
||||||
foreach (var file in csvFiles)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Extract meeting name and race number from filename pattern YYYYMMDD-meeting-rXX.csv
|
|
||||||
string fileName = Path.GetFileNameWithoutExtension(file);
|
|
||||||
string meetingName = fileName;
|
|
||||||
int raceNumber = 0;
|
|
||||||
var m = Regex.Match(Path.GetFileName(file), @"^\d{8}-(.+)-r(\d+)\.csv$", RegexOptions.IgnoreCase);
|
|
||||||
if (m.Success)
|
|
||||||
{
|
|
||||||
meetingName = string.Join(" ", m.Groups[1].Value.Split('-')
|
|
||||||
.Select(s => s.Length > 0 ? char.ToUpper(s[0]) + s.Substring(1).ToLower() : s));
|
|
||||||
int.TryParse(m.Groups[2].Value, out raceNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read CSV with simple parser (comma-delimited, first row = header)
|
|
||||||
var lines = File.ReadAllLines(file, Encoding.UTF8);
|
|
||||||
if (lines.Length < 2) { processed++; continue; }
|
|
||||||
|
|
||||||
var headers = ParseCsvLine(lines[0]);
|
|
||||||
|
|
||||||
// Ensure all columns exist in the merged DataTable
|
|
||||||
foreach (var h in headers)
|
|
||||||
{
|
|
||||||
string colName = h.Trim();
|
|
||||||
if (string.IsNullOrWhiteSpace(colName)) continue;
|
|
||||||
if (!table.Columns.Contains(colName))
|
|
||||||
table.Columns.Add(colName, typeof(string));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse data rows
|
|
||||||
for (int i = 1; i < lines.Length; i++)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(lines[i])) continue;
|
|
||||||
var values = ParseCsvLine(lines[i]);
|
|
||||||
var row = table.NewRow();
|
|
||||||
row["Meeting"] = meetingName;
|
|
||||||
row["Race"] = raceNumber;
|
|
||||||
for (int c = 0; c < headers.Length && c < values.Length; c++)
|
|
||||||
{
|
|
||||||
string colName = headers[c].Trim();
|
|
||||||
if (string.IsNullOrWhiteSpace(colName)) continue;
|
|
||||||
if (table.Columns.Contains(colName))
|
|
||||||
row[colName] = values[c]?.Trim() ?? "";
|
|
||||||
}
|
|
||||||
table.Rows.Add(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
System.Diagnostics.Debug.WriteLine($"Errore CSV {file}: {ex.Message}");
|
|
||||||
}
|
|
||||||
processed++;
|
|
||||||
pbRacing.Value = (int)((double)processed / csvFiles.Count * 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add row numbers
|
// Add row numbers
|
||||||
InjectRowNumbers(table);
|
InjectRowNumbers(result.table);
|
||||||
|
|
||||||
_racingData = table;
|
_racingData = result.table;
|
||||||
dgRacing.ItemsSource = _racingData?.DefaultView;
|
dgRacing.ItemsSource = _racingData?.DefaultView;
|
||||||
|
UpdateRacingStatCards();
|
||||||
|
|
||||||
if (_racingData.Rows.Count > 0)
|
if (_racingData.Rows.Count > 0)
|
||||||
{
|
{
|
||||||
btnExportRcCsv.IsEnabled = true;
|
btnExportRcCsv.IsEnabled = true;
|
||||||
lblStatusRc.Text = $"Caricati {_racingData.Rows.Count} cavalli da {csvFiles.Count} file CSV";
|
lblStatusRc.Text = $"Caricati {_racingData.Rows.Count} cavalli da {result.fileCount} file CSV";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -567,9 +724,85 @@ namespace HorseRacingPredictor
|
|||||||
lblStatusRc.Text = "Errore nel caricamento CSV";
|
lblStatusRc.Text = "Errore nel caricamento CSV";
|
||||||
pbRacing.Value = 0;
|
pbRacing.Value = 0;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
btnBrowseCsvRc.IsEnabled = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private (DataTable table, int fileCount, string message) LoadCsvFiles(
|
||||||
|
string folderPath, IProgress<int> progress, IProgress<string> status)
|
||||||
|
{
|
||||||
|
var csvFiles = Directory.GetFiles(folderPath, "*.csv", SearchOption.AllDirectories)
|
||||||
|
.OrderBy(f => f)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (csvFiles.Count == 0)
|
||||||
|
return (null, 0, "Nessun file CSV trovato nella cartella selezionata");
|
||||||
|
|
||||||
|
var table = new DataTable();
|
||||||
|
table.Columns.Add("Meeting", typeof(string));
|
||||||
|
table.Columns.Add("Race", typeof(int));
|
||||||
|
|
||||||
|
int processed = 0;
|
||||||
|
foreach (var file in csvFiles)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string fileName = Path.GetFileNameWithoutExtension(file);
|
||||||
|
string meetingName = fileName;
|
||||||
|
int raceNumber = 0;
|
||||||
|
var m = Regex.Match(Path.GetFileName(file), @"^\d{8}-(.+)-r(\d+)\.csv$", RegexOptions.IgnoreCase);
|
||||||
|
if (m.Success)
|
||||||
|
{
|
||||||
|
meetingName = string.Join(" ", m.Groups[1].Value.Split('-')
|
||||||
|
.Select(s => s.Length > 0 ? char.ToUpper(s[0]) + s.Substring(1).ToLower() : s));
|
||||||
|
int.TryParse(m.Groups[2].Value, out raceNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
var lines = File.ReadAllLines(file, Encoding.UTF8);
|
||||||
|
if (lines.Length < 2) { processed++; continue; }
|
||||||
|
|
||||||
|
var headers = ParseCsvLine(lines[0]);
|
||||||
|
|
||||||
|
foreach (var h in headers)
|
||||||
|
{
|
||||||
|
string colName = h.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(colName)) continue;
|
||||||
|
if (!table.Columns.Contains(colName))
|
||||||
|
table.Columns.Add(colName, typeof(string));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < lines.Length; i++)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(lines[i])) continue;
|
||||||
|
var values = ParseCsvLine(lines[i]);
|
||||||
|
var row = table.NewRow();
|
||||||
|
row["Meeting"] = meetingName;
|
||||||
|
row["Race"] = raceNumber;
|
||||||
|
for (int c = 0; c < headers.Length && c < values.Length; c++)
|
||||||
|
{
|
||||||
|
string colName = headers[c].Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(colName)) continue;
|
||||||
|
if (table.Columns.Contains(colName))
|
||||||
|
row[colName] = values[c]?.Trim() ?? "";
|
||||||
|
}
|
||||||
|
table.Rows.Add(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"Errore CSV {file}: {ex.Message}");
|
||||||
|
}
|
||||||
|
processed++;
|
||||||
|
progress?.Report((int)((double)processed / csvFiles.Count * 100));
|
||||||
|
status?.Report($"Lettura CSV… {processed}/{csvFiles.Count}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (table, csvFiles.Count, null);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parses a single CSV line respecting quoted fields (comma delimiter).
|
/// Parses a single CSV line respecting quoted fields (comma delimiter).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -643,7 +876,8 @@ namespace HorseRacingPredictor
|
|||||||
{
|
{
|
||||||
_racingCts.Cancel();
|
_racingCts.Cancel();
|
||||||
_racingCts = null;
|
_racingCts = null;
|
||||||
btnDownloadRc.Content = "Scarica Corse";
|
btnDownloadRcIcon.Text = "\u21E9";
|
||||||
|
btnDownloadRcText.Text = "Scarica";
|
||||||
lblStatusRc.Text = "Annullato";
|
lblStatusRc.Text = "Annullato";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -655,7 +889,8 @@ namespace HorseRacingPredictor
|
|||||||
{
|
{
|
||||||
pbRacing.Value = 0;
|
pbRacing.Value = 0;
|
||||||
lblStatusRc.Text = "Scaricamento corse da FormFav...";
|
lblStatusRc.Text = "Scaricamento corse da FormFav...";
|
||||||
btnDownloadRc.Content = "Annulla";
|
btnDownloadRcIcon.Text = "\u2718";
|
||||||
|
btnDownloadRcText.Text = "Annulla";
|
||||||
dpRacing.IsEnabled = false;
|
dpRacing.IsEnabled = false;
|
||||||
btnExportRcCsv.IsEnabled = false;
|
btnExportRcCsv.IsEnabled = false;
|
||||||
|
|
||||||
@@ -676,6 +911,7 @@ namespace HorseRacingPredictor
|
|||||||
InjectRowNumbers(_racingData);
|
InjectRowNumbers(_racingData);
|
||||||
|
|
||||||
dgRacing.ItemsSource = _racingData?.DefaultView;
|
dgRacing.ItemsSource = _racingData?.DefaultView;
|
||||||
|
UpdateRacingStatCards();
|
||||||
|
|
||||||
if (_racingData != null && _racingData.Rows.Count > 0)
|
if (_racingData != null && _racingData.Rows.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -702,7 +938,8 @@ namespace HorseRacingPredictor
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_racingCts = null;
|
_racingCts = null;
|
||||||
btnDownloadRc.Content = "Scarica Corse";
|
btnDownloadRcIcon.Text = "\u21E9";
|
||||||
|
btnDownloadRcText.Text = "Scarica";
|
||||||
dpRacing.IsEnabled = true;
|
dpRacing.IsEnabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -711,7 +948,7 @@ namespace HorseRacingPredictor
|
|||||||
{
|
{
|
||||||
_racingManager.UpdateApiKey(txtRacingApiKey.Text.Trim());
|
_racingManager.UpdateApiKey(txtRacingApiKey.Text.Trim());
|
||||||
|
|
||||||
var tz = txtRcTimezone?.Text?.Trim();
|
var tz = (cmbRcTimezone?.SelectedItem as string)?.Trim();
|
||||||
if (!string.IsNullOrEmpty(tz))
|
if (!string.IsNullOrEmpty(tz))
|
||||||
_racingManager.Timezone = tz;
|
_racingManager.Timezone = tz;
|
||||||
|
|
||||||
@@ -849,6 +1086,7 @@ namespace HorseRacingPredictor
|
|||||||
var s = UserSettings.Load();
|
var s = UserSettings.Load();
|
||||||
|
|
||||||
txtApiKey.Text = s.ApiKey;
|
txtApiKey.Text = s.ApiKey;
|
||||||
|
SetComboBoxSelectionByContent(cmbFbDataSource, s.FbDataSource);
|
||||||
txtFbExportPath.Text = s.FbExportPath;
|
txtFbExportPath.Text = s.FbExportPath;
|
||||||
txtFbPrefix.Text = s.FbPrefix;
|
txtFbPrefix.Text = s.FbPrefix;
|
||||||
txtFbSuffix.Text = s.FbSuffix;
|
txtFbSuffix.Text = s.FbSuffix;
|
||||||
@@ -856,20 +1094,33 @@ namespace HorseRacingPredictor
|
|||||||
SetComboBoxSelectionByContent(cmbFbDateFormat, s.FbDateFormat);
|
SetComboBoxSelectionByContent(cmbFbDateFormat, s.FbDateFormat);
|
||||||
SetComboBoxSelectionByContent(cmbFbFormat, s.FbFormat);
|
SetComboBoxSelectionByContent(cmbFbFormat, s.FbFormat);
|
||||||
|
|
||||||
// Football Download Options
|
// Football Download Options (dynamic popups)
|
||||||
chkFbFixtures.IsChecked = s.FbDownloadFixtures;
|
SetFbEndpoint("Fixtures", s.FbDownloadFixtures);
|
||||||
chkFbOdds.IsChecked = s.FbDownloadOdds;
|
SetFbEndpoint("Odds", s.FbDownloadOdds);
|
||||||
chkFbPredictions.IsChecked = s.FbDownloadPredictions;
|
SetFbEndpoint("Predictions", s.FbDownloadPredictions);
|
||||||
chkFbStandings.IsChecked = s.FbDownloadStandings;
|
SetFbEndpoint("Standings", s.FbDownloadStandings);
|
||||||
chkFbH2H.IsChecked = s.FbDownloadH2H;
|
SetFbEndpoint("H2H", s.FbDownloadH2H);
|
||||||
chkFbEvents.IsChecked = s.FbDownloadEvents;
|
SetFbEndpoint("Events", s.FbDownloadEvents);
|
||||||
chkFbLineups.IsChecked = s.FbDownloadLineups;
|
SetFbEndpoint("Lineups", s.FbDownloadLineups);
|
||||||
chkFbStatistics.IsChecked = s.FbDownloadStatistics;
|
SetFbEndpoint("Statistics", s.FbDownloadStatistics);
|
||||||
chkFbInjuries.IsChecked = s.FbDownloadInjuries;
|
SetFbEndpoint("Injuries", s.FbDownloadInjuries);
|
||||||
|
UpdateFbEndpointsSummary();
|
||||||
|
|
||||||
|
// Football Supplementary Download Options (dynamic popups)
|
||||||
|
SetFbSupplementary("PlayerStats", s.FbDownloadPlayerStats);
|
||||||
|
SetFbSupplementary("TeamStats", s.FbDownloadTeamStats);
|
||||||
|
SetFbSupplementary("TopScorers", s.FbDownloadTopScorers);
|
||||||
|
SetFbSupplementary("TopAssists", s.FbDownloadTopAssists);
|
||||||
|
SetFbSupplementary("TopCards", s.FbDownloadTopCards);
|
||||||
|
SetFbSupplementary("Squads", s.FbDownloadSquads);
|
||||||
|
SetFbSupplementary("Coaches", s.FbDownloadCoaches);
|
||||||
|
SetFbSupplementary("Transfers", s.FbDownloadTransfers);
|
||||||
|
UpdateFbSupplementarySummary();
|
||||||
|
|
||||||
txtFbBookmakerId.Text = s.FbBookmakerId.ToString();
|
txtFbBookmakerId.Text = s.FbBookmakerId.ToString();
|
||||||
txtFbOddsMaxPages.Text = s.FbOddsMaxPages.ToString();
|
txtFbOddsMaxPages.Text = s.FbOddsMaxPages.ToString();
|
||||||
txtFbMaxFixtures.Text = s.FbMaxFixturesForDetails.ToString();
|
txtFbMaxFixtures.Text = s.FbMaxFixturesForDetails.ToString();
|
||||||
if (txtFbTimezone != null) txtFbTimezone.Text = s.FbTimezone;
|
if (cmbFbTimezone != null) SetTimezoneSelection(cmbFbTimezone, s.FbTimezone);
|
||||||
txtFbLeagueIds.Text = s.FbLeagueIds.Count > 0 ? string.Join(",", s.FbLeagueIds) : "";
|
txtFbLeagueIds.Text = s.FbLeagueIds.Count > 0 ? string.Join(",", s.FbLeagueIds) : "";
|
||||||
chkFbCheckQuota.IsChecked = s.FbCheckQuota;
|
chkFbCheckQuota.IsChecked = s.FbCheckQuota;
|
||||||
txtFbMinQuota.Text = s.FbMinRemainingQuota.ToString();
|
txtFbMinQuota.Text = s.FbMinRemainingQuota.ToString();
|
||||||
@@ -884,12 +1135,14 @@ namespace HorseRacingPredictor
|
|||||||
SetComboBoxSelectionByContent(cmbRcFormat, s.RcFormat);
|
SetComboBoxSelectionByContent(cmbRcFormat, s.RcFormat);
|
||||||
|
|
||||||
txtRacingApiKey.Text = string.IsNullOrEmpty(s.RacingApiKey) ? DefaultRacingApiKey : s.RacingApiKey;
|
txtRacingApiKey.Text = string.IsNullOrEmpty(s.RacingApiKey) ? DefaultRacingApiKey : s.RacingApiKey;
|
||||||
if (txtRcTimezone != null) txtRcTimezone.Text = s.RcTimezone;
|
SetComboBoxSelectionByContent(cmbRcDataSource, s.RcDataSource);
|
||||||
|
if (cmbRcTimezone != null) SetTimezoneSelection(cmbRcTimezone, s.RcTimezone);
|
||||||
SetSelectedCountries(s.RcCountries.ToArray());
|
SetSelectedCountries(s.RcCountries.ToArray());
|
||||||
|
|
||||||
UpdateFbPreview();
|
UpdateFbPreview();
|
||||||
UpdateRcPreview();
|
UpdateRcPreview();
|
||||||
ApplyRacingSettings();
|
ApplyRacingSettings();
|
||||||
|
ApplyDataSourceVisibility();
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
@@ -1127,26 +1380,36 @@ namespace HorseRacingPredictor
|
|||||||
var s = new UserSettings
|
var s = new UserSettings
|
||||||
{
|
{
|
||||||
ApiKey = txtApiKey.Text.Trim(),
|
ApiKey = txtApiKey.Text.Trim(),
|
||||||
|
FbDataSource = (cmbFbDataSource?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "API - API-Football",
|
||||||
FbExportPath = txtFbExportPath.Text.Trim(),
|
FbExportPath = txtFbExportPath.Text.Trim(),
|
||||||
FbPrefix = txtFbPrefix.Text.Trim(),
|
FbPrefix = txtFbPrefix.Text.Trim(),
|
||||||
FbSuffix = txtFbSuffix.Text.Trim(),
|
FbSuffix = txtFbSuffix.Text.Trim(),
|
||||||
FbIncludeDate = chkFbIncludeDate.IsChecked == true,
|
FbIncludeDate = chkFbIncludeDate.IsChecked == true,
|
||||||
FbDateFormat = (cmbFbDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd",
|
FbDateFormat = (cmbFbDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd",
|
||||||
FbFormat = (cmbFbFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV",
|
FbFormat = (cmbFbFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV",
|
||||||
// Football Download Options
|
// Football Download Options (from dynamic popups)
|
||||||
FbDownloadFixtures = chkFbFixtures.IsChecked == true,
|
FbDownloadFixtures = GetFbEndpoint("Fixtures"),
|
||||||
FbDownloadOdds = chkFbOdds.IsChecked == true,
|
FbDownloadOdds = GetFbEndpoint("Odds"),
|
||||||
FbDownloadPredictions = chkFbPredictions.IsChecked == true,
|
FbDownloadPredictions = GetFbEndpoint("Predictions"),
|
||||||
FbDownloadStandings = chkFbStandings.IsChecked == true,
|
FbDownloadStandings = GetFbEndpoint("Standings"),
|
||||||
FbDownloadH2H = chkFbH2H.IsChecked == true,
|
FbDownloadH2H = GetFbEndpoint("H2H"),
|
||||||
FbDownloadEvents = chkFbEvents.IsChecked == true,
|
FbDownloadEvents = GetFbEndpoint("Events"),
|
||||||
FbDownloadLineups = chkFbLineups.IsChecked == true,
|
FbDownloadLineups = GetFbEndpoint("Lineups"),
|
||||||
FbDownloadStatistics = chkFbStatistics.IsChecked == true,
|
FbDownloadStatistics = GetFbEndpoint("Statistics"),
|
||||||
FbDownloadInjuries = chkFbInjuries.IsChecked == true,
|
FbDownloadInjuries = GetFbEndpoint("Injuries"),
|
||||||
|
// Football Supplementary Download Options (from dynamic popups)
|
||||||
|
FbDownloadPlayerStats = GetFbSupplementary("PlayerStats"),
|
||||||
|
FbDownloadTeamStats = GetFbSupplementary("TeamStats"),
|
||||||
|
FbDownloadTopScorers = GetFbSupplementary("TopScorers"),
|
||||||
|
FbDownloadTopAssists = GetFbSupplementary("TopAssists"),
|
||||||
|
FbDownloadTopCards = GetFbSupplementary("TopCards"),
|
||||||
|
FbDownloadSquads = GetFbSupplementary("Squads"),
|
||||||
|
FbDownloadCoaches = GetFbSupplementary("Coaches"),
|
||||||
|
FbDownloadTransfers = GetFbSupplementary("Transfers"),
|
||||||
FbBookmakerId = int.TryParse(txtFbBookmakerId.Text.Trim(), out var bId) ? bId : 8,
|
FbBookmakerId = int.TryParse(txtFbBookmakerId.Text.Trim(), out var bId) ? bId : 8,
|
||||||
FbOddsMaxPages = int.TryParse(txtFbOddsMaxPages.Text.Trim(), out var omp) ? omp : 3,
|
FbOddsMaxPages = int.TryParse(txtFbOddsMaxPages.Text.Trim(), out var omp) ? omp : 3,
|
||||||
FbMaxFixturesForDetails = int.TryParse(txtFbMaxFixtures.Text.Trim(), out var mf) ? mf : 50,
|
FbMaxFixturesForDetails = int.TryParse(txtFbMaxFixtures.Text.Trim(), out var mf) ? mf : 50,
|
||||||
FbTimezone = txtFbTimezone?.Text?.Trim() ?? "Europe/Rome",
|
FbTimezone = (cmbFbTimezone?.SelectedItem as string)?.Trim() ?? "Europe/Rome",
|
||||||
FbLeagueIds = ParseIntList(txtFbLeagueIds?.Text),
|
FbLeagueIds = ParseIntList(txtFbLeagueIds?.Text),
|
||||||
FbCheckQuota = chkFbCheckQuota.IsChecked == true,
|
FbCheckQuota = chkFbCheckQuota.IsChecked == true,
|
||||||
FbMinRemainingQuota = int.TryParse(txtFbMinQuota.Text.Trim(), out var mq) ? mq : 10,
|
FbMinRemainingQuota = int.TryParse(txtFbMinQuota.Text.Trim(), out var mq) ? mq : 10,
|
||||||
@@ -1159,7 +1422,8 @@ namespace HorseRacingPredictor
|
|||||||
RcDateFormat = (cmbRcDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd",
|
RcDateFormat = (cmbRcDateFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "yyyy-MM-dd",
|
||||||
RcFormat = (cmbRcFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV",
|
RcFormat = (cmbRcFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV",
|
||||||
RacingApiKey = txtRacingApiKey.Text.Trim(),
|
RacingApiKey = txtRacingApiKey.Text.Trim(),
|
||||||
RcTimezone = txtRcTimezone?.Text?.Trim() ?? "Australia/Sydney",
|
RcDataSource = (cmbRcDataSource?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "API - FormFav",
|
||||||
|
RcTimezone = (cmbRcTimezone?.SelectedItem as string)?.Trim() ?? "Australia/Sydney",
|
||||||
RcCountries = new List<string>(GetSelectedCountries())
|
RcCountries = new List<string>(GetSelectedCountries())
|
||||||
};
|
};
|
||||||
s.Save();
|
s.Save();
|
||||||
@@ -1167,6 +1431,7 @@ namespace HorseRacingPredictor
|
|||||||
UpdateFbPreview();
|
UpdateFbPreview();
|
||||||
UpdateRcPreview();
|
UpdateRcPreview();
|
||||||
ApplyRacingSettings();
|
ApplyRacingSettings();
|
||||||
|
ApplyDataSourceVisibility();
|
||||||
|
|
||||||
MessageBox.Show("Impostazioni salvate con successo.",
|
MessageBox.Show("Impostazioni salvate con successo.",
|
||||||
"Salvato", MessageBoxButton.OK, MessageBoxImage.Information);
|
"Salvato", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
@@ -1180,6 +1445,52 @@ namespace HorseRacingPredictor
|
|||||||
|
|
||||||
// ???????????? FOOTBALL DOWNLOAD OPTIONS HELPERS ????????????
|
// ???????????? FOOTBALL DOWNLOAD OPTIONS HELPERS ????????????
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aggiorna le stat card nel dashboard Calcio.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateFootballStatCards()
|
||||||
|
{
|
||||||
|
int rows = _footballData?.Rows.Count ?? 0;
|
||||||
|
int cols = _footballData?.Columns.Count ?? 0;
|
||||||
|
lblFbCardCount.Text = rows.ToString();
|
||||||
|
lblFbCardCols.Text = cols > 0 ? cols.ToString() : "--";
|
||||||
|
lblFbCardFormat.Text = (cmbFbFormat?.SelectedItem as ComboBoxItem)?.Content?.ToString() ?? "CSV";
|
||||||
|
|
||||||
|
if (rows > 0)
|
||||||
|
{
|
||||||
|
lblFbCardStatus.Text = "Caricato";
|
||||||
|
dotFbStatus.Fill = FindResource("BrGreen") as System.Windows.Media.SolidColorBrush;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lblFbCardStatus.Text = "Pronto";
|
||||||
|
dotFbStatus.Fill = FindResource("BrOverlay0") as System.Windows.Media.SolidColorBrush;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aggiorna le stat card nel dashboard Corse Cavalli.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateRacingStatCards()
|
||||||
|
{
|
||||||
|
int rows = _racingData?.Rows.Count ?? 0;
|
||||||
|
int cols = _racingData?.Columns.Count ?? 0;
|
||||||
|
lblRcCardCount.Text = rows.ToString();
|
||||||
|
lblRcCardCols.Text = cols > 0 ? cols.ToString() : "--";
|
||||||
|
lblRcCardSource.Text = IsRcApiSource() ? "API" : "CSV";
|
||||||
|
|
||||||
|
if (rows > 0)
|
||||||
|
{
|
||||||
|
lblRcCardStatus.Text = "Caricato";
|
||||||
|
dotRcStatus.Fill = FindResource("BrGreen") as System.Windows.Media.SolidColorBrush;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lblRcCardStatus.Text = "Pronto";
|
||||||
|
dotRcStatus.Fill = FindResource("BrOverlay0") as System.Windows.Media.SolidColorBrush;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Costruisce un FootballDownloadOptions leggendo i valori dalla UI.
|
/// Costruisce un FootballDownloadOptions leggendo i valori dalla UI.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1187,19 +1498,28 @@ namespace HorseRacingPredictor
|
|||||||
{
|
{
|
||||||
return new Football.FootballDownloadOptions
|
return new Football.FootballDownloadOptions
|
||||||
{
|
{
|
||||||
DownloadFixtures = chkFbFixtures.IsChecked == true,
|
DownloadFixtures = GetFbEndpoint("Fixtures"),
|
||||||
DownloadOdds = chkFbOdds.IsChecked == true,
|
DownloadOdds = GetFbEndpoint("Odds"),
|
||||||
DownloadPredictions = chkFbPredictions.IsChecked == true,
|
DownloadPredictions = GetFbEndpoint("Predictions"),
|
||||||
DownloadStandings = chkFbStandings.IsChecked == true,
|
DownloadStandings = GetFbEndpoint("Standings"),
|
||||||
DownloadH2H = chkFbH2H.IsChecked == true,
|
DownloadH2H = GetFbEndpoint("H2H"),
|
||||||
DownloadEvents = chkFbEvents.IsChecked == true,
|
DownloadEvents = GetFbEndpoint("Events"),
|
||||||
DownloadLineups = chkFbLineups.IsChecked == true,
|
DownloadLineups = GetFbEndpoint("Lineups"),
|
||||||
DownloadStatistics = chkFbStatistics.IsChecked == true,
|
DownloadStatistics = GetFbEndpoint("Statistics"),
|
||||||
DownloadInjuries = chkFbInjuries.IsChecked == true,
|
DownloadInjuries = GetFbEndpoint("Injuries"),
|
||||||
|
// Supplementari
|
||||||
|
DownloadPlayerStats = GetFbSupplementary("PlayerStats"),
|
||||||
|
DownloadTeamStats = GetFbSupplementary("TeamStats"),
|
||||||
|
DownloadTopScorers = GetFbSupplementary("TopScorers"),
|
||||||
|
DownloadTopAssists = GetFbSupplementary("TopAssists"),
|
||||||
|
DownloadTopCards = GetFbSupplementary("TopCards"),
|
||||||
|
DownloadSquads = GetFbSupplementary("Squads"),
|
||||||
|
DownloadCoaches = GetFbSupplementary("Coaches"),
|
||||||
|
DownloadTransfers = GetFbSupplementary("Transfers"),
|
||||||
BookmakerId = int.TryParse(txtFbBookmakerId.Text.Trim(), out var bId) ? bId : 8,
|
BookmakerId = int.TryParse(txtFbBookmakerId.Text.Trim(), out var bId) ? bId : 8,
|
||||||
OddsMaxPages = int.TryParse(txtFbOddsMaxPages.Text.Trim(), out var omp) ? omp : 3,
|
OddsMaxPages = int.TryParse(txtFbOddsMaxPages.Text.Trim(), out var omp) ? omp : 3,
|
||||||
MaxFixturesForDetails = int.TryParse(txtFbMaxFixtures.Text.Trim(), out var mf) ? mf : 50,
|
MaxFixturesForDetails = int.TryParse(txtFbMaxFixtures.Text.Trim(), out var mf) ? mf : 50,
|
||||||
Timezone = txtFbTimezone?.Text?.Trim() ?? "Europe/Rome",
|
Timezone = (cmbFbTimezone?.SelectedItem as string)?.Trim() ?? "Europe/Rome",
|
||||||
LeagueIds = ParseIntList(txtFbLeagueIds?.Text),
|
LeagueIds = ParseIntList(txtFbLeagueIds?.Text),
|
||||||
CheckQuota = chkFbCheckQuota.IsChecked == true,
|
CheckQuota = chkFbCheckQuota.IsChecked == true,
|
||||||
MinRemainingQuota = int.TryParse(txtFbMinQuota.Text.Trim(), out var mq) ? mq : 10,
|
MinRemainingQuota = int.TryParse(txtFbMinQuota.Text.Trim(), out var mq) ? mq : 10,
|
||||||
@@ -1221,114 +1541,5 @@ namespace HorseRacingPredictor
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ???????????????????????? VIRTUAL FOOTBALL ????????????????????????
|
|
||||||
|
|
||||||
private void btnVfbNavigate_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var url = txtVfbUrl.Text?.Trim();
|
|
||||||
if (!string.IsNullOrEmpty(url) && wbVirtualFb.CoreWebView2 != null)
|
|
||||||
wbVirtualFb.CoreWebView2.Navigate(url);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
System.Diagnostics.Debug.WriteLine($"[VFB] Navigate error: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void btnVfbRefresh_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
try { wbVirtualFb.CoreWebView2?.Reload(); }
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
System.Diagnostics.Debug.WriteLine($"[VFB] Refresh error: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void btnVfbAddResult_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (!int.TryParse(txtVfbHomeGoals.Text, out int hg)) hg = 0;
|
|
||||||
if (!int.TryParse(txtVfbAwayGoals.Text, out int ag)) ag = 0;
|
|
||||||
|
|
||||||
var match = new VirtualFootball.VirtualMatch
|
|
||||||
{
|
|
||||||
Time = DateTime.Now.ToString("HH:mm"),
|
|
||||||
Home = string.IsNullOrWhiteSpace(txtVfbHome.Text) ? "Casa" : txtVfbHome.Text.Trim(),
|
|
||||||
HomeGoals = hg,
|
|
||||||
AwayGoals = ag,
|
|
||||||
Away = string.IsNullOrWhiteSpace(txtVfbAway.Text) ? "Ospite" : txtVfbAway.Text.Trim()
|
|
||||||
};
|
|
||||||
|
|
||||||
_vfbResults.Insert(0, match);
|
|
||||||
|
|
||||||
// Reset input
|
|
||||||
txtVfbHomeGoals.Text = "0";
|
|
||||||
txtVfbAwayGoals.Text = "0";
|
|
||||||
|
|
||||||
UpdateVfbStats();
|
|
||||||
UpdateVfbSuggestion();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateVfbStats()
|
|
||||||
{
|
|
||||||
if (_vfbResults.Count == 0) { lblVfbStats.Text = "Nessun dato"; return; }
|
|
||||||
|
|
||||||
int total = _vfbResults.Count;
|
|
||||||
int draws = _vfbResults.Count(m => m.Outcome == "X");
|
|
||||||
int home = _vfbResults.Count(m => m.Outcome == "1");
|
|
||||||
int away = _vfbResults.Count(m => m.Outcome == "2");
|
|
||||||
double drawPct = (double)draws / total * 100;
|
|
||||||
double homePct = (double)home / total * 100;
|
|
||||||
double awayPct = (double)away / total * 100;
|
|
||||||
int totalGoals = _vfbResults.Sum(m => m.HomeGoals + m.AwayGoals);
|
|
||||||
double avgGoals = (double)totalGoals / total;
|
|
||||||
int over25 = _vfbResults.Count(m => m.HomeGoals + m.AwayGoals > 2);
|
|
||||||
|
|
||||||
lblVfbStats.Text = $"Partite: {total}\n" +
|
|
||||||
$"1: {home} ({homePct:F1}%) X: {draws} ({drawPct:F1}%) 2: {away} ({awayPct:F1}%)\n" +
|
|
||||||
$"Media gol: {avgGoals:F1} Over 2.5: {over25} ({(double)over25 / total * 100:F1}%)";
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateVfbSuggestion()
|
|
||||||
{
|
|
||||||
if (_vfbResults.Count < 5)
|
|
||||||
{
|
|
||||||
lblVfbSuggestion.Text = "Inserisci almeno 5 risultati";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count consecutive non-draw results (most recent first)
|
|
||||||
int streak = 0;
|
|
||||||
foreach (var m in _vfbResults)
|
|
||||||
{
|
|
||||||
if (m.Outcome != "X") streak++;
|
|
||||||
else break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple strategy: after a long streak without draws, suggest betting on draw
|
|
||||||
if (streak >= 8)
|
|
||||||
{
|
|
||||||
lblVfbSuggestion.Text = $"\u26A0 PUNTA X (pareggio) \u2014 {streak} partite consecutive senza pareggio!\n" +
|
|
||||||
"Puntata alta consigliata.";
|
|
||||||
}
|
|
||||||
else if (streak >= 5)
|
|
||||||
{
|
|
||||||
lblVfbSuggestion.Text = $"\u2705 Punta X (pareggio) \u2014 {streak} partite senza pareggio.\n" +
|
|
||||||
"Puntata media consigliata.";
|
|
||||||
}
|
|
||||||
else if (streak >= 3)
|
|
||||||
{
|
|
||||||
lblVfbSuggestion.Text = $"\u23F3 Possibile X \u2014 {streak} partite senza pareggio.\n" +
|
|
||||||
"Puntata bassa o attendi.";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
double drawPct = (double)_vfbResults.Count(m => m.Outcome == "X") / _vfbResults.Count * 100;
|
|
||||||
lblVfbSuggestion.Text = $"Pareggio recente \u2014 attendi una serie senza X.\n" +
|
|
||||||
$"Frequenza X attuale: {drawPct:F1}%";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user