diff --git a/AsbCloudInfrastructure/Services/Subsystems/DepthInterpolation.cs b/AsbCloudInfrastructure/Services/Subsystems/DepthInterpolation.cs new file mode 100644 index 00000000..2a2fa8eb --- /dev/null +++ b/AsbCloudInfrastructure/Services/Subsystems/DepthInterpolation.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; + +namespace AsbCloudInfrastructure.Services.Subsystems.Utils +{ +#nullable enable + internal class DepthInterpolation + { + private readonly TimeSpan maxDeltaDate = TimeSpan.FromHours(12); + private readonly IEnumerator<(DateTimeOffset x, float y)> enumerator; + private (DateTimeOffset x, float y) p0; + private bool canMoveNext; + + public DepthInterpolation(IEnumerable<(DateTimeOffset x, float y)> collection) + { + enumerator = collection.GetEnumerator(); + canMoveNext = enumerator.MoveNext(); + p0 = enumerator.Current; + } + + ~DepthInterpolation() + { + enumerator.Dispose(); + } + + public float GetDepth(DateTimeOffset date) + { + // ошибка в телеметрии см. прим.: idTelemetry = 93 && date between '2021-11-16 17:18:40.000 +0500' and '2021-11-16 17:19:37.000 +0500' + while (canMoveNext && enumerator.Current.x < date) + { + p0 = enumerator.Current; + canMoveNext = enumerator.MoveNext(); + } + return CalcValue(date); + } + + private float CalcValue(DateTimeOffset date) + { + var p1 = enumerator.Current; + if (canMoveNext || p1 == default || p0.y == p1.y || p0.x > date) + return p0.y; + + if (p1.x < date) + return p1.y; + + if (p1.x - p0.x > maxDeltaDate && Math.Abs(p1.y - p0.y) > 0.2d) + { + if (date - p0.x < p1.x - date) + return p0.y; + else + return p1.y; + } + + var a = (p1.y - p0.y) / (p1.x - p0.x).TotalSeconds; + var b = p0.y; + var x = (date - p0.x).TotalSeconds; + var y = a * x + b; + return (float)y; + } + } +#nullable disable +} diff --git a/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeBackgroundService.cs b/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeBackgroundService.cs index da9e63a3..ddcdbca1 100644 --- a/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeBackgroundService.cs +++ b/AsbCloudInfrastructure/Services/Subsystems/SubsystemOperationTimeBackgroundService.cs @@ -1,12 +1,13 @@ using AsbCloudDb.Model; using AsbCloudDb.Model.Subsystems; +using AsbCloudInfrastructure.Services.Subsystems.Utils; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using Npgsql; using System; using System.Collections.Generic; using System.Data; +using System.Data.Common; using System.Diagnostics; using System.Linq; using System.Threading; @@ -23,6 +24,7 @@ namespace AsbCloudInfrastructure.Services.Subsystems private const int idSubsytemSpinMaster = 65536; private const int idSubsytemAkb = 1; private const int idSubsytemMse = 2; + public SubsystemOperationTimeBackgroundService(IConfiguration configuration) { connectionString = configuration.GetConnectionString("DefaultConnection"); @@ -106,6 +108,25 @@ namespace AsbCloudInfrastructure.Services.Subsystems } return affected; } + + 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 isSubsytemAkb(short? mode) @@ -124,47 +145,6 @@ namespace AsbCloudInfrastructure.Services.Subsystems if ((state & 1) > 0) return true; return false; - } - - static List GetSubsystemOperationTimes (List dataRows, Predicate satisfyCondition, int idSubsystem, int idTelemetry) - { - if (dataRows is null) - return new List(); - if (dataRows.Count < 2) - return new List(); - var listSubsystemOperationTime = new List(); - var foundSubsystem = satisfyCondition(dataRows[0]); - var dateStart = dataRows[0].Date; - var depthStart = dataRows[0].Depth; - - for (int i = 1; i 0" + $" order by date ) as tt " + - $"where (tt.mode_prev is null or tt.mode != tt.mode_prev) and tt.date >= @begin " + + $"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;"; - var idTelemetryParam = new NpgsqlParameter("@idTelemetry", idTelemetry); - var beginParam = new NpgsqlParameter("@begin", begin); + using var result = await ExecuteReaderAsync(db, query, token); - await db.Database.OpenConnectionAsync(token); - using var command = db.Database.GetDbConnection().CreateCommand(); - command.CommandText = query; - command.Parameters.Add(idTelemetryParam); - command.Parameters.Add(beginParam); + var subsystemsOperationTimes = new List(); + + (bool isEnable, DateTimeOffset date, float depth) akbPre = default; + (bool isEnable, DateTimeOffset date, float depth) msePre = default; - using var result = await command.ExecuteReaderAsync(token); - - var subsystemOperationTime = new List(); - var dataRowList = new List(); while (result.Read()) { - var dateRowItem = new DataRow() + var mode = result.GetFieldValue(1); + var state = result.GetFieldValue(3); + + var isAkbEnable = isSubsytemAkb(mode); + var isMseEnable = IsSubsystemMse(state); + var date = result.GetFieldValue(0); + var depth = result.GetFieldValue(2); + + if (!akbPre.isEnable && isAkbEnable) { - Date = result.GetFieldValue(0), - Mode = result.GetFieldValue(1), - Depth = result.GetFieldValue(2), - State = result.GetFieldValue(3) - }; - dataRowList.Add(dateRowItem); + akbPre = (true, date, depth); + } + else if (akbPre.isEnable && !isAkbEnable) + { + var subsystemOperationTime = new SubsystemOperationTime + { + IdTelemetry = idTelemetry, + IdSubsystem = idSubsytemAkb, + DateStart = akbPre.date, + DateEnd = date, + DepthStart = akbPre.depth, + DepthEnd = depth, + }; + if (IsValid(subsystemOperationTime)) + subsystemsOperationTimes.Add(subsystemOperationTime); + akbPre.isEnable = false; + } + + if (!msePre.isEnable && isMseEnable) + { + msePre = (true, date, depth); + } + else if (msePre.isEnable && !isMseEnable) + { + var subsystemOperationTime = new SubsystemOperationTime + { + IdTelemetry = idTelemetry, + IdSubsystem = idSubsytemMse, + DateStart = akbPre.date, + DateEnd = date, + DepthStart = akbPre.depth, + DepthEnd = depth, + }; + if (IsValid(subsystemOperationTime)) + subsystemsOperationTimes.Add(subsystemOperationTime); + + msePre.isEnable = false; + } } - var akbOperationTimes = GetSubsystemOperationTimes(dataRowList, d => isSubsytemAkb(d.Mode), idSubsytemAkb, idTelemetry); - var mseOperationTimes = GetSubsystemOperationTimes(dataRowList, d => IsSubsystemMse(d.State), idSubsytemMse, idTelemetry); - subsystemOperationTime.AddRange(akbOperationTimes); - subsystemOperationTime.AddRange(mseOperationTimes); - return subsystemOperationTime; + + return subsystemsOperationTimes; } - private static async Task?> OperationTimeSpinAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) + private static async Task> OperationTimeSpinAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token) { - static int? GetSubsytemId(short mode, int state) + static int? GetSubsytemId(short? mode, int? state) { if (state == 7 && (mode & 2) > 0) return idSubsytemTorqueMaster; @@ -226,88 +238,134 @@ namespace AsbCloudInfrastructure.Services.Subsystems return null; } - var query = + var querySpin = $"select " + $" tspin.date, " + $" tspin.mode, " + - $" tspin.state, " + - $" tsaub.well_depth " + + $" tspin.state " + $"from ( " + $" select " + $" date, " + $" mode, " + - $" lag(mode, 1) over (order by date) as mode_pre, " + + $" 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_pre " + + $" lag(state, 1) over (order by date) as state_lag " + $" 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 " + + $" 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 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 rows = new List<(int? IdSubsystem, DateTimeOffset Date)>(32); { - 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()) + using var resultSpin = await ExecuteReaderAsync(db, querySpin, token); + int? idSubsystemLast = null; + while (resultSpin.Read()) { - var dateEnd = result.GetFieldValue(0); - var depthEnd = result.GetFieldValue(3); - if (idSubsystem.HasValue) + var mode = resultSpin.GetFieldValue(1); + var state = resultSpin.GetFieldValue(2); + var idSubsystem = GetSubsytemId(mode, state); + if(idSubsystemLast != idSubsystem) { - 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; + 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 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; } } - - internal class DataRow - { - public DateTimeOffset Date { get; set; } - public short? Mode { get; set; } - public float? Depth { get; set; } - public short? State { get; set; } - } - #nullable disable }