Calculations in R6

This commit is contained in:
Michał Zieliński
2025-08-11 21:11:25 +02:00
parent 15ed0075ea
commit 41acf9f93e
2 changed files with 103 additions and 35 deletions

View File

@@ -40,6 +40,30 @@ public class BaseCalc
Formula.All(c => char.IsDigit(c) || c == '[' || c == ']' || c == '+' || c == '-');
}
public double Calculate(IReadOnlyDictionary<string, double> 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<Record> records)
{
var resultCode = ResultCode;
@@ -81,7 +105,6 @@ public class BaseCalc
return result;
}
}
public Record CalculateT1(List<Record> records)
{
var resultCode = ResultCode;
@@ -121,8 +144,7 @@ public class BaseCalc
return result;
}
}
private List<string> GetCodes()
public List<string> GetCodes()
{
var codes = new List<string>();
var endIndex = -1;
@@ -143,4 +165,9 @@ public class BaseCalc
return codes;
}
public string GetResultCode()
{
return ResultCode;
}
}

View File

@@ -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<Record> Mappings = new List<Record>();
private List<string> 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<string>();
@@ -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<Layer> GetDataSources()
{
var costDataSource = _db.Layers
@@ -201,7 +203,6 @@ public class MorskaD6Processor : MorskaBaseProcessor
return [costDataSource, sellDataSource];
}
private List<Record> ProcessRecords(List<Layer> dataSources)
{
var newRecords = new List<Record>();
@@ -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<string, string>();
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<string, double>();
/*
// 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<Record> 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<Record> 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<Record> 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";