Calculations in R6
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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 sellCodeConfig in SellCodesConfiguration)
|
||||||
foreach (var mapping in Mappings)
|
|
||||||
{
|
{
|
||||||
if (mapping.Code != null && mapping.Desc1 != null)
|
var calc = new BaseCalc(sellCodeConfig);
|
||||||
|
if (!calc.IsFormulaCorrect())
|
||||||
{
|
{
|
||||||
// Extract code from "Sell-Code-XXXX" -> "XXXX"
|
_logger.LogDebug("{ProcessorType}: Invalid formula: {SellCodeConfig}", ProcessorType, sellCodeConfig);
|
||||||
var sourceCode = mapping.Code.Replace("Sell-Code-", "");
|
continue;
|
||||||
var targetCode = mapping.Desc1;
|
}
|
||||||
mappingDictionary[sourceCode] = targetCode;
|
var codes = calc.GetCodes();
|
||||||
|
var resultCode = calc.GetResultCode();
|
||||||
_logger.LogDebug("{ProcessorType}: Loaded mapping {SourceCode} -> {TargetCode}",
|
|
||||||
ProcessorType, sourceCode, targetCode);
|
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";
|
||||||
|
|||||||
Reference in New Issue
Block a user