forked from ddrilling/AsbCloudServer
Новые расчёты для автоматического определения операций
1. Поправел excel шаблон 2. Доработаны алгоритмы определения операций бурения 3. Небольшой рефакторинг DetectorAbstract, добавил метод для валидации 4. Закомментированы неиспользуемые детекторы. 5. Обновлена спецификация определения операций бурения 6. Добавлены тесты для определения операций бурения
This commit is contained in:
parent
afccdafebc
commit
8bbaca0d0c
Binary file not shown.
@ -14,7 +14,7 @@ namespace AsbCloudInfrastructure.Services.DetectOperations;
|
|||||||
|
|
||||||
public class DetectedOperationExportService
|
public class DetectedOperationExportService
|
||||||
{
|
{
|
||||||
private readonly DetectorAbstract[] detectors = { new DetectorDrilling(), new DetectorSlipsTime() };
|
private readonly DetectorAbstract[] detectors = { new DetectorDrilling() };
|
||||||
|
|
||||||
private readonly IDictionary<int, string> domains = new Dictionary<int, string>
|
private readonly IDictionary<int, string> domains = new Dictionary<int, string>
|
||||||
{
|
{
|
||||||
@ -122,10 +122,17 @@ public class DetectedOperationExportService
|
|||||||
row.Cell(columnDepthStart).Value = detectedOperations[i].DepthStart;
|
row.Cell(columnDepthStart).Value = detectedOperations[i].DepthStart;
|
||||||
row.Cell(columnDepthEnd).Value = detectedOperations[i].DepthEnd;
|
row.Cell(columnDepthEnd).Value = detectedOperations[i].DepthEnd;
|
||||||
row.Cell(columnDepth).Value = detectedOperations[i].DepthEnd - detectedOperations[i].DepthStart;
|
row.Cell(columnDepth).Value = detectedOperations[i].DepthEnd - detectedOperations[i].DepthStart;
|
||||||
row.Cell(columnIdReasonOfEnd).Value = detectedOperations[i].IdReasonOfEnd;
|
row.Cell(columnIdReasonOfEnd).Value = detectedOperations[i].IdReasonOfEnd switch
|
||||||
|
{
|
||||||
|
0 => "Не определена",
|
||||||
|
1 => "Не определено начало операции",
|
||||||
|
101 => "Разница глубин забоя и положением долота",
|
||||||
|
300 => "Низкое давление",
|
||||||
|
_ => detectedOperations[i].IdReasonOfEnd
|
||||||
|
};
|
||||||
|
|
||||||
var link =
|
var link =
|
||||||
$"{domains[idDomain]}/well/{well.Id}/telemetry/monitoring?end={Uri.EscapeDataString(dateStart.AddSeconds(3544).ToString("yyyy-MM-ddTHH:mm:ss.fff"))}&range=3600";
|
$"{domains[idDomain]}/well/{well.Id}/telemetry/monitoring?end={Uri.EscapeDataString(dateStart.AddSeconds(1800 * 0.9).ToString("yyyy-MM-ddTHH:mm:ss.fff"))}&range=1800";
|
||||||
|
|
||||||
row.Cell(columnDateStart).Value = dateStart;
|
row.Cell(columnDateStart).Value = dateStart;
|
||||||
row.Cell(columnDateStart).SetHyperlink(new XLHyperlink(link));
|
row.Cell(columnDateStart).SetHyperlink(new XLHyperlink(link));
|
||||||
@ -191,10 +198,8 @@ public class DetectedOperationExportService
|
|||||||
var isDetected = false;
|
var isDetected = false;
|
||||||
var positionBegin = 0;
|
var positionBegin = 0;
|
||||||
var positionEnd = data.Length - gap;
|
var positionEnd = data.Length - gap;
|
||||||
var step = 10;
|
|
||||||
while (positionEnd > positionBegin)
|
while (positionEnd > positionBegin)
|
||||||
{
|
{
|
||||||
step++;
|
|
||||||
foreach (var detector in detectors)
|
foreach (var detector in detectors)
|
||||||
{
|
{
|
||||||
if (!detector.TryDetect(idTelemetry, data, positionBegin, positionEnd, lastDetectedOperation, out var result))
|
if (!detector.TryDetect(idTelemetry, data, positionBegin, positionEnd, lastDetectedOperation, out var result))
|
||||||
@ -203,14 +208,11 @@ public class DetectedOperationExportService
|
|||||||
detectedOperations.Add(result!.Operation);
|
detectedOperations.Add(result!.Operation);
|
||||||
lastDetectedOperation = result.Operation;
|
lastDetectedOperation = result.Operation;
|
||||||
isDetected = true;
|
isDetected = true;
|
||||||
step = 1;
|
|
||||||
positionBegin = result.TelemetryEnd;
|
positionBegin = result.TelemetryEnd;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (step > 20)
|
positionBegin += 1;
|
||||||
step = 10;
|
|
||||||
positionBegin += step;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDetected)
|
if (isDetected)
|
||||||
|
@ -4,8 +4,7 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
|
namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
|
||||||
{
|
{
|
||||||
|
public abstract class DetectorAbstract
|
||||||
internal abstract class DetectorAbstract
|
|
||||||
{
|
{
|
||||||
private readonly int stepLength = 3;
|
private readonly int stepLength = 3;
|
||||||
|
|
||||||
@ -34,14 +33,15 @@ namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
|
|||||||
protected const int IdReasonOfEnd_Drilling = 600;
|
protected const int IdReasonOfEnd_Drilling = 600;
|
||||||
|
|
||||||
protected const int IdReasonOfEnd_Custom1 = 10_000;
|
protected const int IdReasonOfEnd_Custom1 = 10_000;
|
||||||
|
|
||||||
public abstract Func<DetectableTelemetry[], int, int, int> GetIdOperation { get; }
|
|
||||||
|
|
||||||
public bool TryDetect(int idTelemetry, DetectableTelemetry[] telemetry, int begin, int end, DetectedOperation? previousOperation, out OperationDetectorResult? result)
|
protected abstract Func<DetectableTelemetry[], int, int, int> GetIdOperation { get; }
|
||||||
|
|
||||||
|
public bool TryDetect(int idTelemetry, DetectableTelemetry[] telemetry, int begin, int end, DetectedOperation? previousOperation,
|
||||||
|
out OperationDetectorResult? result)
|
||||||
{
|
{
|
||||||
// Проверка соответствия критерию начала операции
|
// Проверка соответствия критерию начала операции
|
||||||
if (DetectBegin(telemetry, begin, previousOperation))
|
if (DetectBegin(telemetry, begin, previousOperation))
|
||||||
{
|
{
|
||||||
// Поиск окончания соответствия критерию
|
// Поиск окончания соответствия критерию
|
||||||
int idReasonOfEnd = 0;
|
int idReasonOfEnd = 0;
|
||||||
var positionEnd = begin;
|
var positionEnd = begin;
|
||||||
@ -52,30 +52,31 @@ namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
idReasonOfEnd = DetectEnd(telemetry, positionEnd, previousOperation);
|
idReasonOfEnd = DetectEnd(telemetry, positionEnd, previousOperation);
|
||||||
|
|
||||||
if(idReasonOfEnd is IdReasonOfEnd_DeltaDepthIsHi or IdReasonOfEnd_PressureIsLo &&
|
if (idReasonOfEnd != IdReasonOfEnd_NotDetected)
|
||||||
!IsValidByWellDepthDoesNotChange(telemetry, begin, positionEnd))
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (idReasonOfEnd != IdReasonOfEnd_NotDetected)
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = null;
|
|
||||||
result = MakeOperation(idTelemetry, telemetry, begin, positionEnd, idReasonOfEnd);
|
result = MakeOperation(idTelemetry, telemetry, begin, positionEnd, idReasonOfEnd);
|
||||||
return true;
|
return result is not null;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = null;
|
result = null;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract bool DetectBegin(DetectableTelemetry[] telemetry, int position, DetectedOperation? previousOperation);
|
protected virtual bool IsValidOperationDetectorResult(OperationDetectorResult operationDetectorResult) =>
|
||||||
protected virtual int DetectEnd(DetectableTelemetry[] telemetry, int position, DetectedOperation? previousOperation)
|
operationDetectorResult.Operation.IdReasonOfEnd != IdReasonOfEnd_NotDetected;
|
||||||
=> DetectBegin(telemetry, position, previousOperation)
|
|
||||||
? IdReasonOfEnd_NotDetected
|
|
||||||
: IdReasonOfEnd_NotDetectBegin;
|
|
||||||
|
|
||||||
private OperationDetectorResult MakeOperation(int idTelemetry, DetectableTelemetry[] telemetry, int begin, int end, int idReasonOfEnd)
|
protected abstract bool DetectBegin(DetectableTelemetry[] telemetry, int position, DetectedOperation? previousOperation);
|
||||||
|
|
||||||
|
protected virtual int DetectEnd(DetectableTelemetry[] telemetry, int position, DetectedOperation? previousOperation)
|
||||||
|
=> DetectBegin(telemetry, position, previousOperation)
|
||||||
|
? IdReasonOfEnd_NotDetected
|
||||||
|
: IdReasonOfEnd_NotDetectBegin;
|
||||||
|
|
||||||
|
private OperationDetectorResult? MakeOperation(int idTelemetry, DetectableTelemetry[] telemetry, int begin, int end,
|
||||||
|
int idReasonOfEnd)
|
||||||
{
|
{
|
||||||
var pBegin = telemetry[begin];
|
var pBegin = telemetry[begin];
|
||||||
var pEnd = telemetry[end];
|
var pEnd = telemetry[end];
|
||||||
@ -96,10 +97,9 @@ namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
|
|||||||
IdReasonOfEnd = idReasonOfEnd,
|
IdReasonOfEnd = idReasonOfEnd,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract bool IsValid(DetectableTelemetry[] telemetry, int begin, int end);
|
return !IsValidOperationDetectorResult(result) ? null : result;
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract double CalcValue(DetectableTelemetry[] telemetry, int begin, int end);
|
protected abstract double CalcValue(DetectableTelemetry[] telemetry, int begin, int end);
|
||||||
|
|
||||||
@ -118,16 +118,16 @@ namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
|
|||||||
int fragmentLength)
|
int fragmentLength)
|
||||||
{
|
{
|
||||||
var end = begin + fragmentLength;
|
var end = begin + fragmentLength;
|
||||||
end = end < telemetry.Length
|
end = end < telemetry.Length
|
||||||
? end
|
? end
|
||||||
: telemetry.Length;
|
: telemetry.Length;
|
||||||
var subData = telemetry[begin..end].Select(yGetter);
|
var subData = telemetry[begin..end].Select(yGetter);
|
||||||
if (end - begin > 10)
|
if (end - begin > 10)
|
||||||
{
|
{
|
||||||
var ratio = (end - begin) / 5;
|
var ratio = (end - begin) / 5;
|
||||||
subData = subData.Where((_,i) => i % ratio > 0);
|
subData = subData.Where((_, i) => i % ratio > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
var avg = subData.Average();
|
var avg = subData.Average();
|
||||||
return avg;
|
return avg;
|
||||||
}
|
}
|
||||||
@ -189,9 +189,9 @@ namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
|
|||||||
/// <param name="count"></param>
|
/// <param name="count"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected static (double min, double max, double sum, int count) CalcStat(
|
protected static (double min, double max, double sum, int count) CalcStat(
|
||||||
DetectableTelemetry[] telemetry,
|
DetectableTelemetry[] telemetry,
|
||||||
Func<DetectableTelemetry, double> getter,
|
Func<DetectableTelemetry, double> getter,
|
||||||
int begin,
|
int begin,
|
||||||
int count)
|
int count)
|
||||||
{
|
{
|
||||||
var sum = 0d;
|
var sum = 0d;
|
||||||
@ -210,6 +210,7 @@ namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
|
|||||||
max = itemValue;
|
max = itemValue;
|
||||||
sum += itemValue;
|
sum += itemValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (min, max, sum, end - begin);
|
return (min, max, sum, end - begin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,7 +257,7 @@ namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
|
|||||||
var end = begin + count;
|
var end = begin + count;
|
||||||
end = end < telemetry.Length ? end : telemetry.Length;
|
end = end < telemetry.Length ? end : telemetry.Length;
|
||||||
|
|
||||||
for (var i = begin; i < end; i ++)
|
for (var i = begin; i < end; i++)
|
||||||
{
|
{
|
||||||
var item = telemetry[i];
|
var item = telemetry[i];
|
||||||
var itemValue = getter(item);
|
var itemValue = getter(item);
|
||||||
@ -264,9 +265,10 @@ namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
|
|||||||
min = itemValue;
|
min = itemValue;
|
||||||
if (max < itemValue)
|
if (max < itemValue)
|
||||||
max = itemValue;
|
max = itemValue;
|
||||||
if(max - min > deviation)
|
if (max - min > deviation)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,8 +292,8 @@ namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
|
|||||||
var max = double.MinValue;
|
var max = double.MinValue;
|
||||||
var end = begin + count;
|
var end = begin + count;
|
||||||
end = end < telemetry.Length ? end : telemetry.Length;
|
end = end < telemetry.Length ? end : telemetry.Length;
|
||||||
var step = count > 15 ? count / 5 : count > 3 ? 3: 1;
|
var step = count > 15 ? count / 5 : count > 3 ? 3 : 1;
|
||||||
for (var i = begin; i < end; i+= step)
|
for (var i = begin; i < end; i += step)
|
||||||
{
|
{
|
||||||
var item = telemetry[i];
|
var item = telemetry[i];
|
||||||
var itemValue = getter(item);
|
var itemValue = getter(item);
|
||||||
@ -302,6 +304,7 @@ namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
|
|||||||
if (max - min > deviation)
|
if (max - min > deviation)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -323,6 +326,7 @@ namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
|
|||||||
if (Math.Abs(beginPointValue - itemValue) > deviation)
|
if (Math.Abs(beginPointValue - itemValue) > deviation)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,13 +345,12 @@ namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
|
|||||||
{
|
{
|
||||||
var item = telemetry[i];
|
var item = telemetry[i];
|
||||||
var itemValue = getter(item);
|
var itemValue = getter(item);
|
||||||
if ( itemValue - beginPointValue > deviation)
|
if (itemValue - beginPointValue > deviation)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,68 +1,66 @@
|
|||||||
using System;
|
// using System;
|
||||||
using AsbCloudDb.Model;
|
// using AsbCloudDb.Model;
|
||||||
|
//
|
||||||
namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
|
// namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
|
||||||
{
|
// {
|
||||||
|
//
|
||||||
/// <summary>
|
// /// <summary>
|
||||||
/// Проработка перед наращиванием
|
// /// Проработка перед наращиванием
|
||||||
/// </summary>
|
// /// </summary>
|
||||||
internal class DetectorDevelopment : DetectorAbstract
|
// internal class DetectorDevelopment : DetectorAbstract
|
||||||
{
|
// {
|
||||||
protected override double CalcValue(DetectableTelemetry[] telemetry, int begin, int end)
|
// protected override double CalcValue(DetectableTelemetry[] telemetry, int begin, int end)
|
||||||
=> CalcDeltaMinutes(telemetry, begin, end);
|
// => CalcDeltaMinutes(telemetry, begin, end);
|
||||||
|
//
|
||||||
public override Func<DetectableTelemetry[], int, int, int> GetIdOperation => (_, _, _)
|
// public override Func<DetectableTelemetry[], int, int, int> GetIdOperation => (_, _, _)
|
||||||
=> WellOperationCategory.IdDevelopment;
|
// => WellOperationCategory.IdDevelopment;
|
||||||
|
//
|
||||||
protected override bool DetectBegin(DetectableTelemetry[] telemetry, int position, DetectedOperation? previousOperation)
|
// protected override bool DetectBegin(DetectableTelemetry[] telemetry, int position, DetectedOperation? previousOperation)
|
||||||
{
|
// {
|
||||||
if (previousOperation?.IdCategory == WellOperationCategory.IdSlipsTime)
|
// if (previousOperation?.IdCategory == WellOperationCategory.IdSlipsTime)
|
||||||
return false;
|
// return false;
|
||||||
|
//
|
||||||
var point0 = telemetry[position];
|
// var point0 = telemetry[position];
|
||||||
var delta = point0.WellDepth - point0.BitDepth;
|
// var delta = point0.WellDepth - point0.BitDepth;
|
||||||
if (delta < 0.03d || delta > 30)
|
// if (delta < 0.03d || delta > 30)
|
||||||
return false;
|
// return false;
|
||||||
|
//
|
||||||
if (point0.Pressure < 15)
|
// if (point0.Pressure < 15)
|
||||||
return false;
|
// return false;
|
||||||
|
//
|
||||||
if (point0.BlockPosition > 2.5)
|
// if (point0.BlockPosition > 2.5)
|
||||||
return false;
|
// return false;
|
||||||
|
//
|
||||||
if (point0.RotorSpeed < 10)
|
// if (point0.RotorSpeed < 10)
|
||||||
return false;
|
// return false;
|
||||||
|
//
|
||||||
if (!ContainsDeviationApprox(telemetry, d => d.BlockPosition, position, 60, 0.03))
|
// if (!ContainsDeviationApprox(telemetry, d => d.BlockPosition, position, 60, 0.03))
|
||||||
return false;
|
// return false;
|
||||||
|
//
|
||||||
return true;
|
// return true;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
protected override int DetectEnd(DetectableTelemetry[] telemetry, int position, DetectedOperation? previousOperation)
|
// protected override int DetectEnd(DetectableTelemetry[] telemetry, int position, DetectedOperation? previousOperation)
|
||||||
{
|
// {
|
||||||
var point0 = telemetry[position];
|
// var point0 = telemetry[position];
|
||||||
var delta = point0.WellDepth - point0.BitDepth;
|
// var delta = point0.WellDepth - point0.BitDepth;
|
||||||
if (delta < 0.03d || delta > 30)
|
// if (delta < 0.03d || delta > 30)
|
||||||
return IdReasonOfEnd_DeltaDepthOutOfRange;
|
// return IdReasonOfEnd_DeltaDepthOutOfRange;
|
||||||
|
//
|
||||||
if (point0.Pressure < 15)
|
// if (point0.Pressure < 15)
|
||||||
return IdReasonOfEnd_PressureIsLo;
|
// return IdReasonOfEnd_PressureIsLo;
|
||||||
|
//
|
||||||
if (point0.BlockPosition > 31)
|
// if (point0.BlockPosition > 31)
|
||||||
return IdReasonOfEnd_BlockPositionIsHi;
|
// return IdReasonOfEnd_BlockPositionIsHi;
|
||||||
|
//
|
||||||
if (point0.RotorSpeed < 10)
|
// if (point0.RotorSpeed < 10)
|
||||||
return IdReasonOfEnd_RotorSpeedIsLo;
|
// return IdReasonOfEnd_RotorSpeedIsLo;
|
||||||
|
//
|
||||||
return IdReasonOfEnd_NotDetected;
|
// return IdReasonOfEnd_NotDetected;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
protected override bool IsValid(DetectableTelemetry[] telemetry, int begin, int end)
|
// protected override bool IsValid(DetectableTelemetry[] telemetry, int begin, int end)
|
||||||
=> IsValidByWellDepthDoesNotChange(telemetry, begin, end);
|
// => IsValidByWellDepthDoesNotChange(telemetry, begin, end);
|
||||||
}
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -4,9 +4,9 @@ using AsbCloudDb.Model;
|
|||||||
|
|
||||||
namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors;
|
namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors;
|
||||||
|
|
||||||
internal class DetectorDrilling : DetectorAbstract
|
public class DetectorDrilling : DetectorAbstract
|
||||||
{
|
{
|
||||||
public override Func<DetectableTelemetry[], int, int, int> GetIdOperation => DefineDrillingOperation;
|
protected override Func<DetectableTelemetry[], int, int, int> GetIdOperation => DefineDrillingOperation;
|
||||||
|
|
||||||
protected override bool DetectBegin(DetectableTelemetry[] telemetry, int position, DetectedOperation? previousOperation)
|
protected override bool DetectBegin(DetectableTelemetry[] telemetry, int position, DetectedOperation? previousOperation)
|
||||||
{
|
{
|
||||||
@ -18,9 +18,6 @@ internal class DetectorDrilling : DetectorAbstract
|
|||||||
if (point0.Pressure < 25)
|
if (point0.Pressure < 25)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (point0.RotorSpeed < 5)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,26 +34,38 @@ internal class DetectorDrilling : DetectorAbstract
|
|||||||
|
|
||||||
return IdReasonOfEnd_NotDetected;
|
return IdReasonOfEnd_NotDetected;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool IsValid(DetectableTelemetry[] telemetry, int begin, int end)
|
|
||||||
=> IsValidByWellDepthIncreasing(telemetry, begin, end);
|
|
||||||
|
|
||||||
protected override double CalcValue(DetectableTelemetry[] telemetry, int begin, int end)
|
protected override double CalcValue(DetectableTelemetry[] telemetry, int begin, int end)
|
||||||
=> CalcRop(telemetry, begin, end);
|
=> CalcRop(telemetry, begin, end);
|
||||||
|
|
||||||
|
protected override bool IsValidOperationDetectorResult(OperationDetectorResult operationDetectorResult) =>
|
||||||
|
operationDetectorResult.Operation.IdReasonOfEnd is IdReasonOfEnd_DeltaDepthIsHi or IdReasonOfEnd_PressureIsLo &&
|
||||||
|
Math.Abs(operationDetectorResult.Operation.DepthStart - operationDetectorResult.Operation.DepthEnd) > 0.01;
|
||||||
|
|
||||||
private static int DefineDrillingOperation(DetectableTelemetry[] telemetry, int begin, int end)
|
private static int DefineDrillingOperation(DetectableTelemetry[] telemetry, int begin, int end)
|
||||||
{
|
{
|
||||||
const int idSlideWithOscillation = 12000;
|
const int idSlideWithOscillation = 12000;
|
||||||
|
|
||||||
var telemetryRange = telemetry[begin.. end];
|
var telemetryRange = telemetry[begin.. end]
|
||||||
|
.OrderBy(x => x.DateTime).ToList();
|
||||||
|
|
||||||
|
for (var i = telemetryRange.Count - 1; i >= 0 && telemetryRange.Count > 1; i--)
|
||||||
|
{
|
||||||
|
if (Math.Abs(telemetryRange[i].WellDepth - telemetryRange[i - 1].WellDepth) < 0.001d)
|
||||||
|
{
|
||||||
|
telemetryRange.RemoveAt(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
var avgRotorSpeed = telemetryRange.Average(t => t.RotorSpeed);
|
var avgRotorSpeed = telemetryRange.Average(t => t.RotorSpeed);
|
||||||
|
|
||||||
if (avgRotorSpeed < 10)
|
if (avgRotorSpeed < 10)
|
||||||
return WellOperationCategory.IdSlide;
|
return WellOperationCategory.IdSlide;
|
||||||
|
|
||||||
var despersion = telemetryRange
|
var despersion = telemetryRange.Average(t => Math.Pow(t.RotorSpeed/avgRotorSpeed - 1, 2));
|
||||||
.Average(t => Math.Pow((t.RotorSpeed - avgRotorSpeed) / avgRotorSpeed, 2));
|
|
||||||
|
|
||||||
return despersion < 0.2d ? WellOperationCategory.IdRotor : idSlideWithOscillation;
|
return despersion < 0.2d ? WellOperationCategory.IdRotor : idSlideWithOscillation;
|
||||||
}
|
}
|
||||||
|
@ -1,39 +1,39 @@
|
|||||||
using System;
|
// using System;
|
||||||
using AsbCloudDb.Model;
|
// using AsbCloudDb.Model;
|
||||||
|
//
|
||||||
namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
|
// namespace AsbCloudInfrastructure.Services.DetectOperations.Detectors
|
||||||
{
|
// {
|
||||||
|
//
|
||||||
internal class DetectorSlipsTime : DetectorAbstract
|
// internal class DetectorSlipsTime : DetectorAbstract
|
||||||
{
|
// {
|
||||||
protected override double CalcValue(DetectableTelemetry[] telemetry, int begin, int end)
|
// protected override double CalcValue(DetectableTelemetry[] telemetry, int begin, int end)
|
||||||
=> CalcDeltaMinutes(telemetry, begin, end);
|
// => CalcDeltaMinutes(telemetry, begin, end);
|
||||||
|
//
|
||||||
public override Func<DetectableTelemetry[], int, int, int> GetIdOperation => (_, _, _) => WellOperationCategory.IdSlipsTime;
|
// public override Func<DetectableTelemetry[], int, int, int> GetIdOperation => (_, _, _) => WellOperationCategory.IdSlipsTime;
|
||||||
|
//
|
||||||
protected override bool DetectBegin(DetectableTelemetry[] telemetry, int position, DetectedOperation? previousOperation)
|
// protected override bool DetectBegin(DetectableTelemetry[] telemetry, int position, DetectedOperation? previousOperation)
|
||||||
{
|
// {
|
||||||
var point0 = telemetry[position];
|
// var point0 = telemetry[position];
|
||||||
var delta = point0.WellDepth - point0.BitDepth;
|
// var delta = point0.WellDepth - point0.BitDepth;
|
||||||
if (delta > 2.5d)
|
// if (delta > 2.5d)
|
||||||
return false;
|
// return false;
|
||||||
|
//
|
||||||
if (point0.Pressure > 15)
|
// if (point0.Pressure > 15)
|
||||||
return false;
|
// return false;
|
||||||
|
//
|
||||||
if (point0.BlockPosition > 8)
|
// if (point0.BlockPosition > 8)
|
||||||
return false;
|
// return false;
|
||||||
|
//
|
||||||
if (point0.HookWeight > 20)
|
// if (point0.HookWeight > 20)
|
||||||
return false;
|
// return false;
|
||||||
|
//
|
||||||
return true;
|
// return true;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
protected override bool IsValid(DetectableTelemetry[] telemetry, int begin, int end)
|
// protected override bool IsValid(DetectableTelemetry[] telemetry, int begin, int end)
|
||||||
=> IsValidByWellDepthDoesNotChange(telemetry, begin, end);
|
// => IsValidByWellDepthDoesNotChange(telemetry, begin, end);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
@ -1,29 +1,27 @@
|
|||||||
Метод определения бурения
|
# Алгоритм определения бурения в роторе, слайде, слайде с осцилляцией
|
||||||
|
|
||||||
Признак начала операции =
|
## Метод определения операции бурения
|
||||||
|
|
||||||
расстояние от долота до забоя < 0.03м И
|
Признак начала операции =
|
||||||
|
(расстояние от долота до забоя < 0.03м) И
|
||||||
|
(давление > 25атм)
|
||||||
|
|
||||||
|
Признак окончания операции =
|
||||||
|
(расстояние от долота до забоя > 0.03м) ИЛИ
|
||||||
|
(давление < 25атм)
|
||||||
|
|
||||||
давление > 25атм
|
## Валидация
|
||||||
|
Для точного определения операции бурения, необходимо убрать диапазон в которых сработал признак окончания операции и не менялась глубина:
|
||||||
|
Определили точку окончания операции исходя из Признак окончания операции.
|
||||||
|
Определяем временной интервал, когда не менялась глубина (т.е. время шло, а глубина была неизменна)
|
||||||
|
Определив начальную точку и точку окончания операции
|
||||||
|
Исключаем этот интервал из операции.
|
||||||
|
|
||||||
Признак окончания операции =
|
## Метод определения бурения в слайде
|
||||||
|
Необходимо рассчитать средние обороты ротора за всю операцию бурения.
|
||||||
|
Если среднее арифметическое больше константы (10 об/мин), то это бурение в роторе, если меньше, то это бурение в слайде.
|
||||||
|
|
||||||
расстояние от долота до забоя > 0.03м ИЛИ
|
## Метод определения бурения в роторе, слайде с осцилляцией
|
||||||
|
Необходимо рассчитать десперсию нормированных оборотов ротора по(по среднему значению)
|
||||||
давление < 25атм
|
1. Если полученное значение больше константы(0,2), то мы подтвердили что бурение в роторе.
|
||||||
|
2. Если полученное значение меньше константы, то это бурение в слайде с осцилляцией.
|
||||||
Находим границы
|
|
||||||
|
|
||||||
После того когда мы нашли границы, мы должны определить операцию, тогда мы смотрим на забой точки окончания операций сравниваем с забоем точками начала операций:
|
|
||||||
|
|
||||||
Если они равны друг другу, то мы эту операцию дальше не обрабатываем, а выбрасываем.
|
|
||||||
|
|
||||||
Если они не равны, то у нас произошло увеличение забоя, значит эта операция бурения.
|
|
||||||
|
|
||||||
Дальше мы определяем как мы бурили в роторе или слайде, для этого нам необходимо рассчитать среднюю скорость(среднее арифметическое) за всю операцию бурения . Если среднее арифметическое больше константы (10 об/мин), то это бурение в роторе, если меньше, то это бурение в слайде.
|
|
||||||
|
|
||||||
Если бурение в роторе, то мы считаем только дисперсию нормированных оборотов ротора(по среднему значению). (Так как это может быть бурение в слайде с осцилляцией и выглядеть как бурение в роторе):
|
|
||||||
|
|
||||||
Если полученное значение меньше константы(0,2), то мы подтвердили что бурение в роторе.
|
|
||||||
|
|
||||||
Если полученное значение больше константы, то это бурение в слайде с осцилляцией.
|
|
@ -16,11 +16,11 @@ public class WorkOperationDetection: Work
|
|||||||
{
|
{
|
||||||
private static readonly DetectorAbstract[] detectors = new DetectorAbstract[]
|
private static readonly DetectorAbstract[] detectors = new DetectorAbstract[]
|
||||||
{
|
{
|
||||||
|
new DetectorDrilling()
|
||||||
// new DetectorRotor(),
|
// new DetectorRotor(),
|
||||||
// new DetectorSlide(),
|
// new DetectorSlide(),
|
||||||
//new DetectorDevelopment(),
|
//new DetectorDevelopment(),
|
||||||
//new DetectorTemplating(),
|
//new DetectorTemplating(),
|
||||||
new DetectorSlipsTime(),
|
|
||||||
//new DetectorStaticSurveying(),
|
//new DetectorStaticSurveying(),
|
||||||
//new DetectorFlashingBeforeConnection(),
|
//new DetectorFlashingBeforeConnection(),
|
||||||
//new DetectorFlashing(),
|
//new DetectorFlashing(),
|
||||||
@ -116,7 +116,6 @@ public class WorkOperationDetection: Work
|
|||||||
{
|
{
|
||||||
var data = await query
|
var data = await query
|
||||||
.Where(d => d.DateTime > startDate)
|
.Where(d => d.DateTime > startDate)
|
||||||
.Take(take)
|
|
||||||
.ToArrayAsync(token);
|
.ToArrayAsync(token);
|
||||||
|
|
||||||
if (data.Length < gap)
|
if (data.Length < gap)
|
||||||
@ -125,25 +124,21 @@ public class WorkOperationDetection: Work
|
|||||||
var isDetected = false;
|
var isDetected = false;
|
||||||
var positionBegin = 0;
|
var positionBegin = 0;
|
||||||
var positionEnd = data.Length - gap;
|
var positionEnd = data.Length - gap;
|
||||||
var step = 10;
|
|
||||||
while (positionEnd > positionBegin)
|
while (positionEnd > positionBegin)
|
||||||
{
|
{
|
||||||
step ++;
|
foreach (var detector in detectors)
|
||||||
for (int i = 0; i < detectors.Length; i++)
|
|
||||||
{
|
{
|
||||||
if (detectors[i].TryDetect(idTelemetry, data, positionBegin, positionEnd, lastDetectedOperation, out OperationDetectorResult? result))
|
if (!detector.TryDetect(idTelemetry, data, positionBegin, positionEnd, lastDetectedOperation, out var result))
|
||||||
{
|
continue;
|
||||||
detectedOperations.Add(result!.Operation);
|
|
||||||
lastDetectedOperation = result.Operation;
|
detectedOperations.Add(result!.Operation);
|
||||||
isDetected = true;
|
lastDetectedOperation = result.Operation;
|
||||||
step = 1;
|
isDetected = true;
|
||||||
positionBegin = result.TelemetryEnd;
|
positionBegin = result.TelemetryEnd;
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (step > 20)
|
|
||||||
step = 10;
|
positionBegin += 1;
|
||||||
positionBegin += step;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDetected)
|
if (isDetected)
|
||||||
|
@ -0,0 +1,290 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using AsbCloudDb.Model;
|
||||||
|
using AsbCloudInfrastructure.Services.DetectOperations;
|
||||||
|
using AsbCloudInfrastructure.Services.DetectOperations.Detectors;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace AsbCloudWebApi.Tests.Services.DetectedOperations.Detectors;
|
||||||
|
|
||||||
|
public class DetectorDrillingTests : DetectorDrilling
|
||||||
|
{
|
||||||
|
private const int idSlide = 5002;
|
||||||
|
private const int idRotor = 5003;
|
||||||
|
private const int idSlideWithOscillation = 12000;
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(TelemetryRangeDrillingRotor))]
|
||||||
|
public void DefineDrillingOperation_ShouldReturn_DrillingRotor(DetectableTelemetry[] telemetryRange)
|
||||||
|
{
|
||||||
|
//act
|
||||||
|
var result = GetIdOperation.Invoke(telemetryRange, 0, telemetryRange.Length);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.Equal(idRotor, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(TelemetryRangeDrillingSlide))]
|
||||||
|
public void DefineDrillingOperation_ShouldReturn_DrillingSlide(DetectableTelemetry[] telemetryRange)
|
||||||
|
{
|
||||||
|
//act
|
||||||
|
var result = GetIdOperation.Invoke(telemetryRange, 0, telemetryRange.Length);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.Equal(idSlide, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(TelemetryRangeDrillingSlideWithOscillation))]
|
||||||
|
public void DefineDrillingOperation_ShouldReturn_DrillingSlideWithOscillation(DetectableTelemetry[] telemetryRange)
|
||||||
|
{
|
||||||
|
//act
|
||||||
|
var result = GetIdOperation.Invoke(telemetryRange, 0, telemetryRange.Length);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.Equal(idSlideWithOscillation, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsValidOperationDetectorResult_ShouldReturn_True()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
var operationDetectorResult = new OperationDetectorResult
|
||||||
|
{
|
||||||
|
Operation = new DetectedOperation
|
||||||
|
{
|
||||||
|
IdReasonOfEnd = IdReasonOfEnd_PressureIsLo,
|
||||||
|
DepthStart = 5000,
|
||||||
|
DepthEnd = 6000
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//act
|
||||||
|
var result = IsValidOperationDetectorResult(operationDetectorResult);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.True(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsValidOperationDetectorResult_ShouldReturn_False()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
var operationDetectorResult = new OperationDetectorResult
|
||||||
|
{
|
||||||
|
Operation = new DetectedOperation
|
||||||
|
{
|
||||||
|
IdReasonOfEnd = IdReasonOfEnd_PressureIsLo,
|
||||||
|
DepthStart = 5000,
|
||||||
|
DepthEnd = 5000
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//act
|
||||||
|
var result = IsValidOperationDetectorResult(operationDetectorResult);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> TelemetryRangeDrillingRotor()
|
||||||
|
{
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new DetectableTelemetry
|
||||||
|
{
|
||||||
|
WellDepth = 4.187f,
|
||||||
|
Pressure = 27.815952f,
|
||||||
|
HookWeight = 34.221367f,
|
||||||
|
BlockPosition = 24.388f,
|
||||||
|
BitDepth = 4.187f,
|
||||||
|
RotorSpeed = 40.3f
|
||||||
|
},
|
||||||
|
new DetectableTelemetry
|
||||||
|
{
|
||||||
|
WellDepth = 4.232f,
|
||||||
|
Pressure = 28.080372f,
|
||||||
|
HookWeight = 34.162174f,
|
||||||
|
BlockPosition = 24.343f,
|
||||||
|
BitDepth = 4.232f,
|
||||||
|
RotorSpeed = 40.3f
|
||||||
|
},
|
||||||
|
new DetectableTelemetry
|
||||||
|
{
|
||||||
|
WellDepth = 4.277f,
|
||||||
|
Pressure = 29.047901f,
|
||||||
|
HookWeight = 33.688717f,
|
||||||
|
BlockPosition = 24.298f,
|
||||||
|
BitDepth = 24.298f,
|
||||||
|
RotorSpeed = 40.3f
|
||||||
|
},
|
||||||
|
new DetectableTelemetry
|
||||||
|
{
|
||||||
|
WellDepth = 4.309f,
|
||||||
|
Pressure = 29.574032f,
|
||||||
|
HookWeight = 33.692104f,
|
||||||
|
BlockPosition = 24.266f,
|
||||||
|
BitDepth = 4.309f,
|
||||||
|
RotorSpeed = 40.4f
|
||||||
|
},
|
||||||
|
new DetectableTelemetry
|
||||||
|
{
|
||||||
|
WellDepth = 4.324f,
|
||||||
|
Pressure = 24.007977f,
|
||||||
|
HookWeight = 34.838448f,
|
||||||
|
BlockPosition = 24.251f,
|
||||||
|
BitDepth = 4.324f,
|
||||||
|
RotorSpeed = 40.5f
|
||||||
|
},
|
||||||
|
new DetectableTelemetry
|
||||||
|
{
|
||||||
|
WellDepth = 4.324f,
|
||||||
|
Pressure = 24.04114f,
|
||||||
|
HookWeight = 34.423424f,
|
||||||
|
BlockPosition = 24.252f,
|
||||||
|
BitDepth = 4.323f,
|
||||||
|
RotorSpeed = 40.3f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> TelemetryRangeDrillingSlide()
|
||||||
|
{
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new DetectableTelemetry
|
||||||
|
{
|
||||||
|
WellDepth = 447.276001f,
|
||||||
|
Pressure = 26.619421f,
|
||||||
|
HookWeight = 40.9143829f,
|
||||||
|
BlockPosition = 4.559f,
|
||||||
|
BitDepth = 477.265991f,
|
||||||
|
RotorSpeed = 0
|
||||||
|
},
|
||||||
|
new DetectableTelemetry
|
||||||
|
{
|
||||||
|
WellDepth = 477.289f,
|
||||||
|
Pressure = 28.716f,
|
||||||
|
HookWeight = 38.27f,
|
||||||
|
BlockPosition = 4.5f,
|
||||||
|
BitDepth = 477.289f,
|
||||||
|
RotorSpeed = 0.1f
|
||||||
|
},
|
||||||
|
new DetectableTelemetry
|
||||||
|
{
|
||||||
|
WellDepth = 477.30899f,
|
||||||
|
Pressure = 33.953495f,
|
||||||
|
HookWeight = 38.27f,
|
||||||
|
BlockPosition = 4.5359997f,
|
||||||
|
BitDepth = 477.289001f,
|
||||||
|
RotorSpeed = 0.1f
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> TelemetryRangeDrillingSlideWithOscillation()
|
||||||
|
{
|
||||||
|
yield return new object[]
|
||||||
|
{
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new DetectableTelemetry
|
||||||
|
{
|
||||||
|
IdUser = 1,
|
||||||
|
WellDepth = 415.306f,
|
||||||
|
Pressure = 53.731934f,
|
||||||
|
HookWeight = 41.049942f,
|
||||||
|
BlockPosition = 28.666f,
|
||||||
|
BitDepth = 415.293f,
|
||||||
|
RotorSpeed = 0.3f
|
||||||
|
},
|
||||||
|
new DetectableTelemetry
|
||||||
|
{
|
||||||
|
IdUser = 1,
|
||||||
|
WellDepth = 415.311f,
|
||||||
|
Pressure = 57.660595f,
|
||||||
|
HookWeight = 40.898712f,
|
||||||
|
BlockPosition = 28.648f,
|
||||||
|
BitDepth = 415.311f,
|
||||||
|
RotorSpeed = 0.2f
|
||||||
|
},
|
||||||
|
new DetectableTelemetry
|
||||||
|
{
|
||||||
|
IdUser = 1,
|
||||||
|
WellDepth = 415.326f,
|
||||||
|
Pressure = 59.211086f,
|
||||||
|
HookWeight = 40.882797f,
|
||||||
|
BlockPosition = 28.633f,
|
||||||
|
BitDepth = 415.326f,
|
||||||
|
RotorSpeed = 0.1f
|
||||||
|
},
|
||||||
|
new DetectableTelemetry
|
||||||
|
{
|
||||||
|
IdUser = 1,
|
||||||
|
WellDepth = 415.344f,
|
||||||
|
Pressure = 59.484406f,
|
||||||
|
HookWeight = 40.91972f,
|
||||||
|
BlockPosition = 28.615f,
|
||||||
|
BitDepth = 415.344f,
|
||||||
|
RotorSpeed = 0.2f
|
||||||
|
},
|
||||||
|
new DetectableTelemetry
|
||||||
|
{
|
||||||
|
IdUser = 1,
|
||||||
|
WellDepth = 415.364f,
|
||||||
|
Pressure = 60.739918f,
|
||||||
|
HookWeight = 40.795666f,
|
||||||
|
BlockPosition = 28.595f,
|
||||||
|
BitDepth = 415.364f,
|
||||||
|
RotorSpeed = 4.5f
|
||||||
|
},
|
||||||
|
new DetectableTelemetry
|
||||||
|
{
|
||||||
|
IdUser = 1,
|
||||||
|
WellDepth = 415.378f,
|
||||||
|
Pressure = 62.528984f,
|
||||||
|
HookWeight = 40.52114f,
|
||||||
|
BlockPosition = 28.581f,
|
||||||
|
BitDepth = 415.378f,
|
||||||
|
RotorSpeed = 22.6f
|
||||||
|
},
|
||||||
|
new DetectableTelemetry
|
||||||
|
{
|
||||||
|
IdUser = 1,
|
||||||
|
WellDepth = 415.392f,
|
||||||
|
Pressure = 67.0039f,
|
||||||
|
HookWeight = 38.878895f,
|
||||||
|
BlockPosition = 28.569f,
|
||||||
|
BitDepth = 415.39f,
|
||||||
|
RotorSpeed = 50f
|
||||||
|
},
|
||||||
|
new DetectableTelemetry
|
||||||
|
{
|
||||||
|
IdUser = 1,
|
||||||
|
WellDepth = 415.392f,
|
||||||
|
Pressure = 65.72418f,
|
||||||
|
HookWeight = 42.53173f,
|
||||||
|
BlockPosition = 28.622f,
|
||||||
|
BitDepth = 415.337f,
|
||||||
|
RotorSpeed = 93f
|
||||||
|
},
|
||||||
|
new DetectableTelemetry
|
||||||
|
{
|
||||||
|
IdUser = 1,
|
||||||
|
WellDepth = 415.392f,
|
||||||
|
Pressure = 56.82195f,
|
||||||
|
HookWeight = 43.15844f,
|
||||||
|
BlockPosition = 28.704f,
|
||||||
|
BitDepth = 415.255f,
|
||||||
|
RotorSpeed = 71.5f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user