Allineamento dati con repository di origine
This commit is contained in:
@@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.12.35707.178
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BettingPredictor", "HorseRacingPredictor\BettingPredictor.csproj", "{63138155-B7F3-4246-B47B-B8CC2D7A60A4}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{63138155-B7F3-4246-B47B-B8CC2D7A60A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{63138155-B7F3-4246-B47B-B8CC2D7A60A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{63138155-B7F3-4246-B47B-B8CC2D7A60A4}.Debug|x64.ActiveCfg = Debug|x64
|
||||||
|
{63138155-B7F3-4246-B47B-B8CC2D7A60A4}.Debug|x64.Build.0 = Debug|x64
|
||||||
|
{63138155-B7F3-4246-B47B-B8CC2D7A60A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{63138155-B7F3-4246-B47B-B8CC2D7A60A4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{63138155-B7F3-4246-B47B-B8CC2D7A60A4}.Release|x64.ActiveCfg = Release|x64
|
||||||
|
{63138155-B7F3-4246-B47B-B8CC2D7A60A4}.Release|x64.Build.0 = Release|x64
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<runtime>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<!-- System.Threading.Tasks.Extensions -->
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.2.4.0" newVersion="4.2.4.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
|
||||||
|
<!-- System.Memory -->
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
|
||||||
|
<!-- System.Runtime.CompilerServices.Unsafe -->
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-6.0.3.0" newVersion="6.0.3.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
|
||||||
|
<!-- System.Buffers -->
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
|
||||||
|
<!-- System.Numerics.Vectors -->
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Numerics.Vectors" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-4.1.6.0" newVersion="4.1.6.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
|
||||||
|
<!-- Microsoft.Bcl.AsyncInterfaces -->
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="Microsoft.Bcl.AsyncInterfaces" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Text.Json" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="Microsoft.Bcl.HashCode" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Numerics.Tensors" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="System.Threading.Channels" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
</runtime>
|
||||||
|
</configuration>
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
<?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="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{63138155-B7F3-4246-B47B-B8CC2D7A60A4}</ProjectGuid>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<RootNamespace>HorseRacingPredictor</RootNamespace>
|
||||||
|
<AssemblyName>HorseRacingPredictor</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.8.1</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
|
<NuGetPackageImportStamp>
|
||||||
|
</NuGetPackageImportStamp>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||||
|
<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>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<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>
|
||||||
|
<Reference Include="Dapper, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
|
<HintPath>..\packages\Dapper.2.1.66\lib\net461\Dapper.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>
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
</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="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>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Runtime.CompilerServices.Unsafe, Version=6.0.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Web" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<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>
|
||||||
|
<Compile Include="Football\Database\BetType.cs" />
|
||||||
|
<Compile Include="Football\Database\Bookmaker.cs" />
|
||||||
|
<Compile Include="Football\Database\APIResponse.cs" />
|
||||||
|
<Compile Include="Football\Database\TeamStats.cs" />
|
||||||
|
<Compile Include="Football\Database\LeagueStats.cs" />
|
||||||
|
<Compile Include="Football\Database\H2H.cs" />
|
||||||
|
<Compile Include="Football\Database\Comparison.cs" />
|
||||||
|
<Compile Include="Football\Database\FixtureLeague.cs" />
|
||||||
|
<Compile Include="Football\Main.cs" />
|
||||||
|
<Compile Include="Football\Manager\API.cs" />
|
||||||
|
<Compile Include="Football\API\Fixture.cs" />
|
||||||
|
<Compile Include="Football\API\League.cs" />
|
||||||
|
<Compile Include="Football\API\Odds.cs" />
|
||||||
|
<Compile Include="Football\API\Prediction.cs" />
|
||||||
|
<Compile Include="Football\Database\Fixture.cs" />
|
||||||
|
<Compile Include="Football\Database\Goals.cs" />
|
||||||
|
<Compile Include="Football\Database\League.cs" />
|
||||||
|
<Compile Include="Football\Database\Odds.cs" />
|
||||||
|
<Compile Include="Football\Database\Prediction.cs" />
|
||||||
|
<Compile Include="Football\Database\Score.cs" />
|
||||||
|
<Compile Include="Football\Database\Team.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" />
|
||||||
|
<Compile Include="Football\Manager\Database.cs" />
|
||||||
|
<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>
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</EmbeddedResource>
|
||||||
|
<Compile Include="Properties\Resources.Designer.cs">
|
||||||
|
<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>
|
||||||
|
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||||
|
</None>
|
||||||
|
<Compile Include="Properties\Settings.Designer.cs">
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>Settings.settings</DependentUpon>
|
||||||
|
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<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" />
|
||||||
|
</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>
|
||||||
|
</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'))" />
|
||||||
|
</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')" />
|
||||||
|
</Project>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,59 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using HorseRacingPredictor.Football.Manager;
|
||||||
|
using RestSharp;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football.API
|
||||||
|
{
|
||||||
|
internal class Fixture : HorseRacingPredictor.Football.Manager.API
|
||||||
|
{
|
||||||
|
private const string Endpoint = "fixtures";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene le partite per una data specifica
|
||||||
|
/// </summary>
|
||||||
|
public new RestResponse GetFixturesByDate(DateTime date)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string dateStr = date.ToString("yyyy-MM-dd");
|
||||||
|
return ExecuteRequest($"{Endpoint}?date={dateStr}", ApiTypes.Fixtures);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"Errore durante il recupero delle partite per la data {date.ToShortDateString()}: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Versione asincrona di GetFixturesByDate
|
||||||
|
/// </summary>
|
||||||
|
public new async Task<RestResponse> GetFixturesByDateAsync(DateTime date)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string dateStr = date.ToString("yyyy-MM-dd");
|
||||||
|
return await ExecuteRequestAsync($"{Endpoint}?date={dateStr}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"Errore durante il recupero asincrono delle partite per la data {date.ToShortDateString()}: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene le partite per un ID specifico
|
||||||
|
/// </summary>
|
||||||
|
public new RestResponse GetFixtureById(int fixtureId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ExecuteRequest($"{Endpoint}?id={fixtureId}", ApiTypes.Fixtures);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"Errore durante il recupero della partita con ID {fixtureId}: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using RestSharp;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football.API
|
||||||
|
{
|
||||||
|
internal class League : HorseRacingPredictor.Football.Manager.API
|
||||||
|
{
|
||||||
|
private const string Endpoint = "leagues";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene informazioni sui campionati
|
||||||
|
/// </summary>
|
||||||
|
public new RestResponse GetLeagues()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ExecuteRequest(Endpoint, ApiTypes.Leagues);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"Errore durante il recupero delle leghe: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Versione asincrona di GetLeagues
|
||||||
|
/// </summary>
|
||||||
|
public new async Task<RestResponse> GetLeaguesAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await ExecuteRequestAsync(Endpoint);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"Errore durante il recupero asincrono delle leghe: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene informazioni su una specifica stagione di una lega
|
||||||
|
/// </summary>
|
||||||
|
public new RestResponse GetLeagueBySeason(int leagueId, int season)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ExecuteRequest($"{Endpoint}?id={leagueId}&season={season}", ApiTypes.Leagues);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"Errore durante il recupero della lega {leagueId} per la stagione {season}: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using RestSharp;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football.API
|
||||||
|
{
|
||||||
|
internal class Odds : HorseRacingPredictor.Football.Manager.API
|
||||||
|
{
|
||||||
|
private const string Endpoint = "odds";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene le quote per una data specifica
|
||||||
|
/// </summary>
|
||||||
|
public RestResponse GetOddsByDate(DateTime date, int bookmaker = 8, int page = 1)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string dateStr = date.ToString("yyyy-MM-dd");
|
||||||
|
return ExecuteRequest($"{Endpoint}?date={dateStr}&bookmaker={bookmaker}&page={page}", ApiTypes.Odds);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"Errore durante il recupero delle quote per la data {date.ToShortDateString()}: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Versione asincrona di GetOddsByDate
|
||||||
|
/// </summary>
|
||||||
|
public async Task<RestResponse> GetOddsByDateAsync(DateTime date, int bookmaker = 8, int page = 1)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string dateStr = date.ToString("yyyy-MM-dd");
|
||||||
|
return await ExecuteRequestAsync($"{Endpoint}?date={dateStr}&bookmaker={bookmaker}&page={page}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"Errore durante il recupero asincrono delle quote per la data {date.ToShortDateString()}: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene le quote per una partita specifica
|
||||||
|
/// </summary>
|
||||||
|
public RestResponse GetOddsByFixture(int fixtureId, int bookmaker = 8)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ExecuteRequest($"{Endpoint}?fixture={fixtureId}&bookmaker={bookmaker}", ApiTypes.Odds);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"Errore durante il recupero delle quote per la partita {fixtureId}: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using RestSharp;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football.API
|
||||||
|
{
|
||||||
|
internal class Prediction : HorseRacingPredictor.Football.Manager.API
|
||||||
|
{
|
||||||
|
private const string Endpoint = "predictions";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene le previsioni per una partita specifica
|
||||||
|
/// </summary>
|
||||||
|
public RestResponse GetPredictionByFixture(int fixtureId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ExecuteRequest($"{Endpoint}?fixture={fixtureId}", ApiTypes.Predictions);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"Errore durante il recupero delle previsioni per la partita {fixtureId}: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Versione asincrona di GetPredictionByFixture
|
||||||
|
/// </summary>
|
||||||
|
public async Task<RestResponse> GetPredictionByFixtureAsync(int fixtureId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await ExecuteRequestAsync($"{Endpoint}?fixture={fixtureId}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"Errore durante il recupero asincrono delle previsioni per la partita {fixtureId}: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Text;
|
||||||
|
using RestSharp;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football.Database
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Classe per la gestione della tabella API_Response che memorizza le risposte API prima dell'elaborazione
|
||||||
|
/// </summary>
|
||||||
|
internal class APIResponse : HorseRacingPredictor.Football.Manager.Database
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Inserisce una nuova risposta API nel database
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="apiType">Tipo di API (es. FootballFixtures, FootballOdds)</param>
|
||||||
|
/// <param name="endpoint">Endpoint API chiamato</param>
|
||||||
|
/// <param name="parameters">Parametri utilizzati nella chiamata</param>
|
||||||
|
/// <param name="response">Risposta API (RestResponse)</param>
|
||||||
|
/// <returns>ID della risposta inserita</returns>
|
||||||
|
public int InsertApiResponse(string apiType, string endpoint, string parameters, RestResponse response)
|
||||||
|
{
|
||||||
|
int responseId = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var connection = new SqlConnection(_connectionString))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
string query = @"
|
||||||
|
INSERT INTO API_Response (
|
||||||
|
timestamp, api_type, endpoint, parameters,
|
||||||
|
response_content, status_code, processed, processed_timestamp, error_message
|
||||||
|
) VALUES (
|
||||||
|
@timestamp, @apiType, @endpoint, @parameters,
|
||||||
|
@responseContent, @statusCode, @processed, NULL, @errorMessage
|
||||||
|
);
|
||||||
|
SELECT SCOPE_IDENTITY();";
|
||||||
|
|
||||||
|
using (var command = new SqlCommand(query, connection))
|
||||||
|
{
|
||||||
|
command.Parameters.AddWithValue("@timestamp", DateTime.Now);
|
||||||
|
command.Parameters.AddWithValue("@apiType", apiType);
|
||||||
|
command.Parameters.AddWithValue("@endpoint", endpoint);
|
||||||
|
command.Parameters.AddWithValue("@parameters", parameters ?? (object)DBNull.Value);
|
||||||
|
command.Parameters.AddWithValue("@responseContent", response.Content);
|
||||||
|
command.Parameters.AddWithValue("@statusCode", (int)response.StatusCode);
|
||||||
|
command.Parameters.AddWithValue("@processed", false);
|
||||||
|
command.Parameters.AddWithValue("@errorMessage",
|
||||||
|
response.IsSuccessful ? DBNull.Value : (object)(response.ErrorMessage ?? "Unknown error"));
|
||||||
|
|
||||||
|
var result = command.ExecuteScalar();
|
||||||
|
if (result != null && result != DBNull.Value)
|
||||||
|
{
|
||||||
|
responseId = Convert.ToInt32(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("inserimento risposta API", ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aggiorna lo stato di elaborazione di una risposta API
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="responseId">ID della risposta API</param>
|
||||||
|
/// <param name="processed">Stato di elaborazione</param>
|
||||||
|
/// <param name="errorMessage">Messaggio di errore (se presente)</param>
|
||||||
|
public void UpdateProcessingStatus(int responseId, bool processed, string errorMessage = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var connection = new SqlConnection(_connectionString))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
string query = @"
|
||||||
|
UPDATE API_Response
|
||||||
|
SET processed = @processed,
|
||||||
|
processed_timestamp = @processedTimestamp,
|
||||||
|
error_message = @errorMessage
|
||||||
|
WHERE id = @id";
|
||||||
|
|
||||||
|
using (var command = new SqlCommand(query, connection))
|
||||||
|
{
|
||||||
|
command.Parameters.AddWithValue("@processed", processed);
|
||||||
|
command.Parameters.AddWithValue("@processedTimestamp", processed ? DateTime.Now : (object)DBNull.Value);
|
||||||
|
command.Parameters.AddWithValue("@errorMessage", errorMessage ?? (object)DBNull.Value);
|
||||||
|
command.Parameters.AddWithValue("@id", responseId);
|
||||||
|
|
||||||
|
command.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError($"aggiornamento stato elaborazione risposta API ID:{responseId}", ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene le risposte API non elaborate per un determinato tipo
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="apiType">Tipo di API (opzionale)</param>
|
||||||
|
/// <returns>DataTable con le risposte API non elaborate</returns>
|
||||||
|
public DataTable GetUnprocessedResponses(string apiType = null)
|
||||||
|
{
|
||||||
|
DataTable dataTable = new DataTable();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var connection = new SqlConnection(_connectionString))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
StringBuilder queryBuilder = new StringBuilder(@"
|
||||||
|
SELECT id, timestamp, api_type, endpoint, parameters,
|
||||||
|
response_content, status_code, processed
|
||||||
|
FROM API_Response
|
||||||
|
WHERE processed = 0 AND status_code = 200");
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(apiType))
|
||||||
|
{
|
||||||
|
queryBuilder.Append(" AND api_type = @apiType");
|
||||||
|
}
|
||||||
|
|
||||||
|
queryBuilder.Append(" ORDER BY timestamp ASC");
|
||||||
|
|
||||||
|
using (var command = new SqlCommand(queryBuilder.ToString(), connection))
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(apiType))
|
||||||
|
{
|
||||||
|
command.Parameters.AddWithValue("@apiType", apiType);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var adapter = new SqlDataAdapter(command))
|
||||||
|
{
|
||||||
|
adapter.Fill(dataTable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("recupero risposte API non elaborate", ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene una specifica risposta API dal database
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="responseId">ID della risposta API</param>
|
||||||
|
/// <returns>DataRow con i dati della risposta API o null se non trovata</returns>
|
||||||
|
public DataRow GetApiResponseById(int responseId)
|
||||||
|
{
|
||||||
|
DataTable dataTable = new DataTable();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var connection = new SqlConnection(_connectionString))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
string query = @"
|
||||||
|
SELECT id, timestamp, api_type, endpoint, parameters,
|
||||||
|
response_content, status_code, processed
|
||||||
|
FROM API_Response
|
||||||
|
WHERE id = @id";
|
||||||
|
|
||||||
|
using (var command = new SqlCommand(query, connection))
|
||||||
|
{
|
||||||
|
command.Parameters.AddWithValue("@id", responseId);
|
||||||
|
|
||||||
|
using (var adapter = new SqlDataAdapter(command))
|
||||||
|
{
|
||||||
|
adapter.Fill(dataTable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataTable.Rows.Count > 0)
|
||||||
|
{
|
||||||
|
return dataTable.Rows[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError($"recupero risposta API ID:{responseId}", ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football.Database
|
||||||
|
{
|
||||||
|
internal class BetType : HorseRacingPredictor.Football.Manager.Database
|
||||||
|
{
|
||||||
|
public void Upsert(SqlConnection connection, JsonNode betType)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var id = betType["id"]?.GetValue<int>();
|
||||||
|
if (id == null) return; // Salta il record se l'id è null
|
||||||
|
|
||||||
|
var query = @"
|
||||||
|
IF EXISTS (SELECT 1 FROM BetType WHERE bet_type_id = @bet_type_id)
|
||||||
|
BEGIN
|
||||||
|
UPDATE BetType SET name = @name WHERE bet_type_id = @bet_type_id
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO BetType (bet_type_id, name) VALUES (@bet_type_id, @name)
|
||||||
|
END";
|
||||||
|
using (var command = new SqlCommand(query, connection))
|
||||||
|
{
|
||||||
|
command.Parameters.AddWithValue("@bet_type_id", id);
|
||||||
|
command.Parameters.AddWithValue("@name", betType["name"]?.GetValue<string>() ?? "");
|
||||||
|
command.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("l'upsert del tipo di scommessa", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football.Database
|
||||||
|
{
|
||||||
|
internal class Bookmaker : HorseRacingPredictor.Football.Manager.Database
|
||||||
|
{
|
||||||
|
public void Upsert(SqlConnection connection, JsonNode bookmaker)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var id = bookmaker["id"]?.GetValue<int>();
|
||||||
|
if (id == null) return; // Salta il record se l'id è null
|
||||||
|
|
||||||
|
var query = @"
|
||||||
|
IF EXISTS (SELECT 1 FROM Bookmaker WHERE bookmaker_id = @bookmaker_id)
|
||||||
|
BEGIN
|
||||||
|
UPDATE Bookmaker SET name = @name WHERE bookmaker_id = @bookmaker_id
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO Bookmaker (bookmaker_id, name) VALUES (@bookmaker_id, @name)
|
||||||
|
END";
|
||||||
|
using (var command = new SqlCommand(query, connection))
|
||||||
|
{
|
||||||
|
command.Parameters.AddWithValue("@bookmaker_id", id);
|
||||||
|
command.Parameters.AddWithValue("@name", bookmaker["name"]?.GetValue<string>() ?? "");
|
||||||
|
command.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("l'upsert del bookmaker", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football.Database
|
||||||
|
{
|
||||||
|
internal class Comparison : HorseRacingPredictor.Football.Manager.Database
|
||||||
|
{
|
||||||
|
public void Upsert(SqlConnection connection, int predictionId, JsonNode comparison)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (comparison == null) return;
|
||||||
|
|
||||||
|
var query = @"
|
||||||
|
IF EXISTS (SELECT 1 FROM Comparison WHERE prediction_id = @prediction_id)
|
||||||
|
BEGIN
|
||||||
|
UPDATE Comparison
|
||||||
|
SET form_home = @form_home,
|
||||||
|
form_away = @form_away,
|
||||||
|
att_home = @att_home,
|
||||||
|
att_away = @att_away,
|
||||||
|
def_home = @def_home,
|
||||||
|
def_away = @def_away,
|
||||||
|
poisson_home = @poisson_home,
|
||||||
|
poisson_away = @poisson_away,
|
||||||
|
h2h_home = @h2h_home,
|
||||||
|
h2h_away = @h2h_away,
|
||||||
|
goals_home = @goals_home,
|
||||||
|
goals_away = @goals_away,
|
||||||
|
total_home = @total_home,
|
||||||
|
total_away = @total_away
|
||||||
|
WHERE prediction_id = @prediction_id
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO Comparison (
|
||||||
|
prediction_id, form_home, form_away, att_home, att_away,
|
||||||
|
def_home, def_away, poisson_home, poisson_away,
|
||||||
|
h2h_home, h2h_away, goals_home, goals_away,
|
||||||
|
total_home, total_away
|
||||||
|
) VALUES (
|
||||||
|
@prediction_id, @form_home, @form_away, @att_home, @att_away,
|
||||||
|
@def_home, @def_away, @poisson_home, @poisson_away,
|
||||||
|
@h2h_home, @h2h_away, @goals_home, @goals_away,
|
||||||
|
@total_home, @total_away
|
||||||
|
)
|
||||||
|
END";
|
||||||
|
|
||||||
|
using (var command = new SqlCommand(query, connection))
|
||||||
|
{
|
||||||
|
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.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("l'upsert del confronto di previsione", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
using System;
|
||||||
|
using System.Data;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football.Database
|
||||||
|
{
|
||||||
|
internal class Fixture : HorseRacingPredictor.Football.Manager.Database
|
||||||
|
{
|
||||||
|
public void Upsert(SqlConnection connection, SqlTransaction transaction, JsonNode fixture)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var id = fixture["id"]?.GetValue<int>();
|
||||||
|
if (id == null) return; // Salta il record se l'id è null
|
||||||
|
|
||||||
|
var query = @"
|
||||||
|
IF EXISTS (SELECT 1 FROM Fixture WHERE fixture_id = @fixture_id)
|
||||||
|
BEGIN
|
||||||
|
UPDATE Fixture SET
|
||||||
|
referee = @referee,
|
||||||
|
timezone = @timezone,
|
||||||
|
date = @date,
|
||||||
|
timestamp = @timestamp,
|
||||||
|
venue_id = @venue_id,
|
||||||
|
status_long = @status_long,
|
||||||
|
status_short = @status_short,
|
||||||
|
status_elapsed = @status_elapsed,
|
||||||
|
status_extra = @status_extra
|
||||||
|
WHERE fixture_id = @fixture_id
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO Fixture (
|
||||||
|
fixture_id,
|
||||||
|
referee,
|
||||||
|
timezone,
|
||||||
|
date,
|
||||||
|
timestamp,
|
||||||
|
venue_id,
|
||||||
|
status_long,
|
||||||
|
status_short,
|
||||||
|
status_elapsed,
|
||||||
|
status_extra
|
||||||
|
) VALUES (
|
||||||
|
@fixture_id,
|
||||||
|
@referee,
|
||||||
|
@timezone,
|
||||||
|
@date,
|
||||||
|
@timestamp,
|
||||||
|
@venue_id,
|
||||||
|
@status_long,
|
||||||
|
@status_short,
|
||||||
|
@status_elapsed,
|
||||||
|
@status_extra
|
||||||
|
)
|
||||||
|
END";
|
||||||
|
using (var command = new SqlCommand(query, connection, transaction))
|
||||||
|
{
|
||||||
|
// Estrai tutti i valori dal JSON con gestione dei valori null
|
||||||
|
var status = fixture["status"];
|
||||||
|
var venue = fixture["venue"];
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Parametri venue
|
||||||
|
int? venueId = venue?["id"]?.GetValue<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>() ?? "");
|
||||||
|
|
||||||
|
int? elapsed = status?["elapsed"]?.GetValue<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);
|
||||||
|
command.Parameters.AddWithValue("@status_extra", extra.HasValue ? (object)extra.Value : DBNull.Value);
|
||||||
|
|
||||||
|
command.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gestione della venue, se necessario
|
||||||
|
if (fixture["venue"] != null && fixture["venue"]["id"] != null)
|
||||||
|
{
|
||||||
|
UpsertVenue(connection, transaction, fixture["venue"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("l'upsert del fixture", ex);
|
||||||
|
throw; // Rilancia l'eccezione per il rollback della transazione
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Metodo di supporto per inserire/aggiornare le informazioni sulla venue
|
||||||
|
/// </summary>
|
||||||
|
private void UpsertVenue(SqlConnection connection, SqlTransaction transaction, JsonNode venue)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var id = venue["id"]?.GetValue<int>();
|
||||||
|
if (id == null) return;
|
||||||
|
|
||||||
|
var query = @"
|
||||||
|
IF EXISTS (SELECT 1 FROM Venue WHERE venue_id = @venue_id)
|
||||||
|
BEGIN
|
||||||
|
UPDATE Venue SET name = @name, city = @city WHERE venue_id = @venue_id
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO Venue (venue_id, name, city) VALUES (@venue_id, @name, @city)
|
||||||
|
END";
|
||||||
|
|
||||||
|
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.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("l'upsert della venue", ex);
|
||||||
|
throw; // Rilancia l'eccezione per il rollback della transazione
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recupera i fixture elaborati dal database con tutte le informazioni correlate
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>DataTable contenente tutte le partite con le relative quote e previsioni</returns>
|
||||||
|
public DataTable GetProcessedFixtures()
|
||||||
|
{
|
||||||
|
var result = new DataTable();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var connection = new System.Data.SqlClient.SqlConnection(_connectionString))
|
||||||
|
{
|
||||||
|
connection.Open();
|
||||||
|
|
||||||
|
string query = @"
|
||||||
|
SELECT
|
||||||
|
f.fixture_id AS ID,
|
||||||
|
l.country AS Paese,
|
||||||
|
l.name AS Campionato,
|
||||||
|
f.date AS [Data / Ora],
|
||||||
|
f.status AS Stato,
|
||||||
|
th.name AS Casa,
|
||||||
|
ta.name AS Trasferta,
|
||||||
|
g.home AS [Goals Casa],
|
||||||
|
g.away AS [Goals Trasferta],
|
||||||
|
o1.value AS [Quota Casa],
|
||||||
|
o2.value AS [Quota Pareggio],
|
||||||
|
o3.value AS [Quota Trasferta],
|
||||||
|
p.winner_name AS Previsione
|
||||||
|
FROM
|
||||||
|
Fixture f
|
||||||
|
JOIN FixtureLeague fl ON f.fixture_id = fl.fixture_id
|
||||||
|
JOIN League l ON fl.league_id = l.league_id
|
||||||
|
JOIN Team th ON th.team_id = f.home_team_id
|
||||||
|
JOIN Team ta ON ta.team_id = f.away_team_id
|
||||||
|
LEFT JOIN Goals g ON f.fixture_id = g.fixture_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT fixture_id, value FROM Odds
|
||||||
|
WHERE bet_id = 1 AND value_name = 'Home' AND bookmaker_id = 8
|
||||||
|
) o1 ON f.fixture_id = o1.fixture_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT fixture_id, value FROM Odds
|
||||||
|
WHERE bet_id = 1 AND value_name = 'Draw' AND bookmaker_id = 8
|
||||||
|
) o2 ON f.fixture_id = o2.fixture_id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT fixture_id, value FROM Odds
|
||||||
|
WHERE bet_id = 1 AND value_name = 'Away' AND bookmaker_id = 8
|
||||||
|
) o3 ON f.fixture_id = o3.fixture_id
|
||||||
|
LEFT JOIN Prediction p ON f.fixture_id = p.fixture_id
|
||||||
|
ORDER BY
|
||||||
|
f.date ASC";
|
||||||
|
|
||||||
|
using (var adapter = new System.Data.SqlClient.SqlDataAdapter(query, connection))
|
||||||
|
{
|
||||||
|
adapter.Fill(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("recupero partite elaborate dal database", ex);
|
||||||
|
// Return empty table in case of error
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football.Database
|
||||||
|
{
|
||||||
|
internal class FixtureLeague : HorseRacingPredictor.Football.Manager.Database
|
||||||
|
{
|
||||||
|
public void Upsert(SqlConnection connection, SqlTransaction transaction, int fixtureId, int leagueId, JsonNode 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 standingsValue = standings.HasValue ? standings.Value.ToString() : "NULL";
|
||||||
|
|
||||||
|
// Log dell'operazione prima di tentare l'upsert
|
||||||
|
LogDebugInfo($"Tentativo di upsert FixtureLeague: fixture_id={fixtureId}, league_id={leagueId}, round='{round}', standings={standingsValue}");
|
||||||
|
|
||||||
|
var query = @"
|
||||||
|
IF EXISTS (SELECT 1 FROM FixtureLeague WHERE fixture_id = @fixture_id AND league_id = @league_id)
|
||||||
|
BEGIN
|
||||||
|
UPDATE FixtureLeague
|
||||||
|
SET round = @round, standings = @standings
|
||||||
|
WHERE fixture_id = @fixture_id AND league_id = @league_id
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO FixtureLeague (fixture_id, league_id, round, standings)
|
||||||
|
VALUES (@fixture_id, @league_id, @round, @standings)
|
||||||
|
END";
|
||||||
|
|
||||||
|
using (var command = new SqlCommand(query, connection, transaction))
|
||||||
|
{
|
||||||
|
command.Parameters.AddWithValue("@fixture_id", fixtureId);
|
||||||
|
command.Parameters.AddWithValue("@league_id", leagueId);
|
||||||
|
command.Parameters.AddWithValue("@round", round);
|
||||||
|
command.Parameters.AddWithValue("@standings", standings.HasValue ? (object)standings.Value : DBNull.Value);
|
||||||
|
|
||||||
|
command.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log di successo dopo l'operazione
|
||||||
|
LogDebugInfo($"Upsert FixtureLeague completato con successo: fixture_id={fixtureId}, league_id={leagueId}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Log dettagliato dell'errore
|
||||||
|
StringBuilder detailedError = new StringBuilder();
|
||||||
|
detailedError.AppendLine($"Errore durante l'upsert della relazione fixture-league:");
|
||||||
|
detailedError.AppendLine($"- fixture_id: {fixtureId}");
|
||||||
|
detailedError.AppendLine($"- league_id: {leagueId}");
|
||||||
|
detailedError.AppendLine($"- round: '{leagueData["round"]?.ToString() ?? "NULL"}'");
|
||||||
|
detailedError.AppendLine($"- standings: {leagueData["standings"]?.ToString() ?? "NULL"}");
|
||||||
|
detailedError.AppendLine($"- leagueData: {leagueData?.ToString() ?? "NULL"}");
|
||||||
|
detailedError.AppendLine($"- Messaggio errore: {ex.Message}");
|
||||||
|
|
||||||
|
if (ex.InnerException != null)
|
||||||
|
{
|
||||||
|
detailedError.AppendLine($"- Inner exception: {ex.InnerException.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Controlla se la fixture esiste
|
||||||
|
CheckIfFixtureExists(connection, transaction, fixtureId, detailedError);
|
||||||
|
|
||||||
|
// Controlla se la league esiste
|
||||||
|
CheckIfLeagueExists(connection, transaction, leagueId, detailedError);
|
||||||
|
|
||||||
|
LogError("l'upsert della relazione fixture-league", new Exception(detailedError.ToString(), ex));
|
||||||
|
throw; // Rilancia l'eccezione per il rollback della transazione
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifica se una fixture esiste nel database e aggiunge l'informazione al log
|
||||||
|
/// </summary>
|
||||||
|
private void CheckIfFixtureExists(SqlConnection connection, SqlTransaction transaction, int fixtureId, StringBuilder logBuilder)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var cmd = new SqlCommand("SELECT COUNT(1) FROM Fixture WHERE fixture_id = @id", connection, transaction))
|
||||||
|
{
|
||||||
|
cmd.Parameters.AddWithValue("@id", fixtureId);
|
||||||
|
int count = Convert.ToInt32(cmd.ExecuteScalar());
|
||||||
|
logBuilder.AppendLine($"- Fixture con ID {fixtureId} {(count > 0 ? "esiste" : "NON esiste")} nel database");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logBuilder.AppendLine($"- Errore durante la verifica dell'esistenza della fixture: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifica se una league esiste nel database e aggiunge l'informazione al log
|
||||||
|
/// </summary>
|
||||||
|
private void CheckIfLeagueExists(SqlConnection connection, SqlTransaction transaction, int leagueId, StringBuilder logBuilder)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var cmd = new SqlCommand("SELECT COUNT(1) FROM League WHERE league_id = @id", connection, transaction))
|
||||||
|
{
|
||||||
|
cmd.Parameters.AddWithValue("@id", leagueId);
|
||||||
|
int count = Convert.ToInt32(cmd.ExecuteScalar());
|
||||||
|
logBuilder.AppendLine($"- League con ID {leagueId} {(count > 0 ? "esiste" : "NON esiste")} nel database");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logBuilder.AppendLine($"- Errore durante la verifica dell'esistenza della league: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logga informazioni di debug (utile per tracciare il flusso di esecuzione)
|
||||||
|
/// </summary>
|
||||||
|
private void LogDebugInfo(string message)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Utilizziamo LogError con exception null come metodo per loggare informazioni di debug
|
||||||
|
LogError("DEBUG INFO", new Exception(message));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignoriamo eventuali errori nel logging di debug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using System;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football.Database
|
||||||
|
{
|
||||||
|
internal class Goals : HorseRacingPredictor.Football.Manager.Database
|
||||||
|
{
|
||||||
|
public void Upsert(SqlConnection connection, JsonNode goals, int fixtureId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Utilizza la tabella Score invece di una tabella Goals che non esiste
|
||||||
|
var query = @"
|
||||||
|
IF EXISTS (SELECT 1 FROM Score WHERE fixture_id = @fixture_id)
|
||||||
|
BEGIN
|
||||||
|
UPDATE Score SET home_goals = @home, away_goals = @away WHERE fixture_id = @fixture_id
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO Score (home_goals, away_goals, fixture_id,
|
||||||
|
home_halftime, away_halftime,
|
||||||
|
home_fulltime, away_fulltime,
|
||||||
|
home_extratime, away_extratime,
|
||||||
|
home_penalty, away_penalty)
|
||||||
|
VALUES (@home, @away, @fixture_id, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||||
|
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("@fixture_id", fixtureId);
|
||||||
|
command.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("l'upsert dei goal", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football.Database
|
||||||
|
{
|
||||||
|
internal class H2H : HorseRacingPredictor.Football.Manager.Database
|
||||||
|
{
|
||||||
|
public void Insert(SqlConnection connection, int predictionId, int h2hFixtureId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var query = @"
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM H2H WHERE prediction_id = @prediction_id AND fixture_id = @fixture_id)
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO H2H (prediction_id, fixture_id)
|
||||||
|
VALUES (@prediction_id, @fixture_id)
|
||||||
|
END";
|
||||||
|
|
||||||
|
using (var command = new SqlCommand(query, connection))
|
||||||
|
{
|
||||||
|
command.Parameters.AddWithValue("@prediction_id", predictionId);
|
||||||
|
command.Parameters.AddWithValue("@fixture_id", h2hFixtureId);
|
||||||
|
command.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("l'inserimento della relazione H2H", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteForPrediction(SqlConnection connection, int predictionId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var query = "DELETE FROM H2H WHERE prediction_id = @prediction_id";
|
||||||
|
using (var command = new SqlCommand(query, connection))
|
||||||
|
{
|
||||||
|
command.Parameters.AddWithValue("@prediction_id", predictionId);
|
||||||
|
command.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("l'eliminazione delle relazioni H2H", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
using System;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football.Database
|
||||||
|
{
|
||||||
|
internal class League : HorseRacingPredictor.Football.Manager.Database
|
||||||
|
{
|
||||||
|
public void Upsert(SqlConnection connection, JsonNode league)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var id = league["id"]?.GetValue<int>();
|
||||||
|
if (id == null) return; // Salta il record se l'id è null
|
||||||
|
|
||||||
|
var query = @"
|
||||||
|
IF EXISTS (SELECT 1 FROM League WHERE league_id = @league_id)
|
||||||
|
BEGIN
|
||||||
|
UPDATE League SET name = @name, country = @country, logo = @logo, flag = @flag, season = @season WHERE league_id = @league_id
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO League (league_id, name, country, logo, flag, season) VALUES (@league_id, @name, @country, @logo, @flag, @season)
|
||||||
|
END";
|
||||||
|
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.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("l'upsert della lega", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Upsert(SqlConnection connection, SqlTransaction transaction, JsonNode league)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var id = league["id"]?.GetValue<int>();
|
||||||
|
if (id == null) return; // Salta il record se l'id è null
|
||||||
|
|
||||||
|
var query = @"
|
||||||
|
IF EXISTS (SELECT 1 FROM League WHERE league_id = @league_id)
|
||||||
|
BEGIN
|
||||||
|
UPDATE League SET name = @name, country = @country, logo = @logo, flag = @flag, season = @season WHERE league_id = @league_id
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO League (league_id, name, country, logo, flag, season) VALUES (@league_id, @name, @country, @logo, @flag, @season)
|
||||||
|
END";
|
||||||
|
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.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("l'upsert della lega", ex);
|
||||||
|
throw; // Rilancia l'eccezione per il rollback della transazione
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
using System;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football.Database
|
||||||
|
{
|
||||||
|
internal class LeagueStats : HorseRacingPredictor.Football.Manager.Database
|
||||||
|
{
|
||||||
|
public int Insert(SqlConnection connection, int? teamId, int? predictionId, bool isHome, JsonNode stats)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Ottieni il prossimo ID di statistiche
|
||||||
|
int statsId = GetNextStatsId(connection);
|
||||||
|
if (statsId <= 0) return 0;
|
||||||
|
|
||||||
|
var query = @"
|
||||||
|
INSERT INTO LeagueStats (
|
||||||
|
stats_id, team_id, prediction_id, is_home, form,
|
||||||
|
fixtures_played_home, fixtures_played_away, fixtures_played_total,
|
||||||
|
wins_home, wins_away, wins_total,
|
||||||
|
draws_home, draws_away, draws_total,
|
||||||
|
loses_home, loses_away, loses_total
|
||||||
|
) VALUES (
|
||||||
|
@stats_id, @team_id, @prediction_id, @is_home, @form,
|
||||||
|
@fixtures_played_home, @fixtures_played_away, @fixtures_played_total,
|
||||||
|
@wins_home, @wins_away, @wins_total,
|
||||||
|
@draws_home, @draws_away, @draws_total,
|
||||||
|
@loses_home, @loses_away, @loses_total
|
||||||
|
)";
|
||||||
|
|
||||||
|
using (var command = new SqlCommand(query, connection))
|
||||||
|
{
|
||||||
|
command.Parameters.AddWithValue("@stats_id", statsId);
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
return statsId;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("l'inserimento delle statistiche di lega", ex);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetNextStatsId(SqlConnection connection)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var query = "SELECT ISNULL(MAX(stats_id), 0) + 1 FROM LeagueStats";
|
||||||
|
using (var command = new SqlCommand(query, connection))
|
||||||
|
{
|
||||||
|
return Convert.ToInt32(command.ExecuteScalar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("il recupero del prossimo ID statistiche", ex);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
using System;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football.Database
|
||||||
|
{
|
||||||
|
internal class Odds : HorseRacingPredictor.Football.Manager.Database
|
||||||
|
{
|
||||||
|
public void Upsert(SqlConnection connection, JsonNode 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);
|
||||||
|
|
||||||
|
// Per ogni bookmaker
|
||||||
|
foreach (var bookmaker in bookmakers.AsArray())
|
||||||
|
{
|
||||||
|
int bookmakerId = bookmaker["id"].GetValue<int>();
|
||||||
|
var bets = bookmaker["bets"];
|
||||||
|
|
||||||
|
// Cerchiamo le quote di tipo Match Winner (1X2)
|
||||||
|
foreach (var bet in bets.AsArray())
|
||||||
|
{
|
||||||
|
// Gestiamo in modo più sicuro il recupero di valori dal JSON
|
||||||
|
string betName = GetStringValueSafe(bet, "name");
|
||||||
|
|
||||||
|
// Log per debug
|
||||||
|
LogError($"Elaborazione bet: '{betName}' per bookmaker {bookmakerId}", null);
|
||||||
|
|
||||||
|
// Determina il tipo di scommessa (assumiamo che esista una tabella BetType)
|
||||||
|
int betTypeId = GetBetTypeId(connection, betName);
|
||||||
|
|
||||||
|
// Se non riusciamo a determinare il tipo di scommessa, continuiamo con la prossima
|
||||||
|
if (betTypeId == 0) continue;
|
||||||
|
|
||||||
|
// Estrai e salva le quote
|
||||||
|
foreach (var value in bet["values"].AsArray())
|
||||||
|
{
|
||||||
|
// Gestiamo in modo più sicuro il recupero di valori dal JSON
|
||||||
|
string valueType = GetStringValueSafe(value, "value");
|
||||||
|
decimal oddValue = GetDecimalValueSafe(value, "odd");
|
||||||
|
|
||||||
|
// Log per debug
|
||||||
|
LogError($"Elaborazione quota: tipo={valueType}, valore={oddValue}", null);
|
||||||
|
|
||||||
|
if (oddValue > 0) // Verifichiamo che il valore sia valido
|
||||||
|
{
|
||||||
|
var query = @"
|
||||||
|
IF EXISTS (SELECT 1 FROM Odd WHERE fixture_id = @fixture_id AND bookmaker_id = @bookmaker_id
|
||||||
|
AND bet_type_id = @bet_type_id AND value = @value)
|
||||||
|
BEGIN
|
||||||
|
UPDATE Odd
|
||||||
|
SET odd = @odd, update_time = @update_time
|
||||||
|
WHERE fixture_id = @fixture_id AND bookmaker_id = @bookmaker_id
|
||||||
|
AND bet_type_id = @bet_type_id AND value = @value
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO Odd (fixture_id, bookmaker_id, bet_type_id, value, odd, update_time)
|
||||||
|
VALUES (@fixture_id, @bookmaker_id, @bet_type_id, @value, @odd, @update_time)
|
||||||
|
END";
|
||||||
|
|
||||||
|
using (var command = new SqlCommand(query, connection))
|
||||||
|
{
|
||||||
|
command.Parameters.AddWithValue("@fixture_id", fixtureId);
|
||||||
|
command.Parameters.AddWithValue("@bookmaker_id", bookmakerId);
|
||||||
|
command.Parameters.AddWithValue("@bet_type_id", betTypeId);
|
||||||
|
command.Parameters.AddWithValue("@value", valueType);
|
||||||
|
command.Parameters.AddWithValue("@odd", oddValue);
|
||||||
|
command.Parameters.AddWithValue("@update_time", DateTime.Now);
|
||||||
|
command.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("l'upsert delle quote", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene l'ID del tipo di scommessa in base al nome
|
||||||
|
/// </summary>
|
||||||
|
private int GetBetTypeId(SqlConnection connection, string betTypeName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Prima verifica se il tipo di scommessa esiste
|
||||||
|
var queryCheck = "SELECT bet_type_id FROM BetType WHERE name = @name";
|
||||||
|
using (var command = new SqlCommand(queryCheck, connection))
|
||||||
|
{
|
||||||
|
command.Parameters.AddWithValue("@name", betTypeName);
|
||||||
|
var result = command.ExecuteScalar();
|
||||||
|
|
||||||
|
if (result != null && result != DBNull.Value)
|
||||||
|
{
|
||||||
|
return Convert.ToInt32(result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Se non esiste, inseriscilo (con un ID progressivo)
|
||||||
|
var queryInsert = @"
|
||||||
|
DECLARE @next_id INT;
|
||||||
|
SELECT @next_id = ISNULL(MAX(bet_type_id), 0) + 1 FROM BetType;
|
||||||
|
|
||||||
|
INSERT INTO BetType (bet_type_id, name)
|
||||||
|
VALUES (@next_id, @name);
|
||||||
|
|
||||||
|
SELECT @next_id;";
|
||||||
|
|
||||||
|
using (var insertCommand = new SqlCommand(queryInsert, connection))
|
||||||
|
{
|
||||||
|
insertCommand.Parameters.AddWithValue("@name", betTypeName);
|
||||||
|
return Convert.ToInt32(insertCommand.ExecuteScalar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError($"determinazione bet_type_id per '{betTypeName}'", ex);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene un valore stringa da un JsonNode in modo sicuro
|
||||||
|
/// </summary>
|
||||||
|
private string GetStringValueSafe(JsonNode node, string propertyName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (node == null || node[propertyName] == null)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
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();
|
||||||
|
else
|
||||||
|
return value.ToString();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError($"estrazione valore stringa per proprietà '{propertyName}'", ex);
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene un valore decimale da un JsonNode in modo sicuro
|
||||||
|
/// </summary>
|
||||||
|
private decimal GetDecimalValueSafe(JsonNode node, string propertyName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (node == null || node[propertyName] == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var value = node[propertyName];
|
||||||
|
|
||||||
|
// Gestiamo diversi tipi di nodi
|
||||||
|
if (value.GetValueKind() == System.Text.Json.JsonValueKind.Number)
|
||||||
|
{
|
||||||
|
// Assicuriamoci che il valore sia esattamente quello originale
|
||||||
|
return value.GetValue<decimal>();
|
||||||
|
}
|
||||||
|
else if (value.GetValueKind() == System.Text.Json.JsonValueKind.String)
|
||||||
|
{
|
||||||
|
// Utilizziamo InvariantCulture per garantire una corretta interpretazione dei decimali
|
||||||
|
if (decimal.TryParse(value.GetValue<string>(),
|
||||||
|
System.Globalization.NumberStyles.Any,
|
||||||
|
System.Globalization.CultureInfo.InvariantCulture,
|
||||||
|
out decimal result))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggiungiamo un log più dettagliato per aiutare il debug
|
||||||
|
LogError($"Impossibile convertire '{value}' (tipo: {value.GetValueKind()}) in decimal per proprietà '{propertyName}'", null);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError($"estrazione valore decimal per proprietà '{propertyName}'", ex);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
using System;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football.Database
|
||||||
|
{
|
||||||
|
internal class Prediction : HorseRacingPredictor.Football.Manager.Database
|
||||||
|
{
|
||||||
|
public void Upsert(SqlConnection connection, JsonNode predictions, int fixtureId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Estrazione dei dati dalla risposta JSON
|
||||||
|
string advice = predictions["advice"]?.GetValue<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>();
|
||||||
|
|
||||||
|
// Query per l'upsert con tutti i campi disponibili
|
||||||
|
var query = @"
|
||||||
|
IF EXISTS (SELECT 1 FROM Prediction WHERE fixture_id = @fixture_id)
|
||||||
|
BEGIN
|
||||||
|
UPDATE Prediction
|
||||||
|
SET winner_id = @winner_id,
|
||||||
|
winner_name = @winner_name,
|
||||||
|
winner_comment = @winner_comment,
|
||||||
|
win_or_draw = @win_or_draw,
|
||||||
|
under_over = @under_over,
|
||||||
|
goals_home = @goals_home,
|
||||||
|
goals_away = @goals_away,
|
||||||
|
advice = @advice,
|
||||||
|
percent_home = @percent_home,
|
||||||
|
percent_draw = @percent_draw,
|
||||||
|
percent_away = @percent_away
|
||||||
|
WHERE fixture_id = @fixture_id
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO Prediction (
|
||||||
|
prediction_id,
|
||||||
|
fixture_id,
|
||||||
|
winner_id,
|
||||||
|
winner_name,
|
||||||
|
winner_comment,
|
||||||
|
win_or_draw,
|
||||||
|
under_over,
|
||||||
|
goals_home,
|
||||||
|
goals_away,
|
||||||
|
advice,
|
||||||
|
percent_home,
|
||||||
|
percent_draw,
|
||||||
|
percent_away
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
NEXT VALUE FOR prediction_seq,
|
||||||
|
@fixture_id,
|
||||||
|
@winner_id,
|
||||||
|
@winner_name,
|
||||||
|
@winner_comment,
|
||||||
|
@win_or_draw,
|
||||||
|
@under_over,
|
||||||
|
@goals_home,
|
||||||
|
@goals_away,
|
||||||
|
@advice,
|
||||||
|
@percent_home,
|
||||||
|
@percent_draw,
|
||||||
|
@percent_away
|
||||||
|
)
|
||||||
|
END";
|
||||||
|
|
||||||
|
using (var command = new SqlCommand(query, connection))
|
||||||
|
{
|
||||||
|
// Fixture ID
|
||||||
|
command.Parameters.AddWithValue("@fixture_id", fixtureId);
|
||||||
|
|
||||||
|
// Dati vincitore
|
||||||
|
command.Parameters.AddWithValue("@winner_id", winnerId.HasValue ? (object)winnerId.Value : DBNull.Value);
|
||||||
|
command.Parameters.AddWithValue("@winner_name", string.IsNullOrEmpty(winnerName) ? DBNull.Value : (object)winnerName);
|
||||||
|
command.Parameters.AddWithValue("@winner_comment", string.IsNullOrEmpty(winnerComment) ? DBNull.Value : (object)winnerComment);
|
||||||
|
|
||||||
|
// Altri dati di previsione
|
||||||
|
command.Parameters.AddWithValue("@win_or_draw", winOrDraw.HasValue ? (object)winOrDraw.Value : DBNull.Value);
|
||||||
|
command.Parameters.AddWithValue("@under_over", string.IsNullOrEmpty(underOver) ? DBNull.Value : (object)underOver);
|
||||||
|
command.Parameters.AddWithValue("@goals_home", string.IsNullOrEmpty(goalsHome) ? DBNull.Value : (object)goalsHome);
|
||||||
|
command.Parameters.AddWithValue("@goals_away", string.IsNullOrEmpty(goalsAway) ? DBNull.Value : (object)goalsAway);
|
||||||
|
command.Parameters.AddWithValue("@advice", advice); // Sappiamo che non è null grazie al controllo iniziale
|
||||||
|
|
||||||
|
// Percentuali
|
||||||
|
command.Parameters.AddWithValue("@percent_home", string.IsNullOrEmpty(percentHome) ? DBNull.Value : (object)percentHome);
|
||||||
|
command.Parameters.AddWithValue("@percent_draw", string.IsNullOrEmpty(percentDraw) ? DBNull.Value : (object)percentDraw);
|
||||||
|
command.Parameters.AddWithValue("@percent_away", string.IsNullOrEmpty(percentAway) ? DBNull.Value : (object)percentAway);
|
||||||
|
|
||||||
|
command.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("l'upsert della previsione", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using System;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football.Database
|
||||||
|
{
|
||||||
|
internal class Score : HorseRacingPredictor.Football.Manager.Database
|
||||||
|
{
|
||||||
|
public void Upsert(SqlConnection connection, JsonNode score, int fixtureId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var query = @"
|
||||||
|
IF EXISTS (SELECT 1 FROM Score WHERE fixture_id = @fixture_id)
|
||||||
|
BEGIN
|
||||||
|
UPDATE Score SET home_halftime = @home_halftime, away_halftime = @away_halftime,
|
||||||
|
home_fulltime = @home_fulltime, away_fulltime = @away_fulltime,
|
||||||
|
home_extratime = @home_extratime, away_extratime = @away_extratime,
|
||||||
|
home_penalty = @home_penalty, away_penalty = @away_penalty
|
||||||
|
WHERE fixture_id = @fixture_id
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO Score (home_halftime, away_halftime, home_fulltime, away_fulltime, home_extratime, away_extratime, home_penalty, away_penalty, fixture_id)
|
||||||
|
VALUES (@home_halftime, @away_halftime, @home_fulltime, @away_fulltime, @home_extratime, @away_extratime, @home_penalty, @away_penalty, @fixture_id)
|
||||||
|
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("@fixture_id", fixtureId);
|
||||||
|
command.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("l'upsert del punteggio", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
using System;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football.Database
|
||||||
|
{
|
||||||
|
internal class Team : HorseRacingPredictor.Football.Manager.Database
|
||||||
|
{
|
||||||
|
public void UpsertTeams(SqlConnection connection, SqlTransaction transaction, JsonNode teams, int fixtureId)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var homeTeamId = teams["home"]["id"]?.GetValue<int>();
|
||||||
|
var awayTeamId = teams["away"]["id"]?.GetValue<int>();
|
||||||
|
if (homeTeamId == null || awayTeamId == null) return; // Salta il record se uno degli id è null
|
||||||
|
|
||||||
|
var queryTeam = @"
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM Team WHERE team_id = @team_id)
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO Team (team_id, name, logo) VALUES (@team_id, @name, @logo)
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE Team SET name = @name, logo = @logo WHERE team_id = @team_id
|
||||||
|
END";
|
||||||
|
|
||||||
|
using (var command = new SqlCommand(queryTeam, connection, transaction))
|
||||||
|
{
|
||||||
|
var homeTeam = teams["home"];
|
||||||
|
var awayTeam = teams["away"];
|
||||||
|
|
||||||
|
// 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.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.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggiungiamo la relazione fixture-team solo se fixtureId è valido (non 0)
|
||||||
|
if (fixtureId > 0)
|
||||||
|
{
|
||||||
|
var queryFixtureTeam = @"
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM FixtureTeams WHERE fixture_id = @fixture_id)
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO FixtureTeams (fixture_id, home_team_id, away_team_id, home_winner, away_winner)
|
||||||
|
VALUES (@fixture_id, @home_team_id, @away_team_id, NULL, NULL)
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
UPDATE FixtureTeams SET home_team_id = @home_team_id, away_team_id = @away_team_id
|
||||||
|
WHERE fixture_id = @fixture_id
|
||||||
|
END";
|
||||||
|
|
||||||
|
using (var command = new SqlCommand(queryFixtureTeam, connection, transaction))
|
||||||
|
{
|
||||||
|
// Upsert fixture-team relationship
|
||||||
|
command.Parameters.AddWithValue("@fixture_id", fixtureId);
|
||||||
|
command.Parameters.AddWithValue("@home_team_id", homeTeamId);
|
||||||
|
command.Parameters.AddWithValue("@away_team_id", awayTeamId);
|
||||||
|
command.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("l'upsert delle squadre", ex);
|
||||||
|
throw; // Rilancia l'eccezione per il rollback della transazione
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football.Database
|
||||||
|
{
|
||||||
|
internal class TeamStats : HorseRacingPredictor.Football.Manager.Database
|
||||||
|
{
|
||||||
|
public int Insert(SqlConnection connection, int? teamId, int? predictionId, bool isHome, JsonNode stats)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Ottieni il prossimo ID di statistiche
|
||||||
|
int statsId = GetNextStatsId(connection);
|
||||||
|
if (statsId <= 0) return 0;
|
||||||
|
|
||||||
|
var query = @"
|
||||||
|
INSERT INTO TeamStats (
|
||||||
|
stats_id, team_id, prediction_id, is_home, played, form,
|
||||||
|
att, def, goals_for_total, goals_for_average,
|
||||||
|
goals_against_total, goals_against_average
|
||||||
|
) VALUES (
|
||||||
|
@stats_id, @team_id, @prediction_id, @is_home, @played, @form,
|
||||||
|
@att, @def, @goals_for_total, @goals_for_average,
|
||||||
|
@goals_against_total, @goals_against_average
|
||||||
|
)";
|
||||||
|
|
||||||
|
using (var command = new SqlCommand(query, connection))
|
||||||
|
{
|
||||||
|
command.Parameters.AddWithValue("@stats_id", statsId);
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
return statsId;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("l'inserimento delle statistiche di squadra", ex);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetNextStatsId(SqlConnection connection)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var query = "SELECT ISNULL(MAX(stats_id), 0) + 1 FROM TeamStats";
|
||||||
|
using (var command = new SqlCommand(query, connection))
|
||||||
|
{
|
||||||
|
return Convert.ToInt32(command.ExecuteScalar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("il recupero del prossimo ID statistiche", ex);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,852 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using RestSharp;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Classe centralizzata per la gestione delle API di Football
|
||||||
|
/// </summary>
|
||||||
|
public class Main
|
||||||
|
{
|
||||||
|
private readonly Manager.Database _database;
|
||||||
|
private readonly API.Prediction _predictionManager;
|
||||||
|
private readonly API.Fixture _fixtureAPI;
|
||||||
|
private readonly API.Odds _oddsAPI;
|
||||||
|
|
||||||
|
// Repository utilizzati per la gestione dei dati
|
||||||
|
private readonly Football.Database.League _leagueRepository;
|
||||||
|
private readonly Football.Database.Fixture _fixtureRepository;
|
||||||
|
private readonly Football.Database.Team _teamRepository;
|
||||||
|
private readonly Football.Database.Goals _goalsRepository;
|
||||||
|
private readonly Football.Database.Score _scoreRepository;
|
||||||
|
private readonly Football.Database.Odds _oddsRepository;
|
||||||
|
private readonly Football.Database.Prediction _predictionRepository;
|
||||||
|
|
||||||
|
// Nuovi repository aggiunti
|
||||||
|
private readonly Football.Database.BetType _betTypeRepository;
|
||||||
|
private readonly Football.Database.Bookmaker _bookmakerRepository;
|
||||||
|
private readonly Football.Database.FixtureLeague _fixtureLeagueRepository;
|
||||||
|
private readonly Football.Database.Comparison _comparisonRepository;
|
||||||
|
private readonly Football.Database.H2H _h2hRepository;
|
||||||
|
private readonly Football.Database.LeagueStats _leagueStatsRepository;
|
||||||
|
private readonly Football.Database.TeamStats _teamStatsRepository;
|
||||||
|
|
||||||
|
// Aggiungi il repository per le risposte API
|
||||||
|
private readonly Football.Database.APIResponse _apiResponseRepository;
|
||||||
|
|
||||||
|
public Main()
|
||||||
|
{
|
||||||
|
_database = new Manager.Database();
|
||||||
|
_predictionManager = new API.Prediction();
|
||||||
|
_fixtureAPI = new API.Fixture();
|
||||||
|
_oddsAPI = new API.Odds();
|
||||||
|
|
||||||
|
// Inizializzazione dei repository qui invece che nella classe Database
|
||||||
|
_leagueRepository = new Football.Database.League();
|
||||||
|
_fixtureRepository = new Football.Database.Fixture();
|
||||||
|
_teamRepository = new Football.Database.Team();
|
||||||
|
_goalsRepository = new Football.Database.Goals();
|
||||||
|
_scoreRepository = new Football.Database.Score();
|
||||||
|
_oddsRepository = new Football.Database.Odds();
|
||||||
|
_predictionRepository = new Football.Database.Prediction();
|
||||||
|
|
||||||
|
// Inizializzazione dei nuovi repository
|
||||||
|
_betTypeRepository = new Football.Database.BetType();
|
||||||
|
_bookmakerRepository = new Football.Database.Bookmaker();
|
||||||
|
_fixtureLeagueRepository = new Football.Database.FixtureLeague();
|
||||||
|
_comparisonRepository = new Football.Database.Comparison();
|
||||||
|
_h2hRepository = new Football.Database.H2H();
|
||||||
|
_leagueStatsRepository = new Football.Database.LeagueStats();
|
||||||
|
_teamStatsRepository = new Football.Database.TeamStats();
|
||||||
|
|
||||||
|
// Inizializzazione del repository per le risposte API
|
||||||
|
_apiResponseRepository = new Football.Database.APIResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Metodo centralizzato per ottenere tutte le partite e le relative quote per una data specifica
|
||||||
|
/// Utilizzo della tabella API_Response come intermediario
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="date">Data per cui recuperare le partite</param>
|
||||||
|
/// <param name="progressCallback">Callback per aggiornare il progresso dell'operazione</param>
|
||||||
|
/// <param name="statusCallback">Callback per aggiornare lo stato dell'operazione</param>
|
||||||
|
/// <returns>DataTable contenente tutte le partite con le relative quote (quando disponibili)</returns>
|
||||||
|
public DataTable GetFixturesAndOdds(DateTime date, IProgress<int> progressCallback = null, IProgress<string> statusCallback = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Prima scarica i dati
|
||||||
|
DownloadFixturesAndOdds(date, progressCallback, statusCallback).Wait();
|
||||||
|
|
||||||
|
// Poi importa i dati
|
||||||
|
return ImportFromApiResponses(progressCallback, statusCallback);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_database.LogError("recupero centralizzato di partite e quote", ex);
|
||||||
|
statusCallback?.Report($"Errore: {ex.Message}");
|
||||||
|
return CreateEmptyResultTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scarica i dati dalle API e li salva nella tabella di frontiera API_Response
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="date">Data per cui recuperare le partite</param>
|
||||||
|
/// <param name="progressCallback">Callback per aggiornare il progresso dell'operazione</param>
|
||||||
|
/// <param name="statusCallback">Callback per aggiornare lo stato dell'operazione</param>
|
||||||
|
public async Task DownloadFixturesAndOdds(DateTime date, IProgress<int> progressCallback = null, IProgress<string> statusCallback = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Step 1: Informare l'utente che stiamo ottenendo le partite
|
||||||
|
statusCallback?.Report("Scaricamento delle partite in corso...");
|
||||||
|
progressCallback?.Report(0);
|
||||||
|
|
||||||
|
// Step 2: Recuperare tutte le partite per la data selezionata
|
||||||
|
var fixturesResponse = GetFixtures(date);
|
||||||
|
progressCallback?.Report(20);
|
||||||
|
|
||||||
|
// Step 3: Informare l'utente che stiamo recuperando le quote
|
||||||
|
statusCallback?.Report("Scaricamento delle quote in corso...");
|
||||||
|
progressCallback?.Report(40);
|
||||||
|
|
||||||
|
// Step 4: Recuperare le quote per la data selezionata
|
||||||
|
var oddsResponses = GetOdds(date);
|
||||||
|
progressCallback?.Report(60);
|
||||||
|
|
||||||
|
// Step 5: Informare l'utente che stiamo recuperando le previsioni
|
||||||
|
statusCallback?.Report("Scaricamento delle previsioni in corso...");
|
||||||
|
progressCallback?.Report(70);
|
||||||
|
|
||||||
|
// Step 6: Recuperare le previsioni per le partite
|
||||||
|
await GetPredictionsForFixtures(fixturesResponse);
|
||||||
|
progressCallback?.Report(90);
|
||||||
|
|
||||||
|
// Step 7: Operazione completata
|
||||||
|
statusCallback?.Report("Scaricamento completato. Dati salvati nella tabella di frontiera.");
|
||||||
|
progressCallback?.Report(100);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_database.LogError("scaricamento centralizzato di partite e quote", ex);
|
||||||
|
statusCallback?.Report($"Errore: {ex.Message}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recupera le previsioni per le partite specificate
|
||||||
|
/// </summary>
|
||||||
|
private async Task GetPredictionsForFixtures(RestResponse fixturesResponse)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Verifica che la risposta sia valida
|
||||||
|
if (fixturesResponse == null || !fixturesResponse.IsSuccessful || string.IsNullOrEmpty(fixturesResponse.Content))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = JsonDocument.Parse(fixturesResponse.Content).RootElement;
|
||||||
|
|
||||||
|
// Verifica che la risposta contenga dati validi
|
||||||
|
if (!json.TryGetProperty("response", out var responseElement))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per ogni partita, recupera la previsione
|
||||||
|
foreach (var item in responseElement.EnumerateArray())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int fixtureId = item.GetProperty("fixture").GetProperty("id").GetInt32();
|
||||||
|
|
||||||
|
// Utilizza il prediction manager per ottenere la previsione
|
||||||
|
_predictionManager.GetPredictionByFixture(fixtureId);
|
||||||
|
|
||||||
|
// Breve pausa per non sovraccaricare l'API
|
||||||
|
await Task.Delay(100);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_database.LogError($"recupero previsione", ex);
|
||||||
|
// Continua con la prossima partita
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_database.LogError("recupero previsioni per partite", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Importa i dati dalla tabella di frontiera API_Response alle tabelle finali
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="progressCallback">Callback per aggiornare il progresso dell'operazione</param>
|
||||||
|
/// <param name="statusCallback">Callback per aggiornare lo stato dell'operazione</param>
|
||||||
|
/// <returns>DataTable contenente tutte le partite con le relative quote (quando disponibili)</returns>
|
||||||
|
public DataTable ImportFromApiResponses(IProgress<int> progressCallback = null, IProgress<string> statusCallback = null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Step 1: Informare l'utente che stiamo elaborando le risposte API
|
||||||
|
statusCallback?.Report("Elaborazione delle risposte API in corso...");
|
||||||
|
progressCallback?.Report(0);
|
||||||
|
|
||||||
|
// Step 2: Elaborare le risposte API non elaborate dal database
|
||||||
|
ProcessUnprocessedApiResponses();
|
||||||
|
progressCallback?.Report(50);
|
||||||
|
|
||||||
|
// Step 3: Creare un DataTable vuoto per i fixture
|
||||||
|
var fixturesTable = CreateEmptyFixturesDataTable();
|
||||||
|
|
||||||
|
// Step 4: Recuperare i dati elaborati dal database per visualizzarli
|
||||||
|
statusCallback?.Report("Preparazione dei dati per la visualizzazione...");
|
||||||
|
var combinedTable = GetProcessedFixturesFromDatabase();
|
||||||
|
progressCallback?.Report(90);
|
||||||
|
|
||||||
|
// Step 5: Operazione completata
|
||||||
|
statusCallback?.Report("Importazione completata.");
|
||||||
|
progressCallback?.Report(100);
|
||||||
|
|
||||||
|
return combinedTable;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_database.LogError("importazione centralizzata di partite e quote", ex);
|
||||||
|
statusCallback?.Report($"Errore: {ex.Message}");
|
||||||
|
return CreateEmptyResultTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recupera le partite per la data specificata utilizzando API.Fixture
|
||||||
|
/// </summary>
|
||||||
|
private RestResponse GetFixtures(DateTime date)
|
||||||
|
{
|
||||||
|
return _fixtureAPI.GetFixturesByDate(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recupera le quote per la data specificata (potenzialmente più pagine) utilizzando API.Odds
|
||||||
|
/// </summary>
|
||||||
|
private List<RestResponse> GetOdds(DateTime date)
|
||||||
|
{
|
||||||
|
var responses = new List<RestResponse>();
|
||||||
|
|
||||||
|
int currentPage = 1;
|
||||||
|
int maxPages = 3; // Limita a 3 pagine per evitare troppe chiamate API
|
||||||
|
bool hasMorePages = true;
|
||||||
|
|
||||||
|
while (hasMorePages && currentPage <= maxPages)
|
||||||
|
{
|
||||||
|
var response = _oddsAPI.GetOddsByDate(date, 8, currentPage); // ID 8 = Bet365
|
||||||
|
responses.Add(response);
|
||||||
|
|
||||||
|
// Controlla se ci sono altre pagine
|
||||||
|
var json = JsonDocument.Parse(response.Content).RootElement;
|
||||||
|
if (json.TryGetProperty("paging", out var pagingElement) &&
|
||||||
|
pagingElement.TryGetProperty("total", out var totalElement))
|
||||||
|
{
|
||||||
|
int totalPages = totalElement.GetInt32();
|
||||||
|
hasMorePages = currentPage < totalPages;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hasMorePages = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPage++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return responses;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Elabora i dati JSON e li inserisce nel database utilizzando transazioni e disabilitazione temporanea dei vincoli
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsonResponse">Risposta JSON da elaborare</param>
|
||||||
|
public void ProcessAndInsertData(string jsonResponse)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jsonObject = JsonNode.Parse(jsonResponse);
|
||||||
|
|
||||||
|
_database.ExecuteTransactionalQuery("l'elaborazione dei dati calcistici", (connection, transaction) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Disabilita temporaneamente i vincoli di chiave esterna
|
||||||
|
((Manager.Database)_database).DisableAllConstraints(connection, transaction);
|
||||||
|
|
||||||
|
// FASE 1: Inserisci le leghe
|
||||||
|
foreach (var responseItem in jsonObject["response"].AsArray())
|
||||||
|
{
|
||||||
|
var league = responseItem["league"];
|
||||||
|
if (league != null && league["id"] != null)
|
||||||
|
{
|
||||||
|
_leagueRepository.Upsert(connection, transaction, league);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FASE 2: Inserisci i bookmakers e i tipi di scommessa
|
||||||
|
foreach (var responseItem in jsonObject["response"].AsArray())
|
||||||
|
{
|
||||||
|
if (responseItem["bookmakers"] != null)
|
||||||
|
{
|
||||||
|
foreach (var bookmaker in responseItem["bookmakers"].AsArray())
|
||||||
|
{
|
||||||
|
if (bookmaker["id"] != null)
|
||||||
|
{
|
||||||
|
_bookmakerRepository.Upsert(connection, bookmaker);
|
||||||
|
|
||||||
|
// Tipi di scommessa
|
||||||
|
if (bookmaker["bets"] != null)
|
||||||
|
{
|
||||||
|
foreach (var bet in bookmaker["bets"].AsArray())
|
||||||
|
{
|
||||||
|
if (bet["id"] != null && bet["name"] != null)
|
||||||
|
{
|
||||||
|
_betTypeRepository.Upsert(connection, bet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FASE 3: Inserisci le squadre (senza relazione con fixture)
|
||||||
|
foreach (var responseItem in jsonObject["response"].AsArray())
|
||||||
|
{
|
||||||
|
var teams = responseItem["teams"];
|
||||||
|
if (teams != null && teams["home"] != null && teams["away"] != null &&
|
||||||
|
teams["home"]["id"] != null && teams["away"]["id"] != null)
|
||||||
|
{
|
||||||
|
// Usa 0 come placeholder per fixtureId - in questa fase inseriamo solo le squadre
|
||||||
|
_teamRepository.UpsertTeams(connection, transaction, teams, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FASE 4: Inserisci i fixture e venue
|
||||||
|
foreach (var responseItem in jsonObject["response"].AsArray())
|
||||||
|
{
|
||||||
|
var fixture = responseItem["fixture"];
|
||||||
|
if (fixture != null && fixture["id"] != null)
|
||||||
|
{
|
||||||
|
_fixtureRepository.Upsert(connection, transaction, fixture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FASE 5: Inserisci relazioni tra entità e dati dipendenti
|
||||||
|
foreach (var responseItem in jsonObject["response"].AsArray())
|
||||||
|
{
|
||||||
|
int? fixtureId = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fixture = responseItem["fixture"];
|
||||||
|
if (fixture != null && fixture["id"] != null)
|
||||||
|
{
|
||||||
|
fixtureId = fixture["id"].GetValue<int>();
|
||||||
|
|
||||||
|
// Relazioni fixture-team
|
||||||
|
var teams = responseItem["teams"];
|
||||||
|
if (teams != null && teams["home"] != null && teams["away"] != null &&
|
||||||
|
teams["home"]["id"] != null && teams["away"]["id"] != null)
|
||||||
|
{
|
||||||
|
_teamRepository.UpsertTeams(connection, transaction, teams, fixtureId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Relazioni fixture-league
|
||||||
|
var league = responseItem["league"];
|
||||||
|
if (league != null && league["id"] != null)
|
||||||
|
{
|
||||||
|
int leagueId = league["id"].GetValue<int>();
|
||||||
|
_fixtureLeagueRepository.Upsert(connection, transaction, fixtureId.Value, leagueId, league);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Goals
|
||||||
|
var goals = responseItem["goals"];
|
||||||
|
if (goals != null)
|
||||||
|
{
|
||||||
|
_goalsRepository.Upsert(connection, goals, fixtureId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Score
|
||||||
|
var score = responseItem["score"];
|
||||||
|
if (score != null)
|
||||||
|
{
|
||||||
|
_scoreRepository.Upsert(connection, score, fixtureId.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_database.LogError($"inserimento dati dipendenti per fixture {fixtureId}", ex);
|
||||||
|
// Continuiamo con il prossimo elemento, non interrompiamo l'intera transazione
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FASE 6: Inserisci dati che richiedono fixture e teams: quote e previsioni
|
||||||
|
foreach (var responseItem in jsonObject["response"].AsArray())
|
||||||
|
{
|
||||||
|
int? fixtureId = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fixture = responseItem["fixture"];
|
||||||
|
if (fixture != null && fixture["id"] != null)
|
||||||
|
{
|
||||||
|
fixtureId = fixture["id"].GetValue<int>();
|
||||||
|
|
||||||
|
// Quote
|
||||||
|
if (responseItem["bookmakers"] != null)
|
||||||
|
{
|
||||||
|
_oddsRepository.Upsert(connection, responseItem["bookmakers"], fixtureId.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Previsioni
|
||||||
|
var predictions = responseItem["predictions"];
|
||||||
|
if (predictions != null)
|
||||||
|
{
|
||||||
|
_predictionRepository.Upsert(connection, predictions, fixtureId.Value);
|
||||||
|
|
||||||
|
// Recupera ID previsione
|
||||||
|
int? predictionId = null;
|
||||||
|
using (var cmd = new SqlCommand("SELECT prediction_id FROM Prediction WHERE fixture_id = @fixture_id", connection, transaction))
|
||||||
|
{
|
||||||
|
cmd.Parameters.AddWithValue("@fixture_id", fixtureId.Value);
|
||||||
|
var result = cmd.ExecuteScalar();
|
||||||
|
if (result != null && result != DBNull.Value)
|
||||||
|
{
|
||||||
|
predictionId = Convert.ToInt32(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (predictionId.HasValue)
|
||||||
|
{
|
||||||
|
// Confronto
|
||||||
|
if (predictions["comparison"] != null)
|
||||||
|
{
|
||||||
|
_comparisonRepository.Upsert(connection, predictionId.Value, predictions["comparison"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Head-to-head
|
||||||
|
if (predictions["h2h"] != null && predictions["h2h"].AsArray().Any())
|
||||||
|
{
|
||||||
|
_h2hRepository.DeleteForPrediction(connection, predictionId.Value);
|
||||||
|
|
||||||
|
foreach (var h2hFixture in predictions["h2h"].AsArray())
|
||||||
|
{
|
||||||
|
if (h2hFixture["fixture"] != null && h2hFixture["fixture"]["id"] != null)
|
||||||
|
{
|
||||||
|
_h2hRepository.Insert(connection, predictionId.Value, h2hFixture["fixture"]["id"].GetValue<int>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Statistiche
|
||||||
|
var teams = responseItem["teams"];
|
||||||
|
if (predictions["teams"] != null && teams != null)
|
||||||
|
{
|
||||||
|
// Home team stats
|
||||||
|
if (teams["home"] != null && teams["home"]["id"] != null && predictions["teams"]["home"] != null)
|
||||||
|
{
|
||||||
|
int homeTeamId = teams["home"]["id"].GetValue<int>();
|
||||||
|
|
||||||
|
if (predictions["teams"]["home"]["team"] != null)
|
||||||
|
{
|
||||||
|
_teamStatsRepository.Insert(connection, homeTeamId, predictionId.Value, true,
|
||||||
|
predictions["teams"]["home"]["team"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (predictions["teams"]["home"]["league"] != null)
|
||||||
|
{
|
||||||
|
_leagueStatsRepository.Insert(connection, homeTeamId, predictionId.Value, true,
|
||||||
|
predictions["teams"]["home"]["league"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Away team stats
|
||||||
|
if (teams["away"] != null && teams["away"]["id"] != null && predictions["teams"]["away"] != null)
|
||||||
|
{
|
||||||
|
int awayTeamId = teams["away"]["id"].GetValue<int>();
|
||||||
|
|
||||||
|
if (predictions["teams"]["away"]["team"] != null)
|
||||||
|
{
|
||||||
|
_teamStatsRepository.Insert(connection, awayTeamId, predictionId.Value, false,
|
||||||
|
predictions["teams"]["away"]["team"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (predictions["teams"]["away"]["league"] != null)
|
||||||
|
{
|
||||||
|
_leagueStatsRepository.Insert(connection, awayTeamId, predictionId.Value, false,
|
||||||
|
predictions["teams"]["away"]["league"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_database.LogError($"inserimento previsioni e quote per fixture {fixtureId}", ex);
|
||||||
|
// Continuiamo con il prossimo elemento
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Riattiva i vincoli
|
||||||
|
((Manager.Database)_database).EnableAllConstraints(connection, transaction);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_database.LogError("processamento dati in transazione", ex);
|
||||||
|
throw; // Rilancia l'eccezione per il rollback della transazione
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_database.LogError("l'elaborazione dei dati calcistici", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Aggiorna il database con i dati di partite e quote utilizzando le classi del namespace Football.Database
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateDatabase(RestResponse fixturesResponse, List<RestResponse> oddsResponses)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// In questo metodo non elaboriamo più direttamente le risposte
|
||||||
|
// Le risposte sono già state salvate nel database dalla classe API
|
||||||
|
// e verranno elaborate dal metodo ProcessUnprocessedApiResponses
|
||||||
|
|
||||||
|
// Processa le risposte non elaborate
|
||||||
|
ProcessUnprocessedApiResponses();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_database.LogError("aggiornamento database", ex);
|
||||||
|
throw; // Propaga l'eccezione per gestirla al livello superiore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Crea un DataTable vuoto per i fixture
|
||||||
|
/// </summary>
|
||||||
|
private DataTable CreateEmptyFixturesDataTable()
|
||||||
|
{
|
||||||
|
var dataTable = new DataTable();
|
||||||
|
|
||||||
|
// Aggiungi colonne
|
||||||
|
dataTable.Columns.Add("ID", typeof(int));
|
||||||
|
dataTable.Columns.Add("Paese", typeof(string));
|
||||||
|
dataTable.Columns.Add("Campionato", typeof(string));
|
||||||
|
dataTable.Columns.Add("Data / Ora", typeof(DateTime));
|
||||||
|
dataTable.Columns.Add("Stato", typeof(string));
|
||||||
|
dataTable.Columns.Add("Casa", typeof(string));
|
||||||
|
dataTable.Columns.Add("Trasferta", typeof(string));
|
||||||
|
dataTable.Columns.Add("Goals Casa", typeof(int));
|
||||||
|
dataTable.Columns.Add("Goals Trasferta", typeof(int));
|
||||||
|
dataTable.Columns.Add("Quota Casa", typeof(string));
|
||||||
|
dataTable.Columns.Add("Quota Pareggio", typeof(string));
|
||||||
|
dataTable.Columns.Add("Quota Trasferta", typeof(string));
|
||||||
|
dataTable.Columns.Add("Previsione", typeof(string));
|
||||||
|
|
||||||
|
return dataTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Crea un DataTable con le partite dalla risposta API
|
||||||
|
/// </summary>
|
||||||
|
private DataTable CreateFixturesDataTable(RestResponse response)
|
||||||
|
{
|
||||||
|
var dataTable = CreateEmptyFixturesDataTable();
|
||||||
|
|
||||||
|
// Verifica che la risposta sia valida
|
||||||
|
if (response == null || !response.IsSuccessful || string.IsNullOrEmpty(response.Content))
|
||||||
|
{
|
||||||
|
return dataTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = JsonDocument.Parse(response.Content).RootElement;
|
||||||
|
|
||||||
|
// Verifica che la risposta contenga dati validi
|
||||||
|
if (!json.TryGetProperty("response", out var responseElement))
|
||||||
|
{
|
||||||
|
return dataTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggiungi righe
|
||||||
|
foreach (var item in responseElement.EnumerateArray())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
// 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["Previsione"] = DBNull.Value;
|
||||||
|
|
||||||
|
dataTable.Rows.Add(row);
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Combina i dati delle partite con le quote
|
||||||
|
/// </summary>
|
||||||
|
private DataTable CombineFixturesAndOdds(DataTable fixturesTable, List<RestResponse> oddsResponses)
|
||||||
|
{
|
||||||
|
// Crea una copia del DataTable delle partite
|
||||||
|
var combinedTable = fixturesTable.Copy();
|
||||||
|
|
||||||
|
// Se non ci sono risposte di quote o la tabella delle partite è vuota, ritorna la tabella originale
|
||||||
|
if (oddsResponses == null || oddsResponses.Count == 0 || combinedTable.Rows.Count == 0)
|
||||||
|
{
|
||||||
|
return combinedTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Elabora ogni risposta delle quote
|
||||||
|
foreach (var response in oddsResponses)
|
||||||
|
{
|
||||||
|
if (!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
|
||||||
|
{
|
||||||
|
int fixtureId = item.GetProperty("fixture").GetProperty("id").GetInt32();
|
||||||
|
|
||||||
|
// Trova la riga corrispondente nella tabella delle partite
|
||||||
|
DataRow[] matchingRows = combinedTable.Select($"ID = {fixtureId}");
|
||||||
|
if (matchingRows.Length == 0) continue;
|
||||||
|
|
||||||
|
DataRow row = matchingRows[0];
|
||||||
|
|
||||||
|
// Cerca le quote del bookmaker (assumiamo Bet365 con ID 8)
|
||||||
|
if (item.TryGetProperty("bookmakers", out var bookmakersElement))
|
||||||
|
{
|
||||||
|
foreach (var bookmaker in bookmakersElement.EnumerateArray())
|
||||||
|
{
|
||||||
|
if (bookmaker.GetProperty("id").GetInt32() == 8) // Bet365
|
||||||
|
{
|
||||||
|
if (bookmaker.TryGetProperty("bets", out var betsElement))
|
||||||
|
{
|
||||||
|
foreach (var bet in betsElement.EnumerateArray())
|
||||||
|
{
|
||||||
|
if (bet.GetProperty("name").GetString() == "Match Winner")
|
||||||
|
{
|
||||||
|
// Elabora le quote 1X2
|
||||||
|
foreach (var value in bet.GetProperty("values").EnumerateArray())
|
||||||
|
{
|
||||||
|
string betType = value.GetProperty("value").GetString();
|
||||||
|
string odd = value.GetProperty("odd").GetString();
|
||||||
|
|
||||||
|
switch (betType)
|
||||||
|
{
|
||||||
|
case "Home":
|
||||||
|
row["Quota Casa"] = odd;
|
||||||
|
break;
|
||||||
|
case "Draw":
|
||||||
|
row["Quota Pareggio"] = odd;
|
||||||
|
break;
|
||||||
|
case "Away":
|
||||||
|
row["Quota Trasferta"] = odd;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recupera la previsione per questa partita
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var predictionResponse = _predictionManager.GetPredictionByFixture(fixtureId);
|
||||||
|
if (predictionResponse != null && predictionResponse.IsSuccessful)
|
||||||
|
{
|
||||||
|
var predictionJson = JsonDocument.Parse(predictionResponse.Content).RootElement;
|
||||||
|
if (predictionJson.TryGetProperty("response", out var predResponse) &&
|
||||||
|
predResponse.EnumerateArray().Any())
|
||||||
|
{
|
||||||
|
var prediction = predResponse.EnumerateArray().First();
|
||||||
|
if (prediction.TryGetProperty("predictions", out var predsElement) &&
|
||||||
|
predsElement.TryGetProperty("winner", out var winnerElement) &&
|
||||||
|
winnerElement.TryGetProperty("name", out var winnerNameElement))
|
||||||
|
{
|
||||||
|
row["Previsione"] = winnerNameElement.GetString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_database.LogError($"recupero previsione per partita ID: {fixtureId}", ex);
|
||||||
|
// Continua con la prossima partita
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_database.LogError("elaborazione quote partita", ex);
|
||||||
|
// Continua con la prossima partita
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_database.LogError("combinazione partite e quote", ex);
|
||||||
|
// In caso di errore, ritorna la tabella originale
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ordina le righe per data
|
||||||
|
try
|
||||||
|
{
|
||||||
|
combinedTable.DefaultView.Sort = "Data / Ora ASC";
|
||||||
|
return combinedTable.DefaultView.ToTable();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return combinedTable; // In caso di errore nell'ordinamento, ritorna la tabella non ordinata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Crea un DataTable vuoto in caso di errore
|
||||||
|
/// </summary>
|
||||||
|
private DataTable CreateEmptyResultTable()
|
||||||
|
{
|
||||||
|
var table = new DataTable();
|
||||||
|
table.Columns.Add("Errore", typeof(string));
|
||||||
|
var row = table.NewRow();
|
||||||
|
row["Errore"] = "Si è verificato un errore durante il recupero dei dati.";
|
||||||
|
table.Rows.Add(row);
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Elabora le risposte API non elaborate presenti nel database
|
||||||
|
/// </summary>
|
||||||
|
private void ProcessUnprocessedApiResponses()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Recupera tutte le risposte non elaborate
|
||||||
|
var unprocessedResponses = _apiResponseRepository.GetUnprocessedResponses();
|
||||||
|
|
||||||
|
foreach (DataRow responseRow in unprocessedResponses.Rows)
|
||||||
|
{
|
||||||
|
int responseId = Convert.ToInt32(responseRow["id"]);
|
||||||
|
string content = responseRow["response_content"].ToString();
|
||||||
|
string apiType = responseRow["api_type"].ToString();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Processa il contenuto della risposta API
|
||||||
|
ProcessAndInsertData(content);
|
||||||
|
|
||||||
|
// Aggiorna lo stato della risposta come elaborata
|
||||||
|
_apiResponseRepository.UpdateProcessingStatus(responseId, true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Aggiorna lo stato della risposta con l'errore
|
||||||
|
_apiResponseRepository.UpdateProcessingStatus(responseId, false, ex.Message);
|
||||||
|
_database.LogError($"elaborazione risposta API ID: {responseId}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_database.LogError("elaborazione risposte API non elaborate", ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recupera i fixture elaborati dal database
|
||||||
|
/// </summary>
|
||||||
|
private DataTable GetProcessedFixturesFromDatabase()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Step 1: Creare un DataTable vuoto per i fixture
|
||||||
|
var result = CreateEmptyFixturesDataTable();
|
||||||
|
|
||||||
|
// Step 2: Delegare alla classe repository appropriata il recupero dei dati
|
||||||
|
// Utilizziamo principalmente il repository Fixture che può coordinarsi con gli altri repository
|
||||||
|
return _fixtureRepository.GetProcessedFixtures();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_database.LogError("recupero partite elaborate dal database", ex);
|
||||||
|
// Return empty table in case of error
|
||||||
|
return CreateEmptyFixturesDataTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football.Manager
|
||||||
|
{
|
||||||
|
internal class API : HorseRacingPredictor.Manager.API
|
||||||
|
{
|
||||||
|
// Configurazione dell'API
|
||||||
|
protected const string ApiKey = "f3795ccef056c5478d316162517d9970";
|
||||||
|
protected const string KeyHeader = "x-rapidapi-key";
|
||||||
|
protected const string HostHeader = "x-rapidapi-host";
|
||||||
|
protected const string HostValue = "v3.football.api-sports.io";
|
||||||
|
protected const string BaseUrl = "https://v3.football.api-sports.io";
|
||||||
|
|
||||||
|
// Repository per le risposte API
|
||||||
|
private readonly APIResponse _apiResponseRepository;
|
||||||
|
|
||||||
|
// Costruttore
|
||||||
|
public API()
|
||||||
|
{
|
||||||
|
_apiResponseRepository = new APIResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tipi di API per il salvataggio nel database
|
||||||
|
public static class ApiTypes
|
||||||
|
{
|
||||||
|
public const string Fixtures = "FootballFixtures";
|
||||||
|
public const string Odds = "FootballOdds";
|
||||||
|
public const string Predictions = "FootballPredictions";
|
||||||
|
public const string Leagues = "FootballLeagues";
|
||||||
|
public const string Teams = "FootballTeams";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Esegue una richiesta API, salva la risposta nel database e restituisce la risposta
|
||||||
|
/// </summary>
|
||||||
|
protected RestResponse ExecuteRequest(string endpoint, string apiType = null, string parameters = null)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Versione asincrona di ExecuteRequest
|
||||||
|
/// </summary>
|
||||||
|
protected async Task<RestResponse> ExecuteRequestAsync(string endpoint, string apiType = null, string parameters = null)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Leagues
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene informazioni sui campionati
|
||||||
|
/// </summary>
|
||||||
|
public RestResponse GetLeagues()
|
||||||
|
{
|
||||||
|
return new League().GetLeagues();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Versione asincrona di GetLeagues
|
||||||
|
/// </summary>
|
||||||
|
public async Task<RestResponse> GetLeaguesAsync()
|
||||||
|
{
|
||||||
|
return await new League().GetLeaguesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene informazioni su una specifica stagione di una lega
|
||||||
|
/// </summary>
|
||||||
|
public RestResponse GetLeagueBySeason(int leagueId, int season)
|
||||||
|
{
|
||||||
|
return new League().GetLeagueBySeason(leagueId, season);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Fixtures
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene le partite per una data specifica
|
||||||
|
/// </summary>
|
||||||
|
public RestResponse GetFixturesByDate(DateTime date)
|
||||||
|
{
|
||||||
|
return new Fixture().GetFixturesByDate(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Versione asincrona di GetFixturesByDate
|
||||||
|
/// </summary>
|
||||||
|
public async Task<RestResponse> GetFixturesByDateAsync(DateTime date)
|
||||||
|
{
|
||||||
|
return await new Fixture().GetFixturesByDateAsync(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene le partite per un ID specifico
|
||||||
|
/// </summary>
|
||||||
|
public RestResponse GetFixtureById(int fixtureId)
|
||||||
|
{
|
||||||
|
return new Fixture().GetFixtureById(fixtureId);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using System;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using HorseRacingPredictor.Football.Database;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Football.Manager
|
||||||
|
{
|
||||||
|
internal class Database : HorseRacingPredictor.Manager.Database
|
||||||
|
{
|
||||||
|
// Implementazione della proprietà astratta _connectionString per il database calcio
|
||||||
|
protected override string _connectionString => "Server=DESKTOP-9O9JHFS;Database=TestBS_Football;User Id=sa;Password=Asti2019;";
|
||||||
|
|
||||||
|
// Usato il modificatore "new" per evitare il warning CS0108
|
||||||
|
protected new void LogError(string operation, Exception ex)
|
||||||
|
{
|
||||||
|
base.LogError(operation, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usato il modificatore "new" per evitare il warning CS0108
|
||||||
|
protected new void ExecuteQuery(string operation, Action<SqlConnection> action)
|
||||||
|
{
|
||||||
|
base.ExecuteQuery(operation, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggiunto il modificatore "new" per evitare il warning CS0108
|
||||||
|
protected new void ExecuteTransactionalQuery(string operation, Action<SqlConnection, SqlTransaction> action)
|
||||||
|
{
|
||||||
|
base.ExecuteTransactionalQuery(operation, action);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Questo metodo ora è vuoto e sarà implementato nella Main
|
||||||
|
public void ProcessAndInsertData(string jsonResponse)
|
||||||
|
{
|
||||||
|
// Il codice è stato spostato in Football.Main
|
||||||
|
LogError("l'elaborazione dei dati calcistici", new NotImplementedException("Questo metodo è stato spostato in Football.Main"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disabilita temporaneamente tutti i vincoli di chiave esterna nel database
|
||||||
|
/// </summary>
|
||||||
|
public void DisableAllConstraints(SqlConnection connection, SqlTransaction transaction)
|
||||||
|
{
|
||||||
|
using (var cmd = new SqlCommand("EXEC sp_MSforeachtable 'ALTER TABLE ? NOCHECK CONSTRAINT ALL'", connection, transaction))
|
||||||
|
{
|
||||||
|
cmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Riattiva tutti i vincoli di chiave esterna nel database
|
||||||
|
/// </summary>
|
||||||
|
public void EnableAllConstraints(SqlConnection connection, SqlTransaction transaction)
|
||||||
|
{
|
||||||
|
using (var cmd = new SqlCommand("EXEC sp_MSforeachtable 'ALTER TABLE ? CHECK CONSTRAINT ALL'", connection, transaction))
|
||||||
|
{
|
||||||
|
cmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CalculateRating1(DataRow row)
|
||||||
|
{
|
||||||
|
/* HORSE RATING */
|
||||||
|
|
||||||
|
// Implementa la logica per calcolare il rating del cavallo
|
||||||
|
// Puoi usare i campi come "Handicap Rating", "Career Wins", "Jockey Last 100 Wins", ecc.
|
||||||
|
double rating = 0;
|
||||||
|
|
||||||
|
// Esempio di calcolo del rating (puoi personalizzarlo in base alle tue esigenze)
|
||||||
|
rating += GetValueOrDefault(row, "Handicap Rating");
|
||||||
|
rating += GetValueOrDefault(row, "Career Wins") * 10;
|
||||||
|
rating += GetValueOrDefault(row, "Jockey Last 100 Wins") * 5;
|
||||||
|
|
||||||
|
return (int)rating;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CalculateRating2(DataRow row)
|
||||||
|
{
|
||||||
|
/* AI RATING */
|
||||||
|
|
||||||
|
// Calcola il punteggio IA basato sullo script fornito
|
||||||
|
double rating = 0;
|
||||||
|
|
||||||
|
rating += GetValueOrDefault(row, "Handicap Rating") * 0.3;
|
||||||
|
rating += GetValueOrDefault(row, "Career Strike Rate") * 100 * 0.2;
|
||||||
|
rating += GetValueOrDefault(row, "Average Prize Money") * 0.0001 * 0.1;
|
||||||
|
rating += GetValueOrDefault(row, "This Track Strike Rate") * 100 * 0.1;
|
||||||
|
rating += GetValueOrDefault(row, "This Distance Strike Rate") * 100 * 0.1;
|
||||||
|
rating += GetValueOrDefault(row, "Jockey Last 100 Strike Rate") * 100 * 0.1;
|
||||||
|
rating += GetValueOrDefault(row, "Trainer Last 100 Strike Rate") * 100 * 0.1;
|
||||||
|
|
||||||
|
// Limita il punteggio a un valore intero
|
||||||
|
return (int)rating;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CalculateRating3(DataRow row)
|
||||||
|
{
|
||||||
|
/* COPILOT RATING */
|
||||||
|
|
||||||
|
// Calcola il punteggio Copilot basato sui campi del file CSV
|
||||||
|
double rating = 0;
|
||||||
|
|
||||||
|
// Esempio di calcolo del rating con pesi assegnati a ciascuna statistica
|
||||||
|
rating += GetValueOrDefault(row, "Handicap Rating") * 0.4;
|
||||||
|
rating += GetValueOrDefault(row, "Career Wins") * 0.3;
|
||||||
|
rating += GetValueOrDefault(row, "Career Strike Rate") * 100 * 0.2;
|
||||||
|
rating += GetValueOrDefault(row, "Average Prize Money") * 0.0001 * 0.1;
|
||||||
|
|
||||||
|
return (int)rating;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CalculateRating4(DataRow row)
|
||||||
|
{
|
||||||
|
double rating = 0;
|
||||||
|
|
||||||
|
rating += GetValueOrDefault(row, "Handicap Rating") * 0.3;
|
||||||
|
rating += GetValueOrDefault(row, "Career Wins") * 0.2;
|
||||||
|
rating += GetValueOrDefault(row, "Career Strike Rate") * 100 * 0.2;
|
||||||
|
rating += GetValueOrDefault(row, "Average Prize Money") * 0.0001 * 0.1;
|
||||||
|
rating += GetValueOrDefault(row, "This Track Wins") * 0.1;
|
||||||
|
rating += GetValueOrDefault(row, "This Distance Wins") * 0.1;
|
||||||
|
rating += GetValueOrDefault(row, "Jockey Last 100 Wins") * 0.1;
|
||||||
|
rating += GetValueOrDefault(row, "Trainer Last 100 Wins") * 0.1;
|
||||||
|
|
||||||
|
return (int)rating;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RaceContext
|
||||||
|
{
|
||||||
|
public bool IsWetTrack { get; set; }
|
||||||
|
public double DistanceDelta { get; set; }
|
||||||
|
public int MaxBarrier { get; set; }
|
||||||
|
public double AvgWeight { get; set; }
|
||||||
|
public double MaxPrizeMoney { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public double CalculateRating5(DataRow row)
|
||||||
|
{
|
||||||
|
double score = 0;
|
||||||
|
|
||||||
|
// 1. CORE HORSE PROFILE (15%)
|
||||||
|
// Età ottimale 4-7 anni (curva a campana)
|
||||||
|
int age = (int)GetValueOrDefault(row, "Age");
|
||||||
|
score += Math.Exp(-0.5 * Math.Pow((age - 5.5) / 2.0, 2)) * 0.05;
|
||||||
|
|
||||||
|
// Gender: puledre penalizzate 2%
|
||||||
|
if (row["Gender"].ToString() == "f")
|
||||||
|
score -= 0.02;
|
||||||
|
|
||||||
|
// 2. HISTORICAL PERFORMANCE (25%)
|
||||||
|
double careerRuns = GetValueOrDefault(row, "Career Runs");
|
||||||
|
double careerWins = GetValueOrDefault(row, "Career Wins");
|
||||||
|
double careerStrike = careerRuns > 0 ? (careerWins / careerRuns) : 0;
|
||||||
|
score += careerStrike * 0.15;
|
||||||
|
|
||||||
|
double placingsRate = GetValueOrDefault(row, "Career Placings") / Math.Max(careerRuns, 1);
|
||||||
|
score += placingsRate * 0.10;
|
||||||
|
|
||||||
|
// 3. RECENT FORM (20%)
|
||||||
|
int lastPos = (int)GetValueOrDefault(row, "Last Start Finish Position");
|
||||||
|
score += (lastPos > 0 ? (1.0 / lastPos) : 0) * 0.10;
|
||||||
|
|
||||||
|
double lastMargin = (int)GetValueOrDefault(row, "Last Start Margin");
|
||||||
|
score += (1.0 / (lastMargin + 1)) * 0.05;
|
||||||
|
|
||||||
|
//double daysSinceLast = GetDaysSinceLastRace(row); // Da implementare
|
||||||
|
//score += Math.Exp(-daysSinceLast / 30.0) * 0.05;
|
||||||
|
|
||||||
|
// 4. CONDITIONS SPECIALIZATION (15%)
|
||||||
|
// bool isWet = context.IsWetTrack;
|
||||||
|
// double surfaceRate = isWet ?
|
||||||
|
// GetValueOrDefault(row, "Wet Track Strike Rate") :
|
||||||
|
//// GetValueOrDefault(row, "Dry Track Strike Rate");
|
||||||
|
// score += surfaceRate * 0.10;
|
||||||
|
|
||||||
|
double distanceChange = GetValueOrDefault(row, "Last Start Distance Change");
|
||||||
|
double distanceFit = 1.0 / (Math.Abs(distanceChange) + 1);
|
||||||
|
score += distanceFit * 0.05;
|
||||||
|
|
||||||
|
// 5. CONTEXT-SPECIFIC FACTORS (10%)
|
||||||
|
double trackStrike = GetConditionalRate(row, "This Track Wins", "This Track Runs");
|
||||||
|
score += trackStrike * 0.06;
|
||||||
|
|
||||||
|
//double barrierEffect = (1.0 - (GetValueOrDefault(row, "Barrier") / context.MaxBarrier)) * 0.04;
|
||||||
|
//score += barrierEffect;
|
||||||
|
|
||||||
|
// 6. TEAM PERFORMANCE (15%)
|
||||||
|
double jockeyRecent = GetValueOrDefault(row, "Jockey Last 100 Strike Rate");
|
||||||
|
double jockeyLong = GetValueOrDefault(row, "Jockey 12 Months Strike Rate");
|
||||||
|
score += Math.Max(jockeyRecent, jockeyLong) * 0.08;
|
||||||
|
|
||||||
|
double trainerRecent = GetValueOrDefault(row, "Trainer Last 100 Strike Rate");
|
||||||
|
double trainerLong = GetValueOrDefault(row, "Trainer 12 Months Strike Rate");
|
||||||
|
score += Math.Max(trainerRecent, trainerLong) * 0.07;
|
||||||
|
|
||||||
|
// 7. PHYSICAL FACTORS (10%)
|
||||||
|
// double weightRatio = context.AvgWeight / GetValueOrDefault(row, "Weight Carried");
|
||||||
|
// score += Math.Min(weightRatio, 1.5) * 0.05;
|
||||||
|
|
||||||
|
double prizeMomentum = GetValueOrDefault(row, "Average Prize Money") / GetValueOrDefault(row, "Prize Money");
|
||||||
|
score += prizeMomentum * 0.05;
|
||||||
|
|
||||||
|
return score * 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calcola il rating ML per un cavallo utilizzando il modello di machine learning
|
||||||
|
/// </summary>
|
||||||
|
public int CalculateMLRating(DataRow row)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private double GetValueOrDefault(DataRow row, string columnName)
|
||||||
|
{
|
||||||
|
if (!row.Table.Columns.Contains(columnName) || row[columnName] == DBNull.Value)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return Convert.ToDouble(row[columnName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float GetFloatValueOrDefault(DataRow row, string columnName)
|
||||||
|
{
|
||||||
|
if (!row.Table.Columns.Contains(columnName) || row[columnName] == DBNull.Value)
|
||||||
|
return 0f;
|
||||||
|
|
||||||
|
return Convert.ToSingle(row[columnName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double GetConditionalRate(DataRow row, string numeratorColumn, string denominatorColumn)
|
||||||
|
{
|
||||||
|
double numerator = GetValueOrDefault(row, numeratorColumn);
|
||||||
|
double denominator = GetValueOrDefault(row, denominatorColumn);
|
||||||
|
|
||||||
|
if (denominator == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return numerator / denominator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,457 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
using System.Data;
|
||||||
|
using System.IO;
|
||||||
|
using BettingPredictor;
|
||||||
|
using HorseRacingPredictor.Manager;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Horses
|
||||||
|
{
|
||||||
|
internal class Database : HorseRacingPredictor.Manager.Database
|
||||||
|
{
|
||||||
|
// Implementazione della proprietà astratta _connectionString per il database cavalli
|
||||||
|
protected override string _connectionString => "Server=DESKTOP-9O9JHFS;Database=TestBS_Horses;User Id=sa;Password=Asti2019;";
|
||||||
|
|
||||||
|
private readonly FileReader fileReaderHorses;
|
||||||
|
|
||||||
|
public Database()
|
||||||
|
{
|
||||||
|
fileReaderHorses = new FileReader();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataTable GetAllHorseRaceData()
|
||||||
|
{
|
||||||
|
DataTable horseRaceData = new DataTable();
|
||||||
|
ExecuteQuery("GetAllHorseRaceData", connection =>
|
||||||
|
{
|
||||||
|
using (SqlCommand command = new SqlCommand("SELECT * FROM HorseRaceData", connection))
|
||||||
|
{
|
||||||
|
using (SqlDataAdapter adapter = new SqlDataAdapter(command))
|
||||||
|
{
|
||||||
|
adapter.Fill(horseRaceData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return horseRaceData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ProcessAndInsertHorseRaceData(string filePath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Leggi i dati dal file CSV usando il nuovo metodo che restituisce HorseRaceData
|
||||||
|
List<HorseRaceData> horseDataList = fileReaderHorses.ReadHorseDataFromFile(filePath);
|
||||||
|
|
||||||
|
// Utilizza i metodi del FileReader per estrarre informazioni sulla corsa
|
||||||
|
DateTime raceDate = fileReaderHorses.ExtractRaceDate(filePath);
|
||||||
|
string meeting = fileReaderHorses.ExtractMeetingName(filePath);
|
||||||
|
int race = fileReaderHorses.ExtractRaceNumber(filePath);
|
||||||
|
|
||||||
|
// Inserisci i dati nel database
|
||||||
|
InsertHorseDataInRacesTable(horseDataList, meeting, race, raceDate, filePath);
|
||||||
|
|
||||||
|
Console.WriteLine($"Elaborazione e inserimento dati corsa cavalli completata: {filePath}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError($"l'elaborazione del file {filePath}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InsertHorseDataInRacesTable(List<HorseRaceData> horseDataList, string meeting, int race, DateTime raceDate, string filePath)
|
||||||
|
{
|
||||||
|
string operation = "l'inserimento dei dati dei cavalli nella tabella Races";
|
||||||
|
|
||||||
|
ExecuteQuery(operation, connection =>
|
||||||
|
{
|
||||||
|
foreach (var horseData in horseDataList)
|
||||||
|
{
|
||||||
|
UpsertRacesTableData(connection, horseData, meeting, race, raceDate, filePath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpsertRacesTableData(SqlConnection connection, HorseRaceData horseData, string meeting, int race, DateTime raceDate, string filePath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Numero del cavallo
|
||||||
|
int num = 0;
|
||||||
|
int.TryParse(horseData.Num, out num);
|
||||||
|
|
||||||
|
// Data e ora attuale per i campi Created e Updated
|
||||||
|
DateTime currentDateTime = DateTime.Now;
|
||||||
|
|
||||||
|
string query = @"
|
||||||
|
IF EXISTS (SELECT 1 FROM Races WHERE Data = @Data AND Meeting = @Meeting AND Race = @Race AND Num = @Num)
|
||||||
|
BEGIN
|
||||||
|
UPDATE Races SET
|
||||||
|
FileName = @FileName,
|
||||||
|
HorseName = @HorseName,
|
||||||
|
Age = @Age,
|
||||||
|
Gender = @Gender,
|
||||||
|
HandicapRating = @HandicapRating,
|
||||||
|
CareerRuns = @CareerRuns,
|
||||||
|
CareerWins = @CareerWins,
|
||||||
|
CareerStrikeRate = @CareerStrikeRate,
|
||||||
|
CareerROI = @CareerROI,
|
||||||
|
CareerPlacings = @CareerPlacings,
|
||||||
|
CareerPlaceStrikeRate = @CareerPlaceStrikeRate,
|
||||||
|
DryTrackRuns = @DryTrackRuns,
|
||||||
|
DryTrackWins = @DryTrackWins,
|
||||||
|
DryTrackStrikeRate = @DryTrackStrikeRate,
|
||||||
|
DryTrackROI = @DryTrackROI,
|
||||||
|
WetTrackRuns = @WetTrackRuns,
|
||||||
|
WetTrackWins = @WetTrackWins,
|
||||||
|
WetTrackStrikeRate = @WetTrackStrikeRate,
|
||||||
|
WetTrackROI = @WetTrackROI,
|
||||||
|
AveragePrizeMoney = @AveragePrizeMoney,
|
||||||
|
CareerPrizeMoney = @CareerPrizeMoney,
|
||||||
|
BestFixedOdds = @BestFixedOdds,
|
||||||
|
BetEasyOdds = @BetEasyOdds,
|
||||||
|
Weight = @Weight,
|
||||||
|
WeightCarried = @WeightCarried,
|
||||||
|
Barrier = @Barrier,
|
||||||
|
PrizeMoney = @PrizeMoney,
|
||||||
|
ThisTrackRuns = @ThisTrackRuns,
|
||||||
|
ThisTrackWins = @ThisTrackWins,
|
||||||
|
ThisTrackStrikeRate = @ThisTrackStrikeRate,
|
||||||
|
ThisTrackROI = @ThisTrackROI,
|
||||||
|
ThisTrackPlaces = @ThisTrackPlaces,
|
||||||
|
ThisTrackPlaceStrikeRate = @ThisTrackPlaceStrikeRate,
|
||||||
|
ThisDistanceRuns = @ThisDistanceRuns,
|
||||||
|
ThisDistanceWins = @ThisDistanceWins,
|
||||||
|
ThisDistanceStrikeRate = @ThisDistanceStrikeRate,
|
||||||
|
ThisDistanceROI = @ThisDistanceROI,
|
||||||
|
ThisDistancePlaces = @ThisDistancePlaces,
|
||||||
|
ThisDistancePlaceStrikeRate = @ThisDistancePlaceStrikeRate,
|
||||||
|
ThisTrackDistanceRuns = @ThisTrackDistanceRuns,
|
||||||
|
ThisTrackDistanceWins = @ThisTrackDistanceWins,
|
||||||
|
ThisTrackDistanceStrikeRate = @ThisTrackDistanceStrikeRate,
|
||||||
|
ThisTrackDistanceROI = @ThisTrackDistanceROI,
|
||||||
|
ThisTrackDistancePlaces = @ThisTrackDistancePlaces,
|
||||||
|
ThisTrackDistancePlaceStrikeRate = @ThisTrackDistancePlaceStrikeRate,
|
||||||
|
ThisConditionRuns = @ThisConditionRuns,
|
||||||
|
ThisConditionWins = @ThisConditionWins,
|
||||||
|
ThisConditionStrikeRate = @ThisConditionStrikeRate,
|
||||||
|
ThisConditionROI = @ThisConditionROI,
|
||||||
|
ThisConditionPlaces = @ThisConditionPlaces,
|
||||||
|
ThisConditionPlaceStrikeRate = @ThisConditionPlaceStrikeRate,
|
||||||
|
Jockey = @Jockey,
|
||||||
|
Apprentice = @Apprentice,
|
||||||
|
JockeyWeightClaim = @JockeyWeightClaim,
|
||||||
|
JockeyLast100HorseEarnings = @JockeyLast100HorseEarnings,
|
||||||
|
JockeyLast100AvgHorseEarnings = @JockeyLast100AvgHorseEarnings,
|
||||||
|
JockeyLast100Starts = @JockeyLast100Starts,
|
||||||
|
JockeyLast100Wins = @JockeyLast100Wins,
|
||||||
|
JockeyLast100StrikeRate = @JockeyLast100StrikeRate,
|
||||||
|
JockeyLast100ROI = @JockeyLast100ROI,
|
||||||
|
JockeyLast100Places = @JockeyLast100Places,
|
||||||
|
JockeyLast100PlaceStrikeRate = @JockeyLast100PlaceStrikeRate,
|
||||||
|
Jockey12MonthHorseEarnings = @Jockey12MonthHorseEarnings,
|
||||||
|
Jockey12MonthAvgHorseEarnings = @Jockey12MonthAvgHorseEarnings,
|
||||||
|
Jockey12MonthsStarts = @Jockey12MonthsStarts,
|
||||||
|
Jockey12MonthsWins = @Jockey12MonthsWins,
|
||||||
|
Jockey12MonthsStrikeRate = @Jockey12MonthsStrikeRate,
|
||||||
|
Jockey12MonthsROI = @Jockey12MonthsROI,
|
||||||
|
Jockey12MonthsPlaces = @Jockey12MonthsPlaces,
|
||||||
|
Jockey12MonthsPlaceStrikeRate = @Jockey12MonthsPlaceStrikeRate,
|
||||||
|
JockeyThisSeasonHorseEarnings = @JockeyThisSeasonHorseEarnings,
|
||||||
|
JockeyThisSeasonAvgHorseEarnings = @JockeyThisSeasonAvgHorseEarnings,
|
||||||
|
JockeyThisSeasonStarts = @JockeyThisSeasonStarts,
|
||||||
|
JockeyThisSeasonWins = @JockeyThisSeasonWins,
|
||||||
|
JockeyThisSeasonStrikeRate = @JockeyThisSeasonStrikeRate,
|
||||||
|
JockeyThisSeasonROI = @JockeyThisSeasonROI,
|
||||||
|
JockeyThisSeasonPlaces = @JockeyThisSeasonPlaces,
|
||||||
|
JockeyThisSeasonPlaceStrikeRate = @JockeyThisSeasonPlaceStrikeRate,
|
||||||
|
JockeyLastSeasonHorseEarnings = @JockeyLastSeasonHorseEarnings,
|
||||||
|
JockeyLastSeasonAvgHorseEarnings = @JockeyLastSeasonAvgHorseEarnings,
|
||||||
|
JockeyLastSeasonStarts = @JockeyLastSeasonStarts,
|
||||||
|
JockeyLastSeasonWins = @JockeyLastSeasonWins,
|
||||||
|
JockeyLastSeasonStrikeRate = @JockeyLastSeasonStrikeRate,
|
||||||
|
JockeyLastSeasonROI = @JockeyLastSeasonROI,
|
||||||
|
JockeyLastSeasonPlaces = @JockeyLastSeasonPlaces,
|
||||||
|
JockeyLastSeasonPlaceStrikeRate = @JockeyLastSeasonPlaceStrikeRate,
|
||||||
|
Trainer = @Trainer,
|
||||||
|
TrainerLast100HorseEarnings = @TrainerLast100HorseEarnings,
|
||||||
|
TrainerLast100AvgHorseEarnings = @TrainerLast100AvgHorseEarnings,
|
||||||
|
TrainerLast100Starts = @TrainerLast100Starts,
|
||||||
|
TrainerLast100Wins = @TrainerLast100Wins,
|
||||||
|
TrainerLast100StrikeRate = @TrainerLast100StrikeRate,
|
||||||
|
TrainerLast100ROI = @TrainerLast100ROI,
|
||||||
|
TrainerLast100Places = @TrainerLast100Places,
|
||||||
|
TrainerLast100PlaceStrikeRate = @TrainerLast100PlaceStrikeRate,
|
||||||
|
Trainer12MonthHorseEarnings = @Trainer12MonthHorseEarnings,
|
||||||
|
Trainer12MonthAvgHorseEarnings = @Trainer12MonthAvgHorseEarnings,
|
||||||
|
Trainer12MonthsStarts = @Trainer12MonthsStarts,
|
||||||
|
Trainer12MonthsWins = @Trainer12MonthsWins,
|
||||||
|
Trainer12MonthsStrikeRate = @Trainer12MonthsStrikeRate,
|
||||||
|
Trainer12MonthsROI = @Trainer12MonthsROI,
|
||||||
|
Trainer12MonthsPlaces = @Trainer12MonthsPlaces,
|
||||||
|
Trainer12MonthsPlaceStrikeRate = @Trainer12MonthsPlaceStrikeRate,
|
||||||
|
TrainerThisSeasonHorseEarnings = @TrainerThisSeasonHorseEarnings,
|
||||||
|
TrainerThisSeasonAvgHorseEarnings = @TrainerThisSeasonAvgHorseEarnings,
|
||||||
|
TrainerThisSeasonStarts = @TrainerThisSeasonStarts,
|
||||||
|
TrainerThisSeasonWins = @TrainerThisSeasonWins,
|
||||||
|
TrainerThisSeasonStrikeRate = @TrainerThisSeasonStrikeRate,
|
||||||
|
TrainerThisSeasonROI = @TrainerThisSeasonROI,
|
||||||
|
TrainerThisSeasonPlaces = @TrainerThisSeasonPlaces,
|
||||||
|
TrainerThisSeasonPlaceStrikeRate = @TrainerThisSeasonPlaceStrikeRate,
|
||||||
|
TrainerLastSeasonHorseEarnings = @TrainerLastSeasonHorseEarnings,
|
||||||
|
TrainerLastSeasonAvgHorseEarnings = @TrainerLastSeasonAvgHorseEarnings,
|
||||||
|
TrainerLastSeasonStarts = @TrainerLastSeasonStarts,
|
||||||
|
TrainerLastSeasonWins = @TrainerLastSeasonWins,
|
||||||
|
TrainerLastSeasonStrikeRate = @TrainerLastSeasonStrikeRate,
|
||||||
|
TrainerLastSeasonROI = @TrainerLastSeasonROI,
|
||||||
|
TrainerLastSeasonPlaces = @TrainerLastSeasonPlaces,
|
||||||
|
TrainerLastSeasonPlaceStrikeRate = @TrainerLastSeasonPlaceStrikeRate,
|
||||||
|
LastStartFinishPosition = @LastStartFinishPosition,
|
||||||
|
LastStartMargin = @LastStartMargin,
|
||||||
|
LastStartDistance = @LastStartDistance,
|
||||||
|
LastStartDistanceChange = @LastStartDistanceChange,
|
||||||
|
LastStartPrizeMoney = @LastStartPrizeMoney,
|
||||||
|
FormGuideUrl = @FormGuideUrl,
|
||||||
|
HorseProfileUrl = @HorseProfileUrl,
|
||||||
|
JockeyProfileUrl = @JockeyProfileUrl,
|
||||||
|
TrainerProfileUrl = @TrainerProfileUrl,
|
||||||
|
FinishResult = @FinishResult,
|
||||||
|
Updated = @Updated
|
||||||
|
WHERE Data = @Data AND Meeting = @Meeting AND Race = @Race AND Num = @Num
|
||||||
|
END
|
||||||
|
ELSE
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO Races (
|
||||||
|
Data, Meeting, Race, Num, FileName, 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, FormGuideUrl, HorseProfileUrl, JockeyProfileUrl,
|
||||||
|
TrainerProfileUrl, FinishResult, Created, Updated
|
||||||
|
) VALUES (
|
||||||
|
@Data, @Meeting, @Race, @Num, @FileName, @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, @FormGuideUrl, @HorseProfileUrl, @JockeyProfileUrl,
|
||||||
|
@TrainerProfileUrl, @FinishResult, @Created, @Updated
|
||||||
|
)
|
||||||
|
END";
|
||||||
|
|
||||||
|
using (var command = new SqlCommand(query, connection))
|
||||||
|
{
|
||||||
|
// Chiavi primarie
|
||||||
|
command.Parameters.AddWithValue("@Data", raceDate);
|
||||||
|
command.Parameters.AddWithValue("@Meeting", meeting ?? string.Empty);
|
||||||
|
command.Parameters.AddWithValue("@Race", race);
|
||||||
|
command.Parameters.AddWithValue("@Num", num);
|
||||||
|
|
||||||
|
// Dati accessori
|
||||||
|
command.Parameters.AddWithValue("@FileName", filePath ?? string.Empty);
|
||||||
|
|
||||||
|
// Dati cavallo
|
||||||
|
command.Parameters.AddWithValue("@HorseName", horseData.HorseName ?? string.Empty);
|
||||||
|
command.Parameters.AddWithValue("@Age", horseData.Age);
|
||||||
|
command.Parameters.AddWithValue("@Gender", horseData.Gender ?? string.Empty);
|
||||||
|
command.Parameters.AddWithValue("@HandicapRating", horseData.HandicapRating);
|
||||||
|
command.Parameters.AddWithValue("@CareerRuns", horseData.CareerRuns);
|
||||||
|
command.Parameters.AddWithValue("@CareerWins", horseData.CareerWins);
|
||||||
|
command.Parameters.AddWithValue("@CareerStrikeRate", horseData.CareerStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@CareerROI", horseData.CareerROI);
|
||||||
|
command.Parameters.AddWithValue("@CareerPlacings", horseData.CareerPlacings);
|
||||||
|
command.Parameters.AddWithValue("@CareerPlaceStrikeRate", horseData.CareerPlaceStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@DryTrackRuns", horseData.DryTrackRuns);
|
||||||
|
command.Parameters.AddWithValue("@DryTrackWins", horseData.DryTrackWins);
|
||||||
|
command.Parameters.AddWithValue("@DryTrackStrikeRate", horseData.DryTrackStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@DryTrackROI", horseData.DryTrackROI);
|
||||||
|
command.Parameters.AddWithValue("@WetTrackRuns", horseData.WetTrackRuns);
|
||||||
|
command.Parameters.AddWithValue("@WetTrackWins", horseData.WetTrackWins);
|
||||||
|
command.Parameters.AddWithValue("@WetTrackStrikeRate", horseData.WetTrackStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@WetTrackROI", horseData.WetTrackROI);
|
||||||
|
command.Parameters.AddWithValue("@AveragePrizeMoney", horseData.AveragePrizeMoney);
|
||||||
|
command.Parameters.AddWithValue("@CareerPrizeMoney", horseData.CareerPrizeMoney);
|
||||||
|
command.Parameters.AddWithValue("@BestFixedOdds", horseData.BestFixedOdds);
|
||||||
|
command.Parameters.AddWithValue("@BetEasyOdds", horseData.BetEasyOdds);
|
||||||
|
command.Parameters.AddWithValue("@Weight", horseData.Weight);
|
||||||
|
command.Parameters.AddWithValue("@WeightCarried", horseData.WeightCarried);
|
||||||
|
command.Parameters.AddWithValue("@Barrier", horseData.Barrier);
|
||||||
|
command.Parameters.AddWithValue("@PrizeMoney", horseData.PrizeMoney);
|
||||||
|
|
||||||
|
// Statistiche pista
|
||||||
|
command.Parameters.AddWithValue("@ThisTrackRuns", horseData.ThisTrackRuns);
|
||||||
|
command.Parameters.AddWithValue("@ThisTrackWins", horseData.ThisTrackWins);
|
||||||
|
command.Parameters.AddWithValue("@ThisTrackStrikeRate", horseData.ThisTrackStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@ThisTrackROI", horseData.ThisTrackROI);
|
||||||
|
command.Parameters.AddWithValue("@ThisTrackPlaces", horseData.ThisTrackPlaces);
|
||||||
|
command.Parameters.AddWithValue("@ThisTrackPlaceStrikeRate", horseData.ThisTrackPlaceStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@ThisDistanceRuns", horseData.ThisDistanceRuns);
|
||||||
|
command.Parameters.AddWithValue("@ThisDistanceWins", horseData.ThisDistanceWins);
|
||||||
|
command.Parameters.AddWithValue("@ThisDistanceStrikeRate", horseData.ThisDistanceStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@ThisDistanceROI", horseData.ThisDistanceROI);
|
||||||
|
command.Parameters.AddWithValue("@ThisDistancePlaces", horseData.ThisDistancePlaces);
|
||||||
|
command.Parameters.AddWithValue("@ThisDistancePlaceStrikeRate", horseData.ThisDistancePlaceStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@ThisTrackDistanceRuns", horseData.ThisTrackDistanceRuns);
|
||||||
|
command.Parameters.AddWithValue("@ThisTrackDistanceWins", horseData.ThisTrackDistanceWins);
|
||||||
|
command.Parameters.AddWithValue("@ThisTrackDistanceStrikeRate", horseData.ThisTrackDistanceStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@ThisTrackDistanceROI", horseData.ThisTrackDistanceROI);
|
||||||
|
command.Parameters.AddWithValue("@ThisTrackDistancePlaces", horseData.ThisTrackDistancePlaces);
|
||||||
|
command.Parameters.AddWithValue("@ThisTrackDistancePlaceStrikeRate", horseData.ThisTrackDistancePlaceStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@ThisConditionRuns", horseData.ThisConditionRuns);
|
||||||
|
command.Parameters.AddWithValue("@ThisConditionWins", horseData.ThisConditionWins);
|
||||||
|
command.Parameters.AddWithValue("@ThisConditionStrikeRate", horseData.ThisConditionStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@ThisConditionROI", horseData.ThisConditionROI);
|
||||||
|
command.Parameters.AddWithValue("@ThisConditionPlaces", horseData.ThisConditionPlaces);
|
||||||
|
command.Parameters.AddWithValue("@ThisConditionPlaceStrikeRate", horseData.ThisConditionPlaceStrikeRate);
|
||||||
|
|
||||||
|
// Jockey
|
||||||
|
command.Parameters.AddWithValue("@Jockey", horseData.Jockey ?? string.Empty);
|
||||||
|
command.Parameters.AddWithValue("@Apprentice", horseData.Apprentice ?? string.Empty);
|
||||||
|
command.Parameters.AddWithValue("@JockeyWeightClaim", horseData.JockeyWeightClaim);
|
||||||
|
command.Parameters.AddWithValue("@JockeyLast100HorseEarnings", horseData.JockeyLast100HorseEarnings);
|
||||||
|
command.Parameters.AddWithValue("@JockeyLast100AvgHorseEarnings", horseData.JockeyLast100AvgHorseEarnings);
|
||||||
|
command.Parameters.AddWithValue("@JockeyLast100Starts", horseData.JockeyLast100Starts);
|
||||||
|
command.Parameters.AddWithValue("@JockeyLast100Wins", horseData.JockeyLast100Wins);
|
||||||
|
command.Parameters.AddWithValue("@JockeyLast100StrikeRate", horseData.JockeyLast100StrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@JockeyLast100ROI", horseData.JockeyLast100ROI);
|
||||||
|
command.Parameters.AddWithValue("@JockeyLast100Places", horseData.JockeyLast100Places);
|
||||||
|
command.Parameters.AddWithValue("@JockeyLast100PlaceStrikeRate", horseData.JockeyLast100PlaceStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@Jockey12MonthHorseEarnings", horseData.Jockey12MonthHorseEarnings);
|
||||||
|
command.Parameters.AddWithValue("@Jockey12MonthAvgHorseEarnings", horseData.Jockey12MonthAvgHorseEarnings);
|
||||||
|
command.Parameters.AddWithValue("@Jockey12MonthsStarts", horseData.Jockey12MonthsStarts);
|
||||||
|
command.Parameters.AddWithValue("@Jockey12MonthsWins", horseData.Jockey12MonthsWins);
|
||||||
|
command.Parameters.AddWithValue("@Jockey12MonthsStrikeRate", horseData.Jockey12MonthsStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@Jockey12MonthsROI", horseData.Jockey12MonthsROI);
|
||||||
|
command.Parameters.AddWithValue("@Jockey12MonthsPlaces", horseData.Jockey12MonthsPlaces);
|
||||||
|
command.Parameters.AddWithValue("@Jockey12MonthsPlaceStrikeRate", horseData.Jockey12MonthsPlaceStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@JockeyThisSeasonHorseEarnings", horseData.JockeyThisSeasonHorseEarnings);
|
||||||
|
command.Parameters.AddWithValue("@JockeyThisSeasonAvgHorseEarnings", horseData.JockeyThisSeasonAvgHorseEarnings);
|
||||||
|
command.Parameters.AddWithValue("@JockeyThisSeasonStarts", horseData.JockeyThisSeasonStarts);
|
||||||
|
command.Parameters.AddWithValue("@JockeyThisSeasonWins", horseData.JockeyThisSeasonWins);
|
||||||
|
command.Parameters.AddWithValue("@JockeyThisSeasonStrikeRate", horseData.JockeyThisSeasonStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@JockeyThisSeasonROI", horseData.JockeyThisSeasonROI);
|
||||||
|
command.Parameters.AddWithValue("@JockeyThisSeasonPlaces", horseData.JockeyThisSeasonPlaces);
|
||||||
|
command.Parameters.AddWithValue("@JockeyThisSeasonPlaceStrikeRate", horseData.JockeyThisSeasonPlaceStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@JockeyLastSeasonHorseEarnings", horseData.JockeyLastSeasonHorseEarnings);
|
||||||
|
command.Parameters.AddWithValue("@JockeyLastSeasonAvgHorseEarnings", horseData.JockeyLastSeasonAvgHorseEarnings);
|
||||||
|
command.Parameters.AddWithValue("@JockeyLastSeasonStarts", horseData.JockeyLastSeasonStarts);
|
||||||
|
command.Parameters.AddWithValue("@JockeyLastSeasonWins", horseData.JockeyLastSeasonWins);
|
||||||
|
command.Parameters.AddWithValue("@JockeyLastSeasonStrikeRate", horseData.JockeyLastSeasonStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@JockeyLastSeasonROI", horseData.JockeyLastSeasonROI);
|
||||||
|
command.Parameters.AddWithValue("@JockeyLastSeasonPlaces", horseData.JockeyLastSeasonPlaces);
|
||||||
|
command.Parameters.AddWithValue("@JockeyLastSeasonPlaceStrikeRate", horseData.JockeyLastSeasonPlaceStrikeRate);
|
||||||
|
|
||||||
|
// Trainer
|
||||||
|
command.Parameters.AddWithValue("@Trainer", horseData.Trainer ?? string.Empty);
|
||||||
|
command.Parameters.AddWithValue("@TrainerLast100HorseEarnings", horseData.TrainerLast100HorseEarnings);
|
||||||
|
command.Parameters.AddWithValue("@TrainerLast100AvgHorseEarnings", horseData.TrainerLast100AvgHorseEarnings);
|
||||||
|
command.Parameters.AddWithValue("@TrainerLast100Starts", horseData.TrainerLast100Starts);
|
||||||
|
command.Parameters.AddWithValue("@TrainerLast100Wins", horseData.TrainerLast100Wins);
|
||||||
|
command.Parameters.AddWithValue("@TrainerLast100StrikeRate", horseData.TrainerLast100StrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@TrainerLast100ROI", horseData.TrainerLast100ROI);
|
||||||
|
command.Parameters.AddWithValue("@TrainerLast100Places", horseData.TrainerLast100Places);
|
||||||
|
command.Parameters.AddWithValue("@TrainerLast100PlaceStrikeRate", horseData.TrainerLast100PlaceStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@Trainer12MonthHorseEarnings", horseData.Trainer12MonthHorseEarnings);
|
||||||
|
command.Parameters.AddWithValue("@Trainer12MonthAvgHorseEarnings", horseData.Trainer12MonthAvgHorseEarnings);
|
||||||
|
command.Parameters.AddWithValue("@Trainer12MonthsStarts", horseData.Trainer12MonthsStarts);
|
||||||
|
command.Parameters.AddWithValue("@Trainer12MonthsWins", horseData.Trainer12MonthsWins);
|
||||||
|
command.Parameters.AddWithValue("@Trainer12MonthsStrikeRate", horseData.Trainer12MonthsStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@Trainer12MonthsROI", horseData.Trainer12MonthsROI);
|
||||||
|
command.Parameters.AddWithValue("@Trainer12MonthsPlaces", horseData.Trainer12MonthsPlaces);
|
||||||
|
command.Parameters.AddWithValue("@Trainer12MonthsPlaceStrikeRate", horseData.Trainer12MonthsPlaceStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@TrainerThisSeasonHorseEarnings", horseData.TrainerThisSeasonHorseEarnings);
|
||||||
|
command.Parameters.AddWithValue("@TrainerThisSeasonAvgHorseEarnings", horseData.TrainerThisSeasonAvgHorseEarnings);
|
||||||
|
command.Parameters.AddWithValue("@TrainerThisSeasonStarts", horseData.TrainerThisSeasonStarts);
|
||||||
|
command.Parameters.AddWithValue("@TrainerThisSeasonWins", horseData.TrainerThisSeasonWins);
|
||||||
|
command.Parameters.AddWithValue("@TrainerThisSeasonStrikeRate", horseData.TrainerThisSeasonStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@TrainerThisSeasonROI", horseData.TrainerThisSeasonROI);
|
||||||
|
command.Parameters.AddWithValue("@TrainerThisSeasonPlaces", horseData.TrainerThisSeasonPlaces);
|
||||||
|
command.Parameters.AddWithValue("@TrainerThisSeasonPlaceStrikeRate", horseData.TrainerThisSeasonPlaceStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@TrainerLastSeasonHorseEarnings", horseData.TrainerLastSeasonHorseEarnings);
|
||||||
|
command.Parameters.AddWithValue("@TrainerLastSeasonAvgHorseEarnings", horseData.TrainerLastSeasonAvgHorseEarnings);
|
||||||
|
command.Parameters.AddWithValue("@TrainerLastSeasonStarts", horseData.TrainerLastSeasonStarts);
|
||||||
|
command.Parameters.AddWithValue("@TrainerLastSeasonWins", horseData.TrainerLastSeasonWins);
|
||||||
|
command.Parameters.AddWithValue("@TrainerLastSeasonStrikeRate", horseData.TrainerLastSeasonStrikeRate);
|
||||||
|
command.Parameters.AddWithValue("@TrainerLastSeasonROI", horseData.TrainerLastSeasonROI);
|
||||||
|
command.Parameters.AddWithValue("@TrainerLastSeasonPlaces", horseData.TrainerLastSeasonPlaces);
|
||||||
|
command.Parameters.AddWithValue("@TrainerLastSeasonPlaceStrikeRate", horseData.TrainerLastSeasonPlaceStrikeRate);
|
||||||
|
|
||||||
|
// Informazioni ultima corsa
|
||||||
|
command.Parameters.AddWithValue("@LastStartFinishPosition", horseData.LastStartFinishPosition);
|
||||||
|
command.Parameters.AddWithValue("@LastStartMargin", horseData.LastStartMargin);
|
||||||
|
command.Parameters.AddWithValue("@LastStartDistance", horseData.LastStartDistance);
|
||||||
|
command.Parameters.AddWithValue("@LastStartDistanceChange", horseData.LastStartDistanceChange);
|
||||||
|
command.Parameters.AddWithValue("@LastStartPrizeMoney", horseData.LastStartPrizeMoney);
|
||||||
|
|
||||||
|
// URL e link
|
||||||
|
command.Parameters.AddWithValue("@FormGuideUrl", horseData.FormGuideUrl ?? string.Empty);
|
||||||
|
command.Parameters.AddWithValue("@HorseProfileUrl", horseData.HorseProfileUrl ?? string.Empty);
|
||||||
|
command.Parameters.AddWithValue("@JockeyProfileUrl", horseData.JockeyProfileUrl ?? string.Empty);
|
||||||
|
command.Parameters.AddWithValue("@TrainerProfileUrl", horseData.TrainerProfileUrl ?? string.Empty);
|
||||||
|
|
||||||
|
// Risultato finale
|
||||||
|
command.Parameters.AddWithValue("@FinishResult", horseData.FinishResult ?? string.Empty);
|
||||||
|
|
||||||
|
// Campi per timestamp
|
||||||
|
command.Parameters.AddWithValue("@Created", currentDateTime);
|
||||||
|
command.Parameters.AddWithValue("@Updated", currentDateTime);
|
||||||
|
|
||||||
|
command.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError($"l'upsert dei dati del cavallo {horseData.HorseName}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
using CsvHelper;
|
||||||
|
using CsvHelper.Configuration;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Horses
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gestore per la lettura e l'elaborazione dei file CSV relativi alle corse dei cavalli
|
||||||
|
/// </summary>
|
||||||
|
public class FileReader : HorseRacingPredictor.Manager.FileReader
|
||||||
|
{
|
||||||
|
private readonly Manager.FileReader _fileReader;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Costruttore della classe FileReader
|
||||||
|
/// </summary>
|
||||||
|
public FileReader()
|
||||||
|
{
|
||||||
|
_fileReader = new Manager.FileReader();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Estrae il nome del meeting dal nome del file
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">Percorso del file</param>
|
||||||
|
/// <returns>Nome del meeting</returns>
|
||||||
|
public string ExtractMeetingName(string filePath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(filePath))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Il percorso del file non può essere vuoto.", nameof(filePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ottiene solo il nome del file senza percorso
|
||||||
|
string fileName = Path.GetFileName(filePath);
|
||||||
|
|
||||||
|
// Cerca il pattern YYYYMMDD-meetingname-rXX.csv
|
||||||
|
var regex = new Regex(@"^\d{8}-([a-zA-Z-]+)-r\d+\.csv$", RegexOptions.IgnoreCase);
|
||||||
|
var match = regex.Match(fileName);
|
||||||
|
|
||||||
|
if (match.Success && match.Groups.Count > 1)
|
||||||
|
{
|
||||||
|
// Estrae il nome del meeting e lo formatta
|
||||||
|
string meetingName = match.Groups[1].Value;
|
||||||
|
|
||||||
|
// Sostituisce i trattini con spazi e formatta ogni parola con l'iniziale maiuscola
|
||||||
|
meetingName = string.Join(" ", meetingName.Split('-')
|
||||||
|
.Select(s => char.ToUpper(s[0]) + s.Substring(1).ToLower()));
|
||||||
|
|
||||||
|
return meetingName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se il pattern non corrisponde, restituisce il nome del file senza estensione
|
||||||
|
return Path.GetFileNameWithoutExtension(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Estrae il numero della corsa dal nome del file
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">Percorso del file</param>
|
||||||
|
/// <returns>Numero della corsa o -1 se non può essere determinato</returns>
|
||||||
|
public int ExtractRaceNumber(string filePath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(filePath))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Il percorso del file non può essere vuoto.", nameof(filePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ottiene solo il nome del file senza percorso
|
||||||
|
string fileName = Path.GetFileName(filePath);
|
||||||
|
|
||||||
|
// Cerca il pattern YYYYMMDD-meetingname-rXX.csv dove XX è il numero della corsa
|
||||||
|
var regex = new Regex(@"^\d{8}-[a-zA-Z-]+-r(\d+)\.csv$", RegexOptions.IgnoreCase);
|
||||||
|
var match = regex.Match(fileName);
|
||||||
|
|
||||||
|
if (match.Success && match.Groups.Count > 1)
|
||||||
|
{
|
||||||
|
// Estrae il numero della corsa
|
||||||
|
if (int.TryParse(match.Groups[1].Value, out int raceNumber))
|
||||||
|
{
|
||||||
|
return raceNumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1; // Restituisce -1 se non è possibile determinare il numero della corsa
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Estrae la data della corsa dal nome del file
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">Percorso del file</param>
|
||||||
|
/// <returns>Data della corsa o DateTime.MinValue se non può essere determinata</returns>
|
||||||
|
public DateTime ExtractRaceDate(string filePath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(filePath))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Il percorso del file non può essere vuoto.", nameof(filePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ottiene solo il nome del file senza percorso
|
||||||
|
string fileName = Path.GetFileName(filePath);
|
||||||
|
|
||||||
|
// Cerca il pattern YYYYMMDD all'inizio del nome file
|
||||||
|
var regex = new Regex(@"^(\d{4})(\d{2})(\d{2})-", RegexOptions.IgnoreCase);
|
||||||
|
var match = regex.Match(fileName);
|
||||||
|
|
||||||
|
if (match.Success && match.Groups.Count > 3)
|
||||||
|
{
|
||||||
|
// Estrae anno, mese e giorno
|
||||||
|
if (int.TryParse(match.Groups[1].Value, out int year) &&
|
||||||
|
int.TryParse(match.Groups[2].Value, out int month) &&
|
||||||
|
int.TryParse(match.Groups[3].Value, out int day))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new DateTime(year, month, day);
|
||||||
|
}
|
||||||
|
catch (ArgumentOutOfRangeException)
|
||||||
|
{
|
||||||
|
// Data non valida
|
||||||
|
return DateTime.MinValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DateTime.MinValue; // Restituisce DateTime.MinValue se non è possibile determinare la data
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Legge i dati dei cavalli da un file CSV utilizzando CsvHelper
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">Percorso del file CSV</param>
|
||||||
|
/// <returns>Lista di oggetti HorseRaceData</returns>
|
||||||
|
public List<HorseRaceData> ReadHorseDataFromFile(string filePath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(filePath))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Il percorso del file non può essere vuoto.", nameof(filePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException($"File non trovato: {filePath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Configurazione per CsvHelper che utilizza i convertitori centralizzati
|
||||||
|
var myConfig = new CsvConfiguration(CultureInfo.InvariantCulture)
|
||||||
|
{
|
||||||
|
Delimiter = ",",
|
||||||
|
HasHeaderRecord = true,
|
||||||
|
HeaderValidated = null,
|
||||||
|
MissingFieldFound = null,
|
||||||
|
BadDataFound = null
|
||||||
|
};
|
||||||
|
|
||||||
|
using (var reader = new StreamReader(filePath))
|
||||||
|
using (var csv = new CsvReader(reader, myConfig))
|
||||||
|
{
|
||||||
|
// Utilizzo dei convertitori centralizzati
|
||||||
|
csv.Context.TypeConverterCache.AddConverter<decimal?>(new NullableDecimalConverter());
|
||||||
|
csv.Context.TypeConverterCache.AddConverter<int?>(new NullableIntConverter());
|
||||||
|
|
||||||
|
// Registriamo la mappa di conversione tra CSV e modello
|
||||||
|
csv.Context.RegisterClassMap<Punters>();
|
||||||
|
|
||||||
|
// Leggi i record
|
||||||
|
var records = csv.GetRecords<HorseRaceData>().ToList();
|
||||||
|
|
||||||
|
// Stampa il numero di record letti
|
||||||
|
Console.WriteLine($"Letti {records.Count} record dal file {Path.GetFileName(filePath)}");
|
||||||
|
|
||||||
|
return records;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Errore durante la lettura del file CSV {filePath}: {ex.Message}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene tutti i file CSV relativi alle corse dei cavalli in una cartella
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="folderPath">Percorso della cartella</param>
|
||||||
|
/// <returns>Lista di percorsi dei file CSV</returns>
|
||||||
|
public List<string> GetHorseRaceFiles(string folderPath)
|
||||||
|
{
|
||||||
|
return _fileReader.GetCsvFiles(folderPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene informazioni sulla corsa dal nome del file
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">Percorso del file</param>
|
||||||
|
/// <returns>Tupla contenente (NomeMeeting, NumeroCorsa, DataCorsa)</returns>
|
||||||
|
public (string MeetingName, int RaceNumber, DateTime RaceDate) GetRaceInfoFromFileName(string filePath)
|
||||||
|
{
|
||||||
|
string meetingName = ExtractMeetingName(filePath);
|
||||||
|
int raceNumber = ExtractRaceNumber(filePath);
|
||||||
|
DateTime raceDate = ExtractRaceDate(filePath);
|
||||||
|
|
||||||
|
return (meetingName, raceNumber, raceDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Metodo per processare tutti i file di corse in una cartella
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="folderPath">Percorso della cartella contenente i file CSV</param>
|
||||||
|
/// <returns>Dictionary con chiave = identificativo corsa, valore = lista di dati cavalli</returns>
|
||||||
|
public Dictionary<string, List<HorseRaceData>> ProcessAllRaceFiles(string folderPath)
|
||||||
|
{
|
||||||
|
var result = new Dictionary<string, List<HorseRaceData>>();
|
||||||
|
var files = GetHorseRaceFiles(folderPath);
|
||||||
|
|
||||||
|
Console.WriteLine($"Trovati {files.Count} file CSV nella cartella {folderPath}");
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Elaborazione file: {Path.GetFileName(file)}");
|
||||||
|
|
||||||
|
var horseDataList = ReadHorseDataFromFile(file);
|
||||||
|
var raceInfo = GetRaceInfoFromFileName(file);
|
||||||
|
|
||||||
|
string key = $"{raceInfo.RaceDate:yyyy-MM-dd} - {raceInfo.MeetingName} - Race {raceInfo.RaceNumber}";
|
||||||
|
result.Add(key, horseDataList);
|
||||||
|
|
||||||
|
Console.WriteLine($"Elaborati {horseDataList.Count} record per la corsa {key}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Errore nel processare il file {file}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"Totale corse elaborate: {result.Count}");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
using CsvHelper.Configuration;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Horses
|
||||||
|
{
|
||||||
|
public sealed class Punters : ClassMap<HorseRaceData>
|
||||||
|
{
|
||||||
|
public Punters()
|
||||||
|
{
|
||||||
|
// Mappatura informazioni principali sul cavallo
|
||||||
|
Map(m => m.Num).Name("Num", "#Num", "Number");
|
||||||
|
Map(m => m.HorseName).Name("Horse Name", "HorseName", "Name");
|
||||||
|
Map(m => m.Age).Name("Age");
|
||||||
|
Map(m => m.Gender).Name("Gender");
|
||||||
|
Map(m => m.HandicapRating).Name("Handicap Rating");
|
||||||
|
|
||||||
|
// Statistiche di carriera
|
||||||
|
Map(m => m.CareerRuns).Name("Career Runs");
|
||||||
|
Map(m => m.CareerWins).Name("Career Wins");
|
||||||
|
Map(m => m.CareerStrikeRate).Name("Career Strike Rate");
|
||||||
|
Map(m => m.CareerROI).Name("Career ROI");
|
||||||
|
Map(m => m.CareerPlacings).Name("Career Placings");
|
||||||
|
Map(m => m.CareerPlaceStrikeRate).Name("Career Place Strike Rate");
|
||||||
|
|
||||||
|
// Statistiche su pista secca
|
||||||
|
Map(m => m.DryTrackRuns).Name("Dry Track Runs");
|
||||||
|
Map(m => m.DryTrackWins).Name("Dry Track Wins");
|
||||||
|
Map(m => m.DryTrackStrikeRate).Name("Dry Track Strike Rate");
|
||||||
|
Map(m => m.DryTrackROI).Name("Dry Track ROI");
|
||||||
|
|
||||||
|
// Statistiche su pista bagnata
|
||||||
|
Map(m => m.WetTrackRuns).Name("Wet Track Runs");
|
||||||
|
Map(m => m.WetTrackWins).Name("Wet Track Wins");
|
||||||
|
Map(m => m.WetTrackStrikeRate).Name("Wet Track Strike Rate");
|
||||||
|
Map(m => m.WetTrackROI).Name("Wet Track ROI");
|
||||||
|
|
||||||
|
// Statistiche sui premi
|
||||||
|
Map(m => m.AveragePrizeMoney).Name("Average Prize Money");
|
||||||
|
Map(m => m.CareerPrizeMoney).Name("Career Prize Money");
|
||||||
|
|
||||||
|
// Quote e pesi
|
||||||
|
Map(m => m.BestFixedOdds).Name("Best Fixed Odds");
|
||||||
|
Map(m => m.BetEasyOdds).Name("BetEasy Odds");
|
||||||
|
Map(m => m.Weight).Name("Weight");
|
||||||
|
Map(m => m.WeightCarried).Name("Weight Carried");
|
||||||
|
Map(m => m.Barrier).Name("Barrier");
|
||||||
|
Map(m => m.PrizeMoney).Name("Prize Money");
|
||||||
|
|
||||||
|
// Statistiche sulla pista attuale
|
||||||
|
Map(m => m.ThisTrackRuns).Name("This Track Runs");
|
||||||
|
Map(m => m.ThisTrackWins).Name("This Track Wins");
|
||||||
|
Map(m => m.ThisTrackStrikeRate).Name("This Track Strike Rate");
|
||||||
|
Map(m => m.ThisTrackROI).Name("This Track ROI");
|
||||||
|
Map(m => m.ThisTrackPlaces).Name("This Track Places");
|
||||||
|
Map(m => m.ThisTrackPlaceStrikeRate).Name("This Track Place Strike Rate");
|
||||||
|
|
||||||
|
// Statistiche sulla distanza
|
||||||
|
Map(m => m.ThisDistanceRuns).Name("This Distance Runs");
|
||||||
|
Map(m => m.ThisDistanceWins).Name("This Distance Wins");
|
||||||
|
Map(m => m.ThisDistanceStrikeRate).Name("This Distance Strike Rate");
|
||||||
|
Map(m => m.ThisDistanceROI).Name("This Distance ROI");
|
||||||
|
Map(m => m.ThisDistancePlaces).Name("This Distance Places");
|
||||||
|
Map(m => m.ThisDistancePlaceStrikeRate).Name("This Distance Place Strike Rate");
|
||||||
|
|
||||||
|
// Statistiche sulla combinazione pista-distanza
|
||||||
|
Map(m => m.ThisTrackDistanceRuns).Name("This Track Distance Runs");
|
||||||
|
Map(m => m.ThisTrackDistanceWins).Name("This Track Distance Wins");
|
||||||
|
Map(m => m.ThisTrackDistanceStrikeRate).Name("This Track Distance Strike Rate");
|
||||||
|
Map(m => m.ThisTrackDistanceROI).Name("This Track Distance ROI");
|
||||||
|
Map(m => m.ThisTrackDistancePlaces).Name("This Track Distance Places");
|
||||||
|
Map(m => m.ThisTrackDistancePlaceStrikeRate).Name("This Track Distance Place Strike Rate");
|
||||||
|
|
||||||
|
// Statistiche sulle condizioni
|
||||||
|
Map(m => m.ThisConditionRuns).Name("This Condition Runs");
|
||||||
|
Map(m => m.ThisConditionWins).Name("This Condition Wins");
|
||||||
|
Map(m => m.ThisConditionStrikeRate).Name("This Condition Strike Rate");
|
||||||
|
Map(m => m.ThisConditionROI).Name("This Condition ROI");
|
||||||
|
Map(m => m.ThisConditionPlaces).Name("This Condition Places");
|
||||||
|
Map(m => m.ThisConditionPlaceStrikeRate).Name("This Condition Place Strike Rate");
|
||||||
|
|
||||||
|
// Fantino
|
||||||
|
Map(m => m.Jockey).Name("Jockey");
|
||||||
|
Map(m => m.Apprentice).Name("Apprentice");
|
||||||
|
Map(m => m.JockeyWeightClaim).Name("Jockey Weight Claim");
|
||||||
|
|
||||||
|
// Statistiche fantino - ultimi 100
|
||||||
|
Map(m => m.JockeyLast100HorseEarnings).Name("Jockey Last 100 Horse Earnings");
|
||||||
|
Map(m => m.JockeyLast100AvgHorseEarnings).Name("Jockey Last 100 Avg Horse Earnings");
|
||||||
|
Map(m => m.JockeyLast100Starts).Name("Jockey Last 100 Starts");
|
||||||
|
Map(m => m.JockeyLast100Wins).Name("Jockey Last 100 Wins");
|
||||||
|
Map(m => m.JockeyLast100StrikeRate).Name("Jockey Last 100 Strike Rate");
|
||||||
|
Map(m => m.JockeyLast100ROI).Name("Jockey Last 100 ROI");
|
||||||
|
Map(m => m.JockeyLast100Places).Name("Jockey Last 100 Places");
|
||||||
|
Map(m => m.JockeyLast100PlaceStrikeRate).Name("Jockey Last 100 Place Strike Rate");
|
||||||
|
|
||||||
|
// Statistiche fantino - ultimi 12 mesi
|
||||||
|
Map(m => m.Jockey12MonthHorseEarnings).Name("Jockey 12 Month Horse Earnings");
|
||||||
|
Map(m => m.Jockey12MonthAvgHorseEarnings).Name("Jockey 12 Month Avg Horse Earnings");
|
||||||
|
Map(m => m.Jockey12MonthsStarts).Name("Jockey 12 Months Starts");
|
||||||
|
Map(m => m.Jockey12MonthsWins).Name("Jockey 12 Months Wins");
|
||||||
|
Map(m => m.Jockey12MonthsStrikeRate).Name("Jockey 12 Months Strike Rate");
|
||||||
|
Map(m => m.Jockey12MonthsROI).Name("Jockey 12 Months ROI");
|
||||||
|
Map(m => m.Jockey12MonthsPlaces).Name("Jockey 12 Months Places");
|
||||||
|
Map(m => m.Jockey12MonthsPlaceStrikeRate).Name("Jockey 12 Months Place Strike Rate");
|
||||||
|
|
||||||
|
// Statistiche fantino - stagione corrente
|
||||||
|
Map(m => m.JockeyThisSeasonHorseEarnings).Name("Jockey This Season Horse Earnings");
|
||||||
|
Map(m => m.JockeyThisSeasonAvgHorseEarnings).Name("Jockey This Season Avg Horse Earnings");
|
||||||
|
Map(m => m.JockeyThisSeasonStarts).Name("Jockey This Season Starts");
|
||||||
|
Map(m => m.JockeyThisSeasonWins).Name("Jockey This Season Wins");
|
||||||
|
Map(m => m.JockeyThisSeasonStrikeRate).Name("Jockey This Season Strike Rate");
|
||||||
|
Map(m => m.JockeyThisSeasonROI).Name("Jockey This Season ROI");
|
||||||
|
Map(m => m.JockeyThisSeasonPlaces).Name("Jockey This Season Places");
|
||||||
|
Map(m => m.JockeyThisSeasonPlaceStrikeRate).Name("Jockey This Season Place Strike Rate");
|
||||||
|
|
||||||
|
// Statistiche fantino - stagione precedente
|
||||||
|
Map(m => m.JockeyLastSeasonHorseEarnings).Name("Jockey Last Season Horse Earnings");
|
||||||
|
Map(m => m.JockeyLastSeasonAvgHorseEarnings).Name("Jockey Last Season Avg Horse Earnings");
|
||||||
|
Map(m => m.JockeyLastSeasonStarts).Name("Jockey Last Season Starts");
|
||||||
|
Map(m => m.JockeyLastSeasonWins).Name("Jockey Last Season Wins");
|
||||||
|
Map(m => m.JockeyLastSeasonStrikeRate).Name("Jockey Last Season Strike Rate");
|
||||||
|
Map(m => m.JockeyLastSeasonROI).Name("Jockey Last Season ROI");
|
||||||
|
Map(m => m.JockeyLastSeasonPlaces).Name("Jockey Last Season Places");
|
||||||
|
Map(m => m.JockeyLastSeasonPlaceStrikeRate).Name("Jockey Last Season Place Strike Rate");
|
||||||
|
|
||||||
|
// Allenatore
|
||||||
|
Map(m => m.Trainer).Name("Trainer");
|
||||||
|
|
||||||
|
// Statistiche allenatore - ultimi 100
|
||||||
|
Map(m => m.TrainerLast100HorseEarnings).Name("Trainer Last 100 Horse Earnings");
|
||||||
|
Map(m => m.TrainerLast100AvgHorseEarnings).Name("Trainer Last 100 Avg Horse Earnings");
|
||||||
|
Map(m => m.TrainerLast100Starts).Name("Trainer Last 100 Starts");
|
||||||
|
Map(m => m.TrainerLast100Wins).Name("Trainer Last 100 Wins");
|
||||||
|
Map(m => m.TrainerLast100StrikeRate).Name("Trainer Last 100 Strike Rate");
|
||||||
|
Map(m => m.TrainerLast100ROI).Name("Trainer Last 100 ROI");
|
||||||
|
Map(m => m.TrainerLast100Places).Name("Trainer Last 100 Places");
|
||||||
|
Map(m => m.TrainerLast100PlaceStrikeRate).Name("Trainer Last 100 Place Strike Rate");
|
||||||
|
|
||||||
|
// Statistiche allenatore - ultimi 12 mesi
|
||||||
|
Map(m => m.Trainer12MonthHorseEarnings).Name("Trainer 12 Month Horse Earnings");
|
||||||
|
Map(m => m.Trainer12MonthAvgHorseEarnings).Name("Trainer 12 Month Avg Horse Earnings");
|
||||||
|
Map(m => m.Trainer12MonthsStarts).Name("Trainer 12 Months Starts");
|
||||||
|
Map(m => m.Trainer12MonthsWins).Name("Trainer 12 Months Wins");
|
||||||
|
Map(m => m.Trainer12MonthsStrikeRate).Name("Trainer 12 Months Strike Rate");
|
||||||
|
Map(m => m.Trainer12MonthsROI).Name("Trainer 12 Months ROI");
|
||||||
|
Map(m => m.Trainer12MonthsPlaces).Name("Trainer 12 Months Places");
|
||||||
|
Map(m => m.Trainer12MonthsPlaceStrikeRate).Name("Trainer 12 Months Place Strike Rate");
|
||||||
|
|
||||||
|
// Statistiche allenatore - stagione corrente
|
||||||
|
Map(m => m.TrainerThisSeasonHorseEarnings).Name("Trainer This Season Horse Earnings");
|
||||||
|
Map(m => m.TrainerThisSeasonAvgHorseEarnings).Name("Trainer This Season Avg Horse Earnings");
|
||||||
|
Map(m => m.TrainerThisSeasonStarts).Name("Trainer This Season Starts");
|
||||||
|
Map(m => m.TrainerThisSeasonWins).Name("Trainer This Season Wins");
|
||||||
|
Map(m => m.TrainerThisSeasonStrikeRate).Name("Trainer This Season Strike Rate");
|
||||||
|
Map(m => m.TrainerThisSeasonROI).Name("Trainer This Season ROI");
|
||||||
|
Map(m => m.TrainerThisSeasonPlaces).Name("Trainer This Season Places");
|
||||||
|
Map(m => m.TrainerThisSeasonPlaceStrikeRate).Name("Trainer This Season Place Strike Rate");
|
||||||
|
|
||||||
|
// Statistiche allenatore - stagione precedente
|
||||||
|
Map(m => m.TrainerLastSeasonHorseEarnings).Name("Trainer Last Season Horse Earnings");
|
||||||
|
Map(m => m.TrainerLastSeasonAvgHorseEarnings).Name("Trainer Last Season Avg Horse Earnings");
|
||||||
|
Map(m => m.TrainerLastSeasonStarts).Name("Trainer Last Season Starts");
|
||||||
|
Map(m => m.TrainerLastSeasonWins).Name("Trainer Last Season Wins");
|
||||||
|
Map(m => m.TrainerLastSeasonStrikeRate).Name("Trainer Last Season Strike Rate");
|
||||||
|
Map(m => m.TrainerLastSeasonROI).Name("Trainer Last Season ROI");
|
||||||
|
Map(m => m.TrainerLastSeasonPlaces).Name("Trainer Last Season Places");
|
||||||
|
Map(m => m.TrainerLastSeasonPlaceStrikeRate).Name("Trainer Last Season Place Strike Rate");
|
||||||
|
|
||||||
|
// Informazioni ultima corsa
|
||||||
|
Map(m => m.LastStartFinishPosition).Name("Last Start Finish Position");
|
||||||
|
Map(m => m.LastStartMargin).Name("Last Start Margin");
|
||||||
|
Map(m => m.LastStartDistance).Name("Last Start Distance");
|
||||||
|
Map(m => m.LastStartDistanceChange).Name("Last Start Distance Change");
|
||||||
|
Map(m => m.LastStartPrizeMoney).Name("Last Start Prize Money");
|
||||||
|
|
||||||
|
// URL e link
|
||||||
|
Map(m => m.FormGuideUrl).Name("Form Guide Url");
|
||||||
|
Map(m => m.HorseProfileUrl).Name("Horse Profile Url");
|
||||||
|
Map(m => m.JockeyProfileUrl).Name("Jockey Profile Url");
|
||||||
|
Map(m => m.TrainerProfileUrl).Name("Trainer Profile Url");
|
||||||
|
|
||||||
|
// Risultato finale
|
||||||
|
Map(m => m.FinishResult).Name("Finish Result (Updates after race)", "Finish Result (Updates");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,536 @@
|
|||||||
|
using CsvHelper.Configuration.Attributes;
|
||||||
|
using static HorseRacingPredictor.Manager.FileReader;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Horses
|
||||||
|
{
|
||||||
|
public class HorseRaceData
|
||||||
|
{
|
||||||
|
// Informazioni principali sul cavallo
|
||||||
|
[Name("Num")]
|
||||||
|
public string Num { get; set; }
|
||||||
|
|
||||||
|
[Name("Horse Name")]
|
||||||
|
public string HorseName { get; set; }
|
||||||
|
|
||||||
|
[Name("Age")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? Age { get; set; }
|
||||||
|
|
||||||
|
[Name("Gender")]
|
||||||
|
public string Gender { get; set; }
|
||||||
|
|
||||||
|
[Name("Handicap Rating")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? HandicapRating { get; set; }
|
||||||
|
|
||||||
|
// Statistiche di carriera
|
||||||
|
[Name("Career Runs")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? CareerRuns { get; set; }
|
||||||
|
|
||||||
|
[Name("Career Wins")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? CareerWins { get; set; }
|
||||||
|
|
||||||
|
[Name("Career Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? CareerStrikeRate { get; set; }
|
||||||
|
|
||||||
|
[Name("Career ROI")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? CareerROI { get; set; }
|
||||||
|
|
||||||
|
[Name("Career Placings")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? CareerPlacings { get; set; }
|
||||||
|
|
||||||
|
[Name("Career Place Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? CareerPlaceStrikeRate { get; set; }
|
||||||
|
|
||||||
|
// Statistiche su pista secca
|
||||||
|
[Name("Dry Track Runs")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? DryTrackRuns { get; set; }
|
||||||
|
|
||||||
|
[Name("Dry Track Wins")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? DryTrackWins { get; set; }
|
||||||
|
|
||||||
|
[Name("Dry Track Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? DryTrackStrikeRate { get; set; }
|
||||||
|
|
||||||
|
[Name("Dry Track ROI")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? DryTrackROI { get; set; }
|
||||||
|
|
||||||
|
// Statistiche su pista bagnata
|
||||||
|
[Name("Wet Track Runs")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? WetTrackRuns { get; set; }
|
||||||
|
|
||||||
|
[Name("Wet Track Wins")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? WetTrackWins { get; set; }
|
||||||
|
|
||||||
|
[Name("Wet Track Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? WetTrackStrikeRate { get; set; }
|
||||||
|
|
||||||
|
[Name("Wet Track ROI")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? WetTrackROI { get; set; }
|
||||||
|
|
||||||
|
// Statistiche sui premi
|
||||||
|
[Name("Average Prize Money")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? AveragePrizeMoney { get; set; }
|
||||||
|
|
||||||
|
[Name("Career Prize Money")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? CareerPrizeMoney { get; set; }
|
||||||
|
|
||||||
|
// Quote e pesi
|
||||||
|
[Name("Best Fixed Odds")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? BestFixedOdds { get; set; }
|
||||||
|
|
||||||
|
[Name("BetEasy Odds")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? BetEasyOdds { get; set; }
|
||||||
|
|
||||||
|
[Name("Weight")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? Weight { get; set; }
|
||||||
|
|
||||||
|
[Name("Weight Carried")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? WeightCarried { get; set; }
|
||||||
|
|
||||||
|
[Name("Barrier")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? Barrier { get; set; }
|
||||||
|
|
||||||
|
[Name("Prize Money")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? PrizeMoney { get; set; }
|
||||||
|
|
||||||
|
// Statistiche sulla pista attuale
|
||||||
|
[Name("This Track Runs")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? ThisTrackRuns { get; set; }
|
||||||
|
|
||||||
|
[Name("This Track Wins")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? ThisTrackWins { get; set; }
|
||||||
|
|
||||||
|
[Name("This Track Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? ThisTrackStrikeRate { get; set; }
|
||||||
|
|
||||||
|
[Name("This Track ROI")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? ThisTrackROI { get; set; }
|
||||||
|
|
||||||
|
[Name("This Track Places")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? ThisTrackPlaces { get; set; }
|
||||||
|
|
||||||
|
[Name("This Track Place Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? ThisTrackPlaceStrikeRate { get; set; }
|
||||||
|
|
||||||
|
// Statistiche sulla distanza
|
||||||
|
[Name("This Distance Runs")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? ThisDistanceRuns { get; set; }
|
||||||
|
|
||||||
|
[Name("This Distance Wins")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? ThisDistanceWins { get; set; }
|
||||||
|
|
||||||
|
[Name("This Distance Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? ThisDistanceStrikeRate { get; set; }
|
||||||
|
|
||||||
|
[Name("This Distance ROI")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? ThisDistanceROI { get; set; }
|
||||||
|
|
||||||
|
[Name("This Distance Places")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? ThisDistancePlaces { get; set; }
|
||||||
|
|
||||||
|
[Name("This Distance Place Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? ThisDistancePlaceStrikeRate { get; set; }
|
||||||
|
|
||||||
|
// Statistiche sulla combinazione pista-distanza
|
||||||
|
[Name("This Track Distance Runs")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? ThisTrackDistanceRuns { get; set; }
|
||||||
|
|
||||||
|
[Name("This Track Distance Wins")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? ThisTrackDistanceWins { get; set; }
|
||||||
|
|
||||||
|
[Name("This Track Distance Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? ThisTrackDistanceStrikeRate { get; set; }
|
||||||
|
|
||||||
|
[Name("This Track Distance ROI")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? ThisTrackDistanceROI { get; set; }
|
||||||
|
|
||||||
|
[Name("This Track Distance Places")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? ThisTrackDistancePlaces { get; set; }
|
||||||
|
|
||||||
|
[Name("This Track Distance Place Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? ThisTrackDistancePlaceStrikeRate { get; set; }
|
||||||
|
|
||||||
|
// Statistiche sulle condizioni
|
||||||
|
[Name("This Condition Runs")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? ThisConditionRuns { get; set; }
|
||||||
|
|
||||||
|
[Name("This Condition Wins")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? ThisConditionWins { get; set; }
|
||||||
|
|
||||||
|
[Name("This Condition Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? ThisConditionStrikeRate { get; set; }
|
||||||
|
|
||||||
|
[Name("This Condition ROI")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? ThisConditionROI { get; set; }
|
||||||
|
|
||||||
|
[Name("This Condition Places")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? ThisConditionPlaces { get; set; }
|
||||||
|
|
||||||
|
[Name("This Condition Place Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? ThisConditionPlaceStrikeRate { get; set; }
|
||||||
|
|
||||||
|
// Informazioni sul fantino
|
||||||
|
[Name("Jockey")]
|
||||||
|
public string Jockey { get; set; }
|
||||||
|
|
||||||
|
[Name("Apprentice")]
|
||||||
|
public string Apprentice { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey Weight Claim")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? JockeyWeightClaim { get; set; }
|
||||||
|
|
||||||
|
// Statistiche fantino - ultimi 100
|
||||||
|
[Name("Jockey Last 100 Horse Earnings")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? JockeyLast100HorseEarnings { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey Last 100 Avg Horse Earnings")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? JockeyLast100AvgHorseEarnings { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey Last 100 Starts")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? JockeyLast100Starts { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey Last 100 Wins")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? JockeyLast100Wins { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey Last 100 Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? JockeyLast100StrikeRate { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey Last 100 ROI")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? JockeyLast100ROI { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey Last 100 Places")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? JockeyLast100Places { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey Last 100 Place Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? JockeyLast100PlaceStrikeRate { get; set; }
|
||||||
|
|
||||||
|
// Statistiche fantino - ultimi 12 mesi
|
||||||
|
[Name("Jockey 12 Month Horse Earnings")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? Jockey12MonthHorseEarnings { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey 12 Month Avg Horse Earnings")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? Jockey12MonthAvgHorseEarnings { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey 12 Months Starts")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? Jockey12MonthsStarts { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey 12 Months Wins")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? Jockey12MonthsWins { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey 12 Months Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? Jockey12MonthsStrikeRate { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey 12 Months ROI")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? Jockey12MonthsROI { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey 12 Months Places")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? Jockey12MonthsPlaces { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey 12 Months Place Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? Jockey12MonthsPlaceStrikeRate { get; set; }
|
||||||
|
|
||||||
|
// Statistiche fantino - stagione corrente
|
||||||
|
[Name("Jockey This Season Horse Earnings")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? JockeyThisSeasonHorseEarnings { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey This Season Avg Horse Earnings")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? JockeyThisSeasonAvgHorseEarnings { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey This Season Starts")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? JockeyThisSeasonStarts { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey This Season Wins")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? JockeyThisSeasonWins { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey This Season Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? JockeyThisSeasonStrikeRate { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey This Season ROI")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? JockeyThisSeasonROI { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey This Season Places")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? JockeyThisSeasonPlaces { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey This Season Place Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? JockeyThisSeasonPlaceStrikeRate { get; set; }
|
||||||
|
|
||||||
|
// Statistiche fantino - stagione precedente
|
||||||
|
[Name("Jockey Last Season Horse Earnings")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? JockeyLastSeasonHorseEarnings { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey Last Season Avg Horse Earnings")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? JockeyLastSeasonAvgHorseEarnings { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey Last Season Starts")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? JockeyLastSeasonStarts { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey Last Season Wins")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? JockeyLastSeasonWins { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey Last Season Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? JockeyLastSeasonStrikeRate { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey Last Season ROI")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? JockeyLastSeasonROI { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey Last Season Places")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? JockeyLastSeasonPlaces { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey Last Season Place Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? JockeyLastSeasonPlaceStrikeRate { get; set; }
|
||||||
|
|
||||||
|
// Informazioni sull'allenatore
|
||||||
|
[Name("Trainer")]
|
||||||
|
public string Trainer { get; set; }
|
||||||
|
|
||||||
|
// Statistiche allenatore - ultimi 100
|
||||||
|
[Name("Trainer Last 100 Horse Earnings")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? TrainerLast100HorseEarnings { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer Last 100 Avg Horse Earnings")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? TrainerLast100AvgHorseEarnings { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer Last 100 Starts")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? TrainerLast100Starts { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer Last 100 Wins")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? TrainerLast100Wins { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer Last 100 Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? TrainerLast100StrikeRate { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer Last 100 ROI")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? TrainerLast100ROI { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer Last 100 Places")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? TrainerLast100Places { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer Last 100 Place Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? TrainerLast100PlaceStrikeRate { get; set; }
|
||||||
|
|
||||||
|
// Statistiche allenatore - ultimi 12 mesi
|
||||||
|
[Name("Trainer 12 Month Horse Earnings")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? Trainer12MonthHorseEarnings { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer 12 Month Avg Horse Earnings")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? Trainer12MonthAvgHorseEarnings { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer 12 Months Starts")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? Trainer12MonthsStarts { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer 12 Months Wins")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? Trainer12MonthsWins { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer 12 Months Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? Trainer12MonthsStrikeRate { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer 12 Months ROI")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? Trainer12MonthsROI { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer 12 Months Places")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? Trainer12MonthsPlaces { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer 12 Months Place Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? Trainer12MonthsPlaceStrikeRate { get; set; }
|
||||||
|
|
||||||
|
// Statistiche allenatore - stagione corrente
|
||||||
|
[Name("Trainer This Season Horse Earnings")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? TrainerThisSeasonHorseEarnings { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer This Season Avg Horse Earnings")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? TrainerThisSeasonAvgHorseEarnings { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer This Season Starts")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? TrainerThisSeasonStarts { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer This Season Wins")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? TrainerThisSeasonWins { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer This Season Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? TrainerThisSeasonStrikeRate { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer This Season ROI")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? TrainerThisSeasonROI { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer This Season Places")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? TrainerThisSeasonPlaces { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer This Season Place Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? TrainerThisSeasonPlaceStrikeRate { get; set; }
|
||||||
|
|
||||||
|
// Statistiche allenatore - stagione precedente
|
||||||
|
[Name("Trainer Last Season Horse Earnings")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? TrainerLastSeasonHorseEarnings { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer Last Season Avg Horse Earnings")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? TrainerLastSeasonAvgHorseEarnings { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer Last Season Starts")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? TrainerLastSeasonStarts { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer Last Season Wins")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? TrainerLastSeasonWins { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer Last Season Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? TrainerLastSeasonStrikeRate { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer Last Season ROI")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? TrainerLastSeasonROI { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer Last Season Places")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? TrainerLastSeasonPlaces { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer Last Season Place Strike Rate")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? TrainerLastSeasonPlaceStrikeRate { get; set; }
|
||||||
|
|
||||||
|
// Informazioni ultima corsa
|
||||||
|
[Name("Last Start Finish Position")]
|
||||||
|
[TypeConverter(typeof(NullableIntConverter))]
|
||||||
|
public int? LastStartFinishPosition { get; set; }
|
||||||
|
|
||||||
|
[Name("Last Start Margin")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? LastStartMargin { get; set; }
|
||||||
|
|
||||||
|
[Name("Last Start Distance")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? LastStartDistance { get; set; }
|
||||||
|
|
||||||
|
[Name("Last Start Distance Change")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? LastStartDistanceChange { get; set; }
|
||||||
|
|
||||||
|
[Name("Last Start Prize Money")]
|
||||||
|
[TypeConverter(typeof(NullableDecimalConverter))]
|
||||||
|
public decimal? LastStartPrizeMoney { get; set; }
|
||||||
|
|
||||||
|
// Link e URL
|
||||||
|
[Name("Form Guide Url")]
|
||||||
|
public string FormGuideUrl { get; set; }
|
||||||
|
|
||||||
|
[Name("Horse Profile Url")]
|
||||||
|
public string HorseProfileUrl { get; set; }
|
||||||
|
|
||||||
|
[Name("Jockey Profile Url")]
|
||||||
|
public string JockeyProfileUrl { get; set; }
|
||||||
|
|
||||||
|
[Name("Trainer Profile Url")]
|
||||||
|
public string TrainerProfileUrl { get; set; }
|
||||||
|
|
||||||
|
// Risultato finale (aggiornato dopo la corsa)
|
||||||
|
[Name("Finish Result (Updates after race)")]
|
||||||
|
public string FinishResult { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,573 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,360 @@
|
|||||||
|
using System;
|
||||||
|
using System.Data;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace BettingPredictor
|
||||||
|
{
|
||||||
|
partial class Main
|
||||||
|
{
|
||||||
|
// Container
|
||||||
|
private System.ComponentModel.IContainer components = null;
|
||||||
|
|
||||||
|
// Grafica
|
||||||
|
private TabControl tabControl;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// Replace the buttonLoadFootball with buttonImportFootball and add buttonDownloadFootball
|
||||||
|
// Football tab
|
||||||
|
private TabPage tabPageFootball;
|
||||||
|
private DateTimePicker dateTimePicker;
|
||||||
|
private Button buttonImportFootball;
|
||||||
|
private Button buttonDownloadFootball;
|
||||||
|
private DataGridView dataGridViewFootball;
|
||||||
|
private ProgressBar progressBarFootball;
|
||||||
|
private Label labelStatusFootball;
|
||||||
|
|
||||||
|
// 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";
|
||||||
|
|
||||||
|
public static readonly string[] AllOptions = { GetLeagueFixtures, GetLeagueOdds };
|
||||||
|
}
|
||||||
|
|
||||||
|
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";
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
components.Dispose();
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.ResumeLayout(false);
|
||||||
|
this.PerformLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private System.ComponentModel.IContainer components = null;
|
||||||
|
private ProgressBar progressBar;
|
||||||
|
private Label labelProgress;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,747 @@
|
|||||||
|
using HorseRacingPredictor.Football;
|
||||||
|
using HorseRacingPredictor.Football.Manager;
|
||||||
|
using HorseRacingPredictor.Horses;
|
||||||
|
using HorseRacingPredictor.Horses.ML;
|
||||||
|
using System;
|
||||||
|
using System.Data;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace BettingPredictor
|
||||||
|
{
|
||||||
|
public partial class Main : Form
|
||||||
|
{
|
||||||
|
private readonly HorseRacingPredictor.Horses.Calculator calculator;
|
||||||
|
private readonly HorseRacingPredictor.Horses.Database horseDbManager;
|
||||||
|
private readonly HorseRacingPredictor.Horses.FileReader fileReaderHorses;
|
||||||
|
private readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
|
// Nuova classe centralizzata per l'API Football
|
||||||
|
private readonly HorseRacingPredictor.Football.Main footballManager;
|
||||||
|
|
||||||
|
private readonly MachineLearningService mlService = new MachineLearningService();
|
||||||
|
|
||||||
|
// Memorizza i dati importati per utilizzarli nella fase di predizione
|
||||||
|
private DataTable importedHorsesData;
|
||||||
|
|
||||||
|
public Main()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
calculator = new HorseRacingPredictor.Horses.Calculator();
|
||||||
|
horseDbManager = new HorseRacingPredictor.Horses.Database();
|
||||||
|
fileReaderHorses = new HorseRacingPredictor.Horses.FileReader();
|
||||||
|
|
||||||
|
// Inizializza il gestore centralizzato del Football
|
||||||
|
footballManager = new HorseRacingPredictor.Football.Main();
|
||||||
|
|
||||||
|
// Disabilita il pulsante di importazione fino a quando non viene selezionato un percorso
|
||||||
|
buttonImport.Enabled = false;
|
||||||
|
|
||||||
|
// Il pulsante di predizione deve essere sempre abilitato
|
||||||
|
buttonPredict.Enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Importa i dati dei file CSV delle corse dei cavalli
|
||||||
|
/// </summary>
|
||||||
|
private void ImportCsvFilesHorse(string folderPath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(folderPath))
|
||||||
|
{
|
||||||
|
MessageBox.Show("Seleziona un percorso valido.", "Errore", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Usa il metodo FileReaderManagerHorses per ottenere i file CSV
|
||||||
|
var csvFiles = fileReaderHorses.GetHorseRaceFiles(folderPath);
|
||||||
|
if (csvFiles.Count == 0)
|
||||||
|
{
|
||||||
|
MessageBox.Show("Nessun file CSV trovato nel percorso selezionato.", "Errore", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creiamo una tabella combinata per tutti i dati
|
||||||
|
DataTable combinedTable = null;
|
||||||
|
|
||||||
|
// Inizializza la progress bar
|
||||||
|
progressBarHorse.Minimum = 0;
|
||||||
|
progressBarHorse.Maximum = csvFiles.Count;
|
||||||
|
progressBarHorse.Value = 0;
|
||||||
|
labelStatusHorse.Text = "Inizializzazione importazione...";
|
||||||
|
|
||||||
|
// Utilizziamo Task per non bloccare l'UI
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < csvFiles.Count; i++)
|
||||||
|
{
|
||||||
|
var file = csvFiles[i];
|
||||||
|
|
||||||
|
// Aggiorna la UI con lo stato corrente
|
||||||
|
this.Invoke(new Action(() =>
|
||||||
|
{
|
||||||
|
progressBarHorse.Value = i + 1;
|
||||||
|
labelStatusHorse.Text = $"Importazione file {i + 1} di {csvFiles.Count}: {Path.GetFileName(file)}";
|
||||||
|
}));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Leggi i dati dal file CSV
|
||||||
|
var horsesData = fileReaderHorses.ReadHorseDataFromFile(file);
|
||||||
|
|
||||||
|
// Al primo file, creiamo la tabella combinata
|
||||||
|
if (combinedTable == null)
|
||||||
|
{
|
||||||
|
// Creiamo una nuova tabella vuota
|
||||||
|
combinedTable = new DataTable();
|
||||||
|
|
||||||
|
// Aggiungiamo le colonne necessarie
|
||||||
|
combinedTable.Columns.Add("File", typeof(string));
|
||||||
|
combinedTable.Columns.Add("Data Corsa", typeof(DateTime));
|
||||||
|
combinedTable.Columns.Add("Meeting", typeof(string));
|
||||||
|
combinedTable.Columns.Add("Corsa", typeof(int));
|
||||||
|
combinedTable.Columns.Add("Numero", typeof(string));
|
||||||
|
combinedTable.Columns.Add("Nome Cavallo", typeof(string));
|
||||||
|
combinedTable.Columns.Add("Età", typeof(int));
|
||||||
|
combinedTable.Columns.Add("Genere", typeof(string));
|
||||||
|
combinedTable.Columns.Add("Peso", typeof(decimal));
|
||||||
|
combinedTable.Columns.Add("Quota", typeof(decimal));
|
||||||
|
combinedTable.Columns.Add("Risultato Reale", typeof(string));
|
||||||
|
// La colonna "Posizione Prevista" verrà aggiunta nella fase di predizione
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inserisci i dati nel database
|
||||||
|
horseDbManager.ProcessAndInsertHorseRaceData(file);
|
||||||
|
|
||||||
|
// Estrai le informazioni sulla corsa dal nome del file
|
||||||
|
var raceInfo = fileReaderHorses.GetRaceInfoFromFileName(file);
|
||||||
|
|
||||||
|
// Aggiungi i dati dei cavalli alla tabella combinata (senza predizioni)
|
||||||
|
foreach (var horseData in horsesData)
|
||||||
|
{
|
||||||
|
DataRow newRow = combinedTable.NewRow();
|
||||||
|
newRow["File"] = Path.GetFileName(file);
|
||||||
|
newRow["Data Corsa"] = raceInfo.RaceDate;
|
||||||
|
newRow["Meeting"] = raceInfo.MeetingName;
|
||||||
|
newRow["Corsa"] = raceInfo.RaceNumber;
|
||||||
|
newRow["Numero"] = horseData.Num;
|
||||||
|
newRow["Nome Cavallo"] = horseData.HorseName;
|
||||||
|
newRow["Età"] = horseData.Age;
|
||||||
|
newRow["Genere"] = horseData.Gender;
|
||||||
|
newRow["Peso"] = horseData.WeightCarried;
|
||||||
|
newRow["Quota"] = horseData.BestFixedOdds;
|
||||||
|
newRow["Risultato Reale"] = horseData.FinishResult;
|
||||||
|
combinedTable.Rows.Add(newRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
this.Invoke(new Action(() =>
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Errore durante l'elaborazione del file {Path.GetFileName(file)}: {ex.Message}",
|
||||||
|
"Errore Elaborazione", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
|
}));
|
||||||
|
// Continua con il prossimo file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Completamento dell'operazione
|
||||||
|
this.Invoke(new Action(() =>
|
||||||
|
{
|
||||||
|
if (combinedTable != null && combinedTable.Rows.Count > 0)
|
||||||
|
{
|
||||||
|
// Salva i dati importati nella variabile di classe
|
||||||
|
importedHorsesData = combinedTable;
|
||||||
|
|
||||||
|
// Mostra i dati nella DataGridView
|
||||||
|
dataGridViewHorse.DataSource = importedHorsesData;
|
||||||
|
|
||||||
|
// Formatta la griglia
|
||||||
|
FormatHorseDataGrid(dataGridViewHorse);
|
||||||
|
|
||||||
|
labelStatusHorse.Text = $"Importazione completata: {combinedTable.Rows.Count} cavalli importati";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
labelStatusHorse.Text = "Nessun dato valido trovato nei file CSV";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Riabilita il pulsante importa al termine dell'operazione
|
||||||
|
buttonImport.Enabled = true;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Errore durante il caricamento dei file CSV: {ex.Message}",
|
||||||
|
"Errore", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
labelStatusHorse.Text = "Errore durante l'importazione";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowStrikeRateStats(DataTable table)
|
||||||
|
{
|
||||||
|
// Raggruppa per gara usando Data Corsa, Meeting, Corsa
|
||||||
|
var grouped = table.AsEnumerable()
|
||||||
|
.Where(row => row["Risultato Reale"] != DBNull.Value && row["Posizione Prevista"] != DBNull.Value)
|
||||||
|
.GroupBy(row => new {
|
||||||
|
DataCorsa = row["Data Corsa"],
|
||||||
|
Meeting = row["Meeting"].ToString(),
|
||||||
|
Corsa = row["Corsa"]
|
||||||
|
});
|
||||||
|
|
||||||
|
string stats = "Statistiche Strike Rate per Gara:\n";
|
||||||
|
int gareTotali = 0, winTotali = 0, placeTotali = 0;
|
||||||
|
|
||||||
|
foreach (var group in grouped)
|
||||||
|
{
|
||||||
|
gareTotali++;
|
||||||
|
int tot = 0, winOk = 0, placeOk = 0;
|
||||||
|
foreach (var row in group)
|
||||||
|
{
|
||||||
|
if (int.TryParse(row["Risultato Reale"].ToString(), out int real) &&
|
||||||
|
float.TryParse(row["Posizione Prevista"].ToString(), out float pred))
|
||||||
|
{
|
||||||
|
tot++;
|
||||||
|
if (real == 1 && pred <= 1.5f) winOk++;
|
||||||
|
if (real <= 3 && pred <= 3.5f) placeOk++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
winTotali += winOk;
|
||||||
|
placeTotali += placeOk;
|
||||||
|
stats += $"Gara: {group.Key.DataCorsa:dd/MM/yyyy} - {group.Key.Meeting} - N°{group.Key.Corsa}\n" +
|
||||||
|
$" Cavalli: {tot}\n" +
|
||||||
|
$" Vincitore previsto corretto: {winOk} ({(winOk * 100.0 / tot):F1}%)\n" +
|
||||||
|
$" Piazzati previsti corretti: {placeOk} ({(placeOk * 100.0 / tot):F1}%)\n\n";
|
||||||
|
}
|
||||||
|
if (gareTotali > 0)
|
||||||
|
{
|
||||||
|
stats += $"Totale gare: {gareTotali}\n" +
|
||||||
|
$"Totale vincitori previsti corretti: {winTotali}\n" +
|
||||||
|
$"Totale piazzati previsti corretti: {placeTotali}\n";
|
||||||
|
MessageBox.Show(stats, "Strike Rate per Gara", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Formatta la griglia dei dati dei cavalli per una migliore visualizzazione
|
||||||
|
/// </summary>
|
||||||
|
private void FormatHorseDataGrid(DataGridView grid)
|
||||||
|
{
|
||||||
|
if (grid.Columns.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Nascondi le colonne Età, Genere e Peso
|
||||||
|
foreach (var colName in new[] { "Età", "Genere", "Peso" })
|
||||||
|
{
|
||||||
|
if (grid.Columns.Contains(colName))
|
||||||
|
grid.Columns[colName].Visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Imposta l'ordine delle colonne principali
|
||||||
|
if (grid.Columns.Contains("File"))
|
||||||
|
grid.Columns["File"].DisplayIndex = 0;
|
||||||
|
if (grid.Columns.Contains("Data Corsa"))
|
||||||
|
grid.Columns["Data Corsa"].DisplayIndex = 1;
|
||||||
|
if (grid.Columns.Contains("Meeting"))
|
||||||
|
grid.Columns["Meeting"].DisplayIndex = 2;
|
||||||
|
if (grid.Columns.Contains("Corsa"))
|
||||||
|
grid.Columns["Corsa"].DisplayIndex = 3;
|
||||||
|
if (grid.Columns.Contains("Numero"))
|
||||||
|
grid.Columns["Numero"].DisplayIndex = 4;
|
||||||
|
if (grid.Columns.Contains("Nome Cavallo"))
|
||||||
|
grid.Columns["Nome Cavallo"].DisplayIndex = 5;
|
||||||
|
if (grid.Columns.Contains("Quota"))
|
||||||
|
grid.Columns["Quota"].DisplayIndex = 6;
|
||||||
|
if (grid.Columns.Contains("Risultato Reale"))
|
||||||
|
grid.Columns["Risultato Reale"].DisplayIndex = 7;
|
||||||
|
if (grid.Columns.Contains("Posizione Prevista"))
|
||||||
|
grid.Columns["Posizione Prevista"].DisplayIndex = 8;
|
||||||
|
|
||||||
|
// Imposta il formato delle date
|
||||||
|
if (grid.Columns.Contains("Data Corsa"))
|
||||||
|
grid.Columns["Data Corsa"].DefaultCellStyle.Format = "dd/MM/yyyy";
|
||||||
|
|
||||||
|
// Imposta il formato numerico per le quote
|
||||||
|
if (grid.Columns.Contains("Quota"))
|
||||||
|
grid.Columns["Quota"].DefaultCellStyle.Format = "N2";
|
||||||
|
|
||||||
|
if (grid.Columns.Contains("Posizione Prevista"))
|
||||||
|
grid.Columns["Posizione Prevista"].DefaultCellStyle.Format = "N1";
|
||||||
|
|
||||||
|
// Imposta l'allineamento al centro per alcune colonne
|
||||||
|
foreach (var columnName in new[] { "Numero", "Età", "Corsa", "Risultato Reale", "Posizione Prevista" })
|
||||||
|
{
|
||||||
|
if (grid.Columns.Contains(columnName))
|
||||||
|
grid.Columns[columnName].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Colora le righe in base al risultato
|
||||||
|
grid.CellFormatting += (sender, e) =>
|
||||||
|
{
|
||||||
|
if (e.RowIndex >= 0 && e.ColumnIndex >= 0)
|
||||||
|
{
|
||||||
|
var row = grid.Rows[e.RowIndex];
|
||||||
|
if (grid.Columns.Contains("Risultato Reale") && row.Cells["Risultato Reale"].Value != null)
|
||||||
|
{
|
||||||
|
string resultStr = row.Cells["Risultato Reale"].Value.ToString();
|
||||||
|
if (int.TryParse(resultStr, out int result))
|
||||||
|
{
|
||||||
|
if (result == 1)
|
||||||
|
row.DefaultCellStyle.BackColor = System.Drawing.Color.LightGreen;
|
||||||
|
else if (result <= 3)
|
||||||
|
row.DefaultCellStyle.BackColor = System.Drawing.Color.LightYellow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (grid.Columns.Contains("Risultato Reale") && grid.Columns.Contains("Posizione Prevista"))
|
||||||
|
{
|
||||||
|
var realResultCell = row.Cells["Risultato Reale"];
|
||||||
|
var predictedCell = row.Cells["Posizione Prevista"];
|
||||||
|
if (realResultCell.Value != null && predictedCell.Value != null)
|
||||||
|
{
|
||||||
|
if (int.TryParse(realResultCell.Value.ToString(), out int realPos) &&
|
||||||
|
float.TryParse(predictedCell.Value.ToString(), out float predictedPos))
|
||||||
|
{
|
||||||
|
if (Math.Abs(realPos - predictedPos) <= 0.5)
|
||||||
|
{
|
||||||
|
predictedCell.Style.BackColor = System.Drawing.Color.LightGreen;
|
||||||
|
predictedCell.Style.ForeColor = System.Drawing.Color.DarkGreen;
|
||||||
|
predictedCell.Style.Font = new System.Drawing.Font(grid.Font, System.Drawing.FontStyle.Bold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Abilita il riordino delle colonne
|
||||||
|
grid.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
|
||||||
|
grid.AllowUserToOrderColumns = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add these methods to handle the download and import operations
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Nuovo metodo per gestire lo scaricamento dei dati calcistici
|
||||||
|
/// </summary>
|
||||||
|
private async Task DownloadFootballDataAsync(DateTime selectedDate)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Inizializza e mostra la barra di avanzamento
|
||||||
|
progressBarFootball.Minimum = 0;
|
||||||
|
progressBarFootball.Maximum = 100;
|
||||||
|
progressBarFootball.Value = 0;
|
||||||
|
labelStatusFootball.Text = "Scaricamento delle partite in corso...";
|
||||||
|
|
||||||
|
// Disabilita i controlli durante l'elaborazione
|
||||||
|
dateTimePicker.Enabled = false;
|
||||||
|
buttonDownloadFootball.Enabled = false;
|
||||||
|
buttonImportFootball.Enabled = false;
|
||||||
|
|
||||||
|
// Crea gli oggetti progress che aggiornano la UI in modo thread-safe
|
||||||
|
var progressCallback = new Progress<int>(value =>
|
||||||
|
{
|
||||||
|
progressBarFootball.Value = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
var statusCallback = new Progress<string>(status =>
|
||||||
|
{
|
||||||
|
labelStatusFootball.Text = status;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Usa la nuova classe centralizzata del Football per scaricare i dati
|
||||||
|
await Task.Run(() =>
|
||||||
|
footballManager.DownloadFixturesAndOdds(selectedDate, progressCallback, statusCallback));
|
||||||
|
|
||||||
|
// Riabilita i controlli
|
||||||
|
dateTimePicker.Enabled = true;
|
||||||
|
buttonDownloadFootball.Enabled = true;
|
||||||
|
buttonImportFootball.Enabled = true;
|
||||||
|
labelStatusFootball.Text = "Scaricamento completato. Pronto per l'importazione.";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Gestione degli errori
|
||||||
|
MessageBox.Show($"Errore durante lo scaricamento dei dati calcistici: {ex.Message}",
|
||||||
|
"Errore", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
|
||||||
|
// Riabilita i controlli anche in caso di errore
|
||||||
|
dateTimePicker.Enabled = true;
|
||||||
|
buttonDownloadFootball.Enabled = true;
|
||||||
|
buttonImportFootball.Enabled = true;
|
||||||
|
labelStatusFootball.Text = "Errore nello scaricamento dati";
|
||||||
|
progressBarFootball.Value = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Nuovo metodo per gestire l'importazione dei dati calcistici
|
||||||
|
/// </summary>
|
||||||
|
private async Task ImportFootballDataAsync(DataGridView dataGridViewFootball)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Inizializza e mostra la barra di avanzamento
|
||||||
|
progressBarFootball.Minimum = 0;
|
||||||
|
progressBarFootball.Maximum = 100;
|
||||||
|
progressBarFootball.Value = 0;
|
||||||
|
labelStatusFootball.Text = "Importazione dati dalle risposte API in corso...";
|
||||||
|
|
||||||
|
// Disabilita i controlli durante l'elaborazione
|
||||||
|
dateTimePicker.Enabled = false;
|
||||||
|
buttonDownloadFootball.Enabled = false;
|
||||||
|
buttonImportFootball.Enabled = false;
|
||||||
|
|
||||||
|
// Crea gli oggetti progress che aggiornano la UI in modo thread-safe
|
||||||
|
var progressCallback = new Progress<int>(value =>
|
||||||
|
{
|
||||||
|
progressBarFootball.Value = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
var statusCallback = new Progress<string>(status =>
|
||||||
|
{
|
||||||
|
labelStatusFootball.Text = status;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Usa la nuova classe centralizzata del Football per importare i dati
|
||||||
|
var footballDataTable = await Task.Run(() =>
|
||||||
|
footballManager.ImportFromApiResponses(progressCallback, statusCallback));
|
||||||
|
|
||||||
|
// Aggiorna la UI con i risultati (già thread-safe perché eseguito sul thread UI)
|
||||||
|
dataGridViewFootball.DataSource = footballDataTable;
|
||||||
|
|
||||||
|
// Formatta la griglia dati
|
||||||
|
FormatFootballDataGrid(dataGridViewFootball);
|
||||||
|
|
||||||
|
// Riabilita i controlli
|
||||||
|
dateTimePicker.Enabled = true;
|
||||||
|
buttonDownloadFootball.Enabled = true;
|
||||||
|
buttonImportFootball.Enabled = true;
|
||||||
|
labelStatusFootball.Text = $"Importati {footballDataTable?.Rows.Count ?? 0} eventi";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Gestione degli errori
|
||||||
|
MessageBox.Show($"Errore durante l'importazione dei dati calcistici: {ex.Message}",
|
||||||
|
"Errore", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
|
||||||
|
// Riabilita i controlli anche in caso di errore
|
||||||
|
dateTimePicker.Enabled = true;
|
||||||
|
buttonDownloadFootball.Enabled = true;
|
||||||
|
buttonImportFootball.Enabled = true;
|
||||||
|
labelStatusFootball.Text = "Errore nell'importazione dati";
|
||||||
|
progressBarFootball.Value = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Metodo aggiornato per il processo completo (scaricamento + importazione)
|
||||||
|
/// </summary>
|
||||||
|
private async Task ProcessFootballDataAsync(DateTime selectedDate, DataGridView dataGridViewFootball)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Prima scarica, poi importa
|
||||||
|
await DownloadFootballDataAsync(selectedDate);
|
||||||
|
await ImportFootballDataAsync(dataGridViewFootball);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Gestione degli errori
|
||||||
|
MessageBox.Show($"Errore durante l'elaborazione dei dati calcistici: {ex.Message}",
|
||||||
|
"Errore", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
|
||||||
|
// Reset dello stato UI
|
||||||
|
dateTimePicker.Enabled = true;
|
||||||
|
buttonDownloadFootball.Enabled = true;
|
||||||
|
buttonImportFootball.Enabled = true;
|
||||||
|
labelStatusFootball.Text = "Errore nell'elaborazione dati";
|
||||||
|
progressBarFootball.Value = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Formatta la griglia dei dati calcistici per una migliore visualizzazione
|
||||||
|
/// </summary>
|
||||||
|
private void FormatFootballDataGrid(DataGridView grid)
|
||||||
|
{
|
||||||
|
if (grid.Columns.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Imposta l'ordine delle colonne principali
|
||||||
|
if (grid.Columns.Contains("Data / Ora"))
|
||||||
|
grid.Columns["Data / Ora"].DisplayIndex = 0;
|
||||||
|
if (grid.Columns.Contains("Paese"))
|
||||||
|
grid.Columns["Paese"].DisplayIndex = 1;
|
||||||
|
if (grid.Columns.Contains("Campionato"))
|
||||||
|
grid.Columns["Campionato"].DisplayIndex = 2;
|
||||||
|
if (grid.Columns.Contains("Casa"))
|
||||||
|
grid.Columns["Casa"].DisplayIndex = 3;
|
||||||
|
if (grid.Columns.Contains("Trasferta"))
|
||||||
|
grid.Columns["Trasferta"].DisplayIndex = 4;
|
||||||
|
if (grid.Columns.Contains("Previsione"))
|
||||||
|
grid.Columns["Previsione"].DisplayIndex = 5;
|
||||||
|
|
||||||
|
// Imposta il formato delle date
|
||||||
|
if (grid.Columns.Contains("Data / Ora"))
|
||||||
|
grid.Columns["Data / Ora"].DefaultCellStyle.Format = "dd/MM/yyyy HH:mm";
|
||||||
|
|
||||||
|
// Imposta il formato numerico per le quote
|
||||||
|
foreach (var quotaColumn in new[] { "Quota Casa", "Quota Pareggio", "Quota Trasferta" })
|
||||||
|
{
|
||||||
|
if (grid.Columns.Contains(quotaColumn))
|
||||||
|
grid.Columns[quotaColumn].DefaultCellStyle.Format = "N2";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Imposta l'allineamento al centro per alcune colonne
|
||||||
|
foreach (var columnName in new[] { "Goals Casa", "Goals Trasferta", "Risultato", "Stato" })
|
||||||
|
{
|
||||||
|
if (grid.Columns.Contains(columnName))
|
||||||
|
grid.Columns[columnName].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Colora le righe in base al risultato della previsione
|
||||||
|
grid.CellFormatting += (sender, e) =>
|
||||||
|
{
|
||||||
|
if (e.RowIndex >= 0 && grid.Columns.Contains("Risultato") && e.ColumnIndex >= 0)
|
||||||
|
{
|
||||||
|
var resultCell = grid.Rows[e.RowIndex].Cells["Risultato"];
|
||||||
|
if (resultCell.Value != null)
|
||||||
|
{
|
||||||
|
// Se la previsione è corretta, colora di verde
|
||||||
|
if (Convert.ToInt32(resultCell.Value) == 1)
|
||||||
|
grid.Rows[e.RowIndex].DefaultCellStyle.BackColor = System.Drawing.Color.LightGreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Abilita il riordino delle colonne
|
||||||
|
grid.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
|
||||||
|
grid.AllowUserToOrderColumns = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BrowseFolder()
|
||||||
|
{
|
||||||
|
using (FolderBrowserDialog folderBrowserDialog = new FolderBrowserDialog())
|
||||||
|
{
|
||||||
|
folderBrowserDialog.Description = "Seleziona la cartella contenente i file CSV delle corse dei cavalli";
|
||||||
|
folderBrowserDialog.ShowNewFolderButton = false;
|
||||||
|
return folderBrowserDialog.ShowDialog() == DialogResult.OK ? folderBrowserDialog.SelectedPath : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region EventHandlers
|
||||||
|
|
||||||
|
// Gestore evento per il caricamento dei dati dei cavalli - solo per importazione
|
||||||
|
private void buttonBrowse_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// Seleziona cartella con file CSV
|
||||||
|
string folderPath = BrowseFolder();
|
||||||
|
if (!string.IsNullOrEmpty(folderPath))
|
||||||
|
{
|
||||||
|
// Imposta il percorso nel textbox
|
||||||
|
textBoxFolderPath.Text = folderPath;
|
||||||
|
|
||||||
|
// Abilita il pulsante di importazione
|
||||||
|
buttonImport.Enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace buttonLoadFootball_Click with these two event handlers:
|
||||||
|
private void buttonDownloadFootball_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// Usa direttamente la proprietà Value del DateTimePicker
|
||||||
|
DateTime selectedDate = dateTimePicker.Value;
|
||||||
|
_ = DownloadFootballDataAsync(selectedDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gestore evento per il pulsante di importazione dati football
|
||||||
|
private void buttonImportFootball_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// Process and import data from frontier table to final tables
|
||||||
|
_ = ImportFootballDataAsync(dataGridViewFootball);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nel metodo Main_Load, rimuovi il codice che popolava il ComboBox
|
||||||
|
private void Main_Load(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// Imposta la data di ieri per default
|
||||||
|
dateTimePicker.Value = DateTime.Today.AddDays(-1);
|
||||||
|
|
||||||
|
// Inizializza le barre di avanzamento
|
||||||
|
progressBarHorse.Value = 0;
|
||||||
|
progressBarFootball.Value = 0;
|
||||||
|
labelStatusHorse.Text = "Pronto";
|
||||||
|
labelStatusFootball.Text = "Pronto";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gestore dell'evento per il pulsante di generazione predizioni
|
||||||
|
private async void buttonPredict_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// Inizializza e configura il servizio ML
|
||||||
|
string connectionString = "Server=DESKTOP-9O9JHFS;Database=TestBS_Horses;User Id=sa;Password=Asti2019;";
|
||||||
|
mlService.ConnectionString = connectionString;
|
||||||
|
|
||||||
|
// Verifica se esiste il modello ML o se deve essere addestrato
|
||||||
|
string modelPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Models", "HorseRaceModel.zip");
|
||||||
|
if (!File.Exists(modelPath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
labelStatusHorse.Text = "Addestramento modello ML in corso...";
|
||||||
|
await Task.Run(() => mlService.TrainModel(connectionString));
|
||||||
|
labelStatusHorse.Text = "Addestramento modello ML completato";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show("Errore durante l'addestramento del modello ML:\n" + ex.Message, "Errore ML", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carica il modello ML
|
||||||
|
try
|
||||||
|
{
|
||||||
|
labelStatusHorse.Text = "Caricamento del modello ML...";
|
||||||
|
mlService.LoadModel();
|
||||||
|
labelStatusHorse.Text = "Modello ML caricato";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show("Impossibile caricare il modello ML:\n" + ex.Message, "Errore ML", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Genera le predizioni
|
||||||
|
GeneratePredictions();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metodo per generare predizioni in base ai dati importati o dal database
|
||||||
|
private void GeneratePredictions()
|
||||||
|
{
|
||||||
|
DataTable workingTable = importedHorsesData;
|
||||||
|
if (workingTable == null || workingTable.Rows.Count == 0)
|
||||||
|
{
|
||||||
|
// Se non ci sono dati importati, carica dal database
|
||||||
|
workingTable = horseDbManager.GetAllHorseRaceData();
|
||||||
|
}
|
||||||
|
if (workingTable == null || workingTable.Rows.Count == 0)
|
||||||
|
{
|
||||||
|
MessageBox.Show("Nessun dato disponibile per la predizione.", "Nessun dato", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Assicurati che il modello ML sia caricato
|
||||||
|
if (mlService == null)
|
||||||
|
{
|
||||||
|
MessageBox.Show("Errore: servizio ML non inizializzato.", "Errore", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Controlla se la colonna delle previsioni esiste già
|
||||||
|
if (!workingTable.Columns.Contains("Posizione Prevista"))
|
||||||
|
workingTable.Columns.Add("Posizione Prevista", typeof(float));
|
||||||
|
|
||||||
|
// Copia i dati per lavorare su una nuova tabella
|
||||||
|
DataTable predictedTable = workingTable.Copy();
|
||||||
|
|
||||||
|
// Inizializza la progress bar
|
||||||
|
progressBarHorse.Minimum = 0;
|
||||||
|
progressBarHorse.Maximum = predictedTable.Rows.Count;
|
||||||
|
progressBarHorse.Value = 0;
|
||||||
|
labelStatusHorse.Text = "Inizializzazione predizioni...";
|
||||||
|
buttonPredict.Enabled = false;
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < predictedTable.Rows.Count; i++)
|
||||||
|
{
|
||||||
|
var row = predictedTable.Rows[i];
|
||||||
|
this.Invoke(new Action(() =>
|
||||||
|
{
|
||||||
|
progressBarHorse.Value = i + 1;
|
||||||
|
labelStatusHorse.Text = $"Generazione predizioni {i + 1} di {predictedTable.Rows.Count}";
|
||||||
|
}));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var mlInput = new ModelInput
|
||||||
|
{
|
||||||
|
Age = Convert.ToInt32(row["Età"] != DBNull.Value ? row["Età"] : 0),
|
||||||
|
HandicapRating = 0,
|
||||||
|
Weight = row["Peso"] != DBNull.Value ? Convert.ToSingle(row["Peso"]) : 0,
|
||||||
|
WeightCarried = row["Peso"] != DBNull.Value ? Convert.ToSingle(row["Peso"]) : 0,
|
||||||
|
Barrier = 0,
|
||||||
|
CareerRuns = 0,
|
||||||
|
CareerWins = 0,
|
||||||
|
CareerStrikeRate = 0,
|
||||||
|
CareerROI = 0,
|
||||||
|
ThisTrackRuns = 0,
|
||||||
|
ThisTrackWins = 0,
|
||||||
|
ThisTrackStrikeRate = 0,
|
||||||
|
ThisDistanceRuns = 0,
|
||||||
|
ThisDistanceWins = 0,
|
||||||
|
ThisDistanceStrikeRate = 0,
|
||||||
|
JockeyLast100Wins = 0,
|
||||||
|
JockeyLast100StrikeRate = 0,
|
||||||
|
TrainerLast100Wins = 0,
|
||||||
|
TrainerLast100StrikeRate = 0,
|
||||||
|
BestFixedOdds = row["Quota"] != DBNull.Value ? Convert.ToSingle(row["Quota"]) : 0
|
||||||
|
};
|
||||||
|
float? predicted = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var prediction = mlService.PredictFinishPosition(mlInput);
|
||||||
|
predicted = prediction?.PredictedPosition;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Errore ML.NET: {ex.Message}\n{ex.StackTrace}");
|
||||||
|
}
|
||||||
|
row["Posizione Prevista"] = predicted.HasValue ? (object)predicted.Value : DBNull.Value;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Errore durante la predizione per riga {i}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.Invoke(new Action(() =>
|
||||||
|
{
|
||||||
|
importedHorsesData = predictedTable;
|
||||||
|
dataGridViewHorse.DataSource = importedHorsesData;
|
||||||
|
FormatHorseDataGrid(dataGridViewHorse);
|
||||||
|
ShowStrikeRateStats(predictedTable);
|
||||||
|
labelStatusHorse.Text = $"Predizioni completate: {predictedTable.Rows.Count} cavalli analizzati";
|
||||||
|
buttonPredict.Enabled = true;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Errore durante la generazione delle predizioni: {ex.Message}", "Errore", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
labelStatusHorse.Text = "Errore durante la generazione delle predizioni";
|
||||||
|
buttonPredict.Enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buttonImport_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// Verifica che sia stato selezionato un percorso
|
||||||
|
if (string.IsNullOrEmpty(textBoxFolderPath.Text))
|
||||||
|
{
|
||||||
|
MessageBox.Show("Seleziona prima una cartella contenente i file CSV.",
|
||||||
|
"Percorso mancante", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inizia il processo di importazione
|
||||||
|
ImportCsvFilesHorse(textBoxFolderPath.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
</root>
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.IO;
|
||||||
|
using RestSharp;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Manager
|
||||||
|
{
|
||||||
|
internal class API
|
||||||
|
{
|
||||||
|
// Intervallo tra le chiamate API per evitare limitazioni di rate
|
||||||
|
private const int DefaultApiCallDelay = 6000;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Esegue una chiamata API generica e restituisce la risposta
|
||||||
|
/// </summary>
|
||||||
|
public static RestResponse ExecuteApiRequest(string url, string apiKey, string apiKeyHeader, string hostHeader, string hostValue, int delay = DefaultApiCallDelay)
|
||||||
|
{
|
||||||
|
var clientApi = new RestClient(url);
|
||||||
|
var request = new RestRequest();
|
||||||
|
request.AddHeader(apiKeyHeader, apiKey);
|
||||||
|
request.AddHeader(hostHeader, hostValue);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = clientApi.Execute(request);
|
||||||
|
if (!response.IsSuccessful)
|
||||||
|
{
|
||||||
|
throw new Exception($"Errore nella richiesta API: {response.ErrorMessage}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggiungi una pausa tra una chiamata e l'altra
|
||||||
|
if (delay > 0)
|
||||||
|
{
|
||||||
|
Thread.Sleep(delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"Errore durante la richiesta API: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Esegue una chiamata API generica con informazioni di tipo per il salvataggio nel database
|
||||||
|
/// </summary>
|
||||||
|
public static RestResponse ExecuteApiRequest(string url, string apiKey, string apiKeyHeader, string hostHeader, string hostValue,
|
||||||
|
string apiType, string parameters, int delay = DefaultApiCallDelay)
|
||||||
|
{
|
||||||
|
// La logica è la stessa ma ora passa anche i parametri necessari per salvare nel database
|
||||||
|
return ExecuteApiRequest(url, apiKey, apiKeyHeader, hostHeader, hostValue, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Versione asincrona di ExecuteApiRequest
|
||||||
|
/// </summary>
|
||||||
|
public static async Task<RestResponse> ExecuteApiRequestAsync(string url, string apiKey, string apiKeyHeader, string hostHeader, string hostValue, int delay = DefaultApiCallDelay)
|
||||||
|
{
|
||||||
|
var clientApi = new RestClient(url);
|
||||||
|
var request = new RestRequest();
|
||||||
|
request.AddHeader(apiKeyHeader, apiKey);
|
||||||
|
request.AddHeader(hostHeader, hostValue);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await clientApi.ExecuteAsync(request);
|
||||||
|
if (!response.IsSuccessful)
|
||||||
|
{
|
||||||
|
throw new Exception($"Errore nella richiesta API: {response.ErrorMessage}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggiungi una pausa tra una chiamata e l'altra
|
||||||
|
if (delay > 0)
|
||||||
|
{
|
||||||
|
await Task.Delay(delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"Errore durante la richiesta API: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Versione asincrona di ExecuteApiRequest con informazioni di tipo
|
||||||
|
/// </summary>
|
||||||
|
public static async Task<RestResponse> ExecuteApiRequestAsync(string url, string apiKey, string apiKeyHeader, string hostHeader, string hostValue,
|
||||||
|
string apiType, string parameters, int delay = DefaultApiCallDelay)
|
||||||
|
{
|
||||||
|
// La logica è la stessa ma ora passa anche i parametri necessari per salvare nel database
|
||||||
|
return await ExecuteApiRequestAsync(url, apiKey, apiKeyHeader, hostHeader, hostValue, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Salva la risposta dell'API su file per debugging
|
||||||
|
/// </summary>
|
||||||
|
public static void SaveResponseToFile(string url, string content, string baseFolderName = "ApiResponses")
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string folderPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, baseFolderName);
|
||||||
|
|
||||||
|
if (!Directory.Exists(folderPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(folderPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
string fileName = Path.Combine(folderPath, $"ApiResponse_{DateTime.Now:yyyyMMdd_HHmmss}.json");
|
||||||
|
|
||||||
|
File.WriteAllText(fileName, content);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"Errore durante il salvataggio della risposta API: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
using System;
|
||||||
|
using System.Data.SqlClient;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Manager
|
||||||
|
{
|
||||||
|
internal abstract class Database
|
||||||
|
{
|
||||||
|
// La stringa di connessione viene rimossa da qui e definita nelle classi derivate
|
||||||
|
protected abstract string _connectionString { get; }
|
||||||
|
|
||||||
|
protected SqlConnection GetConnection()
|
||||||
|
{
|
||||||
|
var connection = new SqlConnection(_connectionString);
|
||||||
|
connection.Open();
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogError(string operation, Exception ex)
|
||||||
|
{
|
||||||
|
var message = ex != null ? ex.Message : "Nessuna eccezione fornita";
|
||||||
|
Console.WriteLine($"Errore durante {operation}: {message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Esegue una query SQL e gestisce le eccezioni in modo centralizzato
|
||||||
|
/// </summary>
|
||||||
|
public void ExecuteQuery(string operation, Action<SqlConnection> action)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var connection = GetConnection())
|
||||||
|
{
|
||||||
|
action(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError(operation, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Esegue una query SQL all'interno di una transazione e gestisce le eccezioni
|
||||||
|
/// </summary>
|
||||||
|
public void ExecuteTransactionalQuery(string operation, Action<SqlConnection, SqlTransaction> action)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var connection = GetConnection())
|
||||||
|
using (var transaction = connection.BeginTransaction())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
action(connection, transaction);
|
||||||
|
transaction.Commit();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
transaction.Rollback();
|
||||||
|
LogError($"{operation} (transazione rollback)", ex);
|
||||||
|
throw; // Rilancia l'eccezione per gestione di livello superiore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError(operation, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Metodo per verificare se la connessione al database è valida
|
||||||
|
/// </summary>
|
||||||
|
public bool TestConnection()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var connection = GetConnection())
|
||||||
|
{
|
||||||
|
return connection.State == System.Data.ConnectionState.Open;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogError("test della connessione", ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
using CsvHelper;
|
||||||
|
using CsvHelper.Configuration;
|
||||||
|
using CsvHelper.TypeConversion;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace HorseRacingPredictor.Manager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gestore generico per la lettura di file CSV
|
||||||
|
/// </summary>
|
||||||
|
public class FileReader
|
||||||
|
{
|
||||||
|
public class NullableDecimalConverter : DefaultTypeConverter
|
||||||
|
{
|
||||||
|
public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
|
||||||
|
{
|
||||||
|
return ConvertToDecimal(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NullableIntConverter : DefaultTypeConverter
|
||||||
|
{
|
||||||
|
public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
|
||||||
|
{
|
||||||
|
return ConvertToInt(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Legge un file CSV e lo converte in un DataTable
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">Percorso del file CSV</param>
|
||||||
|
/// <returns>DataTable contenente i dati del file CSV</returns>
|
||||||
|
public DataTable ReadCsvToDataTable(string filePath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(filePath))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Il percorso del file non può essere vuoto.", nameof(filePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException($"File non trovato: {filePath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataTable = new DataTable();
|
||||||
|
|
||||||
|
// Configurazione per CsvHelper
|
||||||
|
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
|
||||||
|
{
|
||||||
|
HasHeaderRecord = true,
|
||||||
|
MissingFieldFound = null, // Ignora campi mancanti
|
||||||
|
TrimOptions = TrimOptions.Trim, // Rimuove spazi iniziali e finali
|
||||||
|
PrepareHeaderForMatch = args => args.Header.Replace("#", "").Trim() // Rimuove # e spazi dai nomi delle colonne
|
||||||
|
};
|
||||||
|
|
||||||
|
using (var reader = new StreamReader(filePath))
|
||||||
|
using (var csv = new CsvReader(reader, config))
|
||||||
|
{
|
||||||
|
// Legge l'intestazione
|
||||||
|
csv.Read();
|
||||||
|
csv.ReadHeader();
|
||||||
|
|
||||||
|
// Crea le colonne della tabella dai nomi delle colonne del CSV
|
||||||
|
foreach (var header in csv.HeaderRecord)
|
||||||
|
{
|
||||||
|
string columnName = header.Replace("#", "").Trim();
|
||||||
|
dataTable.Columns.Add(columnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legge i dati riga per riga
|
||||||
|
while (csv.Read())
|
||||||
|
{
|
||||||
|
var row = dataTable.NewRow();
|
||||||
|
for (int i = 0; i < csv.HeaderRecord.Length; i++)
|
||||||
|
{
|
||||||
|
string columnName = csv.HeaderRecord[i].Replace("#", "").Trim();
|
||||||
|
string value = csv.GetField(i) ?? string.Empty;
|
||||||
|
row[columnName] = value;
|
||||||
|
}
|
||||||
|
dataTable.Rows.Add(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene una lista di tutti i file CSV in una cartella e nelle sue sottocartelle
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="folderPath">Percorso della cartella da esplorare</param>
|
||||||
|
/// <returns>Lista di percorsi dei file CSV trovati</returns>
|
||||||
|
public List<string> GetCsvFiles(string folderPath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(folderPath))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Il percorso della cartella non può essere vuoto.", nameof(folderPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Directory.Exists(folderPath))
|
||||||
|
{
|
||||||
|
throw new DirectoryNotFoundException($"Directory non trovata: {folderPath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var csvFiles = Directory.GetFiles(folderPath, "*.csv", SearchOption.AllDirectories);
|
||||||
|
return new List<string>(csvFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Metodo helper per estrarre un valore stringa da una riga di DataTable
|
||||||
|
/// </summary>
|
||||||
|
public string GetStringValue(DataRow row, string columnName)
|
||||||
|
{
|
||||||
|
if (!row.Table.Columns.Contains(columnName) || row[columnName] == DBNull.Value)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
return row[columnName].ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Metodo helper per estrarre un valore intero da una riga di DataTable
|
||||||
|
/// Gestisce valori nulli o non validi restituendo 0
|
||||||
|
/// </summary>
|
||||||
|
public int GetIntValue(DataRow row, string columnName)
|
||||||
|
{
|
||||||
|
if (!row.Table.Columns.Contains(columnName) || row[columnName] == DBNull.Value)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (int.TryParse(row[columnName].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out int result))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Metodo helper per estrarre un valore intero nullable da una riga di DataTable
|
||||||
|
/// Restituisce null per valori mancanti o non validi
|
||||||
|
/// </summary>
|
||||||
|
public int? GetNullableIntValue(DataRow row, string columnName)
|
||||||
|
{
|
||||||
|
if (!row.Table.Columns.Contains(columnName) || row[columnName] == DBNull.Value)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string stringValue = row[columnName].ToString();
|
||||||
|
if (string.IsNullOrWhiteSpace(stringValue))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (int.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out int result))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Metodo helper per estrarre un valore decimale da una riga di DataTable con controllo di precisione
|
||||||
|
/// Gestisce valori nulli o non validi restituendo 0
|
||||||
|
/// </summary>
|
||||||
|
public decimal GetDecimalValue(DataRow row, string columnName, int precision, int scale)
|
||||||
|
{
|
||||||
|
if (!row.Table.Columns.Contains(columnName) || row[columnName] == DBNull.Value)
|
||||||
|
{
|
||||||
|
return 0.0M;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decimal.TryParse(row[columnName].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out decimal result))
|
||||||
|
{
|
||||||
|
// Arrotonda il valore per rispettare il limite di precisione e scala
|
||||||
|
decimal factor = (decimal)Math.Pow(10, scale);
|
||||||
|
result = Math.Round(result, scale);
|
||||||
|
|
||||||
|
// Verifica se il valore è entro i limiti di precisione
|
||||||
|
decimal maxValue = (decimal)Math.Pow(10, precision - scale) - (1 / factor);
|
||||||
|
if (Math.Abs(result) > maxValue)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Valore fuori range per {columnName}: {result}, limitato a {maxValue}");
|
||||||
|
result = result < 0 ? -maxValue : maxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return 0.0M;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Metodo helper per estrarre un valore decimale nullable da una riga di DataTable
|
||||||
|
/// Restituisce null per valori mancanti o non validi
|
||||||
|
/// </summary>
|
||||||
|
public decimal? GetNullableDecimalValue(DataRow row, string columnName, int precision, int scale)
|
||||||
|
{
|
||||||
|
if (!row.Table.Columns.Contains(columnName) || row[columnName] == DBNull.Value)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string stringValue = row[columnName].ToString();
|
||||||
|
if (string.IsNullOrWhiteSpace(stringValue))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decimal.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal result))
|
||||||
|
{
|
||||||
|
// Arrotonda il valore per rispettare il limite di precisione e scala
|
||||||
|
decimal factor = (decimal)Math.Pow(10, scale);
|
||||||
|
result = Math.Round(result, scale);
|
||||||
|
|
||||||
|
// Verifica se il valore è entro i limiti di precisione
|
||||||
|
decimal maxValue = (decimal)Math.Pow(10, precision - scale) - (1 / factor);
|
||||||
|
if (Math.Abs(result) > maxValue)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Valore fuori range per {columnName}: {result}, limitato a {maxValue}");
|
||||||
|
result = result < 0 ? -maxValue : maxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converte un valore stringa in int con gestione dei valori nulli o non validi
|
||||||
|
/// </summary>
|
||||||
|
public static int ConvertToInt(string value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out int result))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converte un valore stringa in decimal con gestione dei valori nulli o non validi
|
||||||
|
/// </summary>
|
||||||
|
public static decimal ConvertToDecimal(string value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
return 0m;
|
||||||
|
|
||||||
|
if (decimal.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal result))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
return 0m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converte un valore stringa in int? con gestione dei valori nulli o non validi
|
||||||
|
/// </summary>
|
||||||
|
public static int? ConvertToNullableInt(string value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out int result))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converte un valore stringa in decimal? con gestione dei valori nulli o non validi
|
||||||
|
/// </summary>
|
||||||
|
public static decimal? ConvertToNullableDecimal(string value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (decimal.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal result))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace BettingPredictor
|
||||||
|
{
|
||||||
|
internal static class Program
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Punto di ingresso principale dell'applicazione.
|
||||||
|
/// </summary>
|
||||||
|
[STAThread]
|
||||||
|
static void Main()
|
||||||
|
{
|
||||||
|
Application.EnableVisualStyles();
|
||||||
|
Application.SetCompatibleTextRenderingDefault(false);
|
||||||
|
Application.Run(new Main());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// Le informazioni generali relative a un assembly sono controllate dal seguente
|
||||||
|
// set di attributi. Modificare i valori di questi attributi per modificare le informazioni
|
||||||
|
// associate a un assembly.
|
||||||
|
[assembly: AssemblyTitle("BettingPredictor")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("BettingPredictor")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2025")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Se si imposta ComVisible su false, i tipi in questo assembly non saranno visibili
|
||||||
|
// ai componenti COM. Se è necessario accedere a un tipo in questo assembly da
|
||||||
|
// COM, impostare su true l'attributo ComVisible per tale tipo.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// Se il progetto viene esposto a COM, il GUID seguente verrà utilizzato come ID della libreria dei tipi
|
||||||
|
[assembly: Guid("63138155-b7f3-4246-b47b-b8cc2d7a60a4")]
|
||||||
|
|
||||||
|
// Le informazioni sulla versione di un assembly sono costituite dai seguenti quattro valori:
|
||||||
|
//
|
||||||
|
// Versione principale
|
||||||
|
// Versione secondaria
|
||||||
|
// Numero di build
|
||||||
|
// Revisione
|
||||||
|
//
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// Codice generato da uno strumento.
|
||||||
|
// Versione runtime:4.0.30319.42000
|
||||||
|
//
|
||||||
|
// Le modifiche apportate a questo file possono causare un comportamento non corretto e andranno perse se
|
||||||
|
// il codice viene rigenerato.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace BettingPredictor.Properties
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Classe di risorse fortemente tipizzata per la ricerca di stringhe localizzate e così via.
|
||||||
|
/// </summary>
|
||||||
|
// Questa classe è stata generata automaticamente dalla classe StronglyTypedResourceBuilder
|
||||||
|
// tramite uno strumento quale ResGen o Visual Studio.
|
||||||
|
// Per aggiungere o rimuovere un membro, modificare il file .ResX, quindi eseguire di nuovo ResGen
|
||||||
|
// con l'opzione /str oppure ricompilare il progetto VS.
|
||||||
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
internal class Resources
|
||||||
|
{
|
||||||
|
|
||||||
|
private static global::System.Resources.ResourceManager resourceMan;
|
||||||
|
|
||||||
|
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||||
|
|
||||||
|
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||||
|
internal Resources()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Restituisce l'istanza di ResourceManager memorizzata nella cache e usata da questa classe.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
internal static global::System.Resources.ResourceManager ResourceManager
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if ((resourceMan == null))
|
||||||
|
{
|
||||||
|
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("BettingPredictor.Properties.Resources", typeof(Resources).Assembly);
|
||||||
|
resourceMan = temp;
|
||||||
|
}
|
||||||
|
return resourceMan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Esegue l'override della proprietà CurrentUICulture del thread corrente per tutte
|
||||||
|
/// le ricerche di risorse che utilizzano questa classe di risorse fortemente tipizzata.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
internal static global::System.Globalization.CultureInfo Culture
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return resourceCulture;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
resourceCulture = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
</root>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This code was generated by a tool.
|
||||||
|
// Runtime Version:4.0.30319.42000
|
||||||
|
//
|
||||||
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
|
// the code is regenerated.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
namespace BettingPredictor.Properties
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
|
||||||
|
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
|
||||||
|
{
|
||||||
|
|
||||||
|
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||||
|
|
||||||
|
public static Settings Default
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return defaultInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
|
||||||
|
<Profiles>
|
||||||
|
<Profile Name="(Default)" />
|
||||||
|
</Profiles>
|
||||||
|
<Settings />
|
||||||
|
</SettingsFile>
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/****** 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
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/****** 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
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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
|
||||||
|
)
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
/****** 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
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?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="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.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="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.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.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.Threading.Tasks.Extensions" version="4.6.3" targetFramework="net481" />
|
||||||
|
</packages>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<packages>
|
||||||
|
<!-- Pacchetti esistenti -->
|
||||||
|
|
||||||
|
<!-- Pacchetti aggiunti per ML.NET -->
|
||||||
|
<package id="Microsoft.ML" version="2.0.1" targetFramework="net481" />
|
||||||
|
<package id="Microsoft.ML.CpuMath" version="2.0.1" targetFramework="net481" />
|
||||||
|
<package id="Microsoft.ML.DataView" version="2.0.1" targetFramework="net481" />
|
||||||
|
<package id="Microsoft.ML.FastTree" version="2.0.1" targetFramework="net481" />
|
||||||
|
<package id="System.Buffers" version="4.5.1" targetFramework="net481" />
|
||||||
|
<package id="System.CodeDom" version="4.7.0" targetFramework="net481" />
|
||||||
|
<package id="System.Collections.Immutable" version="1.7.1" targetFramework="net481" />
|
||||||
|
<package id="System.Memory" version="4.5.5" targetFramework="net481" />
|
||||||
|
<package id="System.Numerics.Vectors" version="4.5.0" targetFramework="net481" />
|
||||||
|
<package id="System.Reflection.Emit.Lightweight" version="4.7.0" targetFramework="net481" />
|
||||||
|
<package id="System.Runtime.CompilerServices.Unsafe" version="4.7.1" targetFramework="net481" />
|
||||||
|
<package id="System.Threading.Channels" version="4.7.1" targetFramework="net481" />
|
||||||
|
<package id="System.Threading.Tasks.Extensions" version="4.5.4" targetFramework="net481" />
|
||||||
|
</packages>
|
||||||
Reference in New Issue
Block a user