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 == '-'); 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) public Record CalculateT3(List<Record> records)
{ {
var resultCode = ResultCode; var resultCode = ResultCode;
@@ -81,7 +105,6 @@ public class BaseCalc
return result; return result;
} }
} }
public Record CalculateT1(List<Record> records) public Record CalculateT1(List<Record> records)
{ {
var resultCode = ResultCode; var resultCode = ResultCode;
@@ -121,8 +144,7 @@ public class BaseCalc
return result; return result;
} }
} }
public List<string> GetCodes()
private List<string> GetCodes()
{ {
var codes = new List<string>(); var codes = new List<string>();
var endIndex = -1; var endIndex = -1;
@@ -143,4 +165,9 @@ public class BaseCalc
return codes; return codes;
} }
public string GetResultCode()
{
return ResultCode;
}
} }

View File

@@ -6,6 +6,7 @@ using Google.Apis.Sheets.v4.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using DiunaBI.Core.Models; using DiunaBI.Core.Models;
using DiunaBI.Core.Database.Context; using DiunaBI.Core.Database.Context;
using DiunaBI.Core.Services.Calculations;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace DiunaBI.Plugins.Morska.Processors; namespace DiunaBI.Plugins.Morska.Processors;
@@ -22,7 +23,7 @@ public class MorskaD6Processor : MorskaBaseProcessor
private int Year { get; set; } private int Year { get; set; }
private string? CostSource { get; set; } private string? CostSource { get; set; }
private string? SellSource { get; set; } private string? SellSource { get; set; }
private List<Record> Mappings = new List<Record>(); private List<string> SellCodesConfiguration { get; set; } = new();
public MorskaD6Processor( public MorskaD6Processor(
AppDbContext db, AppDbContext db,
@@ -60,7 +61,6 @@ public class MorskaD6Processor : MorskaBaseProcessor
throw; throw;
} }
} }
private void LoadConfiguration(Layer processWorker) private void LoadConfiguration(Layer processWorker)
{ {
if (processWorker.Records == null) if (processWorker.Records == null)
@@ -88,17 +88,22 @@ public class MorskaD6Processor : MorskaBaseProcessor
throw new InvalidOperationException("SourceLayerCosts record not found"); throw new InvalidOperationException("SourceLayerCosts record not found");
} }
Mappings = processWorker.Records.Where(x => x.Code!.StartsWith("Sell-Code-")).ToList(); SellCodesConfiguration = processWorker.Records
if (Mappings.Count == 0) .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( _logger.LogDebug(
"{ProcessorType}: Configuration loaded - Year: {Year}, SourceCost: {CostSource}, SourceSell: {SellSource}", "{ProcessorType}: Configuration loaded - Year: {Year}, SourceCost: {CostSource}, SourceSell: {SellSource}",
ProcessorType, Year, CostSource, SellSource); ProcessorType, Year, CostSource, SellSource);
} }
private void ValidateConfiguration() private void ValidateConfiguration()
{ {
var errors = new List<string>(); var errors = new List<string>();
@@ -114,7 +119,6 @@ public class MorskaD6Processor : MorskaBaseProcessor
_logger.LogDebug("{ProcessorType}: Configuration validation passed", ProcessorType); _logger.LogDebug("{ProcessorType}: Configuration validation passed", ProcessorType);
} }
private void PerformProcessing(Layer processWorker) private void PerformProcessing(Layer processWorker)
{ {
_logger.LogDebug( _logger.LogDebug(
@@ -135,7 +139,6 @@ public class MorskaD6Processor : MorskaBaseProcessor
"{ProcessorType}: Successfully processed {RecordCount} records for layer {LayerName} ({LayerId})", "{ProcessorType}: Successfully processed {RecordCount} records for layer {LayerName} ({LayerId})",
ProcessorType, newRecords.Count, processedLayer.Name, processedLayer.Id); ProcessorType, newRecords.Count, processedLayer.Name, processedLayer.Id);
} }
private Layer GetOrCreateProcessedLayer(Layer processWorker) private Layer GetOrCreateProcessedLayer(Layer processWorker)
{ {
var processedLayer = _db.Layers var processedLayer = _db.Layers
@@ -172,7 +175,6 @@ public class MorskaD6Processor : MorskaBaseProcessor
return processedLayer; return processedLayer;
} }
private List<Layer> GetDataSources() private List<Layer> GetDataSources()
{ {
var costDataSource = _db.Layers var costDataSource = _db.Layers
@@ -201,7 +203,6 @@ public class MorskaD6Processor : MorskaBaseProcessor
return [costDataSource, sellDataSource]; return [costDataSource, sellDataSource];
} }
private List<Record> ProcessRecords(List<Layer> dataSources) private List<Record> ProcessRecords(List<Layer> dataSources)
{ {
var newRecords = new List<Record>(); var newRecords = new List<Record>();
@@ -240,7 +241,7 @@ public class MorskaD6Processor : MorskaBaseProcessor
} }
var groupedData = firstDataSource.Records 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 .Select(record => new
{ {
Month = record.Code!.Substring(4, 2), Month = record.Code!.Substring(4, 2),
@@ -275,13 +276,28 @@ public class MorskaD6Processor : MorskaBaseProcessor
foreach (var groupedRecord in groupedData) 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 var newRecord = new Record
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
Code = groupedRecord.FinalCode, Code = groupedRecord.FinalCode,
CreatedAt = DateTime.UtcNow, CreatedAt = DateTime.UtcNow,
ModifiedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow,
Value1 = groupedRecord.TotalValue, Value1 = value,
Desc1 = groupedRecord.Department Desc1 = groupedRecord.Department
}; };
@@ -303,25 +319,52 @@ public class MorskaD6Processor : MorskaBaseProcessor
return newRecords; return newRecords;
} }
// Create mapping dictionary from Mappings
var mappingDictionary = new Dictionary<string, string>();
foreach (var mapping in Mappings)
{
if (mapping.Code != null && mapping.Desc1 != null)
{
// 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}", foreach (var sellCodeConfig in SellCodesConfiguration)
ProcessorType, sourceCode, targetCode); {
var calc = new BaseCalc(sellCodeConfig);
if (!calc.IsFormulaCorrect())
{
_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 // Process records from secondDataSource
foreach (var record in secondDataSource.Records) foreach (var record in secondDataSource.Records)
{ {
@@ -391,10 +434,9 @@ public class MorskaD6Processor : MorskaBaseProcessor
_logger.LogInformation( _logger.LogInformation(
"{ProcessorType}: Processed {GroupCount} unique grouped records from {OriginalCount} original records", "{ProcessorType}: Processed {GroupCount} unique grouped records from {OriginalCount} original records",
ProcessorType, newRecords.Count, firstDataSource.Records.Count); ProcessorType, newRecords.Count, firstDataSource.Records.Count);
*/
return newRecords; return newRecords;
} }
private void SaveProcessedLayer(Layer processedLayer, List<Record> newRecords) private void SaveProcessedLayer(Layer processedLayer, List<Record> newRecords)
{ {
var existsInDb = _db.Layers.Any(x => x.Id == processedLayer.Id); 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}", _logger.LogDebug("{ProcessorType}: Saved {RecordCount} records for layer {LayerId}",
ProcessorType, newRecords.Count, processedLayer.Id); ProcessorType, newRecords.Count, processedLayer.Id);
} }
private void SaveRecords(Guid layerId, ICollection<Record> records) private void SaveRecords(Guid layerId, ICollection<Record> records)
{ {
// Remove existing records for this layer // Remove existing records for this layer
@@ -442,12 +483,10 @@ public class MorskaD6Processor : MorskaBaseProcessor
_logger.LogDebug("{ProcessorType}: Added {RecordCount} new records for layer {LayerId}", _logger.LogDebug("{ProcessorType}: Added {RecordCount} new records for layer {LayerId}",
ProcessorType, records.Count, layerId); ProcessorType, records.Count, layerId);
} }
private string? GetRecordValue(ICollection<Record> records, string code) private string? GetRecordValue(ICollection<Record> records, string code)
{ {
return records.FirstOrDefault(x => x.Code == code)?.Desc1; return records.FirstOrDefault(x => x.Code == code)?.Desc1;
} }
private string GetDepartmentByType(string type, string originalDepartment) private string GetDepartmentByType(string type, string originalDepartment)
{ {
var typesThatUseDepartment = new[] { "02", "09", "10", "11", "12", "13", "14", "15" }; 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) private void UpdateGoogleSheetReport(Guid sourceId)
{ {
const string googleSheetName = "Raport_R6_DRAFT_2025"; const string googleSheetName = "Raport_R6_DRAFT_2025";