using AsbCloudDb.Model; using AsbCloudDb.Model.Subsystems; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Npgsql; using System; using System.Collections.Generic; using System.Data; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services.Subsystems { #nullable enable internal class SubsystemOperationTimeBackgroundService : BackgroundService { private readonly string connectionString; private readonly TimeSpan period = TimeSpan.FromHours(1); private const int idSubsytemTorqueMaster = 65537; private const int idSubsytemSpinMaster = 65536; private const int idSubsytemAkb = 1; private const int idSubsytemMse = 2; public SubsystemOperationTimeBackgroundService(IConfiguration configuration) { connectionString = configuration.GetConnectionString("DefaultConnection"); } protected override async Task ExecuteAsync(CancellationToken token) { var timeToStart = DateTime.Now; var options = new DbContextOptionsBuilder() .UseNpgsql(connectionString) .Options; while (!token.IsCancellationRequested) { if (DateTime.Now > timeToStart) { timeToStart = DateTime.Now + period; try { using var context = new AsbCloudDbContext(options); var added = await OperationTimeAllTelemetriesAsync(context, token); Trace.TraceInformation($"Total subsystem operation time complete. Added {added} operations time."); } catch (Exception ex) { Trace.TraceError(ex.Message); } GC.Collect(); } var ms = (int)(timeToStart - DateTime.Now).TotalMilliseconds; ms = ms > 100 ? ms : 100; await Task.Delay(ms, token).ConfigureAwait(false); } } public override async Task StopAsync(CancellationToken token) { await base.StopAsync(token).ConfigureAwait(false); } private static async Task OperationTimeAllTelemetriesAsync(IAsbCloudDbContext db, CancellationToken token) { var lastDetectedDates = await db.SubsystemOperationTimes .GroupBy(o => o.IdTelemetry) .Select(g => new { IdTelemetry = g.Key, LastDate = g.Max(o => o.DateEnd) }) .ToListAsync(token); var telemetryIds = await db.Telemetries .Where(t => t.Info != null && t.TimeZone != null) .Select(t => t.Id) .ToListAsync(token); var telemetryLastDetectedDates = telemetryIds .GroupJoin(lastDetectedDates, t => t, o => o.IdTelemetry, (outer, inner) => new { IdTelemetry = outer, inner.SingleOrDefault()?.LastDate, }); var affected = 0; foreach (var item in telemetryLastDetectedDates) { var newOperationsSaub = await OperationTimeSaubAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); if (newOperationsSaub?.Any() == true) { db.SubsystemOperationTimes.AddRange(newOperationsSaub); affected += await db.SaveChangesAsync(token); } var newOperationsSpin = await OperationTimeSpinAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); if (newOperationsSpin?.Any() == true) { db.SubsystemOperationTimes.AddRange(newOperationsSpin); affected += await db.SaveChangesAsync(token); } } return affected; } private static async Task> OperationTimeSaubAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) { static int? GetSubsytemId(short mode) { if (mode == 1 | mode == 3) return 1; return null; } static bool IsSubsystemMse(short state) { if ((state & 1) > 0) return true; return false; } var query = $"select tt.date, tt.mode, tt.well_depth, tt.mse_state " + $"from ( " + $" select " + $" date, " + $" mode, " + $" mse_state, " + $" well_depth, " + $" lag(mode,1) over (order by date) as mode_prev " + $" from t_telemetry_data_saub " + $" where id_telemetry = @idTelemetry " + $" order by date ) as tt " + $"where (tt.mode_prev is null or tt.mode != tt.mode_prev) and tt.date >= @begin " + $"order by tt.date;"; var idTelemetryParam = new NpgsqlParameter("@idTelemetry", idTelemetry); var beginParam = new NpgsqlParameter("@begin", begin); await db.Database.OpenConnectionAsync(token); using var command = db.Database.GetDbConnection().CreateCommand(); command.CommandText = query; command.Parameters.Add(idTelemetryParam); command.Parameters.Add(beginParam); using var result = await command.ExecuteReaderAsync(token); var subsystemOperationTime = new List(32); var prevSubsystem1 = false; DateTimeOffset? prevDate = null; if (result.Read()) { var mode = result.GetFieldValue(1); var mseState = result.GetFieldValue(3); var idSubsystem = GetSubsytemId(mode); var dateStart = result.GetFieldValue(0); var depthStart = result.GetFieldValue(2); while (result.Read()) { var dateEnd = result.GetFieldValue(0); var depthEnd = result.GetFieldValue(2); if (idSubsystem.HasValue) { if (idSubsystem == 1) { prevSubsystem1 = true; prevDate = dateStart; } else { prevSubsystem1 = false; prevDate = null; } var operationTimeItem = new SubsystemOperationTime() { IdTelemetry = idTelemetry, IdSubsystem = idSubsystem.Value, DateStart = (DateTimeOffset)(prevSubsystem1 ? prevDate : dateStart), DateEnd = dateEnd, DepthStart = depthStart, DepthEnd = depthEnd }; subsystemOperationTime.Add(operationTimeItem); if (IsSubsystemMse(mseState)) { var operationTimeItemMse = new SubsystemOperationTime() { IdTelemetry = idTelemetry, IdSubsystem = 2, DateStart = (DateTimeOffset)(prevSubsystem1 ? prevDate : dateStart), DateEnd = dateEnd, DepthStart = depthStart, DepthEnd = depthEnd }; subsystemOperationTime.Add(operationTimeItemMse); } } mode = result.GetFieldValue(1); mseState = result.GetFieldValue(3); idSubsystem = GetSubsytemId(mode); dateStart = dateEnd; depthStart = depthEnd; } } return subsystemOperationTime; } private static async Task?> OperationTimeSpinAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) { static int? GetSubsytemId(short mode, int state) { if (state == 7 && (mode & 2) > 0) return idSubsytemTorqueMaster; if (state != 0 & state != 6 & state != 7) return idSubsytemSpinMaster; return null; } var query = $"select " + $" tspin.date, " + $" tspin.mode, " + $" tspin.state, " + $" tsaub.well_depth " + $"from ( " + $" select " + $" date, " + $" mode, " + $" lag(mode, 1) over (order by date) as mode_pre, " + $" state, " + $" lag(state, 1) over (order by date) as state_pre " + $" from t_telemetry_data_spin " + $" where id_telemetry = @idTelemetry and date >= @begin" + $" order by date ) as tspin " + $"left outer join ( " + $" select " + $" date, " + $" well_depth " + $" from t_telemetry_data_saub " + $" where id_telemetry = @idTelemetry and date >= @begin) as tsaub " + $"on EXTRACT(EPOCH from tspin.date) = EXTRACT(EPOCH from tsaub.date) " + $"where mode_pre is null or state_pre is null or mode != mode_pre or state != state_pre " + $"order by date;"; var idTelemetryParam = new NpgsqlParameter("@idTelemetry", idTelemetry); var beginParam = new NpgsqlParameter("@begin", begin); await db.Database.OpenConnectionAsync(token); using var command = db.Database.GetDbConnection().CreateCommand(); command.CommandText = query; command.Parameters.Add(idTelemetryParam); command.Parameters.Add(beginParam); using var result = await command.ExecuteReaderAsync(token); var subsystemOperationTime = new List(32); if (result.Read()) { var mode = result.GetFieldValue(1); var state = result.GetFieldValue(2); var idSubsystem = GetSubsytemId(mode, state); var dateStart = result.GetFieldValue(0); var depthStart = result.GetFieldValue(3); while (result.Read()) { var dateEnd = result.GetFieldValue(0); var depthEnd = result.GetFieldValue(3); if (idSubsystem.HasValue) { var operationTimeItem = new SubsystemOperationTime() { IdTelemetry = idTelemetry, IdSubsystem = idSubsystem.Value, DateStart = dateStart, DateEnd = dateEnd, DepthStart = depthStart, DepthEnd = depthEnd }; subsystemOperationTime.Add(operationTimeItem); } mode = result.GetFieldValue(1); state = result.GetFieldValue(2); idSubsystem = GetSubsytemId(mode, state); dateStart = dateEnd; depthStart = depthEnd; } } return subsystemOperationTime; } } #nullable disable }