From 41acf9f93e305c3a2b756debf79058d7bae67ee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Zieli=C5=84ski?= Date: Mon, 11 Aug 2025 21:11:25 +0200 Subject: [PATCH] Calculations in R6 --- .../Services/Calculations/BaseCalc.cs | 33 +++++- .../Processors/MorskaD6Processor.cs | 105 ++++++++++++------ 2 files changed, 103 insertions(+), 35 deletions(-) diff --git a/src/Backend/DiunaBI.Core/Services/Calculations/BaseCalc.cs b/src/Backend/DiunaBI.Core/Services/Calculations/BaseCalc.cs index 0ae6397..0e2b494 100644 --- a/src/Backend/DiunaBI.Core/Services/Calculations/BaseCalc.cs +++ b/src/Backend/DiunaBI.Core/Services/Calculations/BaseCalc.cs @@ -40,6 +40,30 @@ public class BaseCalc Formula.All(c => char.IsDigit(c) || c == '[' || c == ']' || c == '+' || c == '-'); } + public double Calculate(IReadOnlyDictionary ingredients) + { + if (ingredients == null) + { + throw new ArgumentNullException(nameof(ingredients)); + } + + var codes = GetCodes(); + var missing = codes.Where(x => !ingredients.ContainsKey(x)).ToList(); + if (missing.Any()) + { + throw new ArgumentException($"Missing ingredients: {string.Join(", ", missing)}"); + } + + var formula = ingredients.Aggregate(Formula, + (current, ingredient) => current.Replace($"[{ingredient.Key}]", ingredient.Value.ToString(CultureInfo.InvariantCulture))); + if (formula.Contains('[')) + { + throw new Exception($"Not all placeholders were replaced. Value{1} [{formula}]"); + } + Entity expr = formula; + return (double)expr.EvalNumerical(); + } + public Record CalculateT3(List records) { var resultCode = ResultCode; @@ -81,7 +105,6 @@ public class BaseCalc return result; } } - public Record CalculateT1(List records) { var resultCode = ResultCode; @@ -121,8 +144,7 @@ public class BaseCalc return result; } } - - private List GetCodes() + public List GetCodes() { var codes = new List(); var endIndex = -1; @@ -143,4 +165,9 @@ public class BaseCalc return codes; } + + public string GetResultCode() + { + return ResultCode; + } } \ No newline at end of file diff --git a/src/Backend/DiunaBI.Plugins.Morska/Processors/MorskaD6Processor.cs b/src/Backend/DiunaBI.Plugins.Morska/Processors/MorskaD6Processor.cs index 2aea581..247c126 100644 --- a/src/Backend/DiunaBI.Plugins.Morska/Processors/MorskaD6Processor.cs +++ b/src/Backend/DiunaBI.Plugins.Morska/Processors/MorskaD6Processor.cs @@ -6,6 +6,7 @@ using Google.Apis.Sheets.v4.Data; using Microsoft.EntityFrameworkCore; using DiunaBI.Core.Models; using DiunaBI.Core.Database.Context; +using DiunaBI.Core.Services.Calculations; using Microsoft.Extensions.Logging; namespace DiunaBI.Plugins.Morska.Processors; @@ -22,7 +23,7 @@ public class MorskaD6Processor : MorskaBaseProcessor private int Year { get; set; } private string? CostSource { get; set; } private string? SellSource { get; set; } - private List Mappings = new List(); + private List SellCodesConfiguration { get; set; } = new(); public MorskaD6Processor( AppDbContext db, @@ -60,7 +61,6 @@ public class MorskaD6Processor : MorskaBaseProcessor throw; } } - private void LoadConfiguration(Layer processWorker) { if (processWorker.Records == null) @@ -88,17 +88,22 @@ public class MorskaD6Processor : MorskaBaseProcessor throw new InvalidOperationException("SourceLayerCosts record not found"); } - Mappings = processWorker.Records.Where(x => x.Code!.StartsWith("Sell-Code-")).ToList(); - if (Mappings.Count == 0) + SellCodesConfiguration = processWorker.Records + .Where(x => string.Equals(x.Code, "Sell-Code", StringComparison.OrdinalIgnoreCase)) + .Select(x => x.Desc1!.Trim()) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Distinct(StringComparer.Ordinal) + .ToList(); + + if (!SellCodesConfiguration.Any()) { - throw new InvalidOperationException("No Sell-Code- records found"); + throw new InvalidOperationException("Sell-Code records not found"); } _logger.LogDebug( "{ProcessorType}: Configuration loaded - Year: {Year}, SourceCost: {CostSource}, SourceSell: {SellSource}", ProcessorType, Year, CostSource, SellSource); } - private void ValidateConfiguration() { var errors = new List(); @@ -114,7 +119,6 @@ public class MorskaD6Processor : MorskaBaseProcessor _logger.LogDebug("{ProcessorType}: Configuration validation passed", ProcessorType); } - private void PerformProcessing(Layer processWorker) { _logger.LogDebug( @@ -135,7 +139,6 @@ public class MorskaD6Processor : MorskaBaseProcessor "{ProcessorType}: Successfully processed {RecordCount} records for layer {LayerName} ({LayerId})", ProcessorType, newRecords.Count, processedLayer.Name, processedLayer.Id); } - private Layer GetOrCreateProcessedLayer(Layer processWorker) { var processedLayer = _db.Layers @@ -172,7 +175,6 @@ public class MorskaD6Processor : MorskaBaseProcessor return processedLayer; } - private List GetDataSources() { var costDataSource = _db.Layers @@ -201,7 +203,6 @@ public class MorskaD6Processor : MorskaBaseProcessor return [costDataSource, sellDataSource]; } - private List ProcessRecords(List dataSources) { var newRecords = new List(); @@ -240,7 +241,7 @@ public class MorskaD6Processor : MorskaBaseProcessor } var groupedData = firstDataSource.Records - .Where(record => record.Code != null && record.Code.Length >= 8 && record.Value1.HasValue) + .Where(record => record is { Code: { Length: >= 8 }, Value1: not null }) .Select(record => new { Month = record.Code!.Substring(4, 2), @@ -275,13 +276,28 @@ public class MorskaD6Processor : MorskaBaseProcessor foreach (var groupedRecord in groupedData) { + // hack for 2206 ([2206]=[2206]+[2203] + double value = groupedRecord.TotalValue; + if (groupedRecord.FinalCode.StartsWith("2206")) + { + var month = groupedRecord.FinalCode.Substring(4, 2); + var toSumUp = groupedData.FirstOrDefault(x => x.FinalCode == $"2203{month}"); + if (toSumUp == null) + { + _logger.LogWarning("{ProcessorType}: 2203{month} not found (to sum up with 2206{month}", ProcessorType, month, month); + } + else + { + value+= toSumUp.TotalValue; + } + } var newRecord = new Record { Id = Guid.NewGuid(), Code = groupedRecord.FinalCode, CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow, - Value1 = groupedRecord.TotalValue, + Value1 = value, Desc1 = groupedRecord.Department }; @@ -303,25 +319,52 @@ public class MorskaD6Processor : MorskaBaseProcessor return newRecords; } - // Create mapping dictionary from Mappings - var mappingDictionary = new Dictionary(); - foreach (var mapping in Mappings) + + foreach (var sellCodeConfig in SellCodesConfiguration) { - if (mapping.Code != null && mapping.Desc1 != null) + var calc = new BaseCalc(sellCodeConfig); + if (!calc.IsFormulaCorrect()) { - // Extract code from "Sell-Code-XXXX" -> "XXXX" - var sourceCode = mapping.Code.Replace("Sell-Code-", ""); - var targetCode = mapping.Desc1; - mappingDictionary[sourceCode] = targetCode; - - _logger.LogDebug("{ProcessorType}: Loaded mapping {SourceCode} -> {TargetCode}", - ProcessorType, sourceCode, targetCode); + _logger.LogDebug("{ProcessorType}: Invalid formula: {SellCodeConfig}", ProcessorType, sellCodeConfig); + continue; + } + var codes = calc.GetCodes(); + var resultCode = calc.GetResultCode(); + + for (var i = 1; i <= 12; i++) + { + var monthRecords = secondDataSource.Records + .Where(x => x.Code is { Length: 6 } && x.Code.EndsWith($"{i:D2}") && x.Value1.HasValue) + .ToList(); + if (monthRecords.Count == 0) + { + continue; + } + var ingredients = monthRecords.ToDictionary(x => x.Code!.Substring(0, 4), x => x.Value1!.Value); + double result = 0; + try + { + result = calc.Calculate(ingredients); + } + catch (Exception e) + { + _logger.LogError(e, "{ProcessorType}: Failed to calculate sell code {ResultCode} for month {Month}", + ProcessorType, resultCode, i); + } + + var newRecord = new Record + { + Id = Guid.NewGuid(), + Code = $"{resultCode}{i:D2}", + CreatedAt = DateTime.UtcNow, + ModifiedAt = DateTime.UtcNow, + Value1 = result + }; + newRecords.Add(newRecord); } } - - // Dictionary to collect results (key: target code with month, value: sum) - var sellResults = new Dictionary(); - + + /* // Process records from secondDataSource foreach (var record in secondDataSource.Records) { @@ -391,10 +434,9 @@ public class MorskaD6Processor : MorskaBaseProcessor _logger.LogInformation( "{ProcessorType}: Processed {GroupCount} unique grouped records from {OriginalCount} original records", ProcessorType, newRecords.Count, firstDataSource.Records.Count); - +*/ return newRecords; } - private void SaveProcessedLayer(Layer processedLayer, List newRecords) { var existsInDb = _db.Layers.Any(x => x.Id == processedLayer.Id); @@ -416,7 +458,6 @@ public class MorskaD6Processor : MorskaBaseProcessor _logger.LogDebug("{ProcessorType}: Saved {RecordCount} records for layer {LayerId}", ProcessorType, newRecords.Count, processedLayer.Id); } - private void SaveRecords(Guid layerId, ICollection records) { // Remove existing records for this layer @@ -442,12 +483,10 @@ public class MorskaD6Processor : MorskaBaseProcessor _logger.LogDebug("{ProcessorType}: Added {RecordCount} new records for layer {LayerId}", ProcessorType, records.Count, layerId); } - private string? GetRecordValue(ICollection records, string code) { return records.FirstOrDefault(x => x.Code == code)?.Desc1; } - private string GetDepartmentByType(string type, string originalDepartment) { var typesThatUseDepartment = new[] { "02", "09", "10", "11", "12", "13", "14", "15" }; @@ -481,6 +520,8 @@ public class MorskaD6Processor : MorskaBaseProcessor } } + // Export to Google + private void UpdateGoogleSheetReport(Guid sourceId) { const string googleSheetName = "Raport_R6_DRAFT_2025";