using AsbCloudDb.Model; using AsbCloudDb.Model.Subsystems; using AsbCloudInfrastructure.Background; using AsbCloudInfrastructure.Services.Subsystems.Utils; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace AsbCloudInfrastructure.Services.Subsystems { internal static class SubsystemOperationTimeCalcWorkFactory { private const string workId = "Subsystem operation time calc"; private static readonly TimeSpan workPeriod = TimeSpan.FromMinutes(30); private const int idSubsytemTorqueMaster = 65537; private const int idSubsytemSpinMaster = 65536; private const int idSubsystemAPDRotor = 11; private const int idSubsystemAPDSlide = 12; private const int idSubsytemMse = 2; public static WorkPeriodic MakeWork() { var workPeriodic = new WorkPeriodic(workId, WorkAction, workPeriod) { Timeout = TimeSpan.FromMinutes(30) }; return workPeriodic; } // TODO: Разделить этот акшн на более мелкие части И использовать telemetryServiceData<..> вместо прямого обращения к БД. private static async Task WorkAction(string _, IServiceProvider serviceProvider, CancellationToken token) { using var db = serviceProvider.GetRequiredService(); 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, }); 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); await db.SaveChangesAsync(token); } var newOperationsSpin = await OperationTimeSpinAsync(item.IdTelemetry, item.LastDate ?? DateTimeOffset.MinValue, db, token); if (newOperationsSpin?.Any() == true) { db.SubsystemOperationTimes.AddRange(newOperationsSpin); await db.SaveChangesAsync(token); } } } private static async Task ExecuteReaderAsync(IAsbCloudDbContext db, string query, CancellationToken token) { var connection = db.Database.GetDbConnection(); if ( connection?.State is null || connection.State == ConnectionState.Broken || connection.State == ConnectionState.Closed) { await db.Database.OpenConnectionAsync(token); connection = db.Database.GetDbConnection(); } using var command = connection.CreateCommand(); command.CommandText = query; var result = await command.ExecuteReaderAsync(token); return result; } private static async Task> OperationTimeSaubAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) { static bool isSubsytemAkbRotor(short? mode) => mode == 1; static bool isSubsytemAkbSlide(short? mode) => mode == 3; static bool IsSubsystemMse(short? state) => (state & 1) > 0; 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_lag, " + $" lead(mode,1) over (order by date) as mode_lead " + $" from t_telemetry_data_saub " + $" where id_telemetry = {idTelemetry} and well_depth is not null and well_depth > 0" + $" order by date ) as tt " + $"where (tt.mode_lag is null or (tt.mode != tt.mode_lag and tt.mode_lead != tt.mode_lag)) and tt.date >= '{begin:u}' " + $"order by tt.date;"; using var result = await ExecuteReaderAsync(db, query, token); var subsystemsOperationTimes = new List(); var detectorRotor = new SubsystemDetector(idTelemetry, idSubsystemAPDRotor, isSubsytemAkbRotor, IsValid); var detectorSlide = new SubsystemDetector(idTelemetry, idSubsystemAPDSlide, isSubsytemAkbSlide, IsValid); var detectorMse = new SubsystemDetector(idTelemetry, idSubsytemMse, IsSubsystemMse, IsValid); while (result.Read()) { var mode = result.GetFieldValue(1); var state = result.GetFieldValue(3); var isAkbRotorEnable = isSubsytemAkbRotor(mode); var isAkbSlideEnable = isSubsytemAkbSlide(mode); var isMseEnable = IsSubsystemMse(state); var date = result.GetFieldValue(0); var depth = result.GetFieldValue(2); if (detectorRotor.TryDetect(mode, date, depth, out var detectedRotor)) subsystemsOperationTimes.Add(detectedRotor!); if (detectorSlide.TryDetect(mode, date, depth, out var detectedSlide)) subsystemsOperationTimes.Add(detectedSlide!); if (detectorMse.TryDetect(mode, date, depth, out var detectedMse)) subsystemsOperationTimes.Add(detectedMse!); } return subsystemsOperationTimes; } private static async Task> OperationTimeSpinAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) { static int? GetSubsytemId(short? mode, int? state) { // При изменении следующего кода сообщи в Vladimir.Sobolev@nedra.digital if (state == 7 && (mode & 2) > 0) return idSubsytemTorqueMaster;// демпфер if (state != 0 && state != 5 && state != 6 && state != 7) return idSubsytemSpinMaster;// осцилляция return null; } var querySpin = $"select " + $" tspin.date, " + $" tspin.mode, " + $" tspin.state " + $"from ( " + $" select " + $" date, " + $" mode, " + $" lag(mode, 1) over (order by date) as mode_lag, " + $" lead(mode, 1) over (order by date) as mode_lead, " + $" state, " + $" lag(state, 1) over (order by date) as state_lag " + $" from t_telemetry_data_spin " + $" where id_telemetry = {idTelemetry} and date >= '{begin:u}'" + $" order by date ) as tspin " + $"where mode_lag is null or state_lag is null or (mode != mode_lag and mode_lead != mode_lag) or state != state_lag " + $"order by date;"; var rows = new List<(int? IdSubsystem, DateTimeOffset Date)>(32); using var resultSpin = await ExecuteReaderAsync(db, querySpin, token); int? idSubsystemLast = null; while (resultSpin.Read()) { var mode = resultSpin.GetFieldValue(1); var state = resultSpin.GetFieldValue(2); var idSubsystem = GetSubsytemId(mode, state); if (idSubsystemLast != idSubsystem) { idSubsystemLast = idSubsystem; var date = resultSpin.GetFieldValue(0); rows.Add((idSubsystem, date)); } } await resultSpin.DisposeAsync(); if (rows.Count < 2) return Enumerable.Empty(); var minSpinDate = rows.Min(i => i.Date); var maxSpinDate = rows.Max(i => i.Date); var depthInterpolation = await GetInterpolation(db, idTelemetry, minSpinDate, maxSpinDate, token); if (depthInterpolation is null) return Enumerable.Empty(); var subsystemsOperationTimes = new List(32); for (int i = 1; i < rows.Count; i++) { var r0 = rows[i - 1]; var r1 = rows[i]; if (r0.IdSubsystem is not null && r0.IdSubsystem != r1.IdSubsystem) { var subsystemOperationTime = new SubsystemOperationTime() { IdTelemetry = idTelemetry, IdSubsystem = r0.IdSubsystem.Value, DateStart = r0.Date, DateEnd = r1.Date, DepthStart = depthInterpolation.GetDepth(r0.Date), DepthEnd = depthInterpolation.GetDepth(r1.Date), }; if (IsValid(subsystemOperationTime)) subsystemsOperationTimes.Add(subsystemOperationTime); } } return subsystemsOperationTimes; } private static bool IsValid(SubsystemOperationTime item) { var validateCode = GetValidateErrorCode(item); if (validateCode != 0) { var str = System.Text.Json.JsonSerializer.Serialize(item); Trace.TraceWarning($"Wrong({validateCode}) SubsystemOperationTime: {str}"); } return validateCode == 0; } private static int GetValidateErrorCode(SubsystemOperationTime item) { if (item.DateStart > item.DateEnd) return -1; if ((item.DateEnd - item.DateStart).TotalHours > 48) return -2; if (item.DepthEnd < item.DepthStart) return -3; if (item.DepthEnd - item.DepthStart > 2000d) return -4; if (item.DepthEnd < 0d) return -5; if (item.DepthStart < 0d) return -6; if (item.DepthEnd > 24_0000d) return -7; if (item.DepthStart > 24_0000d) return -8; return 0; } private static async Task GetInterpolation(IAsbCloudDbContext db, int idTelemetry, DateTimeOffset dateBegin, DateTimeOffset dateEnd, CancellationToken token) { var dataDepthFromSaub = await db.TelemetryDataSaub .Where(d => d.IdTelemetry == idTelemetry) .Where(d => d.DateTime >= dateBegin) .Where(d => d.DateTime <= dateEnd) .Where(d => d.WellDepth != null) .Where(d => d.WellDepth > 0) .GroupBy(d => Math.Ceiling(d.WellDepth ?? 0 * 10)) .Select(g => new { DateMin = g.Min(d => d.DateTime), DepthMin = g.Min(d => d.WellDepth) ?? 0, }) .OrderBy(i => i.DateMin) .ToArrayAsync(token); if (!dataDepthFromSaub.Any()) return null; var depthInterpolation = new DepthInterpolation(dataDepthFromSaub.Select(i => (i.DateMin, i.DepthMin))); return depthInterpolation; } } }