Compare commits

...

8 Commits

Author SHA1 Message Date
97951ac6f0 Aggiunta sezione Calcio Virtuale con WebView2 e statistiche
Introdotta una nuova pagina "Calcio Virtuale" con browser WebView2 integrato per navigare siti di virtual football (default Bet365). Aggiunta la possibilità di inserire manualmente i risultati delle partite virtuali, visualizzarli in lista, calcolare statistiche (1/X/2, media gol, over 2.5) e ricevere suggerimenti di puntata dinamici. Creata la classe VirtualMatch per rappresentare i risultati. Aggiornate le dipendenze e l'interfaccia senza impattare le altre sezioni.
2026-03-11 23:28:05 +01:00
0abb8467f2 Aggiorna riferimenti NuGet in BettingPredictor.csproj
Aggiornati i riferimenti a Microsoft.ML, CsvHelper, Newtonsoft.Json, RestSharp e vari pacchetti System.* nel file di progetto alla versioni più recenti. Modificati i percorsi <HintPath> e le condizioni di errore per riflettere i nuovi numeri di versione.
2026-03-03 08:55:08 +01:00
fc46e56b82 Esportazione avanzata e UI migliorata
Aggiunta esportazione dati in JSON/XML oltre a CSV, con nome file personalizzabile (prefisso, suffisso, formato data, anteprima). Nuove impostazioni per esportazione e preview dinamica. Gestione sorgente corse cavalli da API o CSV Punters, con aggregazione automatica. Migliorati stili DataGrid e numerazione righe. Rimossa pagina info, stili globali consolidati, ComboBox più leggibili. Migliorata gestione errori e messaggi utente.
2026-03-01 13:40:19 +01:00
8fe93de673 Rimozione completa della componente Machine Learning
Sono state eliminate tutte le dipendenze, file sorgente e script SQL relativi al machine learning (Microsoft.ML, Horses\ML, procedure e tabelle per rating automatico). Il calcolo dei rating ora è deterministico. Aggiornato il parsing JSON in Main.cs con Newtonsoft.Json. Il progetto risulta semplificato e più focalizzato sulle funzionalità manuali.
2026-02-25 21:17:14 +01:00
c8c674dd1c Aggiorna gestione JSON e aggiungi controlli UI moderni
Sostituito System.Text.Json con Newtonsoft.Json (JToken) nei database manager per maggiore compatibilità e robustezza.
Aggiunti nuovi controlli personalizzati (ModernButton, ModernDataGridView, ModernDateTimePicker, ModernLabel, ModernPanel, ModernProgressBar, ModernTabControl, ModernTextBox) per un'interfaccia moderna a tema scuro.
Aggiornate e aggiunte dipendenze NuGet (CsvHelper, Microsoft.ML, Newtonsoft.Json, RestSharp, ecc.).
Rimossa una using non utilizzata in API.cs.
2026-02-25 20:00:09 +01:00
72bd778d56 Migrazione UI a WPF e integrazione Racing API moderna
Transizione completa da WinForms a WPF con interfaccia moderna (sidebar, pagine, palette Catppuccin Mocha).
Aggiunta integrazione con The Racing API per le corse dei cavalli, inclusi nuovi moduli di backend e parsing dati.
Introdotti componenti UI personalizzati (CardPanel, ModernButton, ModernProgressBar, NavButton, ModernTheme) per un look coerente e moderno.
Gestione avanzata delle impostazioni, esportazione CSV migliorata, refactoring della logica di business e maggiore robustezza nelle chiamate API.
Aggiunti stub CsvHelper e file di progetto di backup per facilitare la compatibilità e la migrazione.
2026-02-25 18:49:48 +01:00
Alberto Balbo
82fcaaaf90 Modernizzazione UI con controlli e tema scuro
- Introdotti nuovi controlli moderni (es. ModernButton, ModernDataGridView).
- Aggiunto il tema scuro con colori, font e spaziature uniformi.
- Aggiornati `Main.Designer.cs` e `Main.cs` per utilizzare i nuovi controlli.
- Rimossi controlli e metodi obsoleti (es. ProgressDialog).
- Migliorati layout, dimensioni e testi per una migliore usabilità.
- Aggiunti metodi helper per configurazioni rapide nei controlli.
- Implementato design modulare per una maggiore manutenibilità.
2025-10-21 09:35:23 +02:00
Alberto Balbo
2236c19c07 Aggiornamento dipendenze e passaggio a Newtonsoft.Json
- Aggiornamento alla versione 5.0.0-preview.25503.2 di Microsoft.ML.
- Aggiornamento alla versione 33.1.0 di CsvHelper.
- Aggiornamento alla versione 13.0.4 di Newtonsoft.Json.
- Aggiornamento alla versione 112.1.1-alpha.0.4 di RestSharp.
- Sostituzione di System.Text.Json con Newtonsoft.Json.
- Modifiche nei metodi per utilizzare JToken e JObject.
- Aggiornamento dei file .csproj e packages.config.
- Miglioramenti nella gestione delle eccezioni e nei log.
- Rimozione di riferimenti inutilizzati e miglioramenti generali.
2025-10-08 17:55:48 +02:00
56 changed files with 763985 additions and 43157 deletions

View File

@@ -0,0 +1,6 @@
<Application x:Class="HorseRacingPredictor.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources />
</Application>

View File

@@ -0,0 +1,8 @@
using System.Windows;
namespace HorseRacingPredictor
{
public partial class App : Application
{
}
}

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\Microsoft.ML.FastTree.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.FastTree.props" Condition="Exists('..\packages\Microsoft.ML.FastTree.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.FastTree.props')" />
<Import Project="..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.props" Condition="Exists('..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.props')" />
<Import Project="..\packages\Microsoft.ML.CpuMath.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.CpuMath.props" Condition="Exists('..\packages\Microsoft.ML.CpuMath.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.CpuMath.props')" />
<Import Project="..\packages\Microsoft.ML.FastTree.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.FastTree.props" Condition="Exists('..\packages\Microsoft.ML.FastTree.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.FastTree.props')" />
<Import Project="..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.props" Condition="Exists('..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.props')" />
<Import Project="..\packages\Microsoft.ML.CpuMath.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.CpuMath.props" Condition="Exists('..\packages\Microsoft.ML.CpuMath.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.CpuMath.props')" />
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -13,6 +13,7 @@
<AssemblyName>HorseRacingPredictor</AssemblyName>
<TargetFrameworkVersion>v4.8.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<NuGetPackageImportStamp>
@@ -43,9 +44,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
@@ -53,79 +52,51 @@
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="CsvHelper, Version=33.0.0.0, Culture=neutral, PublicKeyToken=8c4959082be5c823, processorArchitecture=MSIL">
<HintPath>..\packages\CsvHelper.33.0.1\lib\net48\CsvHelper.dll</HintPath>
<Reference Include="CsvHelper, Version=33.0.0.0, Culture=neutral, PublicKeyToken=8c4a6d608ce8f59c, processorArchitecture=MSIL">
<HintPath>..\packages\CsvHelper.33.1.0\lib\net48\CsvHelper.dll</HintPath>
</Reference>
<Reference Include="Dapper, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Dapper.2.1.66\lib\net461\Dapper.dll</HintPath>
<Reference Include="Microsoft.Web.WebView2.Core, Version=1.0.3800.47, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Web.WebView2.1.0.3800.47\lib\net462\Microsoft.Web.WebView2.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bcl.AsyncInterfaces, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.AsyncInterfaces.10.0.0-preview.4.25258.110\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll</HintPath>
<Reference Include="Microsoft.Web.WebView2.Wpf, Version=1.0.3800.47, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Web.WebView2.1.0.3800.47\lib\net462\Microsoft.Web.WebView2.Wpf.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bcl.HashCode, Version=6.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.HashCode.6.0.0\lib\net462\Microsoft.Bcl.HashCode.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Bcl.Numerics, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Bcl.Numerics.10.0.0-preview.4.25258.110\lib\net462\Microsoft.Bcl.Numerics.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ML.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ML.CpuMath, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ML.CpuMath.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.CpuMath.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ML.Data, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.Data.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ML.DataView, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ML.DataView.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.DataView.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ML.FastTree, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ML.FastTree.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.FastTree.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ML.KMeansClustering, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.KMeansClustering.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ML.PCA, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.PCA.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ML.StandardTrainers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.StandardTrainers.dll</HintPath>
</Reference>
<Reference Include="Microsoft.ML.Transforms, Version=1.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\lib\netstandard2.0\Microsoft.ML.Transforms.dll</HintPath>
<Reference Include="Microsoft.Web.WebView2.WinForms, Version=1.0.3800.47, Culture=neutral, PublicKeyToken=2a8ab48044d2601e, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Web.WebView2.1.0.3800.47\lib\net462\Microsoft.Web.WebView2.WinForms.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
<HintPath>..\packages\Newtonsoft.Json.13.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="RestSharp, Version=112.1.1.0, Culture=neutral, PublicKeyToken=598062e77f915f75, processorArchitecture=MSIL">
<HintPath>..\packages\RestSharp.112.1.1-alpha.0.4\lib\net48\RestSharp.dll</HintPath>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="WindowsBase" />
<Reference Include="System.Xaml" />
<Reference Include="System" />
<Reference Include="System.Buffers, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Buffers.4.6.1\lib\net462\System.Buffers.dll</HintPath>
</Reference>
<Reference Include="System.CodeDom, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.CodeDom.10.0.0-preview.4.25258.110\lib\net462\System.CodeDom.dll</HintPath>
<HintPath>..\packages\System.CodeDom.10.0.0-rc.1.25451.107\lib\net462\System.CodeDom.dll</HintPath>
</Reference>
<Reference Include="System.Collections.Immutable, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.10.0.0-preview.4.25258.110\lib\net462\System.Collections.Immutable.dll</HintPath>
<HintPath>..\packages\System.Collections.Immutable.10.0.0-rc.1.25451.107\lib\net462\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.Core" />
<Reference Include="System.IO.Pipelines, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.IO.Pipelines.10.0.0-preview.2.25163.2\lib\net462\System.IO.Pipelines.dll</HintPath>
<HintPath>..\packages\System.IO.Pipelines.10.0.0-rc.1.25451.107\lib\net462\System.IO.Pipelines.dll</HintPath>
</Reference>
<Reference Include="System.Memory, Version=4.0.5.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Memory.4.6.3\lib\net462\System.Memory.dll</HintPath>
</Reference>
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Tensors, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Tensors.10.0.0-preview.4.25258.110\lib\net462\System.Numerics.Tensors.dll</HintPath>
<HintPath>..\packages\System.Numerics.Tensors.10.0.0-rc.1.25451.107\lib\net462\System.Numerics.Tensors.dll</HintPath>
</Reference>
<Reference Include="System.Numerics.Vectors, Version=4.1.6.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Numerics.Vectors.4.6.1\lib\net462\System.Numerics.Vectors.dll</HintPath>
@@ -134,13 +105,13 @@
<HintPath>..\packages\System.Runtime.CompilerServices.Unsafe.6.1.2\lib\net462\System.Runtime.CompilerServices.Unsafe.dll</HintPath>
</Reference>
<Reference Include="System.Text.Encodings.Web, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Encodings.Web.10.0.0-preview.2.25163.2\lib\net462\System.Text.Encodings.Web.dll</HintPath>
<HintPath>..\packages\System.Text.Encodings.Web.10.0.0-rc.1.25451.107\lib\net462\System.Text.Encodings.Web.dll</HintPath>
</Reference>
<Reference Include="System.Text.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.Json.10.0.0-preview.2.25163.2\lib\net462\System.Text.Json.dll</HintPath>
<HintPath>..\packages\System.Text.Json.10.0.0-rc.1.25451.107\lib\net462\System.Text.Json.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Channels, Version=10.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Channels.10.0.0-preview.4.25258.110\lib\net462\System.Threading.Channels.dll</HintPath>
<HintPath>..\packages\System.Threading.Channels.10.0.0-rc.1.25451.107\lib\net462\System.Threading.Channels.dll</HintPath>
</Reference>
<Reference Include="System.Threading.Tasks.Extensions, Version=4.2.4.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.Threading.Tasks.Extensions.4.6.3\lib\net462\System.Threading.Tasks.Extensions.dll</HintPath>
@@ -150,13 +121,28 @@
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Compile Include="VirtualFootball\VirtualMatch.cs" />
<Page Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="CsvHelperStubs.cs" />
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Football\Database\BetType.cs" />
<Compile Include="Football\Database\Bookmaker.cs" />
<Compile Include="Football\Database\APIResponse.cs" />
@@ -178,12 +164,10 @@
<Compile Include="Football\Database\Prediction.cs" />
<Compile Include="Football\Database\Score.cs" />
<Compile Include="Football\Database\Team.cs" />
<Compile Include="HorseRacing\API\RacingApiClient.cs" />
<Compile Include="HorseRacing\Main.cs" />
<Compile Include="Horses\Calculator.cs" />
<Compile Include="Horses\Files\Maps\Punters.cs" />
<Compile Include="Horses\ML\HorseRacePrediction.cs" />
<Compile Include="Horses\ML\MachineLearningService.cs" />
<Compile Include="Horses\ML\ModelInput.cs" />
<Compile Include="Horses\ML\ModelOutput.cs" />
<Compile Include="Manager\API.cs" />
<Compile Include="Manager\FileReader.cs" />
<Compile Include="Manager\Database.cs" />
@@ -191,17 +175,7 @@
<Compile Include="Horses\FileReader.cs" />
<Compile Include="Horses\Database.cs" />
<Compile Include="Horses\Files\Punters.cs" />
<Compile Include="Main.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Main.Designer.cs">
<DependentUpon>Main.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Main.resx">
<DependentUpon>Main.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
@@ -211,7 +185,6 @@
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<None Include="Dati\Result.csv" />
<None Include="packages.config" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
@@ -227,23 +200,20 @@
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<Content Include="Query\ScanFavoriteHorseWeights.sql" />
<Content Include="Query\PredictionScanResults.sql" />
<Content Include="Query\GetFavoriteHorses.sql" />
<Content Include="Query\GetValidHorses.sql" />
<Content Include="Query\Races.sql" />
<Folder Include="Themes\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets" Condition="Exists('..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>Questo progetto fa riferimento a uno o più pacchetti NuGet che non sono presenti in questo computer. Usare lo strumento di ripristino dei pacchetti NuGet per scaricarli. Per altre informazioni, vedere http://go.microsoft.com/fwlink/?LinkID=322105. Il file mancante è {0}.</ErrorText>
<ErrorText>Questo progetto fa riferimento a uno o pi<EFBFBD> pacchetti NuGet che non sono presenti in questo computer. Usare lo strumento di ripristino dei pacchetti NuGet per scaricarli. Per altre informazioni, vedere http://go.microsoft.com/fwlink/?LinkID=322105. Il file mancante <EFBFBD> {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\System.ValueTuple.4.6.1\build\net471\System.ValueTuple.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.CpuMath.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.CpuMath.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.CpuMath.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.CpuMath.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.FastTree.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.FastTree.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.FastTree.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.FastTree.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.CpuMath.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.CpuMath.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.CpuMath.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.CpuMath.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.props'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.targets'))" />
<Error Condition="!Exists('..\packages\Microsoft.ML.FastTree.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.FastTree.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.ML.FastTree.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.FastTree.props'))" />
</Target>
<Import Project="..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.targets" Condition="Exists('..\packages\Microsoft.ML.5.0.0-preview.1.25127.4\build\netstandard2.0\Microsoft.ML.targets')" />
<Import Project="..\packages\Microsoft.Web.WebView2.1.0.3800.47\build\Microsoft.Web.WebView2.targets" Condition="Exists('..\packages\Microsoft.Web.WebView2.1.0.3800.47\build\Microsoft.Web.WebView2.targets')" />
<Import Project="..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.targets" Condition="Exists('..\packages\Microsoft.ML.5.0.0-preview.25503.2\build\netstandard2.0\Microsoft.ML.targets')" />
</Project>

View File

