summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSerghei Cebotari <serghei@cebotari.ru>2023-09-19 14:56:55 +0300
committerSerghei Cebotari <serghei@cebotari.ru>2023-09-19 14:56:55 +0300
commit12b557f53d1767123c8c3a1835d2856ddc5eba3d (patch)
tree8c28a66ab0a7c916992784db5b472fafb27fabaf
parenta8ec0cee16b8022ebe0d030c422935a4f33cc1ec (diff)
ML.Net query modifier implementation
-rw-r--r--.gitignore1
-rw-r--r--RhSolutions.Api/Middleware/QueryModifier.cs30
-rw-r--r--RhSolutions.Api/Program.cs6
-rw-r--r--RhSolutions.Api/RhSolutions.Api.csproj7
-rw-r--r--RhSolutions.Api/Services/BypassQueryModifier.cs11
-rw-r--r--RhSolutions.Api/Services/IProductQueryModifier.cs7
-rw-r--r--RhSolutions.Api/Services/IProductTypePredicter.cs6
-rw-r--r--RhSolutions.Api/Services/ProductQueryModifierFactory.cs15
-rw-r--r--RhSolutions.Api/Services/ProductTypePredicter.cs44
-rw-r--r--RhSolutions.Api/Services/TPieceQueryModifier.cs42
10 files changed, 168 insertions, 1 deletions
diff --git a/.gitignore b/.gitignore
index 2afa2e2..9692748 100644
--- a/.gitignore
+++ b/.gitignore
@@ -452,3 +452,4 @@ $RECYCLE.BIN/
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
+/RhSolutions.Api/MLModels
diff --git a/RhSolutions.Api/Middleware/QueryModifier.cs b/RhSolutions.Api/Middleware/QueryModifier.cs
new file mode 100644
index 0000000..810c7dd
--- /dev/null
+++ b/RhSolutions.Api/Middleware/QueryModifier.cs
@@ -0,0 +1,30 @@
+using Microsoft.AspNetCore.Http.Extensions;
+using RhSolutions.Api.Services;
+
+namespace RhSolutions.Api.Middleware;
+
+public class QueryModifier
+{
+ private RequestDelegate _next;
+
+ public QueryModifier(RequestDelegate nextDelegate)
+ {
+ _next = nextDelegate;
+ }
+
+ public async Task Invoke(HttpContext context, IProductTypePredicter typePredicter, ProductQueryModifierFactory productQueryModifierFactory)
+ {
+ if (context.Request.Method == HttpMethods.Get
+ && context.Request.Path == "/api/search")
+ {
+ string query = context.Request.Query["query"].ToString();
+ var productType = typePredicter.GetPredictedProductType(query);
+ var modifier = productQueryModifierFactory.GetModifier(productType!);
+ if (modifier.TryQueryModify(context.Request.Query, out var newQuery))
+ {
+ context.Request.QueryString = newQuery;
+ }
+ }
+ await _next(context);
+ }
+}
diff --git a/RhSolutions.Api/Program.cs b/RhSolutions.Api/Program.cs
index 00a8c84..530be7f 100644
--- a/RhSolutions.Api/Program.cs
+++ b/RhSolutions.Api/Program.cs
@@ -1,6 +1,7 @@
using Microsoft.EntityFrameworkCore;
using RhSolutions.Models;
using RhSolutions.Api.Services;
+using RhSolutions.Api.Middleware;
var builder = WebApplication.CreateBuilder(args);
@@ -21,12 +22,15 @@ builder.Services.AddDbContext<RhSolutionsContext>(opts =>
opts.EnableSensitiveDataLogging(true);
}
});
-builder.Services.AddScoped<IPricelistParser, ClosedXMLParser>();
+builder.Services.AddScoped<IPricelistParser, ClosedXMLParser>()
+ .AddScoped<IProductTypePredicter, ProductTypePredicter>()
+ .AddSingleton<ProductQueryModifierFactory>();
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
+app.UseMiddleware<QueryModifier>();
var context = app.Services.CreateScope().ServiceProvider
.GetRequiredService<RhSolutionsContext>();
diff --git a/RhSolutions.Api/RhSolutions.Api.csproj b/RhSolutions.Api/RhSolutions.Api.csproj
index 270c488..5f122f2 100644
--- a/RhSolutions.Api/RhSolutions.Api.csproj
+++ b/RhSolutions.Api/RhSolutions.Api.csproj
@@ -13,9 +13,16 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
+ <PackageReference Include="Microsoft.ML" Version="2.0.1" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.4" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0" />
<PackageReference Include="Rhsolutions.ProductSku" Version="1.0.0" />
</ItemGroup>
+ <ItemGroup>
+ <None Update="MLModels\model.zip">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </None>
+ </ItemGroup>
+
</Project>
diff --git a/RhSolutions.Api/Services/BypassQueryModifier.cs b/RhSolutions.Api/Services/BypassQueryModifier.cs
new file mode 100644
index 0000000..8ba3826
--- /dev/null
+++ b/RhSolutions.Api/Services/BypassQueryModifier.cs
@@ -0,0 +1,11 @@
+namespace RhSolutions.Api.Services
+{
+ public class BypassQueryModifier : IProductQueryModifier
+ {
+ public bool TryQueryModify(IQueryCollection collection, out QueryString queryString)
+ {
+ queryString = QueryString.Empty;
+ return false;
+ }
+ }
+}
diff --git a/RhSolutions.Api/Services/IProductQueryModifier.cs b/RhSolutions.Api/Services/IProductQueryModifier.cs
new file mode 100644
index 0000000..3892a39
--- /dev/null
+++ b/RhSolutions.Api/Services/IProductQueryModifier.cs
@@ -0,0 +1,7 @@
+namespace RhSolutions.Api.Services
+{
+ public interface IProductQueryModifier
+ {
+ public bool TryQueryModify(IQueryCollection collection, out QueryString queryString);
+ }
+}
diff --git a/RhSolutions.Api/Services/IProductTypePredicter.cs b/RhSolutions.Api/Services/IProductTypePredicter.cs
new file mode 100644
index 0000000..b4625ec
--- /dev/null
+++ b/RhSolutions.Api/Services/IProductTypePredicter.cs
@@ -0,0 +1,6 @@
+namespace RhSolutions.Api.Services;
+
+public interface IProductTypePredicter
+{
+ public string? GetPredictedProductType(string productName);
+} \ No newline at end of file
diff --git a/RhSolutions.Api/Services/ProductQueryModifierFactory.cs b/RhSolutions.Api/Services/ProductQueryModifierFactory.cs
new file mode 100644
index 0000000..8f587a8
--- /dev/null
+++ b/RhSolutions.Api/Services/ProductQueryModifierFactory.cs
@@ -0,0 +1,15 @@
+namespace RhSolutions.Api.Services;
+
+public class ProductQueryModifierFactory
+{
+ public IProductQueryModifier GetModifier(string productTypeName)
+ {
+ switch (productTypeName)
+ {
+ case "Тройник RAUTITAN":
+ return new TPieceQueryModifier();
+ default:
+ return new BypassQueryModifier();
+ }
+ }
+}
diff --git a/RhSolutions.Api/Services/ProductTypePredicter.cs b/RhSolutions.Api/Services/ProductTypePredicter.cs
new file mode 100644
index 0000000..2c2c226
--- /dev/null
+++ b/RhSolutions.Api/Services/ProductTypePredicter.cs
@@ -0,0 +1,44 @@
+using Microsoft.ML;
+using Microsoft.ML.Data;
+
+namespace RhSolutions.Api.Services;
+
+public class ProductTypePredicter : IProductTypePredicter
+{
+ private readonly string _modelPath = @"./MLModels/model.zip";
+ private MLContext _mlContext;
+ private ITransformer _loadedModel;
+ private PredictionEngine<Product, TypePrediction> _predEngine;
+
+ public ProductTypePredicter()
+ {
+ _mlContext = new MLContext(seed: 0);
+ _loadedModel = _mlContext.Model.Load(_modelPath, out var _);
+ _predEngine = _mlContext.Model.CreatePredictionEngine<Product, TypePrediction>(_loadedModel);
+ }
+
+ public string? GetPredictedProductType(string productName)
+ {
+ Product p = new()
+ {
+ Name = productName
+ };
+ var prediction = _predEngine.Predict(p);
+ return prediction.Type;
+ }
+
+ public class Product
+ {
+ [LoadColumn(0)]
+ public string? Name { get; set; }
+ [LoadColumn(1)]
+ public string? Type { get; set; }
+ }
+
+ public class TypePrediction
+ {
+ [ColumnName("PredictedLabel")]
+ public string? Type { get; set; }
+ }
+}
+
diff --git a/RhSolutions.Api/Services/TPieceQueryModifier.cs b/RhSolutions.Api/Services/TPieceQueryModifier.cs
new file mode 100644
index 0000000..7ec6439
--- /dev/null
+++ b/RhSolutions.Api/Services/TPieceQueryModifier.cs
@@ -0,0 +1,42 @@
+using Microsoft.AspNetCore.Http.Extensions;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace RhSolutions.Api.Services
+{
+ public class TPieceQueryModifier : IProductQueryModifier
+ {
+ private readonly string pattern = @"(\b16|20|25|32|40|50|63\b)+";
+
+ public bool TryQueryModify(IQueryCollection collection, out QueryString queryString)
+ {
+ queryString = QueryString.Empty;
+ var query = collection["query"].ToString();
+ if (string.IsNullOrEmpty(query))
+ {
+ return false;
+ }
+ var matches = Regex.Matches(query, pattern);
+ StringBuilder sb = new();
+ sb.Append("Тройник RAUTITAN -PLATINUM");
+ if (matches.Count == 1)
+ {
+ sb.Append($" {matches.First().Value}-{matches.First().Value}-{matches.First().Value}");
+ }
+ else if (matches.Count >= 3)
+ {
+ sb.Append($" {matches[0].Value}-{matches[1].Value}-{matches[2].Value}");
+ }
+ else
+ {
+ return false;
+ }
+ QueryBuilder qb = new()
+ {
+ { "query", sb.ToString() }
+ };
+ queryString = qb.ToQueryString();
+ return true;
+ }
+ }
+}