using AnalyticsDAL.CustomCode.OEE; using AnalyticsDAL.DTO.OEE; using GB5Shared.DTO.Framework.Login; using GB5Shared.Telemetry; using Microsoft.Extensions.Logging; using Newtonsoft.Json; namespace AnalyticsBLL.OEE { public class OEECalculationEngine : IOEECalculationEngine { private readonly IOEEEngineDAL _dal; private readonly ILogger _logger; public OEECalculationEngine(IOEEEngineDAL dal, ILogger logger) { _dal = dal; _logger = logger; } public async Task CalculateAndPersistOEE(DateTime fromDate, DateTime toDate, LoginDTO login, CancellationToken ct) { GB5Trace.Step("oee-calculate", new { fromDate, toDate, TenantId = login.ClientId }); int factsWritten = 0; long factId = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); await foreach (var row in _dal.StreamOEEInputData(fromDate, toDate, login, ct).ConfigureAwait(false)) { var fact = BuildOEEFact(row, factId++, login.ClientId); await _dal.UpsertOEEFact(fact, login, ct).ConfigureAwait(false); factsWritten++; } GB5Trace.Step("oee-calculate-done", new { factsWritten }); _logger.LogInformation("OEE calculation complete: {FactsWritten} facts written for TenantId {TenantId}", factsWritten, login.ClientId); return factsWritten; } public async Task GetOEEReport(DateTime fromDate, DateTime toDate, int workCenterId, LoginDTO login, CancellationToken ct) { var items = await _dal.GetOEEReport(fromDate, toDate, workCenterId, login, ct).ConfigureAwait(false); return JsonConvert.SerializeObject(items); } public async Task GetOEELiveStatus(LoginDTO login, CancellationToken ct) { var items = await _dal.GetOEELiveStatus(login, ct).ConfigureAwait(false); return JsonConvert.SerializeObject(items); } // ── OEE Formula ─────────────────────────────────────────────────────── // Availability = RunTime / PlannedProductionTime // Performance = (TotalProduced × IdealCycleTime) / RunTime // Quality = GoodQuantity / TotalProduced // OEE = Availability × Performance × Quality private static OEEFactDTO BuildOEEFact(OEEInputRowDTO row, long factId, int tenantId) { var totalProduced = row.GoodQuantity + row.RejectedQuantity + row.ReworkQuantity; var runTime = row.RunTimeMinutes > 0 ? row.RunTimeMinutes : 0.001m; // avoid /0 var planned = row.PlannedMinutes > 0 ? row.PlannedMinutes : 480m; var availability = Math.Min(runTime / planned, 1m); var performance = row.IdealCycleTimeMin > 0 && runTime > 0 ? Math.Min((totalProduced * row.IdealCycleTimeMin) / runTime, 1m) : 0m; var quality = totalProduced > 0 ? Math.Min(row.GoodQuantity / totalProduced, 1m) : 0m; var oee = availability * performance * quality; return new OEEFactDTO { FactId = factId, MachineId = row.MachineId, WorkCenterId = row.WorkCenterId, FactDate = row.ProductionDate, ShiftId = row.ShiftId, PlannedMinutes = row.PlannedMinutes, RunTimeMinutes = row.RunTimeMinutes, DowntimeMinutes = row.DowntimeMinutes, TotalProduced = totalProduced, GoodQuantity = row.GoodQuantity, RejectedQuantity = row.RejectedQuantity, ReworkQuantity = row.ReworkQuantity, Availability = Math.Round(availability, 4), Performance = Math.Round(performance, 4), Quality = Math.Round(quality, 4), OEE = Math.Round(oee, 4), TenantId = tenantId }; } } }