@@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Globalization;
// Minimal compatibility stubs for CsvHelper types used in the project.
// These are light-weight placeholders so the project can compile when the CsvHelper NuGet
// package is not restored. They intentionally provide only the members the project expects.
namespace CsvHelper.Configuration.Attributes
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class NameAttribute : Attribute
{
public string[] Names { get; }
public NameAttribute(params string[] names) => Names = names ?? Array.Empty<string>();
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class TypeConverterAttribute : Attribute
{
public Type ConverterType { get; }
public TypeConverterAttribute(Type converterType) => ConverterType = converterType;
}
}
namespace CsvHelper.TypeConversion
{
public class DefaultTypeConverter
{
// Signature matches CsvHelper's DefaultTypeConverter override used in the project
public virtual object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData) => (object)text;
}
public class TypeConverterCache
{
private readonly Dictionary<Type, object> _converters = new Dictionary<Type, object>();
public void AddConverter<T>(DefaultTypeConverter converter) => _converters[typeof(T)] = converter;
}
// Minimal placeholders referenced in code
public interface IReaderRow { }
public class MemberMapData { }
}
namespace CsvHelper.Configuration
{
public enum TrimOptions { None, Trim }
public class CsvConfiguration
{
public CsvConfiguration(CultureInfo culture) { Culture = culture; }
public CultureInfo Culture { get; }
public bool HasHeaderRecord { get; set; }
public Func<PrepareHeaderForMatchArgs, string> PrepareHeaderForMatch { get; set; }
public object HeaderValidated { get; set; }
public Action<object> MissingFieldFound { get; set; }
public Action<object> BadDataFound { get; set; }
public TrimOptions TrimOptions { get; set; }
public string Delimiter { get; set; }
}
// Basic ClassMap and mapping helper used by the project's mapping files
public class ClassMap<T>
{
public MemberMap Map(Func<T, object> func) => new MemberMap();
}
public class MemberMap
{
public MemberMap Name(params string[] names) { return this; }
}
// Minimal args used by PrepareHeaderForMatch delegate in code above
public class PrepareHeaderForMatchArgs
{
public string Header { get; set; }
}
}
namespace CsvHelper
{
using CsvHelper.Configuration;
using CsvHelper.TypeConversion;
public class CsvReader : IDisposable
{
private readonly TextReader _reader;
public CsvReader(TextReader reader, CsvConfiguration config)
{
_reader = reader;
Configuration = config;
Context = new ReaderContext();
}
public CsvConfiguration Configuration { get; }
public ReaderContext Context { get; }
public void Dispose() { /* nothing to dispose in stub */ }
public bool Read() => false;
public void ReadHeader() { }
public string[] HeaderRecord => Array.Empty<string>();
public string GetField(int index) => null;
public IEnumerable<T> GetRecords<T>() { return Enumerable.Empty<T>(); }
public class ReaderContext
{
public TypeConverterCache TypeConverterCache { get; } = new TypeConverterCache();
public void RegisterClassMap<TMap>() { }
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,20 +1,16 @@
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
{
internal class BetType : HorseRacingPredictor.Football.Manager.Database
{
public void Upsert(SqlConnection connection, JsonNode betType)
public void Upsert(SqlConnection connection, JToken betType)
{
try
{
var id = betType["id"]?.GetValue<int>();
var id = betType["id"]?.Value<int>();
if (id == null) return; // Salta il record se l'id è null
var query = @"
@@ -29,7 +25,7 @@ namespace HorseRacingPredictor.Football.Database
using (var command = new SqlCommand(query, connection))
{
command.Parameters.AddWithValue("@bet_type_id", id);
command.Parameters.AddWithValue("@name", betType["name"]?.GetValue<string>() ?? "");
command.Parameters.AddWithValue("@name", betType["name"]?.Value<string>() ?? "");
command.ExecuteNonQuery();
}
}

View File

@@ -1,20 +1,16 @@
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
{
internal class Bookmaker : HorseRacingPredictor.Football.Manager.Database
{
public void Upsert(SqlConnection connection, JsonNode bookmaker)
public void Upsert(SqlConnection connection, JToken bookmaker)
{
try
{
var id = bookmaker["id"]?.GetValue<int>();
var id = bookmaker["id"]?.Value<int>();
if (id == null) return; // Salta il record se l'id è null
var query = @"
@@ -29,7 +25,7 @@ namespace HorseRacingPredictor.Football.Database
using (var command = new SqlCommand(query, connection))
{
command.Parameters.AddWithValue("@bookmaker_id", id);
command.Parameters.AddWithValue("@name", bookmaker["name"]?.GetValue<string>() ?? "");
command.Parameters.AddWithValue("@name", bookmaker["name"]?.Value<string>() ?? "");
command.ExecuteNonQuery();
}
}

View File

@@ -1,16 +1,12 @@
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
{
internal class Comparison : HorseRacingPredictor.Football.Manager.Database
{
public void Upsert(SqlConnection connection, int predictionId, JsonNode comparison)
public void Upsert(SqlConnection connection, int predictionId, JToken comparison)
{
try
{
@@ -56,20 +52,20 @@ namespace HorseRacingPredictor.Football.Database
command.Parameters.AddWithValue("@prediction_id", predictionId);
// Aggiungi tutti i parametri del confronto
command.Parameters.AddWithValue("@form_home", comparison["form"]?["home"]?.GetValue<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@form_away", comparison["form"]?["away"]?.GetValue<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@att_home", comparison["att"]?["home"]?.GetValue<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@att_away", comparison["att"]?["away"]?.GetValue<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@def_home", comparison["def"]?["home"]?.GetValue<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@def_away", comparison["def"]?["away"]?.GetValue<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@poisson_home", comparison["poisson_distribution"]?["home"]?.GetValue<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@poisson_away", comparison["poisson_distribution"]?["away"]?.GetValue<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@h2h_home", comparison["h2h"]?["home"]?.GetValue<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@h2h_away", comparison["h2h"]?["away"]?.GetValue<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@goals_home", comparison["goals"]?["home"]?.GetValue<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@goals_away", comparison["goals"]?["away"]?.GetValue<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@total_home", comparison["total"]?["home"]?.GetValue<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@total_away", comparison["total"]?["away"]?.GetValue<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@form_home", comparison["form"]?["home"]?.Value<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@form_away", comparison["form"]?["away"]?.Value<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@att_home", comparison["att"]?["home"]?.Value<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@att_away", comparison["att"]?["away"]?.Value<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@def_home", comparison["def"]?["home"]?.Value<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@def_away", comparison["def"]?["away"]?.Value<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@poisson_home", comparison["poisson_distribution"]?["home"]?.Value<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@poisson_away", comparison["poisson_distribution"]?["away"]?.Value<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@h2h_home", comparison["h2h"]?["home"]?.Value<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@h2h_away", comparison["h2h"]?["away"]?.Value<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@goals_home", comparison["goals"]?["home"]?.Value<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@goals_away", comparison["goals"]?["away"]?.Value<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@total_home", comparison["total"]?["home"]?.Value<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@total_away", comparison["total"]?["away"]?.Value<string>() ?? (object)DBNull.Value);
command.ExecuteNonQuery();
}

View File

@@ -1,17 +1,17 @@
using System;
using System.Data;
using System.Data.SqlClient;
using System.Text.Json.Nodes;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
{
internal class Fixture : HorseRacingPredictor.Football.Manager.Database
{
public void Upsert(SqlConnection connection, SqlTransaction transaction, JsonNode fixture)
public void Upsert(SqlConnection connection, SqlTransaction transaction, JToken fixture)
{
try
{
var id = fixture["id"]?.GetValue<int>();
var id = fixture["id"]?.Value<int>();
if (id == null) return; // Salta il record se l'id è null
var query = @"
@@ -63,20 +63,20 @@ namespace HorseRacingPredictor.Football.Database
// Parametri principali
command.Parameters.AddWithValue("@fixture_id", id);
command.Parameters.AddWithValue("@timezone", fixture["timezone"]?.GetValue<string>() ?? "");
command.Parameters.AddWithValue("@date", fixture["date"]?.GetValue<DateTime>() ?? DateTime.MinValue);
command.Parameters.AddWithValue("@timestamp", fixture["timestamp"]?.GetValue<long>() ?? 0L);
command.Parameters.AddWithValue("@referee", fixture["referee"]?.GetValue<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@timezone", fixture["timezone"]?.Value<string>() ?? "");
command.Parameters.AddWithValue("@date", fixture["date"]?.Value<DateTime>() ?? DateTime.MinValue);
command.Parameters.AddWithValue("@timestamp", fixture["timestamp"]?.Value<long>() ?? 0L);
command.Parameters.AddWithValue("@referee", fixture["referee"]?.Value<string>() ?? (object)DBNull.Value);
// Parametri venue
int? venueId = venue?["id"]?.GetValue<int?>();
int? venueId = venue?["id"]?.Value<int?>();
command.Parameters.AddWithValue("@venue_id", venueId.HasValue ? (object)venueId.Value : DBNull.Value);
// Parametri status
command.Parameters.AddWithValue("@status_long", status?["long"]?.GetValue<string>() ?? "");
command.Parameters.AddWithValue("@status_short", status?["short"]?.GetValue<string>() ?? "");
command.Parameters.AddWithValue("@status_long", status?["long"]?.Value<string>() ?? "");
command.Parameters.AddWithValue("@status_short", status?["short"]?.Value<string>() ?? "");
int? elapsed = status?["elapsed"]?.GetValue<int?>();
int? elapsed = status?["elapsed"]?.Value<int?>();
int? extra = null; // Assumo che l'API restituisca questo valore, altrimenti va gestito come gli altri
command.Parameters.AddWithValue("@status_elapsed", elapsed.HasValue ? (object)elapsed.Value : DBNull.Value);
@@ -101,11 +101,11 @@ namespace HorseRacingPredictor.Football.Database
/// <summary>
/// Metodo di supporto per inserire/aggiornare le informazioni sulla venue
/// </summary>
private void UpsertVenue(SqlConnection connection, SqlTransaction transaction, JsonNode venue)
private void UpsertVenue(SqlConnection connection, SqlTransaction transaction, JToken venue)
{
try
{
var id = venue["id"]?.GetValue<int>();
var id = venue["id"]?.Value<int>();
if (id == null) return;
var query = @"
@@ -121,8 +121,8 @@ namespace HorseRacingPredictor.Football.Database
using (var command = new SqlCommand(query, connection, transaction))
{
command.Parameters.AddWithValue("@venue_id", id);
command.Parameters.AddWithValue("@name", venue["name"]?.GetValue<string>() ?? "");
command.Parameters.AddWithValue("@city", venue["city"]?.GetValue<string>() ?? "");
command.Parameters.AddWithValue("@name", venue["name"]?.Value<string>() ?? "");
command.Parameters.AddWithValue("@city", venue["city"]?.Value<string>() ?? "");
command.ExecuteNonQuery();
}
}
@@ -153,6 +153,7 @@ namespace HorseRacingPredictor.Football.Database
l.country AS Paese,
l.name AS Campionato,
f.date AS [Data / Ora],
f.timestamp AS unix_ts,
f.status AS Stato,
th.name AS Casa,
ta.name AS Trasferta,

View File

@@ -3,20 +3,20 @@ using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Text.Json.Nodes;
using Newtonsoft.Json.Linq;
using System.Threading.Tasks;
namespace HorseRacingPredictor.Football.Database
{
internal class FixtureLeague : HorseRacingPredictor.Football.Manager.Database
{
public void Upsert(SqlConnection connection, SqlTransaction transaction, int fixtureId, int leagueId, JsonNode leagueData)
public void Upsert(SqlConnection connection, SqlTransaction transaction, int fixtureId, int leagueId, JToken leagueData)
{
try
{
// Estrai i valori prima della query per utilizzarli nel log
string round = leagueData["round"]?.GetValue<string>() ?? "";
bool? standings = leagueData["standings"]?.GetValue<bool?>();
string round = leagueData["round"]?.Value<string>() ?? "";
bool? standings = leagueData["standings"]?.Value<bool?>();
string standingsValue = standings.HasValue ? standings.Value.ToString() : "NULL";
// Log dell'operazione prima di tentare l'upsert

View File

@@ -1,12 +1,12 @@
using System;
using System.Data.SqlClient;
using System.Text.Json.Nodes;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
{
internal class Goals : HorseRacingPredictor.Football.Manager.Database
{
public void Upsert(SqlConnection connection, JsonNode goals, int fixtureId)
public void Upsert(SqlConnection connection, JToken goals, int fixtureId)
{
try
{
@@ -27,8 +27,8 @@ namespace HorseRacingPredictor.Football.Database
END";
using (var command = new SqlCommand(query, connection))
{
command.Parameters.AddWithValue("@home", goals["home"]?.GetValue<int?>() ?? 0);
command.Parameters.AddWithValue("@away", goals["away"]?.GetValue<int?>() ?? 0);
command.Parameters.AddWithValue("@home", goals["home"]?.Value<int?>() ?? 0);
command.Parameters.AddWithValue("@away", goals["away"]?.Value<int?>() ?? 0);
command.Parameters.AddWithValue("@fixture_id", fixtureId);
command.ExecuteNonQuery();
}

View File

@@ -1,16 +1,16 @@
using System;
using System.Data.SqlClient;
using System.Text.Json.Nodes;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
{
internal class League : HorseRacingPredictor.Football.Manager.Database
{
public void Upsert(SqlConnection connection, JsonNode league)
public void Upsert(SqlConnection connection, JToken league)
{
try
{
var id = league["id"]?.GetValue<int>();
var id = league["id"]?.Value<int>();
if (id == null) return; // Salta il record se l'id è null
var query = @"
@@ -25,11 +25,11 @@ namespace HorseRacingPredictor.Football.Database
using (var command = new SqlCommand(query, connection))
{
command.Parameters.AddWithValue("@league_id", id);
command.Parameters.AddWithValue("@name", league["name"]?.GetValue<string>() ?? "");
command.Parameters.AddWithValue("@country", league["country"]?.GetValue<string>() ?? "");
command.Parameters.AddWithValue("@logo", league["logo"]?.GetValue<string>() ?? "");
command.Parameters.AddWithValue("@flag", league["flag"]?.GetValue<string>() ?? "");
command.Parameters.AddWithValue("@season", league["season"]?.GetValue<int>() ?? 0);
command.Parameters.AddWithValue("@name", league["name"]?.Value<string>() ?? "");
command.Parameters.AddWithValue("@country", league["country"]?.Value<string>() ?? "");
command.Parameters.AddWithValue("@logo", league["logo"]?.Value<string>() ?? "");
command.Parameters.AddWithValue("@flag", league["flag"]?.Value<string>() ?? "");
command.Parameters.AddWithValue("@season", league["season"]?.Value<int>() ?? 0);
command.ExecuteNonQuery();
}
}
@@ -39,11 +39,11 @@ namespace HorseRacingPredictor.Football.Database
}
}
public void Upsert(SqlConnection connection, SqlTransaction transaction, JsonNode league)
public void Upsert(SqlConnection connection, SqlTransaction transaction, JToken league)
{
try
{
var id = league["id"]?.GetValue<int>();
var id = league["id"]?.Value<int>();
if (id == null) return; // Salta il record se l'id è null
var query = @"
@@ -58,11 +58,11 @@ namespace HorseRacingPredictor.Football.Database
using (var command = new SqlCommand(query, connection, transaction))
{
command.Parameters.AddWithValue("@league_id", id);
command.Parameters.AddWithValue("@name", league["name"]?.GetValue<string>() ?? "");
command.Parameters.AddWithValue("@country", league["country"]?.GetValue<string>() ?? "");
command.Parameters.AddWithValue("@logo", league["logo"]?.GetValue<string>() ?? "");
command.Parameters.AddWithValue("@flag", league["flag"]?.GetValue<string>() ?? "");
command.Parameters.AddWithValue("@season", league["season"]?.GetValue<int>() ?? 0);
command.Parameters.AddWithValue("@name", league["name"]?.Value<string>() ?? "");
command.Parameters.AddWithValue("@country", league["country"]?.Value<string>() ?? "");
command.Parameters.AddWithValue("@logo", league["logo"]?.Value<string>() ?? "");
command.Parameters.AddWithValue("@flag", league["flag"]?.Value<string>() ?? "");
command.Parameters.AddWithValue("@season", league["season"]?.Value<int>() ?? 0);
command.ExecuteNonQuery();
}
}

View File

@@ -1,12 +1,12 @@
using System;
using System.Data.SqlClient;
using System.Text.Json.Nodes;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
{
internal class LeagueStats : HorseRacingPredictor.Football.Manager.Database
{
public int Insert(SqlConnection connection, int? teamId, int? predictionId, bool isHome, JsonNode stats)
public int Insert(SqlConnection connection, int? teamId, int? predictionId, bool isHome, JToken stats)
{
try
{
@@ -35,27 +35,27 @@ namespace HorseRacingPredictor.Football.Database
command.Parameters.AddWithValue("@team_id", teamId.HasValue ? (object)teamId.Value : DBNull.Value);
command.Parameters.AddWithValue("@prediction_id", predictionId.HasValue ? (object)predictionId.Value : DBNull.Value);
command.Parameters.AddWithValue("@is_home", isHome);
command.Parameters.AddWithValue("@form", stats["form"]?.GetValue<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@form", stats["form"]?.Value<string>() ?? (object)DBNull.Value);
var fixtures = stats["fixtures"];
command.Parameters.AddWithValue("@fixtures_played_home", fixtures?["played"]?["home"]?.GetValue<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@fixtures_played_away", fixtures?["played"]?["away"]?.GetValue<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@fixtures_played_total", fixtures?["played"]?["total"]?.GetValue<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@fixtures_played_home", fixtures?["played"]?["home"]?.Value<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@fixtures_played_away", fixtures?["played"]?["away"]?.Value<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@fixtures_played_total", fixtures?["played"]?["total"]?.Value<int?>() ?? (object)DBNull.Value);
var wins = stats["fixtures"]?["wins"];
command.Parameters.AddWithValue("@wins_home", wins?["home"]?.GetValue<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@wins_away", wins?["away"]?.GetValue<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@wins_total", wins?["total"]?.GetValue<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@wins_home", wins?["home"]?.Value<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@wins_away", wins?["away"]?.Value<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@wins_total", wins?["total"]?.Value<int?>() ?? (object)DBNull.Value);
var draws = stats["fixtures"]?["draws"];
command.Parameters.AddWithValue("@draws_home", draws?["home"]?.GetValue<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@draws_away", draws?["away"]?.GetValue<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@draws_total", draws?["total"]?.GetValue<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@draws_home", draws?["home"]?.Value<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@draws_away", draws?["away"]?.Value<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@draws_total", draws?["total"]?.Value<int?>() ?? (object)DBNull.Value);
var loses = stats["fixtures"]?["loses"];
command.Parameters.AddWithValue("@loses_home", loses?["home"]?.GetValue<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@loses_away", loses?["away"]?.GetValue<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@loses_total", loses?["total"]?.GetValue<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@loses_home", loses?["home"]?.Value<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@loses_away", loses?["away"]?.Value<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@loses_total", loses?["total"]?.Value<int?>() ?? (object)DBNull.Value);
command.ExecuteNonQuery();
}

View File

@@ -1,26 +1,27 @@
using System;
using System.Data.SqlClient;
using System.Text.Json.Nodes;
using System.Linq;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
{
internal class Odds : HorseRacingPredictor.Football.Manager.Database
{
public void Upsert(SqlConnection connection, JsonNode bookmakers, int fixtureId)
public void Upsert(SqlConnection connection, JToken bookmakers, int fixtureId)
{
try
{
// Aggiungiamo log per tracciare la struttura dei dati
LogError($"Inizio elaborazione quote per fixture {fixtureId} con {bookmakers?.AsArray().Count ?? 0} bookmakers", null);
LogError($"Inizio elaborazione quote per fixture {fixtureId} con {bookmakers?.Children().Count() ?? 0} bookmakers", null);
// Per ogni bookmaker
foreach (var bookmaker in bookmakers.AsArray())
foreach (var bookmaker in bookmakers.Children())
{
int bookmakerId = bookmaker["id"].GetValue<int>();
int bookmakerId = bookmaker["id"].Value<int>();
var bets = bookmaker["bets"];
// Cerchiamo le quote di tipo Match Winner (1X2)
foreach (var bet in bets.AsArray())
foreach (var bet in bets.Children())
{
// Gestiamo in modo più sicuro il recupero di valori dal JSON
string betName = GetStringValueSafe(bet, "name");
@@ -35,7 +36,7 @@ namespace HorseRacingPredictor.Football.Database
if (betTypeId == 0) continue;
// Estrai e salva le quote
foreach (var value in bet["values"].AsArray())
foreach (var value in bet["values"].Children())
{
// Gestiamo in modo più sicuro il recupero di valori dal JSON
string valueType = GetStringValueSafe(value, "value");
@@ -128,9 +129,9 @@ namespace HorseRacingPredictor.Football.Database
}
/// <summary>
/// Ottiene un valore stringa da un JsonNode in modo sicuro
/// Ottiene un valore stringa da un JToken in modo sicuro
/// </summary>
private string GetStringValueSafe(JsonNode node, string propertyName)
private string GetStringValueSafe(JToken node, string propertyName)
{
try
{
@@ -140,10 +141,10 @@ namespace HorseRacingPredictor.Football.Database
var value = node[propertyName];
// Gestiamo diversi tipi di nodi
if (value.GetValueKind() == System.Text.Json.JsonValueKind.String)
return value.GetValue<string>();
else if (value.GetValueKind() == System.Text.Json.JsonValueKind.Number)
return value.GetValue<decimal>().ToString();
if (value.Type == JTokenType.String)
return value.Value<string>();
else if (value.Type == JTokenType.Float || value.Type == JTokenType.Integer)
return value.Value<decimal>().ToString();
else
return value.ToString();
}
@@ -155,9 +156,9 @@ namespace HorseRacingPredictor.Football.Database
}
/// <summary>
/// Ottiene un valore decimale da un JsonNode in modo sicuro
/// Ottiene un valore decimale da un JToken in modo sicuro
/// </summary>
private decimal GetDecimalValueSafe(JsonNode node, string propertyName)
private decimal GetDecimalValueSafe(JToken node, string propertyName)
{
try
{
@@ -167,15 +168,15 @@ namespace HorseRacingPredictor.Football.Database
var value = node[propertyName];
// Gestiamo diversi tipi di nodi
if (value.GetValueKind() == System.Text.Json.JsonValueKind.Number)
if (value.Type == JTokenType.Float || value.Type == JTokenType.Integer)
{
// Assicuriamoci che il valore sia esattamente quello originale
return value.GetValue<decimal>();
return value.Value<decimal>();
}
else if (value.GetValueKind() == System.Text.Json.JsonValueKind.String)
else if (value.Type == JTokenType.String)
{
// Utilizziamo InvariantCulture per garantire una corretta interpretazione dei decimali
if (decimal.TryParse(value.GetValue<string>(),
if (decimal.TryParse(value.Value<string>(),
System.Globalization.NumberStyles.Any,
System.Globalization.CultureInfo.InvariantCulture,
out decimal result))
@@ -185,7 +186,7 @@ namespace HorseRacingPredictor.Football.Database
}
// Aggiungiamo un log più dettagliato per aiutare il debug
LogError($"Impossibile convertire '{value}' (tipo: {value.GetValueKind()}) in decimal per proprietà '{propertyName}'", null);
LogError($"Impossibile convertire '{value}' (tipo: {value.Type}) in decimal per proprietà '{propertyName}'", null);
return 0;
}
catch (Exception ex)

View File

@@ -1,30 +1,30 @@
using System;
using System.Data.SqlClient;
using System.Text.Json.Nodes;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
{
internal class Prediction : HorseRacingPredictor.Football.Manager.Database
{
public void Upsert(SqlConnection connection, JsonNode predictions, int fixtureId)
public void Upsert(SqlConnection connection, JToken predictions, int fixtureId)
{
try
{
// Estrazione dei dati dalla risposta JSON
string advice = predictions["advice"]?.GetValue<string>();
string advice = predictions["advice"]?.Value<string>();
if (string.IsNullOrEmpty(advice)) return; // Nessuna previsione se non c'è consiglio
// Estrazione degli altri valori, con gestione null
int? winnerId = predictions["winner"]?["id"]?.GetValue<int?>();
string winnerName = predictions["winner"]?["name"]?.GetValue<string>();
string winnerComment = predictions["winner"]?["comment"]?.GetValue<string>();
bool? winOrDraw = predictions["win_or_draw"]?.GetValue<bool?>();
string underOver = predictions["under_over"]?.GetValue<string>();
string goalsHome = predictions["goals"]?["home"]?.GetValue<string>();
string goalsAway = predictions["goals"]?["away"]?.GetValue<string>();
string percentHome = predictions["percent"]?["home"]?.GetValue<string>();
string percentDraw = predictions["percent"]?["draw"]?.GetValue<string>();
string percentAway = predictions["percent"]?["away"]?.GetValue<string>();
int? winnerId = predictions["winner"]?["id"]?.Value<int?>();
string winnerName = predictions["winner"]?["name"]?.Value<string>();
string winnerComment = predictions["winner"]?["comment"]?.Value<string>();
bool? winOrDraw = predictions["win_or_draw"]?.Value<bool?>();
string underOver = predictions["under_over"]?.Value<string>();
string goalsHome = predictions["goals"]?["home"]?.Value<string>();
string goalsAway = predictions["goals"]?["away"]?.Value<string>();
string percentHome = predictions["percent"]?["home"]?.Value<string>();
string percentDraw = predictions["percent"]?["draw"]?.Value<string>();
string percentAway = predictions["percent"]?["away"]?.Value<string>();
// Query per l'upsert con tutti i campi disponibili
var query = @"

View File

@@ -1,12 +1,12 @@
using System;
using System.Data.SqlClient;
using System.Text.Json.Nodes;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
{
internal class Score : HorseRacingPredictor.Football.Manager.Database
{
public void Upsert(SqlConnection connection, JsonNode score, int fixtureId)
public void Upsert(SqlConnection connection, JToken score, int fixtureId)
{
try
{
@@ -26,14 +26,14 @@ namespace HorseRacingPredictor.Football.Database
END";
using (var command = new SqlCommand(query, connection))
{
command.Parameters.AddWithValue("@home_halftime", score["halftime"]["home"]?.GetValue<int?>() ?? 0);
command.Parameters.AddWithValue("@away_halftime", score["halftime"]["away"]?.GetValue<int?>() ?? 0);
command.Parameters.AddWithValue("@home_fulltime", score["fulltime"]["home"]?.GetValue<int?>() ?? 0);
command.Parameters.AddWithValue("@away_fulltime", score["fulltime"]["away"]?.GetValue<int?>() ?? 0);
command.Parameters.AddWithValue("@home_extratime", score["extratime"]["home"]?.GetValue<int?>() ?? 0);
command.Parameters.AddWithValue("@away_extratime", score["extratime"]["away"]?.GetValue<int?>() ?? 0);
command.Parameters.AddWithValue("@home_penalty", score["penalty"]["home"]?.GetValue<int?>() ?? 0);
command.Parameters.AddWithValue("@away_penalty", score["penalty"]["away"]?.GetValue<int?>() ?? 0);
command.Parameters.AddWithValue("@home_halftime", score["halftime"]?["home"]?.Value<int?>() ?? 0);
command.Parameters.AddWithValue("@away_halftime", score["halftime"]?["away"]?.Value<int?>() ?? 0);
command.Parameters.AddWithValue("@home_fulltime", score["fulltime"]?["home"]?.Value<int?>() ?? 0);
command.Parameters.AddWithValue("@away_fulltime", score["fulltime"]?["away"]?.Value<int?>() ?? 0);
command.Parameters.AddWithValue("@home_extratime", score["extratime"]?["home"]?.Value<int?>() ?? 0);
command.Parameters.AddWithValue("@away_extratime", score["extratime"]?["away"]?.Value<int?>() ?? 0);
command.Parameters.AddWithValue("@home_penalty", score["penalty"]?["home"]?.Value<int?>() ?? 0);
command.Parameters.AddWithValue("@away_penalty", score["penalty"]?["away"]?.Value<int?>() ?? 0);
command.Parameters.AddWithValue("@fixture_id", fixtureId);
command.ExecuteNonQuery();
}

View File

@@ -1,17 +1,17 @@
using System;
using System.Data.SqlClient;
using System.Text.Json.Nodes;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
{
internal class Team : HorseRacingPredictor.Football.Manager.Database
{
public void UpsertTeams(SqlConnection connection, SqlTransaction transaction, JsonNode teams, int fixtureId)
public void UpsertTeams(SqlConnection connection, SqlTransaction transaction, JToken teams, int fixtureId)
{
try
{
var homeTeamId = teams["home"]["id"]?.GetValue<int>();
var awayTeamId = teams["away"]["id"]?.GetValue<int>();
var homeTeamId = teams["home"]["id"]?.Value<int>();
var awayTeamId = teams["away"]["id"]?.Value<int>();
if (homeTeamId == null || awayTeamId == null) return; // Salta il record se uno degli id è null
var queryTeam = @"
@@ -31,14 +31,14 @@ namespace HorseRacingPredictor.Football.Database
// Upsert home team
command.Parameters.AddWithValue("@team_id", homeTeamId);
command.Parameters.AddWithValue("@name", homeTeam["name"]?.GetValue<string>() ?? "");
command.Parameters.AddWithValue("@logo", homeTeam["logo"]?.GetValue<string>() ?? "");
command.Parameters.AddWithValue("@name", homeTeam["name"]?.Value<string>() ?? "");
command.Parameters.AddWithValue("@logo", homeTeam["logo"]?.Value<string>() ?? "");
command.ExecuteNonQuery();
// Upsert away team
command.Parameters["@team_id"].Value = awayTeamId;
command.Parameters["@name"].Value = awayTeam["name"]?.GetValue<string>() ?? "";
command.Parameters["@logo"].Value = awayTeam["logo"]?.GetValue<string>() ?? "";
command.Parameters["@name"].Value = awayTeam["name"]?.Value<string>() ?? "";
command.Parameters["@logo"].Value = awayTeam["logo"]?.Value<string>() ?? "";
command.ExecuteNonQuery();
}

View File

@@ -1,16 +1,12 @@
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Text.Json.Nodes;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
namespace HorseRacingPredictor.Football.Database
{
internal class TeamStats : HorseRacingPredictor.Football.Manager.Database
{
public int Insert(SqlConnection connection, int? teamId, int? predictionId, bool isHome, JsonNode stats)
public int Insert(SqlConnection connection, int? teamId, int? predictionId, bool isHome, JToken stats)
{
try
{
@@ -35,18 +31,18 @@ namespace HorseRacingPredictor.Football.Database
command.Parameters.AddWithValue("@team_id", teamId.HasValue ? (object)teamId.Value : DBNull.Value);
command.Parameters.AddWithValue("@prediction_id", predictionId.HasValue ? (object)predictionId.Value : DBNull.Value);
command.Parameters.AddWithValue("@is_home", isHome);
command.Parameters.AddWithValue("@played", stats["played"]?.GetValue<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@form", stats["form"]?.GetValue<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@att", stats["att"]?.GetValue<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@def", stats["def"]?.GetValue<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@played", stats["played"]?.Value<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@form", stats["form"]?.Value<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@att", stats["att"]?.Value<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@def", stats["def"]?.Value<string>() ?? (object)DBNull.Value);
var goalsFor = stats["goals"]?["for"];
command.Parameters.AddWithValue("@goals_for_total", goalsFor?["total"]?.GetValue<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@goals_for_average", goalsFor?["average"]?.GetValue<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@goals_for_total", goalsFor?["total"]?.Value<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@goals_for_average", goalsFor?["average"]?.Value<string>() ?? (object)DBNull.Value);
var goalsAgainst = stats["goals"]?["against"];
command.Parameters.AddWithValue("@goals_against_total", goalsAgainst?["total"]?.GetValue<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@goals_against_average", goalsAgainst?["average"]?.GetValue<string>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@goals_against_total", goalsAgainst?["total"]?.Value<int?>() ?? (object)DBNull.Value);
command.Parameters.AddWithValue("@goals_against_average", goalsAgainst?["average"]?.Value<string>() ?? (object)DBNull.Value);
command.ExecuteNonQuery();
}

View File

@@ -7,6 +7,7 @@ using System.Threading.Tasks;
using RestSharp;
using System.Text.Json;
using System.Text.Json.Nodes;
using Newtonsoft.Json.Linq;
using System.Data.SqlClient;
namespace HorseRacingPredictor.Football
@@ -97,6 +98,42 @@ namespace HorseRacingPredictor.Football
}
}
/// <summary>
/// Recupera solo l'elenco delle partite per la data specificata e le restituisce come DataTable semplice
/// </summary>
public DataTable GetTodayFixtures(DateTime date, IProgress<int> progressCallback = null, IProgress<string> statusCallback = null)
{
try
{
statusCallback?.Report("Scaricamento elenco partite...");
progressCallback?.Report(10);
var fixturesResponse = GetFixtures(date);
progressCallback?.Report(40);
statusCallback?.Report("Elaborazione partite...");
var table = CreateFixturesDataTable(fixturesResponse);
progressCallback?.Report(50);
statusCallback?.Report("Scaricamento quote...");
var oddsResponses = GetOdds(date);
progressCallback?.Report(80);
statusCallback?.Report("Integrazione quote...");
ParseOddsIntoTable(table, oddsResponses);
progressCallback?.Report(100);
statusCallback?.Report($"Trovate {table.Rows.Count} partite con quote");
return table;
}
catch (Exception ex)
{
_database.LogError("recupero partite del giorno", ex);
statusCallback?.Report($"Errore: {ex.Message}");
return CreateEmptyResultTable();
}
}
/// <summary>
/// Scarica i dati dalle API e li salva nella tabella di frontiera API_Response
/// </summary>
@@ -281,7 +318,13 @@ namespace HorseRacingPredictor.Football
{
try
{
var jsonObject = JsonNode.Parse(jsonResponse);
// Parse JSON using System.Text.Json and convert to Newtonsoft.JToken where needed
var jsonNode = JsonNode.Parse(jsonResponse);
var jsonObject = JObject.Parse(jsonNode.ToJsonString());
// Helper to enumerate array-like tokens (compat replacement for JsonNode.AsArray())
var responseArray = jsonObject["response"] as JArray;
Func<JToken, IEnumerable<JToken>> asArray = t => t is JArray a ? a : (t != null ? t.Children() : Enumerable.Empty<JToken>());
_database.ExecuteTransactionalQuery("l'elaborazione dei dati calcistici", (connection, transaction) =>
{
@@ -291,7 +334,7 @@ namespace HorseRacingPredictor.Football
((Manager.Database)_database).DisableAllConstraints(connection, transaction);
// FASE 1: Inserisci le leghe
foreach (var responseItem in jsonObject["response"].AsArray())
foreach (var responseItem in asArray(jsonObject["response"]))
{
var league = responseItem["league"];
if (league != null && league["id"] != null)
@@ -301,11 +344,11 @@ namespace HorseRacingPredictor.Football
}
// FASE 2: Inserisci i bookmakers e i tipi di scommessa
foreach (var responseItem in jsonObject["response"].AsArray())
foreach (var responseItem in asArray(jsonObject["response"]))
{
if (responseItem["bookmakers"] != null)
{
foreach (var bookmaker in responseItem["bookmakers"].AsArray())
foreach (var bookmaker in asArray(responseItem["bookmakers"]))
{
if (bookmaker["id"] != null)
{
@@ -314,7 +357,7 @@ namespace HorseRacingPredictor.Football
// Tipi di scommessa
if (bookmaker["bets"] != null)
{
foreach (var bet in bookmaker["bets"].AsArray())
foreach (var bet in asArray(bookmaker["bets"]))
{
if (bet["id"] != null && bet["name"] != null)
{
@@ -328,7 +371,7 @@ namespace HorseRacingPredictor.Football
}
// FASE 3: Inserisci le squadre (senza relazione con fixture)
foreach (var responseItem in jsonObject["response"].AsArray())
foreach (var responseItem in asArray(jsonObject["response"]))
{
var teams = responseItem["teams"];
if (teams != null && teams["home"] != null && teams["away"] != null &&
@@ -340,7 +383,7 @@ namespace HorseRacingPredictor.Football
}
// FASE 4: Inserisci i fixture e venue
foreach (var responseItem in jsonObject["response"].AsArray())
foreach (var responseItem in asArray(jsonObject["response"]))
{
var fixture = responseItem["fixture"];
if (fixture != null && fixture["id"] != null)
@@ -350,7 +393,7 @@ namespace HorseRacingPredictor.Football
}
// FASE 5: Inserisci relazioni tra entità e dati dipendenti
foreach (var responseItem in jsonObject["response"].AsArray())
foreach (var responseItem in asArray(jsonObject["response"]))
{
int? fixtureId = null;
try
@@ -358,7 +401,7 @@ namespace HorseRacingPredictor.Football
var fixture = responseItem["fixture"];
if (fixture != null && fixture["id"] != null)
{
fixtureId = fixture["id"].GetValue<int>();
fixtureId = fixture["id"].Value<int>();
// Relazioni fixture-team
var teams = responseItem["teams"];
@@ -372,7 +415,7 @@ namespace HorseRacingPredictor.Football
var league = responseItem["league"];
if (league != null && league["id"] != null)
{
int leagueId = league["id"].GetValue<int>();
int leagueId = league["id"].Value<int>();
_fixtureLeagueRepository.Upsert(connection, transaction, fixtureId.Value, leagueId, league);
}
@@ -399,7 +442,7 @@ namespace HorseRacingPredictor.Football
}
// FASE 6: Inserisci dati che richiedono fixture e teams: quote e previsioni
foreach (var responseItem in jsonObject["response"].AsArray())
foreach (var responseItem in asArray(jsonObject["response"]))
{
int? fixtureId = null;
try
@@ -407,7 +450,7 @@ namespace HorseRacingPredictor.Football
var fixture = responseItem["fixture"];
if (fixture != null && fixture["id"] != null)
{
fixtureId = fixture["id"].GetValue<int>();
fixtureId = fixture["id"].Value<int>();
// Quote
if (responseItem["bookmakers"] != null)
@@ -442,15 +485,15 @@ namespace HorseRacingPredictor.Football
}
// Head-to-head
if (predictions["h2h"] != null && predictions["h2h"].AsArray().Any())
if (predictions["h2h"] != null && asArray(predictions["h2h"]).Any())
{
_h2hRepository.DeleteForPrediction(connection, predictionId.Value);
foreach (var h2hFixture in predictions["h2h"].AsArray())
foreach (var h2hFixture in asArray(predictions["h2h"]))
{
if (h2hFixture["fixture"] != null && h2hFixture["fixture"]["id"] != null)
{
_h2hRepository.Insert(connection, predictionId.Value, h2hFixture["fixture"]["id"].GetValue<int>());
_h2hRepository.Insert(connection, predictionId.Value, h2hFixture["fixture"]["id"].Value<int>());
}
}
}
@@ -462,7 +505,7 @@ namespace HorseRacingPredictor.Football
// Home team stats
if (teams["home"] != null && teams["home"]["id"] != null && predictions["teams"]["home"] != null)
{
int homeTeamId = teams["home"]["id"].GetValue<int>();
int homeTeamId = teams["home"]["id"].Value<int>();
if (predictions["teams"]["home"]["team"] != null)
{
@@ -480,7 +523,7 @@ namespace HorseRacingPredictor.Football
// Away team stats
if (teams["away"] != null && teams["away"]["id"] != null && predictions["teams"]["away"] != null)
{
int awayTeamId = teams["away"]["id"].GetValue<int>();
int awayTeamId = teams["away"]["id"].Value<int>();
if (predictions["teams"]["away"]["team"] != null)
{
@@ -563,6 +606,13 @@ namespace HorseRacingPredictor.Football
dataTable.Columns.Add("Quota Casa", typeof(string));
dataTable.Columns.Add("Quota Pareggio", typeof(string));
dataTable.Columns.Add("Quota Trasferta", typeof(string));
dataTable.Columns.Add("Over 2.5", typeof(string));
dataTable.Columns.Add("Under 2.5", typeof(string));
dataTable.Columns.Add("BTTS Sì", typeof(string));
dataTable.Columns.Add("BTTS No", typeof(string));
dataTable.Columns.Add("Doppia Casa/X", typeof(string));
dataTable.Columns.Add("Doppia Casa/Trasf", typeof(string));
dataTable.Columns.Add("Doppia X/Trasf", typeof(string));
dataTable.Columns.Add("Previsione", typeof(string));
return dataTable;
@@ -596,28 +646,65 @@ namespace HorseRacingPredictor.Football
{
try
{
// Verifica che le proprietà essenziali esistano
if (!item.TryGetProperty("fixture", out var fixtureEl) ||
!item.TryGetProperty("league", out var leagueEl) ||
!item.TryGetProperty("teams", out var teamsEl))
{
continue;
}
var row = dataTable.NewRow();
// Estrai i dati dalla risposta JSON
row["ID"] = item.GetProperty("fixture").GetProperty("id").GetInt32();
row["Paese"] = item.GetProperty("league").GetProperty("country").GetString();
row["Campionato"] = item.GetProperty("league").GetProperty("name").GetString();
row["Data / Ora"] = DateTime.Parse(item.GetProperty("fixture").GetProperty("date").GetString());
row["Stato"] = item.GetProperty("fixture").GetProperty("status").GetProperty("long").GetString();
row["Casa"] = item.GetProperty("teams").GetProperty("home").GetProperty("name").GetString();
row["Trasferta"] = item.GetProperty("teams").GetProperty("away").GetProperty("name").GetString();
row["ID"] = fixtureEl.TryGetProperty("id", out var idEl) ? idEl.GetInt32() : 0;
row["Paese"] = leagueEl.TryGetProperty("country", out var countryEl) ? countryEl.GetString() ?? "" : "";
row["Campionato"] = leagueEl.TryGetProperty("name", out var leagueNameEl) ? leagueNameEl.GetString() ?? "" : "";
// Per i goals, controlla se sono null
var goalsElement = item.GetProperty("goals");
row["Goals Casa"] = goalsElement.GetProperty("home").ValueKind == JsonValueKind.Null ?
0 : goalsElement.GetProperty("home").GetInt32();
row["Goals Trasferta"] = goalsElement.GetProperty("away").ValueKind == JsonValueKind.Null ?
0 : goalsElement.GetProperty("away").GetInt32();
if (fixtureEl.TryGetProperty("date", out var dateEl) && dateEl.ValueKind == JsonValueKind.String)
{
DateTime.TryParse(dateEl.GetString(), out var parsedDate);
row["Data / Ora"] = parsedDate;
}
else
{
row["Data / Ora"] = DBNull.Value;
}
row["Stato"] = fixtureEl.TryGetProperty("status", out var statusEl) &&
statusEl.TryGetProperty("long", out var statusLong)
? statusLong.GetString() ?? "" : "";
row["Casa"] = teamsEl.TryGetProperty("home", out var homeEl) &&
homeEl.TryGetProperty("name", out var homeNameEl)
? homeNameEl.GetString() ?? "" : "";
row["Trasferta"] = teamsEl.TryGetProperty("away", out var awayEl) &&
awayEl.TryGetProperty("name", out var awayNameEl)
? awayNameEl.GetString() ?? "" : "";
// Goals (possono essere null per partite non iniziate)
if (item.TryGetProperty("goals", out var goalsElement))
{
row["Goals Casa"] = goalsElement.TryGetProperty("home", out var ghEl) && ghEl.ValueKind == JsonValueKind.Number
? ghEl.GetInt32() : 0;
row["Goals Trasferta"] = goalsElement.TryGetProperty("away", out var gaEl) && gaEl.ValueKind == JsonValueKind.Number
? gaEl.GetInt32() : 0;
}
else
{
row["Goals Casa"] = 0;
row["Goals Trasferta"] = 0;
}
// Le colonne delle quote e previsione rimangono temporaneamente vuote
row["Quota Casa"] = DBNull.Value;
row["Quota Pareggio"] = DBNull.Value;
row["Quota Trasferta"] = DBNull.Value;
row["Over 2.5"] = DBNull.Value;
row["Under 2.5"] = DBNull.Value;
row["BTTS Sì"] = DBNull.Value;
row["BTTS No"] = DBNull.Value;
row["Doppia Casa/X"] = DBNull.Value;
row["Doppia Casa/Trasf"] = DBNull.Value;
row["Doppia X/Trasf"] = DBNull.Value;
row["Previsione"] = DBNull.Value;
dataTable.Rows.Add(row);
@@ -625,14 +712,12 @@ namespace HorseRacingPredictor.Football
catch (Exception ex)
{
_database.LogError($"elaborazione riga partita", ex);
// Continua con la prossima partita
}
}
}
catch (Exception ex)
{
_database.LogError("creazione tabella partite", ex);
// Ritorna la tabella vuota in caso di errore
}
return dataTable;
@@ -788,6 +873,162 @@ namespace HorseRacingPredictor.Football
return table;
}
/// <summary>
/// Analizza le risposte delle quote dall'API e le inserisce direttamente nella DataTable delle partite.
/// Utilizza Bet365 (ID 8) come bookmaker principale; se non presente, usa il primo bookmaker disponibile.
/// </summary>
private void ParseOddsIntoTable(DataTable fixturesTable, List<RestResponse> oddsResponses)
{
if (oddsResponses == null || oddsResponses.Count == 0 || fixturesTable == null || fixturesTable.Rows.Count == 0)
return;
try
{
foreach (var response in oddsResponses)
{
if (response == null || !response.IsSuccessful || string.IsNullOrEmpty(response.Content))
continue;
var json = JsonDocument.Parse(response.Content).RootElement;
if (!json.TryGetProperty("response", out var responseElement))
continue;
foreach (var item in responseElement.EnumerateArray())
{
try
{
if (!item.TryGetProperty("fixture", out var fixtureEl))
continue;
int fixtureId = fixtureEl.GetProperty("id").GetInt32();
DataRow[] matchingRows = fixturesTable.Select($"ID = {fixtureId}");
if (matchingRows.Length == 0)
continue;
DataRow row = matchingRows[0];
if (!item.TryGetProperty("bookmakers", out var bookmakersEl))
continue;
// Cerca Bet365 (ID 8), altrimenti usa il primo bookmaker
JsonElement selectedBookmaker = default;
bool found = false;
foreach (var bm in bookmakersEl.EnumerateArray())
{
if (bm.TryGetProperty("id", out var bmId) && bmId.GetInt32() == 8)
{
selectedBookmaker = bm;
found = true;
break;
}
}
if (!found)
{
var enumerator = bookmakersEl.EnumerateArray();
if (enumerator.MoveNext())
{
selectedBookmaker = enumerator.Current;
found = true;
}
}
if (!found || !selectedBookmaker.TryGetProperty("bets", out var betsEl))
continue;
foreach (var bet in betsEl.EnumerateArray())
{
string betName = bet.TryGetProperty("name", out var nameEl) ? nameEl.GetString() : "";
if (string.IsNullOrEmpty(betName) || !bet.TryGetProperty("values", out var valuesEl))
continue;
switch (betName)
{
case "Match Winner":
foreach (var v in valuesEl.EnumerateArray())
{
string val = GetOddValueString(v, "value");
string odd = GetOddValueString(v, "odd");
if (val == "Home") row["Quota Casa"] = odd;
else if (val == "Draw") row["Quota Pareggio"] = odd;
else if (val == "Away") row["Quota Trasferta"] = odd;
}
break;
case "Goals Over/Under":
foreach (var v in valuesEl.EnumerateArray())
{
string val = GetOddValueString(v, "value");
string odd = GetOddValueString(v, "odd");
if (val == "Over 2.5") row["Over 2.5"] = odd;
else if (val == "Under 2.5") row["Under 2.5"] = odd;
}
break;
case "Both Teams Score":
foreach (var v in valuesEl.EnumerateArray())
{
string val = GetOddValueString(v, "value");
string odd = GetOddValueString(v, "odd");
if (val == "Yes") row["BTTS Sì"] = odd;
else if (val == "No") row["BTTS No"] = odd;
}
break;
case "Double Chance":
foreach (var v in valuesEl.EnumerateArray())
{
string val = GetOddValueString(v, "value");
string odd = GetOddValueString(v, "odd");
if (val == "Home/Draw") row["Doppia Casa/X"] = odd;
else if (val == "Home/Away") row["Doppia Casa/Trasf"] = odd;
else if (val == "Draw/Away") row["Doppia X/Trasf"] = odd;
}
break;
}
}
}
catch (Exception ex)
{
_database.LogError("elaborazione quote per singola partita", ex);
}
}
}
}
catch (Exception ex)
{
_database.LogError("parsing quote nella tabella", ex);
}
}
/// <summary>
/// Estrae un valore stringa da un JsonElement in modo sicuro
/// </summary>
private string GetOddValueString(JsonElement element, string propertyName)
{
try
{
if (!element.TryGetProperty(propertyName, out var prop))
return "";
switch (prop.ValueKind)
{
case JsonValueKind.String:
return prop.GetString() ?? "";
case JsonValueKind.Number:
return prop.GetDecimal().ToString(System.Globalization.CultureInfo.InvariantCulture);
default:
return prop.ToString();
}
}
catch
{
return "";
}
}
/// <summary>
/// Elabora le risposte API non elaborate presenti nel database
/// </summary>

View File

@@ -2,7 +2,6 @@ using System;
using System.Threading.Tasks;
using RestSharp;
using HorseRacingPredictor.Football.Database;
using HorseRacingPredictor.Football.API;
using League = HorseRacingPredictor.Football.API.League;
using Fixture = HorseRacingPredictor.Football.API.Fixture;
@@ -41,26 +40,33 @@ namespace HorseRacingPredictor.Football.Manager
/// </summary>
protected RestResponse ExecuteRequest(string endpoint, string apiType = null, string parameters = null)
{
string url = $"{BaseUrl}/{endpoint}";
var response = ExecuteApiRequest(url, ApiKey, KeyHeader, HostHeader, HostValue);
// Salva la risposta su file per debug (non bloccare in caso di errore)
try
{
string url = $"{BaseUrl}/{endpoint}";
var response = ExecuteApiRequest(url, ApiKey, KeyHeader, HostHeader, HostValue);
// Salva la risposta su file per debug
SaveResponseToFile(url, response.Content, "FootballApiResponses");
// Salva la risposta nel database
if (apiType != null)
{
_apiResponseRepository.InsertApiResponse(apiType, endpoint, parameters, response);
}
return response;
}
catch (Exception ex)
{
throw new Exception($"Errore durante la richiesta all'endpoint {endpoint}: {ex.Message}", ex);
Console.WriteLine($"Avviso: impossibile salvare risposta su file: {ex.Message}");
}
// Salva la risposta nel database (non bloccare in caso di errore)
if (apiType != null)
{
try
{
_apiResponseRepository.InsertApiResponse(apiType, endpoint, parameters, response);
}
catch (Exception ex)
{
Console.WriteLine($"Avviso: impossibile salvare risposta nel database: {ex.Message}");
}
}
return response;
}
/// <summary>
@@ -68,26 +74,33 @@ namespace HorseRacingPredictor.Football.Manager
/// </summary>
protected async Task<RestResponse> ExecuteRequestAsync(string endpoint, string apiType = null, string parameters = null)
{
string url = $"{BaseUrl}/{endpoint}";
var response = await ExecuteApiRequestAsync(url, ApiKey, KeyHeader, HostHeader, HostValue);
// Salva la risposta su file per debug (non bloccare in caso di errore)
try
{
string url = $"{BaseUrl}/{endpoint}";
var response = await ExecuteApiRequestAsync(url, ApiKey, KeyHeader, HostHeader, HostValue);
// Salva la risposta su file per debug
SaveResponseToFile(url, response.Content, "FootballApiResponses");
// Salva la risposta nel database
if (apiType != null)
{
_apiResponseRepository.InsertApiResponse(apiType, endpoint, parameters, response);
}
return response;
}
catch (Exception ex)
{
throw new Exception($"Errore durante la richiesta all'endpoint {endpoint}: {ex.Message}", ex);
Console.WriteLine($"Avviso: impossibile salvare risposta su file: {ex.Message}");
}
// Salva la risposta nel database (non bloccare in caso di errore)
if (apiType != null)
{
try
{
_apiResponseRepository.InsertApiResponse(apiType, endpoint, parameters, response);
}
catch (Exception ex)
{
Console.WriteLine($"Avviso: impossibile salvare risposta nel database: {ex.Message}");
}
}
return response;
}
#region Leagues

View File

@@ -0,0 +1,136 @@
using System;
using System.Text;
using System.Threading;
using RestSharp;
namespace HorseRacingPredictor.HorseRacing.API
{
/// <summary>
/// Client per The Racing API (theracingapi.com)
/// Utilizza HTTP Basic Authentication
/// </summary>
internal class RacingApiClient
{
private const string BaseUrl = "https://api.theracingapi.com/v1";
private const int DefaultDelay = 1100; // Rate limit: 1 req/sec per Free plan
private readonly string _username;
private readonly string _password;
public RacingApiClient(string username, string password)
{
_username = username;
_password = password;
}
/// <summary>
/// Esegue una richiesta GET autenticata con HTTP Basic Auth
/// </summary>
private RestResponse ExecuteRequest(string endpoint, int delay = DefaultDelay)
{
string url = $"{BaseUrl}/{endpoint}";
var client = new RestClient(url);
var request = new RestRequest();
string credentials = Convert.ToBase64String(
Encoding.ASCII.GetBytes($"{_username}:{_password}"));
request.AddHeader("Authorization", $"Basic {credentials}");
try
{
var response = client.Execute(request);
if (!response.IsSuccessful)
{
throw new Exception(
$"Errore API Racing ({(int)response.StatusCode}): {response.StatusDescription}");
}
if (delay > 0)
Thread.Sleep(delay);
return response;
}
catch (Exception ex) when (!(ex.Message.StartsWith("Errore API Racing")))
{
throw new Exception($"Errore durante la richiesta a Racing API: {ex.Message}", ex);
}
}
/// <summary>
/// Ottiene le racecard (programma corse) per oggi o domani
/// </summary>
/// <param name="day">"today" oppure "tomorrow"</param>
/// <param name="regionCodes">Codici regione opzionali (es. "gb", "ire")</param>
public RestResponse GetRacecardsFree(string day = "today", string[] regionCodes = null)
{
var sb = new StringBuilder("racecards/free?");
sb.Append($"day={day}");
if (regionCodes != null && regionCodes.Length > 0)
{
foreach (var rc in regionCodes)
sb.Append($"&region_codes={rc}");
}
return ExecuteRequest(sb.ToString());
}
/// <summary>
/// Ottiene le racecard standard per oggi o domani
/// </summary>
public RestResponse GetRacecardsStandard(string day = "today", string[] regionCodes = null)
{
var sb = new StringBuilder("racecards/standard?");
sb.Append($"day={day}");
if (regionCodes != null && regionCodes.Length > 0)
{
foreach (var rc in regionCodes)
sb.Append($"&region_codes={rc}");
}
return ExecuteRequest(sb.ToString());
}
/// <summary>
/// Ottiene i risultati per un intervallo di date
/// </summary>
public RestResponse GetResults(DateTime startDate, DateTime endDate, string[] regionCodes = null)
{
var sb = new StringBuilder("results?");
sb.Append($"start_date={startDate:yyyy-MM-dd}");
sb.Append($"&end_date={endDate:yyyy-MM-dd}");
if (regionCodes != null && regionCodes.Length > 0)
{
foreach (var rc in regionCodes)
sb.Append($"&region={rc}");
}
return ExecuteRequest(sb.ToString());
}
/// <summary>
/// Ottiene l'elenco delle regioni disponibili
/// </summary>
public RestResponse GetRegions()
{
return ExecuteRequest("courses/regions");
}
/// <summary>
/// Ottiene l'elenco dei corsi per le regioni specificate
/// </summary>
public RestResponse GetCourses(string[] regionCodes = null)
{
var sb = new StringBuilder("courses?");
if (regionCodes != null && regionCodes.Length > 0)
{
foreach (var rc in regionCodes)
sb.Append($"&region_codes={rc}");
}
return ExecuteRequest(sb.ToString());
}
}
}

View File

@@ -0,0 +1,266 @@
using System;
using System.Data;
using System.Text.Json;
using HorseRacingPredictor.HorseRacing.API;
namespace HorseRacingPredictor.HorseRacing
{
/// <summary>
/// Gestore centralizzato per la sezione Corse dei Cavalli.
/// Scarica i dati da The Racing API e li converte in DataTable.
/// </summary>
public class Main
{
private RacingApiClient _client;
public Main(string username, string password)
{
_client = new RacingApiClient(username, password);
}
/// <summary>
/// Aggiorna le credenziali API
/// </summary>
public void UpdateCredentials(string username, string password)
{
_client = new RacingApiClient(username, password);
}
/// <summary>
/// Scarica le racecard (programma corse) per oggi o domani e le restituisce come DataTable
/// </summary>
public DataTable GetRacecards(string day, IProgress<int> progress = null, IProgress<string> status = null)
{
try
{
status?.Report("Connessione a The Racing API...");
progress?.Report(10);
var response = _client.GetRacecardsFree(day);
progress?.Report(60);
status?.Report("Elaborazione racecard...");
var table = ParseRacecardsResponse(response.Content);
progress?.Report(100);
status?.Report($"Trovate {table.Rows.Count} corse");
return table;
}
catch (Exception ex)
{
status?.Report($"Errore: {ex.Message}");
return CreateEmptyRacecardsTable();
}
}
/// <summary>
/// Scarica i risultati per un intervallo di date
/// </summary>
public DataTable GetResults(DateTime startDate, DateTime endDate,
IProgress<int> progress = null, IProgress<string> status = null)
{
try
{
status?.Report("Scaricamento risultati...");
progress?.Report(10);
var response = _client.GetResults(startDate, endDate);
progress?.Report(60);
status?.Report("Elaborazione risultati...");
var table = ParseResultsResponse(response.Content);
progress?.Report(100);
status?.Report($"Trovati {table.Rows.Count} risultati");
return table;
}
catch (Exception ex)
{
status?.Report($"Errore: {ex.Message}");
return CreateEmptyResultsTable();
}
}
#region DataTable creation
private DataTable CreateEmptyRacecardsTable()
{
var dt = new DataTable();
dt.Columns.Add("Ora", typeof(string));
dt.Columns.Add("Ippodromo", typeof(string));
dt.Columns.Add("Regione", typeof(string));
dt.Columns.Add("Corsa", typeof(string));
dt.Columns.Add("Distanza", typeof(string));
dt.Columns.Add("Tipo", typeof(string));
dt.Columns.Add("Classe", typeof(string));
dt.Columns.Add("Terreno", typeof(string));
dt.Columns.Add("N. Corridori", typeof(int));
dt.Columns.Add("Età", typeof(string));
dt.Columns.Add("Premio", typeof(string));
return dt;
}
private DataTable CreateEmptyResultsTable()
{
var dt = new DataTable();
dt.Columns.Add("Data", typeof(string));
dt.Columns.Add("Ippodromo", typeof(string));
dt.Columns.Add("Corsa", typeof(string));
dt.Columns.Add("Distanza", typeof(string));
dt.Columns.Add("Terreno", typeof(string));
dt.Columns.Add("1° Classificato", typeof(string));
dt.Columns.Add("2° Classificato", typeof(string));
dt.Columns.Add("3° Classificato", typeof(string));
dt.Columns.Add("Fantino 1°", typeof(string));
dt.Columns.Add("SP 1°", typeof(string));
return dt;
}
#endregion
#region JSON Parsing
private DataTable ParseRacecardsResponse(string json)
{
var dt = CreateEmptyRacecardsTable();
if (string.IsNullOrEmpty(json)) return dt;
try
{
using (var doc = JsonDocument.Parse(json))
{
var root = doc.RootElement;
if (!root.TryGetProperty("racecards", out var racecardsEl))
return dt;
foreach (var rc in racecardsEl.EnumerateArray())
{
try
{
var row = dt.NewRow();
row["Ora"] = GetString(rc, "off_time", "");
row["Ippodromo"] = GetString(rc, "course", "");
row["Regione"] = GetString(rc, "region", "");
row["Corsa"] = GetString(rc, "race_name", "");
row["Distanza"] = GetString(rc, "distance", "");
row["Tipo"] = GetString(rc, "type", "");
row["Classe"] = GetString(rc, "race_class", "");
row["Terreno"] = GetString(rc, "going", "");
row["Età"] = GetString(rc, "age_band", "");
row["Premio"] = GetString(rc, "prize", "");
if (rc.TryGetProperty("runners", out var runnersEl) &&
runnersEl.ValueKind == JsonValueKind.Array)
{
row["N. Corridori"] = runnersEl.GetArrayLength();
}
else if (rc.TryGetProperty("field_size", out var fsEl) &&
fsEl.ValueKind == JsonValueKind.Number)
{
row["N. Corridori"] = fsEl.GetInt32();
}
else
{
row["N. Corridori"] = 0;
}
dt.Rows.Add(row);
}
catch
{
// Salta righe problematiche
}
}
}
}
catch
{
// Restituisci tabella vuota in caso di errore di parsing
}
return dt;
}
private DataTable ParseResultsResponse(string json)
{
var dt = CreateEmptyResultsTable();
if (string.IsNullOrEmpty(json)) return dt;
try
{
using (var doc = JsonDocument.Parse(json))
{
var root = doc.RootElement;
if (!root.TryGetProperty("results", out var resultsEl))
return dt;
foreach (var res in resultsEl.EnumerateArray())
{
try
{
var row = dt.NewRow();
row["Data"] = GetString(res, "date", "");
row["Ippodromo"] = GetString(res, "course", "");
row["Corsa"] = GetString(res, "race_name", "");
row["Distanza"] = GetString(res, "distance", "");
row["Terreno"] = GetString(res, "going", "");
if (res.TryGetProperty("runners", out var runnersEl) &&
runnersEl.ValueKind == JsonValueKind.Array)
{
int idx = 0;
foreach (var runner in runnersEl.EnumerateArray())
{
var pos = GetString(runner, "position", "");
if (pos == "1" || idx == 0)
{
row["1° Classificato"] = GetString(runner, "horse", "");
row["Fantino 1°"] = GetString(runner, "jockey", "");
row["SP 1°"] = GetString(runner, "sp", "");
}
else if (pos == "2" || idx == 1)
{
row["2° Classificato"] = GetString(runner, "horse", "");
}
else if (pos == "3" || idx == 2)
{
row["3° Classificato"] = GetString(runner, "horse", "");
}
idx++;
if (idx >= 3) break;
}
}
dt.Rows.Add(row);
}
catch
{
// Salta righe problematiche
}
}
}
}
catch
{
// Restituisci tabella vuota in caso di errore di parsing
}
return dt;
}
private static string GetString(JsonElement el, string property, string defaultValue)
{
if (el.TryGetProperty(property, out var prop) && prop.ValueKind == JsonValueKind.String)
return prop.GetString() ?? defaultValue;
if (el.TryGetProperty(property, out prop) && prop.ValueKind == JsonValueKind.Number)
return prop.ToString();
return defaultValue;
}
#endregion
}
}

View File

@@ -5,17 +5,15 @@ using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HorseRacingPredictor.Horses.ML;
namespace HorseRacingPredictor.Horses
{
internal class Calculator
{
private readonly MachineLearningService _mlService;
public Calculator()
{
_mlService = new MachineLearningService();
// Machine learning service removed or unavailable in this build.
// Keep constructor minimal.
}
public int CalculateRating1(DataRow row)
@@ -168,52 +166,15 @@ namespace HorseRacingPredictor.Horses
/// </summary>
public int CalculateMLRating(DataRow row)
{
// Machine learning component removed: fall back to deterministic rating
try
{
// Prepara l'input per il modello ML
var input = new ModelInput
{
Age = GetFloatValueOrDefault(row, "Age"),
HandicapRating = GetFloatValueOrDefault(row, "Handicap Rating"),
Weight = GetFloatValueOrDefault(row, "Weight"),
WeightCarried = GetFloatValueOrDefault(row, "Weight Carried"),
Barrier = GetFloatValueOrDefault(row, "Barrier"),
CareerRuns = GetFloatValueOrDefault(row, "Career Runs"),
CareerWins = GetFloatValueOrDefault(row, "Career Wins"),
CareerStrikeRate = GetFloatValueOrDefault(row, "Career Strike Rate"),
CareerROI = GetFloatValueOrDefault(row, "Career ROI"),
ThisTrackRuns = GetFloatValueOrDefault(row, "This Track Runs"),
ThisTrackWins = GetFloatValueOrDefault(row, "This Track Wins"),
ThisTrackStrikeRate = GetFloatValueOrDefault(row, "This Track Strike Rate"),
ThisDistanceRuns = GetFloatValueOrDefault(row, "This Distance Runs"),
ThisDistanceWins = GetFloatValueOrDefault(row, "This Distance Wins"),
ThisDistanceStrikeRate = GetFloatValueOrDefault(row, "This Distance Strike Rate"),
JockeyLast100Wins = GetFloatValueOrDefault(row, "Jockey Last 100 Wins"),
JockeyLast100StrikeRate = GetFloatValueOrDefault(row, "Jockey Last 100 Strike Rate"),
TrainerLast100Wins = GetFloatValueOrDefault(row, "Trainer Last 100 Wins"),
TrainerLast100StrikeRate = GetFloatValueOrDefault(row, "Trainer Last 100 Strike Rate"),
BestFixedOdds = GetFloatValueOrDefault(row, "Best Fixed Odds")
};
// Effettua la previsione usando il modello ML
var output = _mlService.PredictFinishPosition(input);
// Il rating ML è inversamente proporzionale alla posizione prevista
// Migliore è la posizione prevista (più vicina a 1), più alto sarà il rating
double mlRating = 100 * (1.0 / Math.Max(1, output.PredictedPosition));
// Aggiusta il rating in base alla probabilità di vittoria
mlRating = mlRating * (0.6 + (output.WinProbability * 0.4));
return (int)Math.Min(100, Math.Max(0, mlRating));
}
catch (Exception ex)
{
Console.WriteLine($"Errore nel calcolo del rating ML: {ex.Message}");
// In caso di errore, ritorna un rating basato sul metodo standard
return CalculateRating1(row);
}
catch
{
return 0;
}
}
private double GetValueOrDefault(DataRow row, string columnName)

View File

@@ -1,49 +0,0 @@
using System;
namespace HorseRacingPredictor.Horses.ML
{
/// <summary>
/// Classe che contiene il risultato della previsione per un singolo cavallo in una corsa.
/// </summary>
public class HorseRacePrediction
{
// Informazioni identificative
public string HorseId { get; set; }
public string HorseName { get; set; }
public string RaceMeeting { get; set; }
public int RaceNumber { get; set; }
public DateTime RaceDate { get; set; }
// Risultati della previsione
public float PredictedPosition { get; set; }
public float WinProbability { get; set; }
public float PlaceProbability { get; set; }
// Rating ML calcolato (combinazione di probabilità e altri fattori)
public int MLRating { get; set; }
// Quota attuale per valutazione valore
public float CurrentOdds { get; set; }
// Valore calcolato (rapporto tra probabilità e quota)
public float Value => WinProbability > 0 ? (WinProbability * 100) / (1 / CurrentOdds) : 0;
// Indicazione se questo cavallo è considerato di valore
public bool IsValueBet => Value > 1.1f;
// Posizione reale del cavallo
public string ActualPosition { get; set; }
// Indicazione se la previsione è accurata
public bool IsAccuratePrediction { get; set; }
// Metodo per valutare l'accuratezza della previsione
public void EvaluateAccuracy()
{
if (!string.IsNullOrEmpty(ActualPosition) && int.TryParse(ActualPosition, out int actualPos))
{
IsAccuratePrediction = Math.Abs(actualPos - PredictedPosition) <= 0.5;
}
}
}
}

View File

@@ -1,573 +0,0 @@
using Microsoft.ML;
using Microsoft.ML.Data;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Data.SqlClient;
namespace HorseRacingPredictor.Horses.ML
{
/// <summary>
/// Servizio principale per l'implementazione del machine learning
/// </summary>
public class MachineLearningService
{
private readonly MLContext _mlContext;
private ITransformer _trainedModel;
private readonly string _modelPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Models", "HorseRaceModel.zip");
public string ConnectionString { get; set; }
public MachineLearningService(string connectionString = null)
{
_mlContext = new MLContext(seed: 42);
Directory.CreateDirectory(Path.GetDirectoryName(_modelPath));
ConnectionString = connectionString;
}
/// <summary>
/// Carica un modello ML esistente da file
/// </summary>
public void LoadModel()
{
if (!File.Exists(_modelPath))
throw new FileNotFoundException($"[ML] Modello non trovato: {_modelPath}");
Console.WriteLine($"[ML] Caricamento modello da: {_modelPath}");
_trainedModel = _mlContext.Model.Load(_modelPath, out _);
Console.WriteLine("[ML] Modello ML caricato correttamente.");
}
/// <summary>
/// Salva il modello addestrato su file
/// </summary>
public void SaveModel(ITransformer model)
{
Console.WriteLine($"[ML] Salvataggio modello in: {_modelPath}");
_mlContext.Model.Save(model, null, _modelPath);
}
/// <summary>
/// Ottiene i dati di training dal database e addestra un modello ML
/// </summary>
public void TrainModel(string connectionString)
{
// Imposta a 'true' per ricreare sempre il modello (default).
// Per NON ricreare il modello ad ogni esecuzione, commenta la riga seguente:
bool alwaysRetrain = true;
// bool alwaysRetrain = false;
if (!alwaysRetrain && File.Exists(_modelPath))
{
Console.WriteLine("[ML] Modello già esistente. Caricamento modello da file...");
LoadModel();
return;
}
try
{
Console.WriteLine("[ML] Inizio caricamento dati di training dal database...");
var trainingData = LoadTrainingDataFromDatabase(connectionString);
if (trainingData.Count == 0)
{
Console.WriteLine("[ML][ERRORE] Nessun dato di training disponibile nel database.");
return;
}
Console.WriteLine($"[ML] Numero record caricati per il training: {trainingData.Count}");
var dataView = _mlContext.Data.LoadFromEnumerable(trainingData);
// Suddividi i dati in train e test (80% train, 20% test)
var split = _mlContext.Data.TrainTestSplit(dataView, testFraction: 0.2, seed: 42);
// Costruisci la pipeline di training
var pipeline = _mlContext.Transforms.Concatenate("Features",
"Age", "HandicapRating", "Weight", "WeightCarried", "Barrier",
"CareerRuns", "CareerWins", "CareerStrikeRate", "CareerROI",
"ThisTrackRuns", "ThisTrackWins", "ThisTrackStrikeRate",
"ThisDistanceRuns", "ThisDistanceWins", "ThisDistanceStrikeRate",
"JockeyLast100Wins", "JockeyLast100StrikeRate",
"TrainerLast100Wins", "TrainerLast100StrikeRate", "BestFixedOdds",
"Rating1", "Rating2", "Rating3", "Rating4", "Rating5"
// Qui puoi aggiungere nuove feature create o derivate
)
.Append(_mlContext.Transforms.NormalizeMinMax("Features"))
.Append(_mlContext.Regression.Trainers.FastTree(
labelColumnName: "FinishPosition",
featureColumnName: "Features",
numberOfTrees: 200, // Aumentato per maggiore accuratezza
numberOfLeaves: 32, // Più foglie per maggiore complessità
minimumExampleCountPerLeaf: 5, // Più sensibile ai dettagli
learningRate: 0.15f // Puoi sperimentare anche questo parametro
));
Console.WriteLine("[ML] Inizio addestramento del modello...");
_trainedModel = pipeline.Fit(split.TrainSet);
Console.WriteLine("[ML] Addestramento completato.");
// Valuta sul training set
var trainMetrics = _mlContext.Regression.Evaluate(_trainedModel.Transform(split.TrainSet), labelColumnName: "FinishPosition");
// Valuta sul test set
var testMetrics = _mlContext.Regression.Evaluate(_trainedModel.Transform(split.TestSet), labelColumnName: "FinishPosition");
// Cross-validation
var cvResults = _mlContext.Regression.CrossValidate(data: dataView, estimator: pipeline, numberOfFolds: 5, labelColumnName: "FinishPosition");
var avgRSquared = cvResults.Average(fold => fold.Metrics.RSquared);
Console.WriteLine($"RSquared medio (cross-validation): {avgRSquared:F4}");
SaveModel(_trainedModel);
Console.WriteLine($"[ML] Modello salvato in: {_modelPath}");
Console.WriteLine("[ML] Metriche di valutazione (TRAIN):");
Console.WriteLine($"[ML] - RSquared: {trainMetrics.RSquared:F4}");
Console.WriteLine($"[ML] - MAE: {trainMetrics.MeanAbsoluteError:F4}");
Console.WriteLine($"[ML] - MSE: {trainMetrics.MeanSquaredError:F4}");
Console.WriteLine($"[ML] - RMSE: {trainMetrics.RootMeanSquaredError:F4}");
Console.WriteLine("[ML] Metriche di valutazione (TEST):");
Console.WriteLine($"[ML] - RSquared: {testMetrics.RSquared:F4}");
Console.WriteLine($"[ML] - MAE: {testMetrics.MeanAbsoluteError:F4}");
Console.WriteLine($"[ML] - MSE: {testMetrics.MeanSquaredError:F4}");
Console.WriteLine($"[ML] - RMSE: {testMetrics.RootMeanSquaredError:F4}");
var predictions = _trainedModel.Transform(split.TestSet);
var actuals = _mlContext.Data.CreateEnumerable<ModelInput>(split.TestSet, false).ToList();
var preds = _mlContext.Data.CreateEnumerable<ModelOutput>(predictions, false).ToList();
int top3Correct = actuals.Zip(preds, (a, p) => (a, p))
.Count(x => x.a.FinishPosition <= 3 && x.p.PredictedPosition <= 3);
double accuracyTop3 = (double)top3Correct / actuals.Count;
Console.WriteLine($"Percentuale cavalli previsti nei primi 3: {accuracyTop3:P2}");
}
catch (Exception ex)
{
Console.WriteLine($"[ML][ERRORE] Errore durante l'addestramento del modello: {ex.Message}");
throw;
}
}
/// <summary>
/// Carica i dati di training dal database
/// </summary>
private List<ModelInput> LoadTrainingDataFromDatabase(string connectionString)
{
var trainingData = new List<ModelInput>();
var calculator = new HorseRacingPredictor.Horses.Calculator();
int skippedRows = 0;
int rating1Errors = 0, rating2Errors = 0, rating3Errors = 0, rating4Errors = 0, rating5Errors = 0;
try
{
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
string query = @"
SELECT
CASE WHEN ISNUMERIC(FinishResult) = 1 THEN CAST(FinishResult AS FLOAT) ELSE 999 END AS FinishPosition,
COALESCE(Age, 0) AS Age,
COALESCE(HandicapRating, 0) AS HandicapRating,
COALESCE(Weight, 0) AS Weight,
COALESCE(WeightCarried, 0) AS WeightCarried,
COALESCE(Barrier, 0) AS Barrier,
COALESCE(CareerRuns, 0) AS CareerRuns,
COALESCE(CareerWins, 0) AS CareerWins,
COALESCE(CareerStrikeRate, 0) AS CareerStrikeRate,
COALESCE(CareerROI, 0) AS CareerROI,
COALESCE(ThisTrackRuns, 0) AS ThisTrackRuns,
COALESCE(ThisTrackWins, 0) AS ThisTrackWins,
COALESCE(ThisTrackStrikeRate, 0) AS ThisTrackStrikeRate,
COALESCE(ThisDistanceRuns, 0) AS ThisDistanceRuns,
COALESCE(ThisDistanceWins, 0) AS ThisDistanceWins,
COALESCE(ThisDistanceStrikeRate, 0) AS ThisDistanceStrikeRate,
COALESCE(JockeyLast100Wins, 0) AS JockeyLast100Wins,
COALESCE(JockeyLast100StrikeRate, 0) AS JockeyLast100StrikeRate,
COALESCE(TrainerLast100Wins, 0) AS TrainerLast100Wins,
COALESCE(TrainerLast100StrikeRate, 0) AS TrainerLast100StrikeRate,
COALESCE(BestFixedOdds, 0) AS BestFixedOdds,
COALESCE(Gender, '') AS Gender
FROM Races
WHERE ISNUMERIC(FinishResult) = 1
AND FinishResult IS NOT NULL
AND FinishResult <> ''";
using (var command = new SqlCommand(query, connection))
{
using (var adapter = new SqlDataAdapter(command))
{
var table = new System.Data.DataTable();
adapter.Fill(table);
foreach (System.Data.DataRow row in table.Rows)
{
try {
float finishPos = Convert.ToSingle(row["FinishPosition"]);
if (finishPos >= 15 || finishPos <= 0) {
skippedRows++;
continue;
}
// Calcola i rating con gestione degli errori e sostituzione con valori predefiniti
float rating1 = 0, rating2 = 0, rating3 = 0, rating4 = 0, rating5 = 0;
try {
rating1 = (float)calculator.CalculateRating1(row);
if (float.IsNaN(rating1) || float.IsInfinity(rating1)) {
rating1 = 0;
rating1Errors++;
}
} catch (Exception) {
rating1Errors++;
}
try {
rating2 = (float)calculator.CalculateRating2(row);
if (float.IsNaN(rating2) || float.IsInfinity(rating2)) {
rating2 = 0;
rating2Errors++;
}
} catch (Exception) {
rating2Errors++;
}
try {
rating3 = (float)calculator.CalculateRating3(row);
if (float.IsNaN(rating3) || float.IsInfinity(rating3)) {
rating3 = 0;
rating3Errors++;
}
} catch (Exception) {
rating3Errors++;
}
try {
rating4 = (float)calculator.CalculateRating4(row);
if (float.IsNaN(rating4) || float.IsInfinity(rating4)) {
rating4 = 0;
rating4Errors++;
}
} catch (Exception) {
rating4Errors++;
}
try {
rating5 = (float)calculator.CalculateRating5(row);
if (float.IsNaN(rating5) || float.IsInfinity(rating5)) {
rating5 = 0;
rating5Errors++;
}
} catch (Exception) {
rating5Errors++;
}
// Usa SafeConvert per tutte le conversioni
trainingData.Add(new ModelInput
{
FinishPosition = finishPos,
Age = SafeConvert.ToSingle(row["Age"]),
HandicapRating = SafeConvert.ToSingle(row["HandicapRating"]),
Weight = SafeConvert.ToSingle(row["Weight"]),
WeightCarried = SafeConvert.ToSingle(row["WeightCarried"]),
Barrier = SafeConvert.ToSingle(row["Barrier"]),
CareerRuns = SafeConvert.ToSingle(row["CareerRuns"]),
CareerWins = SafeConvert.ToSingle(row["CareerWins"]),
CareerStrikeRate = SafeConvert.ToSingle(row["CareerStrikeRate"]),
CareerROI = SafeConvert.ToSingle(row["CareerROI"]),
ThisTrackRuns = SafeConvert.ToSingle(row["ThisTrackRuns"]),
ThisTrackWins = SafeConvert.ToSingle(row["ThisTrackWins"]),
ThisTrackStrikeRate = SafeConvert.ToSingle(row["ThisTrackStrikeRate"]),
ThisDistanceRuns = SafeConvert.ToSingle(row["ThisDistanceRuns"]),
ThisDistanceWins = SafeConvert.ToSingle(row["ThisDistanceWins"]),
ThisDistanceStrikeRate = SafeConvert.ToSingle(row["ThisDistanceStrikeRate"]),
JockeyLast100Wins = SafeConvert.ToSingle(row["JockeyLast100Wins"]),
JockeyLast100StrikeRate = SafeConvert.ToSingle(row["JockeyLast100StrikeRate"]),
TrainerLast100Wins = SafeConvert.ToSingle(row["TrainerLast100Wins"]),
TrainerLast100StrikeRate = SafeConvert.ToSingle(row["TrainerLast100StrikeRate"]),
BestFixedOdds = SafeConvert.ToSingle(row["BestFixedOdds"]),
Rating1 = rating1,
Rating2 = rating2,
Rating3 = rating3,
Rating4 = rating4,
Rating5 = rating5
});
} catch (Exception ex) {
skippedRows++;
}
}
Console.WriteLine($"[ML] Record letti dal database: {table.Rows.Count}");
Console.WriteLine($"[ML] Record saltati: {skippedRows}");
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"[ML][ERRORE] Errore durante il caricamento dei dati dal database: {ex.Message}");
}
Console.WriteLine($"[ML] Caricati {trainingData.Count} record per l'addestramento.");
return trainingData;
}
/// <summary>
/// Costruisce la pipeline di addestramento del modello
/// </summary>
private IEstimator<ITransformer> BuildTrainingPipeline()
{
// Crea la pipeline per addestramento
// 1. Concatena tutte le feature in un unico vettore
// 2. Normalizza i dati di input
// 3. Addestra un modello FastTree con FastTreeRegressionTrainer
var pipeline = _mlContext.Transforms.Concatenate("Features",
"Age", "HandicapRating", "Weight", "WeightCarried", "Barrier",
"CareerRuns", "CareerWins", "CareerStrikeRate", "CareerROI",
"ThisTrackRuns", "ThisTrackWins", "ThisTrackStrikeRate",
"ThisDistanceRuns", "ThisDistanceWins", "ThisDistanceStrikeRate",
"JockeyLast100Wins", "JockeyLast100StrikeRate",
"TrainerLast100Wins", "TrainerLast100StrikeRate", "BestFixedOdds",
"Rating1", "Rating2", "Rating3", "Rating4", "Rating5")
.Append(_mlContext.Transforms.NormalizeMinMax("Features"))
.Append(_mlContext.Regression.Trainers.FastTree(
labelColumnName: "FinishPosition",
featureColumnName: "Features",
numberOfTrees: 100,
numberOfLeaves: 20,
minimumExampleCountPerLeaf: 10));
return pipeline;
}
/// <summary>
/// Valuta il modello addestrato
/// </summary>
private RegressionMetrics EvaluateModel(IDataView dataView, ITransformer model)
{
var predictions = model.Transform(dataView);
return _mlContext.Regression.Evaluate(predictions, labelColumnName: "FinishPosition");
}
/// <summary>
/// Predice la posizione finale per un cavallo
/// </summary>
public ModelOutput PredictFinishPosition(ModelInput input)
{
if (_trainedModel == null)
{
Console.WriteLine("Il modello non è stato addestrato o caricato.");
// Restituisci un oggetto vuoto invece di lanciare un'eccezione
return new ModelOutput
{
PredictedPosition = 0,
WinProbability = 0,
PlaceProbability = 0
};
}
// Crea il motore di previsione
var predEngine = _mlContext.Model.CreatePredictionEngine<ModelInput, ModelOutput>(_trainedModel);
// Effettua la previsione
var prediction = predEngine.Predict(input);
// Calcola le probabilità (implementazione semplificata)
prediction.WinProbability = CalculateWinProbability(prediction.PredictedPosition);
prediction.PlaceProbability = CalculatePlaceProbability(prediction.PredictedPosition);
return prediction;
}
/// <summary>
/// Calcola la probabilità di vittoria in base alla posizione prevista
/// </summary>
private float CalculateWinProbability(float predictedPosition)
{
// Implementazione semplice: più il valore è vicino a 1, maggiore è la probabilità di vittoria
if (predictedPosition <= 1) return 0.9f;
if (predictedPosition <= 2) return 0.5f;
if (predictedPosition <= 3) return 0.3f;
if (predictedPosition <= 5) return 0.1f;
return 0.05f;
}
/// <summary>
/// Calcola la probabilità di piazzamento (primi 3) in base alla posizione prevista
/// </summary>
private float CalculatePlaceProbability(float predictedPosition)
{
// Implementazione semplice: più il valore è vicino a 1, 2 o 3, maggiore è la probabilità di piazzamento
if (predictedPosition <= 1) return 0.95f;
if (predictedPosition <= 2) return 0.85f;
if (predictedPosition <= 3) return 0.75f;
if (predictedPosition <= 4) return 0.4f;
if (predictedPosition <= 6) return 0.2f;
return 0.1f;
}
/// <summary>
/// Analizza un'intera corsa e prevede il risultato per tutti i cavalli
/// </summary>
public List<HorseRacePrediction> PredictRaceResult(string meeting, int raceNumber, DateTime raceDate, string connectionString)
{
var predictions = new List<HorseRacePrediction>();
var calculator = new HorseRacingPredictor.Horses.Calculator(); // Inizializza il calculator qui
try
{
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
string query = @"
SELECT
Num, HorseName,
COALESCE(Age, 0) AS Age,
COALESCE(HandicapRating, 0) AS HandicapRating,
COALESCE(Weight, 0) AS Weight,
COALESCE(WeightCarried, 0) AS WeightCarried,
COALESCE(Barrier, 0) AS Barrier,
COALESCE(CareerRuns, 0) AS CareerRuns,
COALESCE(CareerWins, 0) AS CareerWins,
COALESCE(CareerStrikeRate, 0) AS CareerStrikeRate,
COALESCE(CareerROI, 0) AS CareerROI,
COALESCE(ThisTrackRuns, 0) AS ThisTrackRuns,
COALESCE(ThisTrackWins, 0) AS ThisTrackWins,
COALESCE(ThisTrackStrikeRate, 0) AS ThisTrackStrikeRate,
COALESCE(ThisDistanceRuns, 0) AS ThisDistanceRuns,
COALESCE(ThisDistanceWins, 0) AS ThisDistanceWins,
COALESCE(ThisDistanceStrikeRate, 0) AS ThisDistanceStrikeRate,
COALESCE(JockeyLast100Wins, 0) AS JockeyLast100Wins,
COALESCE(JockeyLast100StrikeRate, 0) AS JockeyLast100StrikeRate,
COALESCE(TrainerLast100Wins, 0) AS TrainerLast100Wins,
COALESCE(TrainerLast100StrikeRate, 0) AS TrainerLast100StrikeRate,
COALESCE(BestFixedOdds, 0) AS BestFixedOdds,
COALESCE(Gender, '') AS Gender
FROM Races
WHERE Meeting = @Meeting AND Race = @Race AND Data = @Data";
using (var command = new SqlCommand(query, connection))
{
command.Parameters.AddWithValue("@Meeting", meeting);
command.Parameters.AddWithValue("@Race", raceNumber);
command.Parameters.AddWithValue("@Data", raceDate);
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
// Converti i dati del reader in una DataRow per usare il calculator
DataTable dt = new DataTable();
DataRow row = dt.NewRow();
for (int i = 0; i < reader.FieldCount; i++)
{
if (!dt.Columns.Contains(reader.GetName(i)))
dt.Columns.Add(reader.GetName(i));
row[reader.GetName(i)] = reader.GetValue(i);
}
dt.Rows.Add(row);
// Prepara l'input per il modello
var input = new ModelInput
{
Age = Convert.ToSingle(reader["Age"]),
HandicapRating = Convert.ToSingle(reader["HandicapRating"]),
Weight = Convert.ToSingle(reader["Weight"]),
WeightCarried = Convert.ToSingle(reader["WeightCarried"]),
Barrier = Convert.ToSingle(reader["Barrier"]),
CareerRuns = Convert.ToSingle(reader["CareerRuns"]),
CareerWins = Convert.ToSingle(reader["CareerWins"]),
CareerStrikeRate = Convert.ToSingle(reader["CareerStrikeRate"]),
CareerROI = Convert.ToSingle(reader["CareerROI"]),
ThisTrackRuns = Convert.ToSingle(reader["ThisTrackRuns"]),
ThisTrackWins = Convert.ToSingle(reader["ThisTrackWins"]),
ThisTrackStrikeRate = Convert.ToSingle(reader["ThisTrackStrikeRate"]),
ThisDistanceRuns = Convert.ToSingle(reader["ThisDistanceRuns"]),
ThisDistanceWins = Convert.ToSingle(reader["ThisDistanceWins"]),
ThisDistanceStrikeRate = Convert.ToSingle(reader["ThisDistanceStrikeRate"]),
JockeyLast100Wins = Convert.ToSingle(reader["JockeyLast100Wins"]),
JockeyLast100StrikeRate = Convert.ToSingle(reader["JockeyLast100StrikeRate"]),
TrainerLast100Wins = Convert.ToSingle(reader["TrainerLast100Wins"]),
TrainerLast100StrikeRate = Convert.ToSingle(reader["TrainerLast100StrikeRate"]),
BestFixedOdds = Convert.ToSingle(reader["BestFixedOdds"]),
// Aggiungi i rating calcolati
Rating1 = (float)calculator.CalculateRating1(row),
Rating2 = (float)calculator.CalculateRating2(row),
Rating3 = (float)calculator.CalculateRating3(row),
Rating4 = (float)calculator.CalculateRating4(row),
Rating5 = (float)calculator.CalculateRating5(row)
};
// Effettua la previsione
var output = PredictFinishPosition(input);
// Calcola un rating ML basato sulla posizione prevista
int mlRating = CalculateMLRating(output);
// Aggiungi alla lista delle previsioni
predictions.Add(new HorseRacePrediction
{
HorseId = Convert.ToString(reader["Num"]),
HorseName = Convert.ToString(reader["HorseName"]),
RaceMeeting = meeting,
RaceNumber = raceNumber,
RaceDate = raceDate,
PredictedPosition = output.PredictedPosition,
WinProbability = output.WinProbability,
PlaceProbability = output.PlaceProbability,
MLRating = mlRating,
CurrentOdds = Convert.ToSingle(reader["BestFixedOdds"])
});
}
}
}
}
// Ordina per posizione prevista (dal migliore al peggiore)
return predictions.OrderBy(p => p.PredictedPosition).ToList();
}
catch (Exception ex)
{
Console.WriteLine($"[ML][ERRORE] Errore durante la previsione della corsa: {ex.Message}");
return predictions;
}
}
/// <summary>
/// Calcola un rating ML basato sulla posizione prevista e altre metriche
/// </summary>
private int CalculateMLRating(ModelOutput output)
{
// Formula per il rating ML (convertito in un numero intero da 0 a 100)
double baseRating = (1 / Math.Max(0.1, output.PredictedPosition)) * 100;
double winProbabilityFactor = output.WinProbability * 100;
double placeProbabilityFactor = output.PlaceProbability * 50;
// Combina i fattori con pesi diversi
double combinedRating = (baseRating * 0.4) + (winProbabilityFactor * 0.4) + (placeProbabilityFactor * 0.2);
// Limita il valore tra 0 e 100
return (int)Math.Min(100, Math.Max(0, combinedRating));
}
// Metodo di utilità per convertire in modo sicuro i valori
private static class SafeConvert
{
public static float ToSingle(object value, float defaultValue = 0)
{
if (value == null || value == DBNull.Value)
return defaultValue;
try {
float result = Convert.ToSingle(value);
return float.IsNaN(result) || float.IsInfinity(result) ? defaultValue : result;
}
catch {
return defaultValue;
}
}
}
}
}

View File

@@ -1,109 +0,0 @@
using Microsoft.ML.Data;
namespace HorseRacingPredictor.Horses.ML
{
/// <summary>
/// Classe che rappresenta i dati di input per l'addestramento del modello ML.
/// Contiene le feature selezionate dalle corse passate.
/// </summary>
public class ModelInput
{
// Etichetta da prevedere: posizione finale (1 = vincitore, 2 = secondo posto, ecc.)
[LoadColumn(0)]
[ColumnName("FinishPosition")]
public float FinishPosition { get; set; }
// Feature principali sul cavallo
[LoadColumn(1)]
[ColumnName("Age")]
public float Age { get; set; }
[LoadColumn(2)]
[ColumnName("HandicapRating")]
public float HandicapRating { get; set; }
[LoadColumn(3)]
[ColumnName("Weight")]
public float Weight { get; set; }
[LoadColumn(4)]
[ColumnName("WeightCarried")]
public float WeightCarried { get; set; }
[LoadColumn(5)]
[ColumnName("Barrier")]
public float Barrier { get; set; }
// Statistiche di carriera
[LoadColumn(6)]
[ColumnName("CareerRuns")]
public float CareerRuns { get; set; }
[LoadColumn(7)]
[ColumnName("CareerWins")]
public float CareerWins { get; set; }
[LoadColumn(8)]
[ColumnName("CareerStrikeRate")]
public float CareerStrikeRate { get; set; }
[LoadColumn(9)]
[ColumnName("CareerROI")]
public float CareerROI { get; set; }
// Statistiche di pista
[LoadColumn(10)]
[ColumnName("ThisTrackRuns")]
public float ThisTrackRuns { get; set; }
[LoadColumn(11)]
[ColumnName("ThisTrackWins")]
public float ThisTrackWins { get; set; }
[LoadColumn(12)]
[ColumnName("ThisTrackStrikeRate")]
public float ThisTrackStrikeRate { get; set; }
[LoadColumn(13)]
[ColumnName("ThisDistanceRuns")]
public float ThisDistanceRuns { get; set; }
[LoadColumn(14)]
[ColumnName("ThisDistanceWins")]
public float ThisDistanceWins { get; set; }
[LoadColumn(15)]
[ColumnName("ThisDistanceStrikeRate")]
public float ThisDistanceStrikeRate { get; set; }
// Statistiche fantino
[LoadColumn(16)]
[ColumnName("JockeyLast100Wins")]
public float JockeyLast100Wins { get; set; }
[LoadColumn(17)]
[ColumnName("JockeyLast100StrikeRate")]
public float JockeyLast100StrikeRate { get; set; }
// Statistiche allenatore
[LoadColumn(18)]
[ColumnName("TrainerLast100Wins")]
public float TrainerLast100Wins { get; set; }
[LoadColumn(19)]
[ColumnName("TrainerLast100StrikeRate")]
public float TrainerLast100StrikeRate { get; set; }
// Quota iniziale (può essere predittiva)
[LoadColumn(20)]
[ColumnName("BestFixedOdds")]
public float BestFixedOdds { get; set; }
// Cambia il tipo da int a float
public float Rating1 { get; set; }
public float Rating2 { get; set; }
public float Rating3 { get; set; }
public float Rating4 { get; set; }
public float Rating5 { get; set; }
}
}

View File

@@ -1,20 +0,0 @@
using Microsoft.ML.Data;
namespace HorseRacingPredictor.Horses.ML
{
/// <summary>
/// Classe che rappresenta l'output della previsione del modello ML.
/// </summary>
public class ModelOutput
{
// Previsione: il risultato della classificazione (1 = vincitore, 2 = secondo posto, ecc.)
[ColumnName("Score")]
public float PredictedPosition { get; set; }
[NoColumn] // Indica che questa proprietà non esiste nei dati
public float WinProbability { get; set; }
[NoColumn] // Indica che questa proprietà non esiste nei dati
public float PlaceProbability { get; set; }
}
}

View File

@@ -1,360 +1,583 @@
using System;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
using BettingPredictor.UI;
namespace BettingPredictor
{
partial class Main
{
// Container
private System.ComponentModel.IContainer components = null;
// Grafica
private TabControl tabControl;
// Layout
private Panel panelSidebar;
private Panel panelContent;
private Panel panelHeader;
private Label labelAppTitle;
private Label labelPageTitle;
// Horse tab
private TabPage tabPageHorse;
private TextBox textBoxFolderPath;
private Button buttonBrowse;
private Button buttonPredict; // New button for predictions
private Button buttonImport; // New button for import
private ProgressBar progressBarHorse;
private Label labelStatusHorse;
private DataGridView dataGridViewHorse;
// Nav
private NavButton navFootball;
private NavButton navHorseRacing;
private NavButton navSettings;
private NavButton navInfo;
// Replace the buttonLoadFootball with buttonImportFootball and add buttonDownloadFootball
// Football tab
private TabPage tabPageFootball;
// Pagine
private Panel pageFootball;
private Panel pageHorseRacing;
private Panel pageSettings;
private Panel pageInfo;
// Football
private DateTimePicker dateTimePicker;
private Button buttonImportFootball;
private Button buttonDownloadFootball;
private DataGridView dataGridViewFootball;
private ProgressBar progressBarFootball;
private ModernButton btnDownload;
private ModernButton btnExportCsv;
private ModernProgressBar progressBarFootball;
private Label labelStatusFootball;
private DataGridView dataGridViewFootball;
// ApiOptions class definition - rimosso GetLeagueData
public static class ApiOptions
{
public const string GetLeagueFixtures = "Visualizza elenco partite alla data";
public const string GetLeagueOdds = "Visualizza elenco quote partite";
// Horse Racing
private ComboBox cmbRacingDay;
private ModernButton btnDownloadRacing;
private ModernButton btnExportRacingCsv;
private ModernProgressBar progressBarRacing;
private Label labelStatusRacing;
private DataGridView dataGridViewRacing;
public static readonly string[] AllOptions = { GetLeagueFixtures, GetLeagueOdds };
}
// Impostazioni
private Label lblApiKey;
private TextBox txtApiKey;
private Label lblExportPath;
private TextBox txtExportPath;
private ModernButton btnBrowseExport;
private Label lblRacingUser;
private TextBox txtRacingUser;
private Label lblRacingPass;
private TextBox txtRacingPass;
private ModernButton btnSaveSettings;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.tabControl = new System.Windows.Forms.TabControl();
// Horse Tab
this.tabPageHorse = new System.Windows.Forms.TabPage();
this.textBoxFolderPath = new System.Windows.Forms.TextBox();
this.buttonBrowse = new System.Windows.Forms.Button();
this.buttonPredict = new System.Windows.Forms.Button(); // New button for predictions
this.buttonImport = new System.Windows.Forms.Button(); // New button for import
this.progressBarHorse = new System.Windows.Forms.ProgressBar();
this.labelStatusHorse = new System.Windows.Forms.Label();
this.dataGridViewHorse = new System.Windows.Forms.DataGridView();
// In the InitializeComponent method, update:
// Football Tab
this.tabPageFootball = new System.Windows.Forms.TabPage();
this.dateTimePicker = new System.Windows.Forms.DateTimePicker();
this.buttonImportFootball = new System.Windows.Forms.Button();
this.buttonDownloadFootball = new System.Windows.Forms.Button();
this.progressBarFootball = new System.Windows.Forms.ProgressBar();
this.labelStatusFootball = new System.Windows.Forms.Label();
this.dataGridViewFootball = new System.Windows.Forms.DataGridView();
this.tabControl.SuspendLayout();
this.tabPageHorse.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.dataGridViewHorse)).BeginInit();
this.tabPageFootball.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.dataGridViewFootball)).BeginInit();
this.SuspendLayout();
//
// tabControl
//
this.tabControl.Controls.Add(this.tabPageHorse);
this.tabControl.Controls.Add(this.tabPageFootball);
this.tabControl.Dock = System.Windows.Forms.DockStyle.Fill;
this.tabControl.Location = new System.Drawing.Point(0, 0);
this.tabControl.Name = "tabControl";
this.tabControl.SelectedIndex = 0;
this.tabControl.Size = new System.Drawing.Size(800, 450);
this.tabControl.TabIndex = 0;
//
// tabPageHorse
//
this.tabPageHorse.Controls.Add(this.dataGridViewHorse);
this.tabPageHorse.Controls.Add(this.labelStatusHorse);
this.tabPageHorse.Controls.Add(this.progressBarHorse);
this.tabPageHorse.Controls.Add(this.buttonPredict); // Add new button
this.tabPageHorse.Controls.Add(this.buttonImport); // Add new button
this.tabPageHorse.Controls.Add(this.buttonBrowse);
this.tabPageHorse.Controls.Add(this.textBoxFolderPath);
this.tabPageHorse.Location = new System.Drawing.Point(4, 22);
this.tabPageHorse.Name = "tabPageHorse";
this.tabPageHorse.Padding = new System.Windows.Forms.Padding(3);
this.tabPageHorse.Size = new System.Drawing.Size(792, 424);
this.tabPageHorse.TabIndex = 0;
this.tabPageHorse.Text = "Horse";
this.tabPageHorse.UseVisualStyleBackColor = true;
//
// textBoxFolderPath
//
this.textBoxFolderPath.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBoxFolderPath.Location = new System.Drawing.Point(8, 8);
this.textBoxFolderPath.Name = "textBoxFolderPath";
this.textBoxFolderPath.ReadOnly = true;
this.textBoxFolderPath.Size = new System.Drawing.Size(510, 20);
this.textBoxFolderPath.TabIndex = 0;
//
// buttonBrowse
//
this.buttonBrowse.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.buttonBrowse.Location = new System.Drawing.Point(524, 6); // Moved position to make room for Predict button
this.buttonBrowse.Size = new System.Drawing.Size(80, 23);
this.buttonBrowse.Name = "buttonBrowse";
this.buttonBrowse.TabIndex = 1;
this.buttonBrowse.Text = "Sfoglia...";
this.buttonBrowse.UseVisualStyleBackColor = true;
this.buttonBrowse.Click += new System.EventHandler(this.buttonBrowse_Click);
//
// buttonImport
//
this.buttonImport.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.buttonImport.Location = new System.Drawing.Point(610, 6);
this.buttonImport.Name = "buttonImport";
this.buttonImport.Size = new System.Drawing.Size(80, 23);
this.buttonImport.TabIndex = 6;
this.buttonImport.Text = "Importa";
this.buttonImport.UseVisualStyleBackColor = true;
this.buttonImport.Click += new System.EventHandler(this.buttonImport_Click);
//
// buttonPredict
//
this.buttonPredict.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.buttonPredict.Location = new System.Drawing.Point(696, 6);
this.buttonPredict.Name = "buttonPredict";
this.buttonPredict.Size = new System.Drawing.Size(80, 23);
this.buttonPredict.TabIndex = 5;
this.buttonPredict.Text = "Predici";
this.buttonPredict.UseVisualStyleBackColor = true;
this.buttonPredict.Click += new System.EventHandler(this.buttonPredict_Click);
//
// progressBarHorse
//
this.progressBarHorse.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.progressBarHorse.Location = new System.Drawing.Point(8, 34);
this.progressBarHorse.Name = "progressBarHorse";
this.progressBarHorse.Size = new System.Drawing.Size(762, 20);
this.progressBarHorse.TabIndex = 2;
//
// labelStatusHorse
//
this.labelStatusHorse.AutoSize = true;
this.labelStatusHorse.Location = new System.Drawing.Point(8, 57);
this.labelStatusHorse.Name = "labelStatusHorse";
this.labelStatusHorse.Size = new System.Drawing.Size(38, 13);
this.labelStatusHorse.TabIndex = 3;
this.labelStatusHorse.Text = "Pronto";
//
// dataGridViewHorse
//
this.dataGridViewHorse.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.dataGridViewHorse.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridViewHorse.Location = new System.Drawing.Point(8, 73);
this.dataGridViewHorse.Name = "dataGridViewHorse";
this.dataGridViewHorse.ReadOnly = true;
this.dataGridViewHorse.Size = new System.Drawing.Size(762, 343);
this.dataGridViewHorse.TabIndex = 4;
// Update controls add to the tab page:
this.tabPageFootball.Controls.Add(this.dataGridViewFootball);
this.tabPageFootball.Controls.Add(this.labelStatusFootball);
this.tabPageFootball.Controls.Add(this.progressBarFootball);
this.tabPageFootball.Controls.Add(this.buttonImportFootball);
this.tabPageFootball.Controls.Add(this.buttonDownloadFootball);
this.tabPageFootball.Controls.Add(this.dateTimePicker);
this.tabPageFootball.Location = new System.Drawing.Point(4, 22);
this.tabPageFootball.Name = "tabPageFootball";
this.tabPageFootball.Padding = new System.Windows.Forms.Padding(3);
this.tabPageFootball.Size = new System.Drawing.Size(792, 424);
this.tabPageFootball.TabIndex = 1;
this.tabPageFootball.Text = "Football";
this.tabPageFootball.UseVisualStyleBackColor = true;
//
// dateTimePicker
//
this.dateTimePicker.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.dateTimePicker.Format = System.Windows.Forms.DateTimePickerFormat.Short;
this.dateTimePicker.Location = new System.Drawing.Point(8, 8);
this.dateTimePicker.Name = "dateTimePicker";
// Update dateTimePicker size to make room for two buttons
this.dateTimePicker.Size = new System.Drawing.Size(596, 20);
this.dateTimePicker.TabIndex = 1;
this.dateTimePicker.Value = System.DateTime.Today;
// buttonDownloadFootball properties
this.buttonDownloadFootball.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.buttonDownloadFootball.Location = new System.Drawing.Point(610, 6);
this.buttonDownloadFootball.Name = "buttonDownloadFootball";
this.buttonDownloadFootball.Size = new System.Drawing.Size(75, 23);
this.buttonDownloadFootball.TabIndex = 6;
this.buttonDownloadFootball.Text = "Scarica";
this.buttonDownloadFootball.UseVisualStyleBackColor = true;
this.buttonDownloadFootball.Click += new System.EventHandler(this.buttonDownloadFootball_Click);
// buttonImportFootball properties (replace buttonLoadFootball)
this.buttonImportFootball.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.buttonImportFootball.Location = new System.Drawing.Point(695, 6);
this.buttonImportFootball.Name = "buttonImportFootball";
this.buttonImportFootball.Size = new System.Drawing.Size(75, 23);
this.buttonImportFootball.TabIndex = 2;
this.buttonImportFootball.Text = "Importa";
this.buttonImportFootball.UseVisualStyleBackColor = true;
this.buttonImportFootball.Click += new System.EventHandler(this.buttonImportFootball_Click);
//
// progressBarFootball
//
this.progressBarFootball.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.progressBarFootball.Location = new System.Drawing.Point(8, 35);
this.progressBarFootball.Name = "progressBarFootball";
this.progressBarFootball.Size = new System.Drawing.Size(762, 20);
this.progressBarFootball.TabIndex = 3;
//
// labelStatusFootball
//
this.labelStatusFootball.AutoSize = true;
this.labelStatusFootball.Location = new System.Drawing.Point(8, 58);
this.labelStatusFootball.Name = "labelStatusFootball";
this.labelStatusFootball.Size = new System.Drawing.Size(38, 13);
this.labelStatusFootball.TabIndex = 4;
this.labelStatusFootball.Text = "Pronto";
//
// dataGridViewFootball
//
this.dataGridViewFootball.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.dataGridViewFootball.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridViewFootball.Location = new System.Drawing.Point(8, 74);
this.dataGridViewFootball.Name = "dataGridViewFootball";
this.dataGridViewFootball.ReadOnly = true;
this.dataGridViewFootball.Size = new System.Drawing.Size(762, 342);
this.dataGridViewFootball.TabIndex = 5;
//
// Main
//
this.ClientSize = new System.Drawing.Size(800, 450);
this.Controls.Add(this.tabControl);
this.Name = "Main";
// ============================================================
// FORM
// ============================================================
this.ClientSize = new Size(1050, 620);
this.MinimumSize = new Size(860, 520);
this.Text = "Betting Predictor";
this.Load += new System.EventHandler(this.Main_Load);
this.tabControl.ResumeLayout(false);
this.tabPageHorse.ResumeLayout(false);
this.tabPageHorse.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.dataGridViewHorse)).EndInit();
this.tabPageFootball.ResumeLayout(false);
this.tabPageFootball.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.dataGridViewFootball)).EndInit();
this.ResumeLayout(false);
}
}
this.Name = "Main";
this.StartPosition = FormStartPosition.CenterScreen;
this.BackColor = ModernTheme.ContentBackground;
this.ForeColor = ModernTheme.TextPrimary;
this.Font = ModernTheme.BodyFont;
this.DoubleBuffered = true;
this.Load += new EventHandler(this.Main_Load);
this.Resize += new EventHandler(this.Main_Resize);
public partial class ProgressDialog : Form
{
public ProgressDialog()
{
InitializeComponent();
}
public void UpdateProgress(int currentPage, int totalPages)
{
progressBar.Maximum = totalPages;
progressBar.Value = currentPage;
labelProgress.Text = $"Scaricamento pagina {currentPage} di {totalPages}";
}
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
// ============================================================
// SIDEBAR (Dock=Left, larghezza fissa)
// ============================================================
this.panelSidebar = new Panel
{
components.Dispose();
}
base.Dispose(disposing);
}
Dock = DockStyle.Left,
Width = ModernTheme.SidebarWidth,
BackColor = ModernTheme.SidebarBackground
};
private void InitializeComponent()
{
this.progressBar = new System.Windows.Forms.ProgressBar();
this.labelProgress = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// progressBar
//
this.progressBar.Location = new System.Drawing.Point(12, 12);
this.progressBar.Name = "progressBar";
this.progressBar.Size = new System.Drawing.Size(360, 23);
this.progressBar.TabIndex = 0;
//
// labelProgress
//
this.labelProgress.AutoSize = true;
this.labelProgress.Location = new System.Drawing.Point(12, 38);
this.labelProgress.Name = "labelProgress";
this.labelProgress.Size = new System.Drawing.Size(0, 13);
this.labelProgress.TabIndex = 1;
//
// ProgressDialog
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(384, 61);
this.Controls.Add(this.labelProgress);
this.Controls.Add(this.progressBar);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "ProgressDialog";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Scaricamento in corso...";
this.labelAppTitle = new Label
{
Text = "Betting\nPredictor",
Font = new Font("Segoe UI", 15F, FontStyle.Bold),
ForeColor = ModernTheme.PrimaryColor,
BackColor = ModernTheme.SidebarBackground,
Dock = DockStyle.Top,
Height = 72,
TextAlign = ContentAlignment.MiddleCenter
};
var sep = new Panel
{
Dock = DockStyle.Top,
Height = 1,
BackColor = ModernTheme.CardBorder
};
// Nav — ordine di aggiunta inverso rispetto a quello visivo (Dock=Top)
this.navInfo = new NavButton("Info", "i");
this.navInfo.Click += new EventHandler(this.navInfo_Click);
this.navSettings = new NavButton("Impostazioni", "#");
this.navSettings.Click += new EventHandler(this.navSettings_Click);
this.navHorseRacing = new NavButton("Corse Cavalli", "H");
this.navHorseRacing.Click += new EventHandler(this.navHorseRacing_Click);
this.navFootball = new NavButton("Calcio", "F");
this.navFootball.IsActive = true;
this.navFootball.Click += new EventHandler(this.navFootball_Click);
this.panelSidebar.Controls.Add(this.navInfo);
this.panelSidebar.Controls.Add(this.navSettings);
this.panelSidebar.Controls.Add(this.navHorseRacing);
this.panelSidebar.Controls.Add(this.navFootball);
this.panelSidebar.Controls.Add(sep);
this.panelSidebar.Controls.Add(this.labelAppTitle);
// ============================================================
// HEADER (Dock=Top dentro panelContent)
// ============================================================
this.panelHeader = new Panel
{
Dock = DockStyle.Top,
Height = 56,
BackColor = ModernTheme.HeaderBackground,
Padding = new Padding(24, 0, 24, 0)
};
this.labelPageTitle = new Label
{
Text = "Calcio",
Font = ModernTheme.TitleFont,
ForeColor = ModernTheme.TextPrimary,
BackColor = ModernTheme.HeaderBackground,
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleLeft
};
this.panelHeader.Controls.Add(this.labelPageTitle);
var headerLine = new Panel
{
Dock = DockStyle.Bottom,
Height = 1,
BackColor = ModernTheme.CardBorder
};
this.panelHeader.Controls.Add(headerLine);
// ============================================================
// CONTENT WRAPPER (Dock=Fill, contiene header + pagine)
// ============================================================
this.panelContent = new Panel
{
Dock = DockStyle.Fill,
BackColor = ModernTheme.ContentBackground
};
// ============================================================
// PAGE: CALCIO
// ============================================================
this.pageFootball = new Panel
{
Dock = DockStyle.Fill,
Padding = new Padding(24, 16, 24, 16),
BackColor = ModernTheme.ContentBackground,
Visible = true
};
// -- toolbar (altezza fissa, posizionamento manuale) --
var toolbarFb = new Panel
{
Dock = DockStyle.Top,
Height = 48,
BackColor = ModernTheme.ContentBackground
};
this.dateTimePicker = new DateTimePicker
{
Format = DateTimePickerFormat.Short,
Value = DateTime.Today,
Font = ModernTheme.BodyFont,
CalendarMonthBackground = ModernTheme.InputBackground,
CalendarForeColor = ModernTheme.InputText
};
this.btnDownload = new ModernButton { Text = "Scarica Partite", Size = new Size(140, 34) };
this.btnDownload.Click += new EventHandler(this.btnDownload_Click);
this.btnExportCsv = new ModernButton
{
Text = "Esporta CSV",
AccentColor = ModernTheme.SuccessColor,
Size = new Size(120, 34),
Enabled = false
};
this.btnExportCsv.Click += new EventHandler(this.btnExportCsv_Click);
toolbarFb.Controls.Add(this.dateTimePicker);
toolbarFb.Controls.Add(this.btnDownload);
toolbarFb.Controls.Add(this.btnExportCsv);
// -- status --
var statusFb = new Panel
{
Dock = DockStyle.Top,
Height = 30,
BackColor = ModernTheme.ContentBackground
};
this.progressBarFootball = new ModernProgressBar { Dock = DockStyle.Top };
this.labelStatusFootball = new Label
{
Text = "Pronto",
Font = ModernTheme.SmallFont,
ForeColor = ModernTheme.TextSecondary,
BackColor = ModernTheme.ContentBackground,
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleLeft
};
statusFb.Controls.Add(this.labelStatusFootball);
statusFb.Controls.Add(this.progressBarFootball);
// -- grid --
this.dataGridViewFootball = new DataGridView { Dock = DockStyle.Fill };
((System.ComponentModel.ISupportInitialize)this.dataGridViewFootball).BeginInit();
ModernTheme.StyleDataGridView(this.dataGridViewFootball);
this.pageFootball.Controls.Add(this.dataGridViewFootball);
this.pageFootball.Controls.Add(statusFb);
this.pageFootball.Controls.Add(toolbarFb);
// ============================================================
// PAGE: CORSE CAVALLI
// ============================================================
this.pageHorseRacing = new Panel
{
Dock = DockStyle.Fill,
Padding = new Padding(24, 16, 24, 16),
BackColor = ModernTheme.ContentBackground,
Visible = false
};
// -- toolbar racing --
var toolbarRacing = new Panel
{
Dock = DockStyle.Top,
Height = 48,
BackColor = ModernTheme.ContentBackground,
Name = "toolbarRacing"
};
this.cmbRacingDay = new ComboBox
{
DropDownStyle = ComboBoxStyle.DropDownList,
Font = ModernTheme.BodyFont,
BackColor = ModernTheme.InputBackground,
ForeColor = ModernTheme.InputText,
FlatStyle = FlatStyle.Flat,
Size = new Size(160, 28)
};
this.cmbRacingDay.Items.AddRange(new object[] { "Oggi", "Domani" });
this.cmbRacingDay.SelectedIndex = 0;
this.btnDownloadRacing = new ModernButton { Text = "Scarica Corse", Size = new Size(140, 34) };
this.btnDownloadRacing.Click += new EventHandler(this.btnDownloadRacing_Click);
this.btnExportRacingCsv = new ModernButton
{
Text = "Esporta CSV",
AccentColor = ModernTheme.SuccessColor,
Size = new Size(120, 34),
Enabled = false
};
this.btnExportRacingCsv.Click += new EventHandler(this.btnExportRacingCsv_Click);
toolbarRacing.Controls.Add(this.cmbRacingDay);
toolbarRacing.Controls.Add(this.btnDownloadRacing);
toolbarRacing.Controls.Add(this.btnExportRacingCsv);
// -- status racing --
var statusRacing = new Panel
{
Dock = DockStyle.Top,
Height = 30,
BackColor = ModernTheme.ContentBackground
};
this.progressBarRacing = new ModernProgressBar { Dock = DockStyle.Top };
this.labelStatusRacing = new Label
{
Text = "Pronto",
Font = ModernTheme.SmallFont,
ForeColor = ModernTheme.TextSecondary,
BackColor = ModernTheme.ContentBackground,
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleLeft
};
statusRacing.Controls.Add(this.labelStatusRacing);
statusRacing.Controls.Add(this.progressBarRacing);
// -- grid racing --
this.dataGridViewRacing = new DataGridView { Dock = DockStyle.Fill };
((System.ComponentModel.ISupportInitialize)this.dataGridViewRacing).BeginInit();
ModernTheme.StyleDataGridView(this.dataGridViewRacing);
this.pageHorseRacing.Controls.Add(this.dataGridViewRacing);
this.pageHorseRacing.Controls.Add(statusRacing);
this.pageHorseRacing.Controls.Add(toolbarRacing);
// ============================================================
// PAGE: IMPOSTAZIONI
// ============================================================
this.pageSettings = new Panel
{
Dock = DockStyle.Fill,
Padding = new Padding(24, 16, 24, 16),
BackColor = ModernTheme.ContentBackground,
Visible = false
};
var settingsInner = new Panel
{
Dock = DockStyle.Top,
Height = 400,
BackColor = ModernTheme.CardBackground,
Padding = new Padding(24)
};
this.lblApiKey = new Label
{
Text = "API Key (api-football)",
Font = ModernTheme.SubtitleFont,
ForeColor = ModernTheme.TextPrimary,
BackColor = ModernTheme.CardBackground,
AutoSize = true,
Location = new Point(24, 24)
};
this.txtApiKey = new TextBox
{
Font = ModernTheme.BodyFont,
BackColor = ModernTheme.InputBackground,
ForeColor = ModernTheme.InputText,
BorderStyle = BorderStyle.FixedSingle,
Location = new Point(24, 52),
Size = new Size(500, 28)
};
this.lblExportPath = new Label
{
Text = "Cartella esportazione CSV",
Font = ModernTheme.SubtitleFont,
ForeColor = ModernTheme.TextPrimary,
BackColor = ModernTheme.CardBackground,
AutoSize = true,
Location = new Point(24, 100)
};
this.txtExportPath = new TextBox
{
Font = ModernTheme.BodyFont,
BackColor = ModernTheme.InputBackground,
ForeColor = ModernTheme.InputText,
BorderStyle = BorderStyle.FixedSingle,
ReadOnly = true,
Location = new Point(24, 128),
Size = new Size(400, 28)
};
this.btnBrowseExport = new ModernButton
{
Text = "Sfoglia...",
IsPrimary = false,
Size = new Size(90, 30),
Location = new Point(434, 126)
};
this.btnBrowseExport.Click += new EventHandler(this.btnBrowseExport_Click);
// Racing API credentials
this.lblRacingUser = new Label
{
Text = "Racing API — Username",
Font = ModernTheme.SubtitleFont,
ForeColor = ModernTheme.TextPrimary,
BackColor = ModernTheme.CardBackground,
AutoSize = true,
Location = new Point(24, 176)
};
this.txtRacingUser = new TextBox
{
Font = ModernTheme.BodyFont,
BackColor = ModernTheme.InputBackground,
ForeColor = ModernTheme.InputText,
BorderStyle = BorderStyle.FixedSingle,
Location = new Point(24, 204),
Size = new Size(500, 28)
};
this.lblRacingPass = new Label
{
Text = "Racing API — Password",
Font = ModernTheme.SubtitleFont,
ForeColor = ModernTheme.TextPrimary,
BackColor = ModernTheme.CardBackground,
AutoSize = true,
Location = new Point(24, 248)
};
this.txtRacingPass = new TextBox
{
Font = ModernTheme.BodyFont,
BackColor = ModernTheme.InputBackground,
ForeColor = ModernTheme.InputText,
BorderStyle = BorderStyle.FixedSingle,
UseSystemPasswordChar = true,
Location = new Point(24, 276),
Size = new Size(500, 28)
};
this.btnSaveSettings = new ModernButton
{
Text = "Salva impostazioni",
AccentColor = ModernTheme.SuccessColor,
Size = new Size(170, 36),
Location = new Point(24, 330)
};
this.btnSaveSettings.Click += new EventHandler(this.btnSaveSettings_Click);
settingsInner.Controls.Add(this.btnSaveSettings);
settingsInner.Controls.Add(this.txtRacingPass);
settingsInner.Controls.Add(this.lblRacingPass);
settingsInner.Controls.Add(this.txtRacingUser);
settingsInner.Controls.Add(this.lblRacingUser);
settingsInner.Controls.Add(this.btnBrowseExport);
settingsInner.Controls.Add(this.txtExportPath);
settingsInner.Controls.Add(this.lblExportPath);
settingsInner.Controls.Add(this.txtApiKey);
settingsInner.Controls.Add(this.lblApiKey);
this.pageSettings.Controls.Add(settingsInner);
// ============================================================
// PAGE: INFO
// ============================================================
this.pageInfo = new Panel
{
Dock = DockStyle.Fill,
Padding = new Padding(24, 16, 24, 16),
BackColor = ModernTheme.ContentBackground,
Visible = false
};
var infoInner = new Panel
{
Dock = DockStyle.Top,
Height = 280,
BackColor = ModernTheme.CardBackground,
Padding = new Padding(32)
};
int infoY = 32;
var lblInfoTitle = new Label
{
Text = "Betting Predictor",
Font = new Font("Segoe UI", 20F, FontStyle.Bold),
ForeColor = ModernTheme.PrimaryColor,
BackColor = ModernTheme.CardBackground,
AutoSize = true,
Location = new Point(32, infoY)
};
infoY += 48;
var lblVersion = new Label
{
Text = "Versione 1.0.0",
Font = ModernTheme.SubtitleFont,
ForeColor = ModernTheme.TextSecondary,
BackColor = ModernTheme.CardBackground,
AutoSize = true,
Location = new Point(32, infoY)
};
infoY += 36;
var lblDesc = new Label
{
Text = "Applicazione per lo scaricamento e l'analisi di dati sportivi\n" +
"tramite API esterne. Supporta l'esportazione in CSV.\n\n" +
"Sviluppato con .NET Framework 4.8.1 | WinForms",
Font = ModernTheme.BodyFont,
ForeColor = ModernTheme.TextPrimary,
BackColor = ModernTheme.CardBackground,
AutoSize = true,
Location = new Point(32, infoY)
};
infoY += 90;
var lblCopy = new Label
{
Text = $"© {DateTime.Now.Year} — Tutti i diritti riservati",
Font = ModernTheme.SmallFont,
ForeColor = ModernTheme.TextSecondary,
BackColor = ModernTheme.CardBackground,
AutoSize = true,
Location = new Point(32, infoY)
};
infoInner.Controls.Add(lblCopy);
infoInner.Controls.Add(lblDesc);
infoInner.Controls.Add(lblVersion);
infoInner.Controls.Add(lblInfoTitle);
this.pageInfo.Controls.Add(infoInner);
// ============================================================
// ASSEMBLY
// ============================================================
// Le pagine vanno aggiunte PRIMA dell'header nel panelContent
// perché con Dock=Fill devono occupare lo spazio rimanente.
this.panelContent.Controls.Add(this.pageFootball);
this.panelContent.Controls.Add(this.pageHorseRacing);
this.panelContent.Controls.Add(this.pageSettings);
this.panelContent.Controls.Add(this.pageInfo);
this.panelContent.Controls.Add(this.panelHeader);
this.Controls.Add(this.panelContent);
this.Controls.Add(this.panelSidebar);
((System.ComponentModel.ISupportInitialize)this.dataGridViewFootball).EndInit();
((System.ComponentModel.ISupportInitialize)this.dataGridViewRacing).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
private System.ComponentModel.IContainer components = null;
private ProgressBar progressBar;
private Label labelProgress;
/// <summary>
/// Posiziona manualmente i controlli nelle toolbar
/// </summary>
private void LayoutToolbars()
{
// Football toolbar
if (dateTimePicker != null && dateTimePicker.Parent != null)
{
var tb = dateTimePicker.Parent;
int w = tb.ClientSize.Width;
int y = 7;
dateTimePicker.SetBounds(0, y, 200, 28);
btnExportCsv.Location = new Point(w - btnExportCsv.Width, y);
btnDownload.Location = new Point(btnExportCsv.Left - btnDownload.Width - 10, y);
}
// Horse Racing toolbar
if (cmbRacingDay != null && cmbRacingDay.Parent != null)
{
var tb = cmbRacingDay.Parent;
int w = tb.ClientSize.Width;
int y = 7;
cmbRacingDay.SetBounds(0, y, 160, 28);
btnExportRacingCsv.Location = new Point(w - btnExportRacingCsv.Width, y);
btnDownloadRacing.Location = new Point(btnExportRacingCsv.Left - btnDownloadRacing.Width - 10, y);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,664 @@
<Window x:Class="HorseRacingPredictor.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
Title="Betting Predictor" Width="1100" Height="680"
MinWidth="900" MinHeight="540"
WindowStartupLocation="CenterScreen"
Background="#1E1E2E"
Loaded="Window_Loaded">
<Window.Resources>
<!-- Inlined Global Styles (avoids relying on external resource resolution at runtime) -->
<!-- CATPPUCCIN MOCHA PALETTE (shared) -->
<Color x:Key="CBase">#1E1E2E</Color>
<Color x:Key="CMantle">#181825</Color>
<Color x:Key="CCrust">#11111B</Color>
<Color x:Key="CSurface0">#313244</Color>
<Color x:Key="CSurface1">#45475A</Color>
<Color x:Key="CSurface2">#585B70</Color>
<Color x:Key="COverlay0">#6C7086</Color>
<Color x:Key="CText">#CDD6F4</Color>
<Color x:Key="CSubtext0">#A6ADC8</Color>
<Color x:Key="CSubtext1">#BAC2DE</Color>
<Color x:Key="CBlue">#89B4FA</Color>
<Color x:Key="CGreen">#A6E3A1</Color>
<Color x:Key="CRed">#F38BA8</Color>
<Color x:Key="CPeach">#FAB387</Color>
<Color x:Key="CLavender">#B4BEFE</Color>
<SolidColorBrush x:Key="BrBase" Color="{StaticResource CBase}"/>
<SolidColorBrush x:Key="BrMantle" Color="{StaticResource CMantle}"/>
<SolidColorBrush x:Key="BrSurface0" Color="{StaticResource CSurface0}"/>
<SolidColorBrush x:Key="BrSurface1" Color="{StaticResource CSurface1}"/>
<SolidColorBrush x:Key="BrSurface2" Color="{StaticResource CSurface2}"/>
<SolidColorBrush x:Key="BrOverlay0" Color="{StaticResource COverlay0}"/>
<SolidColorBrush x:Key="BrText" Color="{StaticResource CText}"/>
<SolidColorBrush x:Key="BrSubtext0" Color="{StaticResource CSubtext0}"/>
<SolidColorBrush x:Key="BrBlue" Color="{StaticResource CBlue}"/>
<SolidColorBrush x:Key="BrGreen" Color="{StaticResource CGreen}"/>
<SolidColorBrush x:Key="BrRed" Color="{StaticResource CRed}"/>
<SolidColorBrush x:Key="BrPeach" Color="{StaticResource CPeach}"/>
<SolidColorBrush x:Key="BrLavender" Color="{StaticResource CLavender}"/>
<!-- Force ComboBox text to black for readability on light backgrounds -->
<Style TargetType="ComboBox">
<Setter Property="Foreground" Value="Black"/>
</Style>
<Style TargetType="ComboBoxItem">
<Setter Property="Foreground" Value="Black"/>
</Style>
<!-- Acrylic-like background (semi-transparent fallback) -->
<SolidColorBrush x:Key="AcrylicBackgroundBrush" Color="#0F000000"/>
<!-- Subtle shadow effect for elevation -->
<DropShadowEffect x:Key="SubtleDropShadow" BlurRadius="12" ShadowDepth="2" Color="#50000000"/>
<!-- NAV BUTTON STYLE (icon-only sidebar) -->
<Style x:Key="NavBtn" TargetType="RadioButton">
<Setter Property="Width" Value="48"/>
<Setter Property="Height" Value="48"/>
<Setter Property="Margin" Value="6,4"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Foreground" Value="{StaticResource BrSubtext0}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Grid>
<Border x:Name="Bg" CornerRadius="10" Background="{TemplateBinding Background}"/>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bg" Property="Background" Value="#28283A"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="Bg" Property="Background" Value="{StaticResource BrBlue}"/>
<Setter Property="Foreground" Value="#181825"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- ACCENT BUTTON -->
<Style x:Key="AccentBtn" TargetType="Button">
<Setter Property="Foreground" Value="#181825"/>
<Setter Property="FontFamily" Value="Segoe UI Semibold"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="Padding" Value="18,7"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="Bd" CornerRadius="8"
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="Opacity" Value="0.85"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Bd" Property="Opacity" Value="0.40"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- FLAT TEXTBOX -->
<Style x:Key="FlatTb" TargetType="TextBox">
<Setter Property="Background" Value="{StaticResource BrSurface0}"/>
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
<Setter Property="CaretBrush" Value="{StaticResource BrText}"/>
<Setter Property="BorderBrush" Value="{StaticResource BrSurface1}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="10,7"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Border x:Name="Bd" CornerRadius="6"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<ScrollViewer x:Name="PART_ContentHost"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource BrBlue}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- PASSWORD BOX -->
<Style x:Key="FlatPb" TargetType="PasswordBox">
<Setter Property="Background" Value="{StaticResource BrSurface0}"/>
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
<Setter Property="CaretBrush" Value="{StaticResource BrText}"/>
<Setter Property="BorderBrush" Value="{StaticResource BrSurface1}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="10,7"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="PasswordBox">
<Border x:Name="Bd" CornerRadius="6"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<ScrollViewer x:Name="PART_ContentHost"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource BrBlue}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- PROGRESS BAR -->
<Style x:Key="ModernPb" TargetType="ProgressBar">
<Setter Property="Height" Value="4"/>
<Setter Property="Background" Value="{StaticResource BrSurface0}"/>
<Setter Property="Foreground" Value="{StaticResource BrBlue}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ProgressBar">
<Grid>
<Border CornerRadius="2" Background="{TemplateBinding Background}"/>
<Border x:Name="PART_Indicator" CornerRadius="2"
Background="{TemplateBinding Foreground}"
HorizontalAlignment="Left"/>
<Border x:Name="PART_Track"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- DATAGRID -->
<Style x:Key="ModernDg" TargetType="DataGrid">
<Setter Property="Background" Value="#282A3A"/>
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="RowBackground" Value="#282A3A"/>
<Setter Property="AlternatingRowBackground" Value="#2D2F42"/>
<Setter Property="GridLinesVisibility" Value="Horizontal"/>
<Setter Property="HorizontalGridLinesBrush" Value="#37394E"/>
<Setter Property="HeadersVisibility" Value="Column"/>
<Setter Property="RowHeaderWidth" Value="0"/>
<Setter Property="AutoGenerateColumns" Value="True"/>
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="SelectionMode" Value="Single"/>
<Setter Property="CanUserAddRows" Value="False"/>
<Setter Property="CanUserDeleteRows" Value="False"/>
<Setter Property="CanUserResizeRows" Value="False"/>
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="FontSize" Value="13"/>
</Style>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Background" Value="#23243A"/>
<Setter Property="Foreground" Value="{StaticResource BrBlue}"/>
<Setter Property="FontFamily" Value="Segoe UI Semibold"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="Padding" Value="10,8"/>
<Setter Property="BorderBrush" Value="#37394E"/>
<Setter Property="BorderThickness" Value="0,0,0,1"/>
</Style>
<Style TargetType="DataGridCell">
<Setter Property="Padding" Value="8,6"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DataGridCell">
<Border Padding="{TemplateBinding Padding}" Background="{TemplateBinding Background}">
<ContentPresenter VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#3C3F58"/>
<Setter Property="Foreground" Value="{StaticResource BrBlue}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="DataGridRow">
<Setter Property="Margin" Value="0"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#3C3F58"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- Card style for lists / rows -->
<Style x:Key="CardStyle" TargetType="Border">
<Setter Property="CornerRadius" Value="8"/>
<Setter Property="Background" Value="#23232A"/>
<Setter Property="Padding" Value="12"/>
<Setter Property="Margin" Value="6"/>
</Style>
</Window.Resources>
<!-- ================================================================
MAIN LAYOUT : Sidebar | Content
================================================================ -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="64"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- ============= SIDEBAR ============= -->
<Border Grid.Column="0" Background="#181825">
<DockPanel LastChildFill="False">
<!-- App icon -->
<TextBlock DockPanel.Dock="Top" Text="BP"
FontFamily="Segoe UI Black" FontSize="18"
Foreground="{StaticResource BrBlue}"
HorizontalAlignment="Center" Margin="0,16,0,12"/>
<Border DockPanel.Dock="Top" Height="1" Background="#37394E" Margin="10,0"/>
<!-- Nav icons -->
<StackPanel DockPanel.Dock="Top" Margin="0,8,0,0">
<RadioButton x:Name="navFootball" Style="{StaticResource NavBtn}"
IsChecked="True" ToolTip="Calcio"
Checked="navFootball_Checked">
<TextBlock Text="&#xE8D6;" FontFamily="Segoe MDL2 Assets" FontSize="20"
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=RadioButton}}"/>
</RadioButton>
<RadioButton x:Name="navRacing" Style="{StaticResource NavBtn}"
ToolTip="Corse Cavalli"
Checked="navRacing_Checked">
<TextBlock Text="&#xE7C1;" FontFamily="Segoe MDL2 Assets" FontSize="20"
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=RadioButton}}"/>
</RadioButton>
<RadioButton x:Name="navVirtualFb" Style="{StaticResource NavBtn}"
ToolTip="Calcio Virtuale"
Checked="navVirtualFb_Checked">
<TextBlock Text="&#xE7FC;" FontFamily="Segoe MDL2 Assets" FontSize="20"
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=RadioButton}}"/>
</RadioButton>
<RadioButton x:Name="navSettings" Style="{StaticResource NavBtn}"
ToolTip="Impostazioni"
Checked="navSettings_Checked">
<TextBlock Text="&#xE713;" FontFamily="Segoe MDL2 Assets" FontSize="20"
Foreground="{Binding Foreground, RelativeSource={RelativeSource AncestorType=RadioButton}}"/>
</RadioButton>
<!-- Info tab removed -->
</StackPanel>
</DockPanel>
</Border>
<!-- ============= CONTENT ============= -->
<DockPanel Grid.Column="1">
<!-- Header -->
<Border DockPanel.Dock="Top" Height="52" Background="#181825">
<TextBlock x:Name="lblTitle" Text="Calcio"
FontFamily="Segoe UI Semibold" FontSize="20"
Foreground="{StaticResource BrText}"
VerticalAlignment="Center" Margin="24,0"/>
</Border>
<Border DockPanel.Dock="Top" Height="1" Background="#37394E"/>
<!-- Page host -->
<Grid Background="{StaticResource BrBase}">
<!-- ==== PAGE: FOOTBALL ==== -->
<Grid x:Name="pageFootball" Margin="24,16" Panel.ZIndex="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Toolbar -->
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,12" Panel.ZIndex="20">
<DatePicker x:Name="dpFootball" Width="160"
Background="{StaticResource BrSurface0}"
Foreground="{StaticResource BrText}"
FontSize="13" VerticalContentAlignment="Center" IsHitTestVisible="True"/>
<Button x:Name="btnDownloadFb" Content="Scarica Partite"
Style="{StaticResource AccentBtn}"
Background="{StaticResource BrBlue}" Margin="12,0,0,0"
Click="btnDownloadFb_Click" IsHitTestVisible="True"/>
<Button x:Name="btnExportFbCsv" Content="Esporta CSV"
Style="{StaticResource AccentBtn}"
Background="{StaticResource BrGreen}" Margin="8,0,0,0"
IsEnabled="False"
Click="btnExportFbCsv_Click" IsHitTestVisible="True"/>
</StackPanel>
<!-- Status -->
<StackPanel Grid.Row="1" Margin="0,0,0,8">
<ProgressBar x:Name="pbFootball" Style="{StaticResource ModernPb}" Margin="0,0,0,4"/>
<TextBlock x:Name="lblStatusFb" Text="Pronto"
FontSize="12" Foreground="{StaticResource BrSubtext0}"/>
</StackPanel>
<!-- Grid -->
<DataGrid x:Name="dgFootball" Grid.Row="2" Style="{StaticResource ModernDg}"/>
</Grid>
<!-- ==== PAGE: HORSE RACING ==== -->
<Grid x:Name="pageRacing" Margin="24,16" Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Source selector: API or CSV -->
<StackPanel Grid.Row="0" Margin="0,0,0,12">
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
<RadioButton x:Name="rbRcApi" Content="API" IsChecked="True" Foreground="{StaticResource BrText}" VerticalAlignment="Center" GroupName="RcSource" Checked="rbRcSource_Checked"/>
<RadioButton x:Name="rbRcCsv" Content="CSV (Punters)" Foreground="{StaticResource BrText}" VerticalAlignment="Center" Margin="16,0,0,0" GroupName="RcSource" Checked="rbRcSource_Checked"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<!-- API controls -->
<ComboBox x:Name="cmbDay" Width="140"
Background="{StaticResource BrSurface0}"
Foreground="{StaticResource BrText}"
FontSize="13" VerticalContentAlignment="Center"/>
<Button x:Name="btnDownloadRc" Content="Scarica Corse"
Style="{StaticResource AccentBtn}"
Background="{StaticResource BrBlue}" Margin="12,0,0,0"
Click="btnDownloadRc_Click"/>
<!-- CSV controls -->
<Button x:Name="btnBrowseCsvRc" Content="Seleziona cartella CSV..."
Style="{StaticResource AccentBtn}"
Background="{StaticResource BrBlue}" Margin="12,0,0,0"
Visibility="Collapsed"
Click="btnBrowseCsvRc_Click"/>
<!-- Export (always visible) -->
<Button x:Name="btnExportRcCsv" Content="Esporta"
Style="{StaticResource AccentBtn}"
Background="{StaticResource BrGreen}" Margin="8,0,0,0"
IsEnabled="False"
Click="btnExportRcCsv_Click"/>
</StackPanel>
</StackPanel>
<!-- Status -->
<StackPanel Grid.Row="1" Margin="0,0,0,8">
<ProgressBar x:Name="pbRacing" Style="{StaticResource ModernPb}" Margin="0,0,0,4"/>
<TextBlock x:Name="lblStatusRc" Text="Pronto"
FontSize="12" Foreground="{StaticResource BrSubtext0}"/>
</StackPanel>
<!-- Grid -->
<DataGrid x:Name="dgRacing" Grid.Row="2" Style="{StaticResource ModernDg}" AutoGeneratingColumn="dgRacing_AutoGeneratingColumn"/>
</Grid>
<!-- ==== PAGE: SETTINGS ==== -->
<ScrollViewer x:Name="pageSettings" Visibility="Collapsed"
VerticalScrollBarVisibility="Auto" Padding="24,16">
<StackPanel MaxWidth="600" HorizontalAlignment="Left">
<TextBlock Text="Calcio &#x2014; API" FontSize="16" FontFamily="Segoe UI Semibold"
Foreground="{StaticResource BrBlue}" Margin="0,0,0,8"/>
<Border Background="#282A3A" CornerRadius="10" Padding="20" Margin="0,0,0,20">
<StackPanel>
<TextBlock Text="API Key (api-football)" Foreground="{StaticResource BrText}"
FontSize="13" Margin="0,0,0,6"/>
<TextBox x:Name="txtApiKey" Style="{StaticResource FlatTb}"/>
<!-- File name now built from prefix + date + suffix -->
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Text="Prefisso" Foreground="{StaticResource BrText}" FontSize="13" Margin="0,0,0,6"/>
<TextBox x:Name="txtFbPrefix" Style="{StaticResource FlatTb}"/>
</StackPanel>
<StackPanel Grid.Column="1" Margin="8,0,0,0">
<TextBlock Text="Suffisso" Foreground="{StaticResource BrText}" FontSize="13" Margin="0,0,0,6"/>
<TextBox x:Name="txtFbSuffix" Style="{StaticResource FlatTb}"/>
</StackPanel>
</Grid>
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
<CheckBox x:Name="chkFbIncludeDate" IsChecked="True" Foreground="{StaticResource BrText}" VerticalAlignment="Center">Includi data</CheckBox>
<ComboBox x:Name="cmbFbDateFormat" Width="180" Margin="12,0,0,0" Background="{StaticResource BrSurface0}" Foreground="Black">
<ComboBoxItem Content="yyyy-MM-dd"/>
<ComboBoxItem Content="dd-MM-yyyy"/>
<ComboBoxItem Content="yyyyMMdd"/>
<ComboBoxItem Content="ddMMyyyy"/>
<ComboBoxItem Content="yyyy-MM-dd_HH-mm"/>
</ComboBox>
</StackPanel>
<TextBlock Text="Anteprima nome file:" Foreground="{StaticResource BrSubtext0}" Margin="0,8,0,4"/>
<TextBlock x:Name="txtFbPreview" FontFamily="Segoe UI" FontSize="13" Foreground="{StaticResource BrText}" />
<TextBlock Text="Formato esportazione" Foreground="{StaticResource BrText}" FontSize="13" Margin="0,12,0,6"/>
<ComboBox x:Name="cmbFbFormat" Width="160" SelectedIndex="0" Background="{StaticResource BrSurface0}" Foreground="Black">
<ComboBoxItem Content="CSV"/>
<ComboBoxItem Content="JSON"/>
<ComboBoxItem Content="XML"/>
</ComboBox>
<TextBlock Text="Cartella esportazione CSV" Foreground="{StaticResource BrText}"
FontSize="13" Margin="0,14,0,6"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="txtFbExportPath" Style="{StaticResource FlatTb}" IsReadOnly="True"/>
<Button Grid.Column="1" Content="Sfoglia..." Margin="8,0,0,0"
Style="{StaticResource AccentBtn}" Background="{StaticResource BrSurface2}"
Foreground="{StaticResource BrText}"
Click="btnBrowseFbExport_Click"/>
</Grid>
</StackPanel>
</Border>
<!-- HORSE RACING API -->
<TextBlock Text="Corse Cavalli &#x2014; API" FontSize="16" FontFamily="Segoe UI Semibold"
Foreground="{StaticResource BrBlue}" Margin="0,32,0,8"/>
<Border Background="#282A3A" CornerRadius="10" Padding="20" Margin="0,0,0,20">
<StackPanel>
<TextBlock Text="Username" Foreground="{StaticResource BrText}"
FontSize="13" Margin="0,0,0,6"/>
<TextBox x:Name="txtRacingUser" Style="{StaticResource FlatTb}"/>
<!-- File name now built from prefix + date + suffix -->
<Grid Margin="0,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Text="Prefisso" Foreground="{StaticResource BrText}" FontSize="13" Margin="0,0,0,6"/>
<TextBox x:Name="txtRcPrefix" Style="{StaticResource FlatTb}"/>
</StackPanel>
<StackPanel Grid.Column="1" Margin="8,0,0,0">
<TextBlock Text="Suffisso" Foreground="{StaticResource BrText}" FontSize="13" Margin="0,0,0,6"/>
<TextBox x:Name="txtRcSuffix" Style="{StaticResource FlatTb}"/>
</StackPanel>
</Grid>
<StackPanel Orientation="Horizontal" Margin="0,8,0,0">
<CheckBox x:Name="chkRcIncludeDate" IsChecked="True" Foreground="{StaticResource BrText}" VerticalAlignment="Center">Includi data</CheckBox>
<ComboBox x:Name="cmbRcDateFormat" Width="180" Margin="12,0,0,0" Background="{StaticResource BrSurface0}" Foreground="Black">
<ComboBoxItem Content="yyyy-MM-dd"/>
<ComboBoxItem Content="dd-MM-yyyy"/>
<ComboBoxItem Content="yyyyMMdd"/>
<ComboBoxItem Content="ddMMyyyy"/>
<ComboBoxItem Content="yyyy-MM-dd_HH-mm"/>
</ComboBox>
</StackPanel>
<TextBlock Text="Anteprima nome file:" Foreground="{StaticResource BrSubtext0}" Margin="0,8,0,4"/>
<TextBlock x:Name="txtRcPreview" FontFamily="Segoe UI" FontSize="13" Foreground="{StaticResource BrText}" />
<TextBlock Text="Formato esportazione" Foreground="{StaticResource BrText}" FontSize="13" Margin="0,12,0,6"/>
<ComboBox x:Name="cmbRcFormat" Width="160" SelectedIndex="0" Background="{StaticResource BrSurface0}" Foreground="Black">
<ComboBoxItem Content="CSV"/>
<ComboBoxItem Content="JSON"/>
<ComboBoxItem Content="XML"/>
</ComboBox>
<TextBlock Text="Password" Foreground="{StaticResource BrText}"
FontSize="13" Margin="0,14,0,6"/>
<PasswordBox x:Name="txtRacingPass" Style="{StaticResource FlatPb}"/>
<TextBlock Text="Cartella esportazione CSV" Foreground="{StaticResource BrText}"
FontSize="13" Margin="0,14,0,6"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="txtRcExportPath" Style="{StaticResource FlatTb}" IsReadOnly="True"/>
<Button Grid.Column="1" Content="Sfoglia..." Margin="8,0,0,0"
Style="{StaticResource AccentBtn}" Background="{StaticResource BrSurface2}"
Foreground="{StaticResource BrText}"
Click="btnBrowseRcExport_Click"/>
</Grid>
</StackPanel>
</Border>
<!-- SAVE -->
<Button x:Name="btnSaveSettings" Content="Salva Impostazioni"
Style="{StaticResource AccentBtn}"
Background="{StaticResource BrGreen}"
HorizontalAlignment="Left" Margin="0,4,0,24"
Click="btnSaveSettings_Click"/>
</StackPanel>
</ScrollViewer>
<!-- ==== PAGE: VIRTUAL FOOTBALL ==== -->
<Grid x:Name="pageVirtualFb" Margin="0" Visibility="Collapsed">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="400"/>
<ColumnDefinition Width="360"/>
</Grid.ColumnDefinitions>
<!-- LEFT: WebBrowser -->
<Border Grid.Column="0" Background="#181825" Margin="8">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Margin="8,8,8,4">
<Button x:Name="btnVfbNavigate" Content="Vai"
Style="{StaticResource AccentBtn}" Background="{StaticResource BrBlue}"
Margin="0,0,8,0" Click="btnVfbNavigate_Click"/>
<TextBox x:Name="txtVfbUrl" Style="{StaticResource FlatTb}"
Text="https://www.bet365.it/#/IP/B151"
Width="500" VerticalContentAlignment="Center"/>
<Button x:Name="btnVfbRefresh" Content="&#xE72C;"
FontFamily="Segoe MDL2 Assets" FontSize="14"
Style="{StaticResource AccentBtn}" Background="{StaticResource BrSurface2}"
Foreground="{StaticResource BrText}" Margin="8,0,0,0"
Click="btnVfbRefresh_Click" ToolTip="Aggiorna"/>
</StackPanel>
<wv2:WebView2 x:Name="wbVirtualFb" Margin="8,0,8,8"/>
</DockPanel>
</Border>
<!-- RIGHT: Results Panel -->
<Border Grid.Column="1" Background="#282A3A" CornerRadius="10" Margin="0,8,8,8" Padding="12">
<DockPanel>
<TextBlock DockPanel.Dock="Top" Text="Ultimi Risultati"
FontFamily="Segoe UI Semibold" FontSize="16"
Foreground="{StaticResource BrBlue}" Margin="0,0,0,8"/>
<!-- Add result form -->
<Border DockPanel.Dock="Top" Background="#313244" CornerRadius="8" Padding="10" Margin="0,0,0,8">
<StackPanel>
<TextBlock Text="Aggiungi Risultato" FontSize="13"
Foreground="{StaticResource BrSubtext0}" Margin="0,0,0,6"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="txtVfbHome" Style="{StaticResource FlatTb}"
Text="Casa" FontSize="12"/>
<TextBox x:Name="txtVfbHomeGoals" Grid.Column="1" Style="{StaticResource FlatTb}"
Text="0" FontSize="12" TextAlignment="Center" Margin="4,0"/>
<TextBlock Grid.Column="2" Text="-" Foreground="{StaticResource BrText}"
FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBox x:Name="txtVfbAwayGoals" Grid.Column="3" Style="{StaticResource FlatTb}"
Text="0" FontSize="12" TextAlignment="Center" Margin="4,0"/>
<TextBox x:Name="txtVfbAway" Grid.Column="4" Style="{StaticResource FlatTb}"
Text="Ospite" FontSize="12"/>
</Grid>
<Button x:Name="btnVfbAddResult" Content="Aggiungi"
Style="{StaticResource AccentBtn}" Background="{StaticResource BrBlue}"
HorizontalAlignment="Left" Margin="0,8,0,0"
Click="btnVfbAddResult_Click"/>
</StackPanel>
</Border>
<!-- Suggested bet -->
<Border DockPanel.Dock="Top" Background="#313244" CornerRadius="8" Padding="10" Margin="0,0,0,8">
<StackPanel>
<TextBlock Text="Puntata Consigliata" FontSize="13"
FontFamily="Segoe UI Semibold" Foreground="{StaticResource BrPeach}" Margin="0,0,0,4"/>
<TextBlock x:Name="lblVfbSuggestion" Text="Inserisci almeno 5 risultati"
FontSize="12" Foreground="{StaticResource BrText}" TextWrapping="Wrap"/>
</StackPanel>
</Border>
<!-- Stats -->
<Border DockPanel.Dock="Top" Background="#313244" CornerRadius="8" Padding="10" Margin="0,0,0,8">
<StackPanel>
<TextBlock Text="Statistiche" FontSize="13"
FontFamily="Segoe UI Semibold" Foreground="{StaticResource BrLavender}" Margin="0,0,0,4"/>
<TextBlock x:Name="lblVfbStats" Text="Nessun dato"
FontSize="12" Foreground="{StaticResource BrText}" TextWrapping="Wrap"/>
</StackPanel>
</Border>
<!-- Results list -->
<ListBox x:Name="lbVfbResults" Background="Transparent" BorderThickness="0"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemTemplate>
<DataTemplate>
<Border CornerRadius="6" Padding="10,6" Margin="0,2"
Background="{Binding RowColor}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Time}"
FontSize="11" Foreground="#A6ADC8"
VerticalAlignment="Center" Margin="0,0,8,0"/>
<TextBlock Grid.Column="1" FontSize="12" VerticalAlignment="Center">
<Run Text="{Binding Home, Mode=OneWay}" Foreground="#CDD6F4"/>
<Run Text=" "/>
<Run Text="{Binding Score, Mode=OneWay}" Foreground="White" FontFamily="Segoe UI Semibold"/>
<Run Text=" "/>
<Run Text="{Binding Away, Mode=OneWay}" Foreground="#CDD6F4"/>
</TextBlock>
<TextBlock Grid.Column="2" Text="{Binding Outcome}"
FontSize="12" FontFamily="Segoe UI Semibold"
Foreground="White" VerticalAlignment="Center" Margin="8,0,0,0"/>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</Border>
</Grid>
<!-- Info page removed -->
</Grid>
</DockPanel>
</Grid>
</Window>

File diff suppressed because it is too large Load Diff

View File

@@ -1,88 +0,0 @@
/****** Object: StoredProcedure [dbo].[GetFavoriteHorses] Script Date: 17/07/2025 09:09:35 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[GetFavoriteHorses]
@Date DATETIME = NULL,
@Meeting NVARCHAR(100) = NULL,
@RaceNumber INT = NULL,
@TopN INT = 3,
@Weight_HandicapRating DECIMAL(10,2) = 1,
@Weight_CareerWins DECIMAL(10,2) = 1,
@Weight_CareerStrikeRate DECIMAL(10,2) = 1,
@Weight_JockeyLast100StrikeRate DECIMAL(10,2) = 1,
@Weight_TrainerLast100StrikeRate DECIMAL(10,2) = 1,
@Weight_BestFixedOdds DECIMAL(10,2) = 1,
@Weight_Age DECIMAL(10,2) = 1,
@Weight_CareerPlacings DECIMAL(10,2) = 1,
@Weight_DryTrackStrikeRate DECIMAL(10,2) = 1,
@Weight_ThisTrackStrikeRate DECIMAL(10,2) = 1,
@Weight_LastStartFinishPosition DECIMAL(10,2) = 1,
@Weight_WeightCarried DECIMAL(10,2) = 1
AS
BEGIN
SET NOCOUNT ON;
-- Crea tabella temporanea per i risultati
CREATE TABLE #Results (
DataCorsa NVARCHAR(10),
LuogoCorsa NVARCHAR(100),
NumeroCorsa INT,
NomeCavallo NVARCHAR(100),
Punteggio DECIMAL(18,4),
RisultatoFinale NVARCHAR(50),
PosizioneArrivo INT,
PrevistoVincente BIT
);
WITH Scoring AS (
SELECT
Data,
Meeting,
Race,
HorseName,
-- Calcolo del punteggio pesato con le nuove statistiche
(
ISNULL(HandicapRating,0) * @Weight_HandicapRating +
ISNULL(CareerWins,0) * @Weight_CareerWins +
ISNULL(CareerStrikeRate,0) * @Weight_CareerStrikeRate +
ISNULL(JockeyLast100StrikeRate,0) * @Weight_JockeyLast100StrikeRate +
ISNULL(TrainerLast100StrikeRate,0) * @Weight_TrainerLast100StrikeRate +
ISNULL(BestFixedOdds,0) * @Weight_BestFixedOdds +
ISNULL(Age,0) * @Weight_Age +
ISNULL(CareerPlacings,0) * @Weight_CareerPlacings +
ISNULL(DryTrackStrikeRate,0) * @Weight_DryTrackStrikeRate +
ISNULL(ThisTrackStrikeRate,0) * @Weight_ThisTrackStrikeRate +
-- Per LastStartFinishPosition, valori più bassi sono migliori, quindi invertiamo il segno
(CASE WHEN TRY_CAST(LastStartFinishPosition AS INT) > 0 THEN (1.0 / TRY_CAST(LastStartFinishPosition AS FLOAT)) ELSE 0 END) * @Weight_LastStartFinishPosition +
ISNULL(WeightCarried,0) * @Weight_WeightCarried
) AS Score,
FinishResult
FROM Races
WHERE (FinishResult IS NOT NULL AND FinishResult <> '')
),
Ranked AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY Data, Meeting, Race ORDER BY Score DESC) AS RankByScore
FROM Scoring
)
INSERT INTO #Results
SELECT
CONVERT(VARCHAR(10), Data, 120),
Meeting,
Race,
HorseName,
Score,
FinishResult,
CASE WHEN ISNUMERIC(FinishResult) = 1 THEN CAST(FinishResult AS INT) ELSE NULL END,
CASE WHEN RankByScore = 1 THEN 1 ELSE 0 END
FROM Ranked
WHERE RankByScore = 1;
SELECT * FROM #Results;
DROP TABLE #Results;
END

View File

@@ -1,27 +0,0 @@
/****** Object: View [dbo].[GetValidHorses] Script Date: 17/07/2025 09:10:07 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE VIEW [dbo].[GetValidHorses]
AS
SELECT Data, Meeting, Race, Num, HorseName, Age, Gender, HandicapRating, CareerRuns, CareerWins, CareerStrikeRate, CareerROI, CareerPlacings, CareerPlaceStrikeRate, DryTrackRuns, DryTrackWins, DryTrackStrikeRate,
DryTrackROI, WetTrackRuns, WetTrackWins, WetTrackStrikeRate, WetTrackROI, AveragePrizeMoney, CareerPrizeMoney, BestFixedOdds, BetEasyOdds, Weight, WeightCarried, Barrier, PrizeMoney, ThisTrackRuns,
ThisTrackWins, ThisTrackStrikeRate, ThisTrackROI, ThisTrackPlaces, ThisTrackPlaceStrikeRate, ThisDistanceRuns, ThisDistanceWins, ThisDistanceStrikeRate, ThisDistanceROI, ThisDistancePlaces,
ThisDistancePlaceStrikeRate, ThisTrackDistanceRuns, ThisTrackDistanceWins, ThisTrackDistanceStrikeRate, ThisTrackDistanceROI, ThisTrackDistancePlaces, ThisTrackDistancePlaceStrikeRate, ThisConditionRuns,
ThisConditionWins, ThisConditionStrikeRate, ThisConditionROI, ThisConditionPlaces, ThisConditionPlaceStrikeRate, Jockey, Apprentice, JockeyWeightClaim, JockeyLast100HorseEarnings, JockeyLast100AvgHorseEarnings,
JockeyLast100Starts, JockeyLast100Wins, JockeyLast100StrikeRate, JockeyLast100ROI, JockeyLast100Places, JockeyLast100PlaceStrikeRate, Jockey12MonthHorseEarnings, Jockey12MonthAvgHorseEarnings,
Jockey12MonthsStarts, Jockey12MonthsWins, Jockey12MonthsStrikeRate, Jockey12MonthsROI, Jockey12MonthsPlaces, Jockey12MonthsPlaceStrikeRate, JockeyThisSeasonHorseEarnings,
JockeyThisSeasonAvgHorseEarnings, JockeyThisSeasonStarts, JockeyThisSeasonWins, JockeyThisSeasonStrikeRate, JockeyThisSeasonROI, JockeyThisSeasonPlaces, JockeyThisSeasonPlaceStrikeRate,
JockeyLastSeasonHorseEarnings, JockeyLastSeasonAvgHorseEarnings, JockeyLastSeasonStarts, JockeyLastSeasonWins, JockeyLastSeasonStrikeRate, JockeyLastSeasonROI, JockeyLastSeasonPlaces,
JockeyLastSeasonPlaceStrikeRate, Trainer, TrainerLast100HorseEarnings, TrainerLast100AvgHorseEarnings, TrainerLast100Starts, TrainerLast100Wins, TrainerLast100StrikeRate, TrainerLast100ROI, TrainerLast100Places,
TrainerLast100PlaceStrikeRate, Trainer12MonthHorseEarnings, Trainer12MonthAvgHorseEarnings, Trainer12MonthsStarts, Trainer12MonthsWins, Trainer12MonthsStrikeRate, Trainer12MonthsROI, Trainer12MonthsPlaces,
Trainer12MonthsPlaceStrikeRate, TrainerThisSeasonHorseEarnings, TrainerThisSeasonAvgHorseEarnings, TrainerThisSeasonStarts, TrainerThisSeasonWins, TrainerThisSeasonStrikeRate, TrainerThisSeasonROI,
TrainerThisSeasonPlaces, TrainerThisSeasonPlaceStrikeRate, TrainerLastSeasonHorseEarnings, TrainerLastSeasonAvgHorseEarnings, TrainerLastSeasonStarts, TrainerLastSeasonWins, TrainerLastSeasonStrikeRate,
TrainerLastSeasonROI, TrainerLastSeasonPlaces, TrainerLastSeasonPlaceStrikeRate, LastStartFinishPosition, LastStartMargin, LastStartDistance, LastStartDistanceChange, LastStartPrizeMoney, FinishResult
FROM dbo.Races
WHERE (FinishResult <> '') OR
(FinishResult IS NOT NULL)
GO

View File

@@ -1,20 +0,0 @@
CREATE TABLE [dbo].[PredictionScanResults] (
ScanId INT IDENTITY(1,1) PRIMARY KEY,
SessionId UNIQUEIDENTIFIER NOT NULL,
ScanDate DATETIME NOT NULL DEFAULT GETDATE(),
Weight_HandicapRating DECIMAL(10,2) NOT NULL,
Weight_CareerWins DECIMAL(10,2) NOT NULL,
Weight_CareerStrikeRate DECIMAL(10,2) NOT NULL,
Weight_JockeyLast100StrikeRate DECIMAL(10,2) NOT NULL,
Weight_TrainerLast100StrikeRate DECIMAL(10,2) NOT NULL,
Weight_BestFixedOdds DECIMAL(10,2) NOT NULL,
Weight_Age DECIMAL(10,2) NOT NULL,
Weight_CareerPlacings DECIMAL(10,2) NOT NULL,
Weight_DryTrackStrikeRate DECIMAL(10,2) NOT NULL,
Weight_ThisTrackStrikeRate DECIMAL(10,2) NOT NULL,
Weight_LastStartFinishPosition DECIMAL(10,2) NOT NULL,
Weight_WeightCarried DECIMAL(10,2) NOT NULL,
TotalRaces INT NOT NULL,
CorrectPredictions INT NOT NULL,
WinPercentage DECIMAL(5,2) NOT NULL
)

View File

@@ -1,152 +0,0 @@
/****** Object: Table [dbo].[Races] Script Date: 16/07/2025 23:27:12 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[Races](
[Data] [datetime] NOT NULL,
[Meeting] [nvarchar](100) NOT NULL,
[Race] [int] NOT NULL,
[Num] [int] NOT NULL,
[FileName] [nvarchar](255) NULL,
[HorseName] [nvarchar](100) NULL,
[Age] [int] NULL,
[Gender] [nvarchar](10) NULL,
[HandicapRating] [decimal](10, 2) NULL,
[CareerRuns] [int] NULL,
[CareerWins] [int] NULL,
[CareerStrikeRate] [decimal](10, 2) NULL,
[CareerROI] [decimal](10, 2) NULL,
[CareerPlacings] [int] NULL,
[CareerPlaceStrikeRate] [decimal](10, 2) NULL,
[DryTrackRuns] [int] NULL,
[DryTrackWins] [int] NULL,
[DryTrackStrikeRate] [decimal](10, 2) NULL,
[DryTrackROI] [decimal](10, 2) NULL,
[WetTrackRuns] [int] NULL,
[WetTrackWins] [int] NULL,
[WetTrackStrikeRate] [decimal](10, 2) NULL,
[WetTrackROI] [decimal](10, 2) NULL,
[AveragePrizeMoney] [decimal](15, 2) NULL,
[CareerPrizeMoney] [decimal](15, 2) NULL,
[BestFixedOdds] [decimal](10, 2) NULL,
[BetEasyOdds] [decimal](10, 2) NULL,
[Weight] [decimal](10, 2) NULL,
[WeightCarried] [decimal](10, 2) NULL,
[Barrier] [nvarchar](10) NULL,
[PrizeMoney] [decimal](15, 2) NULL,
[ThisTrackRuns] [int] NULL,
[ThisTrackWins] [int] NULL,
[ThisTrackStrikeRate] [decimal](10, 2) NULL,
[ThisTrackROI] [decimal](10, 2) NULL,
[ThisTrackPlaces] [int] NULL,
[ThisTrackPlaceStrikeRate] [decimal](10, 2) NULL,
[ThisDistanceRuns] [int] NULL,
[ThisDistanceWins] [int] NULL,
[ThisDistanceStrikeRate] [decimal](10, 2) NULL,
[ThisDistanceROI] [decimal](10, 2) NULL,
[ThisDistancePlaces] [int] NULL,
[ThisDistancePlaceStrikeRate] [decimal](10, 2) NULL,
[ThisTrackDistanceRuns] [int] NULL,
[ThisTrackDistanceWins] [int] NULL,
[ThisTrackDistanceStrikeRate] [decimal](10, 2) NULL,
[ThisTrackDistanceROI] [decimal](10, 2) NULL,
[ThisTrackDistancePlaces] [int] NULL,
[ThisTrackDistancePlaceStrikeRate] [decimal](10, 2) NULL,
[ThisConditionRuns] [int] NULL,
[ThisConditionWins] [int] NULL,
[ThisConditionStrikeRate] [decimal](10, 2) NULL,
[ThisConditionROI] [decimal](10, 2) NULL,
[ThisConditionPlaces] [int] NULL,
[ThisConditionPlaceStrikeRate] [decimal](10, 2) NULL,
[Jockey] [nvarchar](100) NULL,
[Apprentice] [nvarchar](10) NULL,
[JockeyWeightClaim] [decimal](10, 2) NULL,
[JockeyLast100HorseEarnings] [decimal](15, 2) NULL,
[JockeyLast100AvgHorseEarnings] [decimal](15, 2) NULL,
[JockeyLast100Starts] [int] NULL,
[JockeyLast100Wins] [int] NULL,
[JockeyLast100StrikeRate] [decimal](10, 2) NULL,
[JockeyLast100ROI] [decimal](10, 2) NULL,
[JockeyLast100Places] [int] NULL,
[JockeyLast100PlaceStrikeRate] [decimal](10, 2) NULL,
[Jockey12MonthHorseEarnings] [decimal](15, 2) NULL,
[Jockey12MonthAvgHorseEarnings] [decimal](15, 2) NULL,
[Jockey12MonthsStarts] [int] NULL,
[Jockey12MonthsWins] [int] NULL,
[Jockey12MonthsStrikeRate] [decimal](10, 2) NULL,
[Jockey12MonthsROI] [decimal](10, 2) NULL,
[Jockey12MonthsPlaces] [int] NULL,
[Jockey12MonthsPlaceStrikeRate] [decimal](10, 2) NULL,
[JockeyThisSeasonHorseEarnings] [decimal](15, 2) NULL,
[JockeyThisSeasonAvgHorseEarnings] [decimal](15, 2) NULL,
[JockeyThisSeasonStarts] [int] NULL,
[JockeyThisSeasonWins] [int] NULL,
[JockeyThisSeasonStrikeRate] [decimal](10, 2) NULL,
[JockeyThisSeasonROI] [decimal](10, 2) NULL,
[JockeyThisSeasonPlaces] [int] NULL,
[JockeyThisSeasonPlaceStrikeRate] [decimal](10, 2) NULL,
[JockeyLastSeasonHorseEarnings] [decimal](15, 2) NULL,
[JockeyLastSeasonAvgHorseEarnings] [decimal](15, 2) NULL,
[JockeyLastSeasonStarts] [int] NULL,
[JockeyLastSeasonWins] [int] NULL,
[JockeyLastSeasonStrikeRate] [decimal](10, 2) NULL,
[JockeyLastSeasonROI] [decimal](10, 2) NULL,
[JockeyLastSeasonPlaces] [int] NULL,
[JockeyLastSeasonPlaceStrikeRate] [decimal](10, 2) NULL,
[Trainer] [nvarchar](100) NULL,
[TrainerLast100HorseEarnings] [decimal](15, 2) NULL,
[TrainerLast100AvgHorseEarnings] [decimal](15, 2) NULL,
[TrainerLast100Starts] [int] NULL,
[TrainerLast100Wins] [int] NULL,
[TrainerLast100StrikeRate] [decimal](10, 2) NULL,
[TrainerLast100ROI] [decimal](10, 2) NULL,
[TrainerLast100Places] [int] NULL,
[TrainerLast100PlaceStrikeRate] [decimal](10, 2) NULL,
[Trainer12MonthHorseEarnings] [decimal](15, 2) NULL,
[Trainer12MonthAvgHorseEarnings] [decimal](15, 2) NULL,
[Trainer12MonthsStarts] [int] NULL,
[Trainer12MonthsWins] [int] NULL,
[Trainer12MonthsStrikeRate] [decimal](10, 2) NULL,
[Trainer12MonthsROI] [decimal](10, 2) NULL,
[Trainer12MonthsPlaces] [int] NULL,
[Trainer12MonthsPlaceStrikeRate] [decimal](10, 2) NULL,
[TrainerThisSeasonHorseEarnings] [decimal](15, 2) NULL,
[TrainerThisSeasonAvgHorseEarnings] [decimal](15, 2) NULL,
[TrainerThisSeasonStarts] [int] NULL,
[TrainerThisSeasonWins] [int] NULL,
[TrainerThisSeasonStrikeRate] [decimal](10, 2) NULL,
[TrainerThisSeasonROI] [decimal](10, 2) NULL,
[TrainerThisSeasonPlaces] [int] NULL,
[TrainerThisSeasonPlaceStrikeRate] [decimal](10, 2) NULL,
[TrainerLastSeasonHorseEarnings] [decimal](15, 2) NULL,
[TrainerLastSeasonAvgHorseEarnings] [decimal](15, 2) NULL,
[TrainerLastSeasonStarts] [int] NULL,
[TrainerLastSeasonWins] [int] NULL,
[TrainerLastSeasonStrikeRate] [decimal](10, 2) NULL,
[TrainerLastSeasonROI] [decimal](10, 2) NULL,
[TrainerLastSeasonPlaces] [int] NULL,
[TrainerLastSeasonPlaceStrikeRate] [decimal](10, 2) NULL,
[LastStartFinishPosition] [nvarchar](10) NULL,
[LastStartMargin] [decimal](10, 2) NULL,
[LastStartDistance] [decimal](10, 2) NULL,
[LastStartDistanceChange] [decimal](10, 2) NULL,
[LastStartPrizeMoney] [decimal](15, 2) NULL,
[FormGuideUrl] [nvarchar](500) NULL,
[HorseProfileUrl] [nvarchar](500) NULL,
[JockeyProfileUrl] [nvarchar](500) NULL,
[TrainerProfileUrl] [nvarchar](500) NULL,
[FinishResult] [nvarchar](50) NULL,
[Created] [datetime] NOT NULL,
[Updated] [datetime] NOT NULL,
CONSTRAINT [PK_Races] PRIMARY KEY CLUSTERED
(
[Data] ASC,
[Meeting] ASC,
[Race] ASC,
[Num] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, OPTIMIZE_FOR_SEQUENTIAL_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO

View File

@@ -1,228 +0,0 @@
ALTER PROCEDURE [dbo].[ScanFavoriteHorseWeights]
AS
BEGIN
SET NOCOUNT ON;
DECLARE @MinValue INT = 1
DECLARE @MaxValue INT = 3 -- Modifica per range più ampi
DECLARE @SessionId UNIQUEIDENTIFIER = NEWID()
DECLARE @ScanDate DATETIME = GETDATE()
-- Parametri
DECLARE @Weight_HandicapRating INT
DECLARE @Weight_CareerWins INT
DECLARE @Weight_CareerStrikeRate INT
DECLARE @Weight_JockeyLast100StrikeRate INT
DECLARE @Weight_TrainerLast100StrikeRate INT
DECLARE @Weight_BestFixedOdds INT
DECLARE @Weight_Age INT
DECLARE @Weight_CareerPlacings INT
DECLARE @Weight_DryTrackStrikeRate INT
DECLARE @Weight_ThisTrackStrikeRate INT
DECLARE @Weight_LastStartFinishPosition INT
DECLARE @Weight_WeightCarried INT
DECLARE @TotalRaces INT, @CorrectPredictions INT, @WinPercentage DECIMAL(5,2)
DECLARE @CombinazioniTotali BIGINT
DECLARE @CombinazioneCorrente BIGINT = 0
-- Carica tutti i cavalli validi in memoria
IF OBJECT_ID('tempdb..#AllHorses') IS NOT NULL DROP TABLE #AllHorses
SELECT
Data,
Meeting,
Race,
HorseName,
HandicapRating,
CareerWins,
CareerStrikeRate,
JockeyLast100StrikeRate,
TrainerLast100StrikeRate,
BestFixedOdds,
Age,
CareerPlacings,
DryTrackStrikeRate,
ThisTrackStrikeRate,
LastStartFinishPosition,
WeightCarried,
FinishResult
INTO #AllHorses
FROM Races
WHERE (FinishResult IS NOT NULL AND FinishResult <> '')
-- Conta il numero di corse distinte
SELECT @TotalRaces = COUNT(DISTINCT
CONVERT(NVARCHAR(20), Data, 120) + '_' + Meeting + '_' + CAST(Race AS NVARCHAR(10))
)
FROM #AllHorses
-- Calcola il numero totale di combinazioni
SET @CombinazioniTotali = POWER(@MaxValue - @MinValue + 1, 12)
-- Inizializza la sessione con un record "vuoto"
INSERT INTO PredictionScanResults (
SessionId, ScanDate,
Weight_HandicapRating, Weight_CareerWins, Weight_CareerStrikeRate, Weight_JockeyLast100StrikeRate,
Weight_TrainerLast100StrikeRate, Weight_BestFixedOdds, Weight_Age, Weight_CareerPlacings,
Weight_DryTrackStrikeRate, Weight_ThisTrackStrikeRate, Weight_LastStartFinishPosition, Weight_WeightCarried,
TotalRaces, CorrectPredictions, WinPercentage
) VALUES (
@SessionId, @ScanDate,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0
)
SET @Weight_HandicapRating = @MinValue
WHILE @Weight_HandicapRating <= @MaxValue
BEGIN
SET @Weight_CareerWins = @MinValue
WHILE @Weight_CareerWins <= @MaxValue
BEGIN
SET @Weight_CareerStrikeRate = @MinValue
WHILE @Weight_CareerStrikeRate <= @MaxValue
BEGIN
SET @Weight_JockeyLast100StrikeRate = @MinValue
WHILE @Weight_JockeyLast100StrikeRate <= @MaxValue
BEGIN
SET @Weight_TrainerLast100StrikeRate = @MinValue
WHILE @Weight_TrainerLast100StrikeRate <= @MaxValue
BEGIN
SET @Weight_BestFixedOdds = @MinValue
WHILE @Weight_BestFixedOdds <= @MaxValue
BEGIN
SET @Weight_Age = @MinValue
WHILE @Weight_Age <= @MaxValue
BEGIN
SET @Weight_CareerPlacings = @MinValue
WHILE @Weight_CareerPlacings <= @MaxValue
BEGIN
SET @Weight_DryTrackStrikeRate = @MinValue
WHILE @Weight_DryTrackStrikeRate <= @MaxValue
BEGIN
SET @Weight_ThisTrackStrikeRate = @MinValue
WHILE @Weight_ThisTrackStrikeRate <= @MaxValue
BEGIN
SET @Weight_LastStartFinishPosition = @MinValue
WHILE @Weight_LastStartFinishPosition <= @MaxValue
BEGIN
SET @Weight_WeightCarried = @MinValue
WHILE @Weight_WeightCarried <= @MaxValue
BEGIN
SET @CombinazioneCorrente = @CombinazioneCorrente + 1
-- Calcolo in memoria: trova il cavallo con punteggio massimo per ogni corsa
IF OBJECT_ID('tempdb..#Results') IS NOT NULL DROP TABLE #Results
SELECT
Data,
Meeting,
Race,
HorseName,
(
ISNULL(HandicapRating,0) * @Weight_HandicapRating +
ISNULL(CareerWins,0) * @Weight_CareerWins +
ISNULL(CareerStrikeRate,0) * @Weight_CareerStrikeRate +
ISNULL(JockeyLast100StrikeRate,0) * @Weight_JockeyLast100StrikeRate +
ISNULL(TrainerLast100StrikeRate,0) * @Weight_TrainerLast100StrikeRate +
ISNULL(BestFixedOdds,0) * @Weight_BestFixedOdds +
ISNULL(Age,0) * @Weight_Age +
ISNULL(CareerPlacings,0) * @Weight_CareerPlacings +
ISNULL(DryTrackStrikeRate,0) * @Weight_DryTrackStrikeRate +
ISNULL(ThisTrackStrikeRate,0) * @Weight_ThisTrackStrikeRate +
(CASE WHEN TRY_CAST(LastStartFinishPosition AS INT) > 0 THEN (1.0 / TRY_CAST(LastStartFinishPosition AS FLOAT)) ELSE 0 END) * @Weight_LastStartFinishPosition +
ISNULL(WeightCarried,0) * @Weight_WeightCarried
) AS Score,
FinishResult
INTO #Results
FROM #AllHorses
-- Seleziona il cavallo con punteggio massimo per ogni corsa
;WITH Ranked AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY Data, Meeting, Race ORDER BY Score DESC) AS RankByScore
FROM #Results
)
SELECT *
INTO #TopResults
FROM Ranked
WHERE RankByScore = 1
-- Calcola le statistiche
SELECT
@CorrectPredictions = SUM(CASE WHEN ISNUMERIC(FinishResult) = 1 AND CAST(FinishResult AS INT) = 1 THEN 1 ELSE 0 END)
FROM #TopResults
-- @TotalRaces già calcolato all'inizio
IF @TotalRaces > 0
SET @WinPercentage = CAST(@CorrectPredictions AS DECIMAL(5,2)) * 100.0 / @TotalRaces
ELSE
SET @WinPercentage = 0
-- Aggiorna solo se la percentuale è migliore
IF EXISTS (
SELECT 1 FROM PredictionScanResults
WHERE SessionId = @SessionId
AND WinPercentage < @WinPercentage
)
BEGIN
UPDATE PredictionScanResults
SET
Weight_HandicapRating = @Weight_HandicapRating,
Weight_CareerWins = @Weight_CareerWins,
Weight_CareerStrikeRate = @Weight_CareerStrikeRate,
Weight_JockeyLast100StrikeRate = @Weight_JockeyLast100StrikeRate,
Weight_TrainerLast100StrikeRate = @Weight_TrainerLast100StrikeRate,
Weight_BestFixedOdds = @Weight_BestFixedOdds,
Weight_Age = @Weight_Age,
Weight_CareerPlacings = @Weight_CareerPlacings,
Weight_DryTrackStrikeRate = @Weight_DryTrackStrikeRate,
Weight_ThisTrackStrikeRate = @Weight_ThisTrackStrikeRate,
Weight_LastStartFinishPosition = @Weight_LastStartFinishPosition,
Weight_WeightCarried = @Weight_WeightCarried,
TotalRaces = @TotalRaces,
CorrectPredictions = @CorrectPredictions,
WinPercentage = @WinPercentage
WHERE SessionId = @SessionId
END
-- Output avanzamento
PRINT CONCAT(
'Combinazione ', @CombinazioneCorrente, '/', @CombinazioniTotali,
' (', CAST((@CombinazioneCorrente * 100.0 / @CombinazioniTotali) AS DECIMAL(5,2)), '%) - ',
'Pesi: ', @Weight_HandicapRating, ',', @Weight_CareerWins, ',', @Weight_CareerStrikeRate, ',',
@Weight_JockeyLast100StrikeRate, ',', @Weight_TrainerLast100StrikeRate, ',', @Weight_BestFixedOdds, ',',
@Weight_Age, ',', @Weight_CareerPlacings, ',', @Weight_DryTrackStrikeRate, ',', @Weight_ThisTrackStrikeRate, ',',
@Weight_LastStartFinishPosition, ',', @Weight_WeightCarried,
' - Win%: ', @WinPercentage
)
DROP TABLE IF EXISTS #Results
DROP TABLE IF EXISTS #TopResults
SET @Weight_WeightCarried = @Weight_WeightCarried + 1
END
SET @Weight_LastStartFinishPosition = @Weight_LastStartFinishPosition + 1
END
SET @Weight_ThisTrackStrikeRate = @Weight_ThisTrackStrikeRate + 1
END
SET @Weight_DryTrackStrikeRate = @Weight_DryTrackStrikeRate + 1
END
SET @Weight_CareerPlacings = @Weight_CareerPlacings + 1
END
SET @Weight_Age = @Weight_Age + 1
END
SET @Weight_BestFixedOdds = @Weight_BestFixedOdds + 1
END
SET @Weight_TrainerLast100StrikeRate = @Weight_TrainerLast100StrikeRate + 1
END
SET @Weight_JockeyLast100StrikeRate = @Weight_JockeyLast100StrikeRate + 1
END
SET @Weight_CareerStrikeRate = @Weight_CareerStrikeRate + 1
END
SET @Weight_CareerWins = @Weight_CareerWins + 1
END
SET @Weight_HandicapRating = @Weight_HandicapRating + 1
END
END

View File

@@ -0,0 +1,40 @@
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace BettingPredictor.UI
{
/// <summary>
/// Pannello con angoli arrotondati e ombra simulata per stile card moderno
/// </summary>
internal class CardPanel : Panel
{
public int Radius { get; set; } = ModernTheme.CardRadius;
public CardPanel()
{
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer | ControlStyles.ResizeRedraw, true);
BackColor = Color.Transparent;
Padding = new Padding(ModernTheme.CardPadding);
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
// Pulisci sfondo
using (var bgBrush = new SolidBrush(Parent?.BackColor ?? ModernTheme.ContentBackground))
g.FillRectangle(bgBrush, ClientRectangle);
var cardRect = new Rectangle(0, 0, Width - 2, Height - 2);
using (var path = ModernTheme.GetRoundedRectPath(cardRect, Radius))
{
using (var brush = new SolidBrush(ModernTheme.CardBackground))
g.FillPath(brush, path);
using (var pen = new Pen(ModernTheme.CardBorder, 1))
g.DrawPath(pen, path);
}
}
}
}

View File

@@ -0,0 +1,107 @@
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace BettingPredictor.UI.Controls
{
/// <summary>
/// Pulsante moderno con tema scuro e effetti hover
/// </summary>
public class ModernButton : Button
{
private bool isHovering = false;
private Color normalColor = ModernTheme.Colors.AccentPrimary;
private Color hoverColor = ModernTheme.Colors.AccentSecondary;
private int borderRadius = ModernTheme.BorderRadius.Medium;
public ModernButton()
{
// Configurazione base
FlatStyle = FlatStyle.Flat;
FlatAppearance.BorderSize = 0;
BackColor = normalColor;
ForeColor = ModernTheme.Colors.TextPrimary;
Font = ModernTheme.Fonts.ButtonFont;
Cursor = Cursors.Hand;
Size = new Size(100, 35);
// Abilita il double buffering per evitare flickering
SetStyle(ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer, true);
}
public Color NormalColor
{
get => normalColor;
set
{
normalColor = value;
if (!isHovering) BackColor = value;
}
}
public Color HoverColor
{
get => hoverColor;
set => hoverColor = value;
}
public int BorderRadius
{
get => borderRadius;
set
{
borderRadius = value;
Invalidate();
}
}
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
isHovering = true;
BackColor = hoverColor;
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
isHovering = false;
BackColor = normalColor;
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
// Disegna il background con bordi arrotondati
using (GraphicsPath path = GetRoundedRectangle(ClientRectangle, borderRadius))
{
using (SolidBrush brush = new SolidBrush(BackColor))
{
e.Graphics.FillPath(brush, path);
}
}
// Disegna il testo centrato
TextRenderer.DrawText(e.Graphics, Text, Font, ClientRectangle,
ForeColor, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
}
private GraphicsPath GetRoundedRectangle(Rectangle rect, int radius)
{
GraphicsPath path = new GraphicsPath();
int diameter = radius * 2;
path.AddArc(rect.X, rect.Y, diameter, diameter, 180, 90);
path.AddArc(rect.Right - diameter, rect.Y, diameter, diameter, 270, 90);
path.AddArc(rect.Right - diameter, rect.Bottom - diameter, diameter, diameter, 0, 90);
path.AddArc(rect.X, rect.Bottom - diameter, diameter, diameter, 90, 90);
path.CloseFigure();
return path;
}
}
}

View File

@@ -0,0 +1,104 @@
using System;
using System.Drawing;
using System.Windows.Forms;
namespace BettingPredictor.UI.Controls
{
/// <summary>
/// DataGridView moderna con tema scuro
/// </summary>
public class ModernDataGridView : DataGridView
{
public ModernDataGridView()
{
// Configurazione base
BackgroundColor = ModernTheme.Colors.PrimaryBackground;
GridColor = ModernTheme.Colors.BorderPrimary;
BorderStyle = BorderStyle.None;
// Colori delle celle
DefaultCellStyle.BackColor = ModernTheme.Colors.GridRowEven;
DefaultCellStyle.ForeColor = ModernTheme.Colors.TextPrimary;
DefaultCellStyle.SelectionBackColor = ModernTheme.Colors.GridSelected;
DefaultCellStyle.SelectionForeColor = ModernTheme.Colors.TextPrimary;
DefaultCellStyle.Font = ModernTheme.Fonts.RegularFont;
// Alternating row color
AlternatingRowsDefaultCellStyle.BackColor = ModernTheme.Colors.GridRowOdd;
AlternatingRowsDefaultCellStyle.ForeColor = ModernTheme.Colors.TextPrimary;
// Header
ColumnHeadersDefaultCellStyle.BackColor = ModernTheme.Colors.GridHeader;
ColumnHeadersDefaultCellStyle.ForeColor = ModernTheme.Colors.TextPrimary;
ColumnHeadersDefaultCellStyle.Font = ModernTheme.Fonts.SubtitleFont;
ColumnHeadersDefaultCellStyle.SelectionBackColor = ModernTheme.Colors.GridHeader;
ColumnHeadersDefaultCellStyle.SelectionForeColor = ModernTheme.Colors.TextPrimary;
ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.Single;
ColumnHeadersHeight = 35;
// Row
RowHeadersDefaultCellStyle.BackColor = ModernTheme.Colors.GridHeader;
RowHeadersDefaultCellStyle.ForeColor = ModernTheme.Colors.TextPrimary;
RowHeadersDefaultCellStyle.SelectionBackColor = ModernTheme.Colors.GridHeader;
RowHeadersDefaultCellStyle.SelectionForeColor = ModernTheme.Colors.TextPrimary;
RowHeadersVisible = false;
// Altre impostazioni
EnableHeadersVisualStyles = false;
AllowUserToAddRows = false;
AllowUserToDeleteRows = false;
ReadOnly = true;
SelectionMode = DataGridViewSelectionMode.FullRowSelect;
MultiSelect = false;
AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
RowTemplate.Height = 30;
// Abilita il double buffering per evitare flickering
DoubleBuffered = true;
// Eventi per l'hover effect
CellMouseEnter += OnCellMouseEnterHandler;
CellMouseLeave += OnCellMouseLeaveHandler;
}
private void OnCellMouseEnterHandler(object sender, DataGridViewCellEventArgs e)
{
if (e.RowIndex >= 0)
{
Rows[e.RowIndex].DefaultCellStyle.BackColor = ModernTheme.Colors.GridHover;
}
}
private void OnCellMouseLeaveHandler(object sender, DataGridViewCellEventArgs e)
{
if (e.RowIndex >= 0)
{
// Ripristina il colore originale in base all'indice della riga
if (e.RowIndex % 2 == 0)
Rows[e.RowIndex].DefaultCellStyle.BackColor = ModernTheme.Colors.GridRowEven;
else
Rows[e.RowIndex].DefaultCellStyle.BackColor = ModernTheme.Colors.GridRowOdd;
}
}
public void HighlightWinnerRows()
{
if (Columns.Contains("Risultato Reale"))
{
foreach (DataGridViewRow row in Rows)
{
if (row.Cells["Risultato Reale"].Value != null &&
row.Cells["Risultato Reale"].Value.ToString() == "1")
{
row.DefaultCellStyle.BackColor = ModernTheme.Colors.StatusWin;
}
else if (row.Cells["Risultato Reale"].Value != null &&
int.TryParse(row.Cells["Risultato Reale"].Value.ToString(), out int pos) && pos <= 3)
{
row.DefaultCellStyle.BackColor = ModernTheme.Colors.StatusPlace;
}
}
}
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using System.Drawing;
using System.Windows.Forms;
namespace BettingPredictor.UI.Controls
{
/// <summary>
/// DateTimePicker moderna con tema scuro
/// Nota: Il controllo DateTimePicker di WinForms non supporta completamente il custom painting,
/// quindi alcuni elementi potrebbero mantenere lo stile di sistema
/// </summary>
public class ModernDateTimePicker : DateTimePicker
{
public ModernDateTimePicker()
{
// Configurazione base
Font = ModernTheme.Fonts.RegularFont;
Format = DateTimePickerFormat.Custom;
CustomFormat = "dd/MM/yyyy";
// Imposta i colori per il calendario
CalendarMonthBackground = ModernTheme.Colors.TertiaryBackground;
CalendarForeColor = ModernTheme.Colors.TextPrimary;
CalendarTitleBackColor = ModernTheme.Colors.SecondaryBackground;
CalendarTitleForeColor = ModernTheme.Colors.TextPrimary;
CalendarTrailingForeColor = ModernTheme.Colors.TextDisabled;
// Dimensioni
Height = 30;
}
/// <summary>
/// Imposta il formato lungo (con ora)
/// </summary>
public void SetLongFormat()
{
CustomFormat = "dd/MM/yyyy HH:mm";
}
/// <summary>
/// Imposta il formato corto (solo data)
/// </summary>
public void SetShortFormat()
{
CustomFormat = "dd/MM/yyyy";
}
}
}

View File

@@ -0,0 +1,78 @@
using System.Drawing;
using System.Windows.Forms;
namespace BettingPredictor.UI.Controls
{
/// <summary>
/// Label moderna con tema scuro e testo ben visibile
/// </summary>
public class ModernLabel : Label
{
public ModernLabel()
{
// Configurazione base per visibilità su sfondo scuro
ForeColor = ModernTheme.Colors.TextSecondary;
Font = ModernTheme.Fonts.RegularFont;
BackColor = Color.Transparent;
AutoSize = true;
// Abilita anti-aliasing per testo più leggibile
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint, true);
}
/// <summary>
/// Imposta lo stile del label come titolo
/// </summary>
public void SetAsTitleLabel()
{
Font = ModernTheme.Fonts.TitleFont;
ForeColor = ModernTheme.Colors.TextPrimary;
}
/// <summary>
/// Imposta lo stile del label come sottotitolo
/// </summary>
public void SetAsSubtitleLabel()
{
Font = ModernTheme.Fonts.SubtitleFont;
ForeColor = ModernTheme.Colors.TextPrimary;
}
/// <summary>
/// Imposta lo stile del label come testo di stato
/// </summary>
public void SetAsStatusLabel()
{
Font = ModernTheme.Fonts.RegularFont;
ForeColor = ModernTheme.Colors.TextSecondary;
}
/// <summary>
/// Imposta lo stile del label come testo di successo
/// </summary>
public void SetAsSuccessLabel()
{
Font = ModernTheme.Fonts.RegularFont;
ForeColor = ModernTheme.Colors.AccentSuccess;
}
/// <summary>
/// Imposta lo stile del label come testo di errore
/// </summary>
public void SetAsErrorLabel()
{
Font = ModernTheme.Fonts.RegularFont;
ForeColor = ModernTheme.Colors.AccentError;
}
/// <summary>
/// Imposta lo stile del label come testo di warning
/// </summary>
public void SetAsWarningLabel()
{
Font = ModernTheme.Fonts.RegularFont;
ForeColor = ModernTheme.Colors.AccentWarning;
}
}
}

View File

@@ -0,0 +1,99 @@
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace BettingPredictor.UI.Controls
{
/// <summary>
/// Panel moderno con tema scuro e bordi arrotondati
/// </summary>
public class ModernPanel : Panel
{
private int borderRadius = ModernTheme.BorderRadius.Medium;
private Color borderColor = ModernTheme.Colors.BorderPrimary;
private int borderWidth = 1;
public ModernPanel()
{
BackColor = ModernTheme.Colors.SecondaryBackground;
// Abilita il double buffering
SetStyle(ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw, true);
}
public int BorderRadius
{
get => borderRadius;
set
{
borderRadius = value;
Invalidate();
}
}
public Color BorderColor
{
get => borderColor;
set
{
borderColor = value;
Invalidate();
}
}
public int BorderWidth
{
get => borderWidth;
set
{
borderWidth = value;
Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
// Disegna il background con bordi arrotondati
using (GraphicsPath path = GetRoundedRectangle(ClientRectangle, borderRadius))
{
// Background
using (SolidBrush brush = new SolidBrush(BackColor))
{
e.Graphics.FillPath(brush, path);
}
// Bordo
if (borderWidth > 0)
{
using (Pen pen = new Pen(borderColor, borderWidth))
{
e.Graphics.DrawPath(pen, path);
}
}
}
}
private GraphicsPath GetRoundedRectangle(Rectangle rect, int radius)
{
GraphicsPath path = new GraphicsPath();
int diameter = radius * 2;
rect.Inflate(-borderWidth / 2, -borderWidth / 2);
path.AddArc(rect.X, rect.Y, diameter, diameter, 180, 90);
path.AddArc(rect.Right - diameter, rect.Y, diameter, diameter, 270, 90);
path.AddArc(rect.Right - diameter, rect.Bottom - diameter, diameter, diameter, 0, 90);
path.AddArc(rect.X, rect.Bottom - diameter, diameter, diameter, 90, 90);
path.CloseFigure();
return path;
}
}
}

View File

@@ -0,0 +1,54 @@
using System.Drawing;
using System.Windows.Forms;
namespace BettingPredictor.UI.Controls
{
/// <summary>
/// ProgressBar moderna con tema scuro e animazioni fluide
/// </summary>
public class ModernProgressBar : ProgressBar
{
public ModernProgressBar()
{
// Configurazione base
SetStyle(ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer, true);
Height = 6;
BackColor = ModernTheme.Colors.TertiaryBackground;
ForeColor = ModernTheme.Colors.AccentPrimary;
}
protected override void OnPaint(PaintEventArgs e)
{
Rectangle rect = ClientRectangle;
Graphics g = e.Graphics;
// Disegna il background
using (SolidBrush brush = new SolidBrush(ModernTheme.Colors.TertiaryBackground))
{
g.FillRectangle(brush, rect);
}
// Calcola la larghezza del progresso
int progressWidth = (int)(rect.Width * ((double)Value / Maximum));
// Disegna la barra di progresso
if (progressWidth > 0)
{
Rectangle progressRect = new Rectangle(rect.X, rect.Y, progressWidth, rect.Height);
using (SolidBrush brush = new SolidBrush(ForeColor))
{
g.FillRectangle(brush, progressRect);
}
}
}
public void SetProgressColor(Color color)
{
ForeColor = color;
Invalidate();
}
}
}

View File

@@ -0,0 +1,98 @@
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace BettingPredictor.UI.Controls
{
/// <summary>
/// TabControl moderna con tema scuro
/// </summary>
public class ModernTabControl : TabControl
{
public ModernTabControl()
{
SetStyle(ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw, true);
DrawMode = TabDrawMode.OwnerDrawFixed;
SizeMode = TabSizeMode.Fixed;
ItemSize = new Size(120, 40);
Padding = new Point(20, 0);
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.Clear(ModernTheme.Colors.PrimaryBackground);
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
// Disegna il background del tab panel
Rectangle tabPanelRect = new Rectangle(0, ItemSize.Height, Width, Height - ItemSize.Height);
using (SolidBrush brush = new SolidBrush(ModernTheme.Colors.SecondaryBackground))
{
e.Graphics.FillRectangle(brush, tabPanelRect);
}
}
protected override void OnDrawItem(DrawItemEventArgs e)
{
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
// Get tab rectangle
Rectangle tabRect = GetTabRect(e.Index);
bool isSelected = (e.Index == SelectedIndex);
// Disegna il background della tab
Color backColor = isSelected ? ModernTheme.Colors.SecondaryBackground : ModernTheme.Colors.PrimaryBackground;
using (SolidBrush brush = new SolidBrush(backColor))
{
g.FillRectangle(brush, tabRect);
}
// Disegna la linea di selezione in alto
if (isSelected)
{
Rectangle accentRect = new Rectangle(tabRect.X, tabRect.Y, tabRect.Width, 3);
using (SolidBrush brush = new SolidBrush(ModernTheme.Colors.AccentPrimary))
{
g.FillRectangle(brush, accentRect);
}
}
// Disegna il testo della tab
string tabText = TabPages[e.Index].Text;
Color textColor = isSelected ? ModernTheme.Colors.TextPrimary : ModernTheme.Colors.TextSecondary;
Font tabFont = isSelected ? ModernTheme.Fonts.SubtitleFont : ModernTheme.Fonts.RegularFont;
StringFormat sf = new StringFormat
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Center
};
using (SolidBrush brush = new SolidBrush(textColor))
{
g.DrawString(tabText, tabFont, brush, tabRect, sf);
}
// Disegna il bordo tra le tabs
if (e.Index < TabCount - 1)
{
using (Pen pen = new Pen(ModernTheme.Colors.BorderPrimary, 1))
{
g.DrawLine(pen, tabRect.Right, tabRect.Top + 10, tabRect.Right, tabRect.Bottom - 10);
}
}
}
protected override void OnSelectedIndexChanged(EventArgs e)
{
base.OnSelectedIndexChanged(e);
Invalidate();
}
}
}

View File

@@ -0,0 +1,139 @@
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace BettingPredictor.UI.Controls
{
/// <summary>
/// TextBox moderno con tema scuro e bordi arrotondati
/// </summary>
public class ModernTextBox : TextBox
{
private Color borderColor = ModernTheme.Colors.BorderPrimary;
private Color focusBorderColor = ModernTheme.Colors.BorderAccent;
private bool isFocused = false;
private int borderRadius = ModernTheme.BorderRadius.Medium;
public ModernTextBox()
{
// Configurazione base
BorderStyle = BorderStyle.None;
BackColor = ModernTheme.Colors.TertiaryBackground;
ForeColor = ModernTheme.Colors.TextPrimary;
Font = ModernTheme.Fonts.RegularFont;
Padding = new Padding(8, 8, 8, 8);
// Abilita il double buffering
SetStyle(ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.OptimizedDoubleBuffer, true);
}
public Color BorderColor
{
get => borderColor;
set
{
borderColor = value;
Invalidate();
}
}
public Color FocusBorderColor
{
get => focusBorderColor;
set
{
focusBorderColor = value;
Invalidate();
}
}
public int BorderRadius
{
get => borderRadius;
set
{
borderRadius = value;
Invalidate();
}
}
protected override void OnEnter(EventArgs e)
{
base.OnEnter(e);
isFocused = true;
Invalidate();
}
protected override void OnLeave(EventArgs e)
{
base.OnLeave(e);
isFocused = false;
Invalidate();
}
protected override void OnReadOnlyChanged(EventArgs e)
{
base.OnReadOnlyChanged(e);
// Cambia il colore del testo per i campi read-only
if (ReadOnly)
{
ForeColor = ModernTheme.Colors.TextSecondary;
BackColor = ModernTheme.Colors.SecondaryBackground;
}
else
{
ForeColor = ModernTheme.Colors.TextPrimary;
BackColor = ModernTheme.Colors.TertiaryBackground;
}
Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
// Disegna il background
using (GraphicsPath path = GetRoundedRectangle(ClientRectangle, borderRadius))
{
using (SolidBrush brush = new SolidBrush(BackColor))
{
e.Graphics.FillPath(brush, path);
}
// Disegna il bordo
Color currentBorderColor = isFocused ? focusBorderColor : borderColor;
using (Pen pen = new Pen(currentBorderColor, 1.5f))
{
e.Graphics.DrawPath(pen, path);
}
}
// Disegna il testo
if (!string.IsNullOrEmpty(Text))
{
TextRenderer.DrawText(e.Graphics, Text, Font,
new Rectangle(8, 0, Width - 16, Height),
ForeColor, TextFormatFlags.Left | TextFormatFlags.VerticalCenter);
}
}
private GraphicsPath GetRoundedRectangle(Rectangle rect, int radius)
{
GraphicsPath path = new GraphicsPath();
int diameter = radius * 2;
rect.Inflate(-1, -1);
path.AddArc(rect.X, rect.Y, diameter, diameter, 180, 90);
path.AddArc(rect.Right - diameter, rect.Y, diameter, diameter, 270, 90);
path.AddArc(rect.Right - diameter, rect.Bottom - diameter, diameter, diameter, 0, 90);
path.AddArc(rect.X, rect.Bottom - diameter, diameter, diameter, 90, 90);
path.CloseFigure();
return path;
}
}
}

View File

@@ -0,0 +1,89 @@
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace BettingPredictor.UI
{
/// <summary>
/// Pulsante con stile moderno (arrotondato, con effetti hover)
/// </summary>
internal class ModernButton : Button
{
private bool _isHovered;
private bool _isPressed;
private Color _accentColor;
public Color AccentColor
{
get => _accentColor;
set { _accentColor = value; Invalidate(); }
}
public bool IsPrimary { get; set; } = true;
public ModernButton()
{
_accentColor = ModernTheme.PrimaryColor;
FlatStyle = FlatStyle.Flat;
FlatAppearance.BorderSize = 0;
Font = new Font("Segoe UI Semibold", 10F);
ForeColor = Color.White;
Cursor = Cursors.Hand;
Height = 36;
MinimumSize = new Size(100, 36);
SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer | ControlStyles.Opaque, true);
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
// Pulisci sfondo
using (var bgBrush = new SolidBrush(Parent?.BackColor ?? ModernTheme.ContentBackground))
g.FillRectangle(bgBrush, ClientRectangle);
var rect = new Rectangle(0, 0, Width - 1, Height - 1);
Color bgColor;
if (!Enabled)
bgColor = Color.FromArgb(100, _accentColor);
else if (_isPressed)
bgColor = ControlPaint.Dark(_accentColor, 0.15f);
else if (_isHovered)
bgColor = ControlPaint.Light(_accentColor, 0.15f);
else
bgColor = _accentColor;
using (var path = ModernTheme.GetRoundedRectPath(rect, 6))
{
if (IsPrimary)
{
using (var brush = new SolidBrush(bgColor))
g.FillPath(brush, path);
}
else
{
using (var brush = new SolidBrush(ModernTheme.CardBackground))
g.FillPath(brush, path);
using (var pen = new Pen(bgColor, 2))
g.DrawPath(pen, path);
}
var textColor = IsPrimary ? Color.FromArgb(24, 24, 37) : ModernTheme.TextPrimary;
using (var textBrush = new SolidBrush(Enabled ? textColor : Color.FromArgb(80, textColor)))
{
var sf = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
g.DrawString(Text, Font, textBrush, rect, sf);
}
}
}
protected override void OnMouseEnter(EventArgs e) { _isHovered = true; Invalidate(); base.OnMouseEnter(e); }
protected override void OnMouseLeave(EventArgs e) { _isHovered = false; _isPressed = false; Invalidate(); base.OnMouseLeave(e); }
protected override void OnMouseDown(MouseEventArgs e) { _isPressed = true; Invalidate(); base.OnMouseDown(e); }
protected override void OnMouseUp(MouseEventArgs e) { _isPressed = false; Invalidate(); base.OnMouseUp(e); }
}
}

View File

@@ -0,0 +1,53 @@
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace BettingPredictor.UI
{
/// <summary>
/// ProgressBar moderna con stile piatto e bordi arrotondati
/// </summary>
internal class ModernProgressBar : ProgressBar
{
public Color BarColor { get; set; } = ModernTheme.PrimaryColor;
public Color TrackColor { get; set; } = Color.FromArgb(49, 50, 68);
public ModernProgressBar()
{
SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.DoubleBuffer | ControlStyles.Opaque, true);
Height = 8;
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
// Pulisci sfondo
using (var bgBrush = new SolidBrush(Parent?.BackColor ?? ModernTheme.ContentBackground))
g.FillRectangle(bgBrush, ClientRectangle);
var rect = new Rectangle(0, 0, Width - 1, Height - 1);
using (var path = ModernTheme.GetRoundedRectPath(rect, Height / 2))
using (var brush = new SolidBrush(TrackColor))
{
g.FillPath(brush, path);
}
if (Maximum > Minimum && Value > Minimum)
{
float fraction = (float)(Value - Minimum) / (Maximum - Minimum);
int fillWidth = (int)(rect.Width * fraction);
if (fillWidth > 4)
{
var fillRect = new Rectangle(0, 0, fillWidth, Height - 1);
using (var path = ModernTheme.GetRoundedRectPath(fillRect, Height / 2))
using (var brush = new LinearGradientBrush(fillRect, BarColor, ControlPaint.Light(BarColor, 0.3f), LinearGradientMode.Horizontal))
{
g.FillPath(brush, path);
}
}
}
}
}
}

View File

@@ -0,0 +1,115 @@
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace BettingPredictor.UI
{
/// <summary>
/// Tema grafico moderno scuro per l'applicazione
/// </summary>
internal static class ModernTheme
{
// Palette colori — DARK THEME
public static readonly Color SidebarBackground = Color.FromArgb(24, 24, 37);
public static readonly Color SidebarHover = Color.FromArgb(40, 40, 58);
public static readonly Color SidebarActive = Color.FromArgb(137, 180, 250);
public static readonly Color SidebarText = Color.FromArgb(186, 194, 222);
public static readonly Color SidebarActiveText = Color.FromArgb(24, 24, 37);
public static readonly Color ContentBackground = Color.FromArgb(30, 30, 46);
public static readonly Color CardBackground = Color.FromArgb(40, 42, 58);
public static readonly Color CardBorder = Color.FromArgb(55, 57, 78);
public static readonly Color HeaderBackground = Color.FromArgb(24, 24, 37);
public static readonly Color PrimaryColor = Color.FromArgb(137, 180, 250);
public static readonly Color PrimaryDark = Color.FromArgb(116, 155, 220);
public static readonly Color SuccessColor = Color.FromArgb(166, 218, 149);
public static readonly Color WarningColor = Color.FromArgb(249, 226, 175);
public static readonly Color DangerColor = Color.FromArgb(243, 139, 168);
public static readonly Color TextPrimary = Color.FromArgb(205, 214, 244);
public static readonly Color TextSecondary = Color.FromArgb(147, 153, 178);
public static readonly Color InputBackground = Color.FromArgb(49, 50, 68);
public static readonly Color InputBorder = Color.FromArgb(69, 71, 90);
public static readonly Color InputText = Color.FromArgb(205, 214, 244);
// Font
public static readonly Font TitleFont = new Font("Segoe UI Semibold", 16F);
public static readonly Font SubtitleFont = new Font("Segoe UI Semibold", 12F);
public static readonly Font BodyFont = new Font("Segoe UI", 10F);
public static readonly Font SmallFont = new Font("Segoe UI", 9F);
public static readonly Font NavFont = new Font("Segoe UI Semibold", 11F);
// Dimensioni
public const int SidebarWidth = 200;
public const int CardRadius = 8;
public const int CardPadding = 16;
/// <summary>
/// Applica lo stile moderno scuro a un DataGridView
/// </summary>
public static void StyleDataGridView(DataGridView grid)
{
grid.BorderStyle = BorderStyle.None;
grid.BackgroundColor = CardBackground;
grid.GridColor = CardBorder;
grid.CellBorderStyle = DataGridViewCellBorderStyle.SingleHorizontal;
grid.EnableHeadersVisualStyles = false;
grid.ColumnHeadersBorderStyle = DataGridViewHeaderBorderStyle.None;
grid.ColumnHeadersDefaultCellStyle = new DataGridViewCellStyle
{
BackColor = Color.FromArgb(35, 36, 52),
ForeColor = PrimaryColor,
Font = new Font("Segoe UI Semibold", 10F),
Padding = new Padding(8, 4, 8, 4),
Alignment = DataGridViewContentAlignment.MiddleLeft
};
grid.ColumnHeadersHeight = 40;
grid.DefaultCellStyle = new DataGridViewCellStyle
{
BackColor = CardBackground,
ForeColor = TextPrimary,
Font = BodyFont,
SelectionBackColor = Color.FromArgb(60, 63, 85),
SelectionForeColor = PrimaryColor,
Padding = new Padding(6, 3, 6, 3)
};
grid.AlternatingRowsDefaultCellStyle = new DataGridViewCellStyle
{
BackColor = Color.FromArgb(45, 47, 64),
ForeColor = TextPrimary,
Font = BodyFont,
SelectionBackColor = Color.FromArgb(60, 63, 85),
SelectionForeColor = PrimaryColor,
Padding = new Padding(6, 3, 6, 3)
};
grid.RowHeadersVisible = false;
grid.RowTemplate.Height = 36;
grid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
grid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
grid.ReadOnly = true;
grid.AllowUserToAddRows = false;
grid.AllowUserToDeleteRows = false;
grid.AllowUserToResizeRows = false;
}
/// <summary>
/// Disegna un rettangolo arrotondato
/// </summary>
public static GraphicsPath GetRoundedRectPath(Rectangle rect, int radius)
{
var path = new GraphicsPath();
int d = radius * 2;
path.AddArc(rect.X, rect.Y, d, d, 180, 90);
path.AddArc(rect.Right - d, rect.Y, d, d, 270, 90);
path.AddArc(rect.Right - d, rect.Bottom - d, d, d, 0, 90);
path.AddArc(rect.X, rect.Bottom - d, d, d, 90, 90);
path.CloseFigure();
return path;
}
}
}

View File

@@ -0,0 +1,116 @@
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace BettingPredictor.UI
{
/// <summary>
/// Pulsante di navigazione per la sidebar
/// </summary>
internal class NavButton : Button
{
private bool _isActive;
private bool _isHovered;
private readonly string _icon;
public bool IsActive
{
get => _isActive;
set { _isActive = value; Invalidate(); }
}
public NavButton(string text, string icon)
{
_icon = icon;
Text = text;
FlatStyle = FlatStyle.Flat;
FlatAppearance.BorderSize = 0;
FlatAppearance.MouseOverBackColor = Color.Transparent;
FlatAppearance.MouseDownBackColor = Color.Transparent;
Font = ModernTheme.NavFont;
ForeColor = ModernTheme.SidebarText;
BackColor = ModernTheme.SidebarBackground;
Cursor = Cursors.Hand;
Height = 44;
Dock = DockStyle.Top;
Padding = new Padding(0);
Margin = new Padding(0);
SetStyle(
ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint |
ControlStyles.DoubleBuffer |
ControlStyles.Opaque, true);
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
var rect = ClientRectangle;
// Sfondo pieno sidebar
using (var bg = new SolidBrush(ModernTheme.SidebarBackground))
g.FillRectangle(bg, rect);
// Stato attivo: rettangolo arrotondato + indicatore
if (_isActive)
{
var activeRect = new Rectangle(8, 4, rect.Width - 16, rect.Height - 8);
using (var path = ModernTheme.GetRoundedRectPath(activeRect, 6))
using (var brush = new SolidBrush(ModernTheme.SidebarActive))
g.FillPath(brush, path);
using (var bar = new SolidBrush(ModernTheme.SidebarActive))
g.FillRectangle(bar, 0, 10, 3, rect.Height - 20);
}
// Stato hover (solo se non attivo)
else if (_isHovered)
{
var hoverRect = new Rectangle(8, 4, rect.Width - 16, rect.Height - 8);
using (var path = ModernTheme.GetRoundedRectPath(hoverRect, 6))
using (var brush = new SolidBrush(ModernTheme.SidebarHover))
g.FillPath(brush, path);
}
// Colori testo/icona
var fg = _isActive ? ModernTheme.SidebarActiveText : ModernTheme.SidebarText;
// Icona
using (var iconFont = new Font("Segoe UI", 11F, FontStyle.Bold))
using (var brush = new SolidBrush(fg))
{
var sf = new StringFormat { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center };
g.DrawString(_icon, iconFont, brush, new RectangleF(14, 0, 28, rect.Height), sf);
}
// Testo
using (var brush = new SolidBrush(fg))
{
var sf = new StringFormat
{
Alignment = StringAlignment.Near,
LineAlignment = StringAlignment.Center,
FormatFlags = StringFormatFlags.NoWrap
};
g.DrawString(Text, Font, brush, new RectangleF(46, 0, rect.Width - 54, rect.Height), sf);
}
}
protected override void OnMouseEnter(EventArgs e)
{
_isHovered = true;
Invalidate();
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(EventArgs e)
{
_isHovered = false;
Invalidate();
base.OnMouseLeave(e);
}
}
}

View File

@@ -0,0 +1,30 @@
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";
}
}

View File

@@ -1,23 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="CsvHelper" version="33.0.1" targetFramework="net481" />
<package id="Microsoft.Bcl.AsyncInterfaces" version="10.0.0-preview.4.25258.110" targetFramework="net481" />
<package id="CsvHelper" version="33.1.0" targetFramework="net481" />
<package id="Microsoft.Bcl.AsyncInterfaces" version="10.0.0-rc.1.25451.107" targetFramework="net481" />
<package id="Microsoft.Bcl.HashCode" version="6.0.0" targetFramework="net481" />
<package id="Microsoft.Bcl.Numerics" version="10.0.0-preview.4.25258.110" targetFramework="net481" />
<package id="Microsoft.Bcl.Numerics" version="10.0.0-rc.1.25451.107" targetFramework="net481" />
<package id="Microsoft.CSharp" version="4.7.0" targetFramework="net481" />
<package id="Microsoft.ML" version="5.0.0-preview.1.25127.4" targetFramework="net481" />
<package id="Microsoft.ML.CpuMath" version="5.0.0-preview.1.25127.4" targetFramework="net481" />
<package id="Microsoft.ML.DataView" version="5.0.0-preview.1.25127.4" targetFramework="net481" />
<package id="Microsoft.ML.FastTree" version="5.0.0-preview.1.25127.4" targetFramework="net481" />
<package id="Newtonsoft.Json" version="13.0.3" targetFramework="net481" />
<package id="Microsoft.ML" version="5.0.0-preview.25503.2" targetFramework="net481" />
<package id="Microsoft.ML.CpuMath" version="5.0.0-preview.25503.2" targetFramework="net481" />
<package id="Microsoft.ML.DataView" version="5.0.0-preview.25503.2" targetFramework="net481" />
<package id="Microsoft.ML.FastTree" version="5.0.0-preview.25503.2" targetFramework="net481" />
<package id="Microsoft.Web.WebView2" version="1.0.3800.47" targetFramework="net481" />
<package id="Newtonsoft.Json" version="13.0.4" targetFramework="net481" />
<package id="RestSharp" version="112.1.1-alpha.0.4" targetFramework="net481" />
<package id="System.Buffers" version="4.6.1" targetFramework="net481" />
<package id="System.CodeDom" version="10.0.0-preview.4.25258.110" targetFramework="net481" />
<package id="System.Collections.Immutable" version="10.0.0-preview.4.25258.110" targetFramework="net481" />
<package id="System.CodeDom" version="10.0.0-rc.1.25451.107" targetFramework="net481" />
<package id="System.Collections.Immutable" version="10.0.0-rc.1.25451.107" targetFramework="net481" />
<package id="System.IO.Pipelines" version="10.0.0-rc.1.25451.107" targetFramework="net481" />
<package id="System.Memory" version="4.6.3" targetFramework="net481" />
<package id="System.Numerics.Tensors" version="10.0.0-preview.4.25258.110" targetFramework="net481" />
<package id="System.Numerics.Tensors" version="10.0.0-rc.1.25451.107" targetFramework="net481" />
<package id="System.Numerics.Vectors" version="4.6.1" targetFramework="net481" />
<package id="System.Reflection.Emit.Lightweight" version="4.7.0" targetFramework="net481" />
<package id="System.Runtime.CompilerServices.Unsafe" version="6.1.2" targetFramework="net481" />
<package id="System.Threading.Channels" version="10.0.0-preview.4.25258.110" targetFramework="net481" />
<package id="System.Text.Encodings.Web" version="10.0.0-rc.1.25451.107" targetFramework="net481" />
<package id="System.Text.Json" version="10.0.0-rc.1.25451.107" targetFramework="net481" />
<package id="System.Threading.Channels" version="10.0.0-rc.1.25451.107" targetFramework="net481" />
<package id="System.Threading.Tasks.Extensions" version="4.6.3" targetFramework="net481" />
<package id="System.ValueTuple" version="4.6.1" targetFramework="net481" />
</packages>

View File

@@ -0,0 +1,249 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- =========================================================
CATPPUCCIN MOCHA PALETTE (shared)
========================================================= -->
<Color x:Key="CBase">#1E1E2E</Color>
<Color x:Key="CMantle">#181825</Color>
<Color x:Key="CCrust">#11111B</Color>
<Color x:Key="CSurface0">#313244</Color>
<Color x:Key="CSurface1">#45475A</Color>
<Color x:Key="CSurface2">#585B70</Color>
<Color x:Key="COverlay0">#6C7086</Color>
<Color x:Key="CText">#CDD6F4</Color>
<Color x:Key="CSubtext0">#A6ADC8</Color>
<Color x:Key="CSubtext1">#BAC2DE</Color>
<Color x:Key="CBlue">#89B4FA</Color>
<Color x:Key="CGreen">#A6E3A1</Color>
<Color x:Key="CRed">#F38BA8</Color>
<Color x:Key="CPeach">#FAB387</Color>
<Color x:Key="CLavender">#B4BEFE</Color>
<SolidColorBrush x:Key="BrBase" Color="{StaticResource CBase}"/>
<SolidColorBrush x:Key="BrMantle" Color="{StaticResource CMantle}"/>
<SolidColorBrush x:Key="BrSurface0" Color="{StaticResource CSurface0}"/>
<SolidColorBrush x:Key="BrSurface1" Color="{StaticResource CSurface1}"/>
<SolidColorBrush x:Key="BrSurface2" Color="{StaticResource CSurface2}"/>
<SolidColorBrush x:Key="BrOverlay0" Color="{StaticResource COverlay0}"/>
<SolidColorBrush x:Key="BrText" Color="{StaticResource CText}"/>
<SolidColorBrush x:Key="BrSubtext0" Color="{StaticResource CSubtext0}"/>
<SolidColorBrush x:Key="BrBlue" Color="{StaticResource CBlue}"/>
<SolidColorBrush x:Key="BrGreen" Color="{StaticResource CGreen}"/>
<SolidColorBrush x:Key="BrRed" Color="{StaticResource CRed}"/>
<SolidColorBrush x:Key="BrPeach" Color="{StaticResource CPeach}"/>
<SolidColorBrush x:Key="BrLavender" Color="{StaticResource CLavender}"/>
<!-- Acrylic-like background (semi-transparent fallback) -->
<SolidColorBrush x:Key="AcrylicBackgroundBrush" Color="#0F000000"/>
<!-- Subtle shadow effect for elevation -->
<DropShadowEffect x:Key="SubtleDropShadow" BlurRadius="12" ShadowDepth="2" Color="#50000000"/>
<!-- NAV BUTTON STYLE (icon-only sidebar) -->
<Style x:Key="NavBtn" TargetType="RadioButton">
<Setter Property="Width" Value="48"/>
<Setter Property="Height" Value="48"/>
<Setter Property="Margin" Value="6,4"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Foreground" Value="{StaticResource BrSubtext0}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Grid>
<Border x:Name="Bg" CornerRadius="10" Background="{TemplateBinding Background}"/>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bg" Property="Background" Value="#28283A"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="Bg" Property="Background" Value="{StaticResource BrBlue}"/>
<Setter Property="Foreground" Value="#181825"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- ACCENT BUTTON -->
<Style x:Key="AccentBtn" TargetType="Button">
<Setter Property="Foreground" Value="#181825"/>
<Setter Property="FontFamily" Value="Segoe UI Semibold"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="Padding" Value="18,7"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border x:Name="Bd" CornerRadius="8"
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Bd" Property="Opacity" Value="0.85"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Bd" Property="Opacity" Value="0.40"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- FLAT TEXTBOX -->
<Style x:Key="FlatTb" TargetType="TextBox">
<Setter Property="Background" Value="{StaticResource BrSurface0}"/>
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
<Setter Property="CaretBrush" Value="{StaticResource BrText}"/>
<Setter Property="BorderBrush" Value="{StaticResource BrSurface1}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="10,7"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Border x:Name="Bd" CornerRadius="6"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<ScrollViewer x:Name="PART_ContentHost"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource BrBlue}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- PASSWORD BOX -->
<Style x:Key="FlatPb" TargetType="PasswordBox">
<Setter Property="Background" Value="{StaticResource BrSurface0}"/>
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
<Setter Property="CaretBrush" Value="{StaticResource BrText}"/>
<Setter Property="BorderBrush" Value="{StaticResource BrSurface1}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="10,7"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="PasswordBox">
<Border x:Name="Bd" CornerRadius="6"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<ScrollViewer x:Name="PART_ContentHost"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource BrBlue}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- PROGRESS BAR -->
<Style x:Key="ModernPb" TargetType="ProgressBar">
<Setter Property="Height" Value="4"/>
<Setter Property="Background" Value="{StaticResource BrSurface0}"/>
<Setter Property="Foreground" Value="{StaticResource BrBlue}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ProgressBar">
<Grid>
<Border CornerRadius="2" Background="{TemplateBinding Background}"/>
<Border x:Name="PART_Indicator" CornerRadius="2"
Background="{TemplateBinding Foreground}"
HorizontalAlignment="Left"/>
<Border x:Name="PART_Track"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- DATAGRID -->
<Style x:Key="ModernDg" TargetType="DataGrid">
<Setter Property="Background" Value="#282A3A"/>
<Setter Property="Foreground" Value="{StaticResource BrText}"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="RowBackground" Value="#282A3A"/>
<Setter Property="AlternatingRowBackground" Value="#2D2F42"/>
<Setter Property="GridLinesVisibility" Value="Horizontal"/>
<Setter Property="HorizontalGridLinesBrush" Value="#37394E"/>
<Setter Property="HeadersVisibility" Value="Column"/>
<Setter Property="RowHeaderWidth" Value="0"/>
<Setter Property="AutoGenerateColumns" Value="True"/>
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="SelectionMode" Value="Single"/>
<Setter Property="CanUserAddRows" Value="False"/>
<Setter Property="CanUserDeleteRows" Value="False"/>
<Setter Property="CanUserResizeRows" Value="False"/>
<Setter Property="FontFamily" Value="Segoe UI"/>
<Setter Property="FontSize" Value="13"/>
</Style>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Background" Value="#23243A"/>
<Setter Property="Foreground" Value="{StaticResource BrBlue}"/>
<Setter Property="FontFamily" Value="Segoe UI Semibold"/>
<Setter Property="FontSize" Value="13"/>
<Setter Property="Padding" Value="10,8"/>
<Setter Property="BorderBrush" Value="#37394E"/>
<Setter Property="BorderThickness" Value="0,0,0,1"/>
</Style>
<Style TargetType="DataGridCell">
<Setter Property="Padding" Value="8,6"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DataGridCell">
<Border Padding="{TemplateBinding Padding}" Background="{TemplateBinding Background}">
<ContentPresenter VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#3C3F58"/>
<Setter Property="Foreground" Value="{StaticResource BrBlue}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="DataGridRow">
<Setter Property="Margin" Value="0"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#3C3F58"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- Card style for lists / rows -->
<Style x:Key="CardStyle" TargetType="Border">
<Setter Property="CornerRadius" Value="8"/>
<Setter Property="Background" Value="#23232A"/>
<Setter Property="Padding" Value="12"/>
<Setter Property="Margin" Value="6"/>
</Style>
</ResourceDictionary>

Binary file not shown.