2021-07-21 15:29:19 +05:00
|
|
|
|
using AsbCloudApp.Data;
|
2021-06-17 15:12:39 +05:00
|
|
|
|
using AsbCloudApp.Services;
|
|
|
|
|
using AsbCloudDb.Model;
|
2021-07-21 15:29:19 +05:00
|
|
|
|
using AsbCloudInfrastructure.Services.Cache;
|
|
|
|
|
using System;
|
2021-06-17 15:12:39 +05:00
|
|
|
|
using System.Collections.Generic;
|
2021-07-21 15:29:19 +05:00
|
|
|
|
using System.Linq;
|
2021-07-21 16:49:24 +05:00
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
2021-06-17 15:12:39 +05:00
|
|
|
|
|
|
|
|
|
namespace AsbCloudInfrastructure.Services
|
|
|
|
|
{
|
|
|
|
|
public class AnalyticsService : IAnalyticsService
|
|
|
|
|
{
|
|
|
|
|
private readonly IAsbCloudDbContext db;
|
|
|
|
|
private readonly ITelemetryService telemetryService;
|
2021-07-20 11:24:57 +05:00
|
|
|
|
private readonly ISaubDataCache saubDataCache;
|
2021-06-24 13:02:31 +05:00
|
|
|
|
private readonly CacheTable<Operation> cacheOperations;
|
2021-06-25 15:10:05 +05:00
|
|
|
|
private readonly OperationDetectorService operationDetectorService;
|
2021-06-24 13:02:31 +05:00
|
|
|
|
private readonly IEnumerable<Operation> operations;
|
2021-06-17 15:12:39 +05:00
|
|
|
|
|
2021-07-21 15:29:19 +05:00
|
|
|
|
public AnalyticsService(IAsbCloudDbContext db, ITelemetryService telemetryService,
|
2021-07-20 11:24:57 +05:00
|
|
|
|
ISaubDataCache saubDataCache, CacheDb cacheDb)
|
2021-06-17 15:12:39 +05:00
|
|
|
|
{
|
|
|
|
|
this.db = db;
|
|
|
|
|
this.telemetryService = telemetryService;
|
2021-07-20 11:24:57 +05:00
|
|
|
|
this.saubDataCache = saubDataCache;
|
2021-06-24 13:02:31 +05:00
|
|
|
|
cacheOperations = cacheDb.GetCachedTable<Operation>((AsbCloudDbContext)db);
|
|
|
|
|
operations = cacheOperations.Select(c => true);
|
2021-06-25 15:10:05 +05:00
|
|
|
|
operationDetectorService = new OperationDetectorService(operations);
|
2021-06-17 15:12:39 +05:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-22 09:49:53 +05:00
|
|
|
|
public IEnumerable<WellDepthToDayDto> GetWellDepthToDay(int wellId)
|
2021-06-17 15:12:39 +05:00
|
|
|
|
{
|
|
|
|
|
var telemetry = telemetryService.GetTelemetryByWellId(wellId);
|
|
|
|
|
|
|
|
|
|
if (telemetry is null)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
var depthToTimeData = (from d in db.DataSaubBases
|
|
|
|
|
where d.IdTelemetry == telemetry.Id
|
2021-06-22 09:49:53 +05:00
|
|
|
|
select new
|
2021-06-17 15:12:39 +05:00
|
|
|
|
{
|
2021-06-22 09:49:53 +05:00
|
|
|
|
d.Id,
|
|
|
|
|
d.WellDepth,
|
|
|
|
|
d.BitDepth,
|
|
|
|
|
d.Date
|
|
|
|
|
});
|
2021-06-17 15:12:39 +05:00
|
|
|
|
|
2021-06-22 09:49:53 +05:00
|
|
|
|
var m = (int)Math.Round(1d * depthToTimeData.Count() / 2048);
|
2021-06-17 15:12:39 +05:00
|
|
|
|
|
|
|
|
|
if (m > 1)
|
2021-06-22 09:49:53 +05:00
|
|
|
|
depthToTimeData = depthToTimeData.Where(d => d.Id % m == 0);
|
2021-06-17 15:12:39 +05:00
|
|
|
|
|
2021-06-22 09:49:53 +05:00
|
|
|
|
return depthToTimeData.Select(d => new WellDepthToDayDto
|
|
|
|
|
{
|
2021-06-25 15:10:05 +05:00
|
|
|
|
WellDepth = d.WellDepth ?? 0.0,
|
|
|
|
|
BitDepth = d.BitDepth ?? 0.0,
|
2021-06-22 09:49:53 +05:00
|
|
|
|
Date = d.Date
|
|
|
|
|
}).ToList();
|
2021-06-17 15:12:39 +05:00
|
|
|
|
}
|
|
|
|
|
|
2021-06-22 09:49:53 +05:00
|
|
|
|
public IEnumerable<WellDepthToIntervalDto> GetWellDepthToInterval(int wellId,
|
2021-07-20 11:50:35 +05:00
|
|
|
|
int intervalSeconds, int workBeginSeconds)
|
2021-06-17 15:12:39 +05:00
|
|
|
|
{
|
2021-07-20 11:50:35 +05:00
|
|
|
|
intervalSeconds = intervalSeconds == 0 ? 86400 : intervalSeconds;
|
2021-06-25 15:10:05 +05:00
|
|
|
|
|
2021-06-17 15:12:39 +05:00
|
|
|
|
var telemetry = telemetryService.GetTelemetryByWellId(wellId);
|
|
|
|
|
|
|
|
|
|
if (telemetry is null)
|
|
|
|
|
return null;
|
|
|
|
|
|
2021-06-25 15:10:05 +05:00
|
|
|
|
var timezoneOffset = telemetryService.GetTimezoneOffsetByTelemetryId(telemetry.Id);
|
2021-06-17 15:12:39 +05:00
|
|
|
|
|
2021-07-20 11:50:35 +05:00
|
|
|
|
var drillingPeriodsInfo = db.GetDepthToInterval(telemetry.Id, intervalSeconds,
|
|
|
|
|
workBeginSeconds, timezoneOffset);
|
2021-06-22 09:49:53 +05:00
|
|
|
|
|
|
|
|
|
var wellDepthToIntervalData = drillingPeriodsInfo.Select(d => new WellDepthToIntervalDto
|
|
|
|
|
{
|
2021-06-25 15:10:05 +05:00
|
|
|
|
IntervalStartDate = d.BeginPeriodDate,
|
2021-07-20 11:50:35 +05:00
|
|
|
|
IntervalDepthProgress = (d.MaxDepth - d.MinDepth) ?? 0.0 / intervalSeconds
|
2021-06-22 09:49:53 +05:00
|
|
|
|
}).OrderBy(d => d.IntervalStartDate).ToList();
|
2021-06-17 15:12:39 +05:00
|
|
|
|
|
2021-06-22 09:49:53 +05:00
|
|
|
|
return wellDepthToIntervalData;
|
|
|
|
|
}
|
2021-06-17 15:12:39 +05:00
|
|
|
|
|
2021-07-21 16:49:24 +05:00
|
|
|
|
public PaginationContainer<OperationDto> GetOperationsByWell(int wellId,
|
|
|
|
|
IEnumerable<int> categoryIds = default, DateTime begin = default,
|
|
|
|
|
DateTime end = default, int skip = 0, int take = 32)
|
|
|
|
|
{
|
|
|
|
|
var telemetry = telemetryService.GetTelemetryByWellId(wellId);
|
|
|
|
|
|
|
|
|
|
if (telemetry is null)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
var operations = from a in db.TelemetryAnalysis.Include(t => t.Operation)
|
|
|
|
|
where a.IdTelemetry == telemetry.Id
|
|
|
|
|
select a;
|
|
|
|
|
|
|
|
|
|
if ((categoryIds != default) && (categoryIds.Any()))
|
|
|
|
|
operations = operations.Where(o => categoryIds.Contains(o.IdOperation));
|
|
|
|
|
|
|
|
|
|
var result = new PaginationContainer<OperationDto>
|
|
|
|
|
{
|
|
|
|
|
Skip = skip,
|
|
|
|
|
Take = take
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
operations = operations.OrderBy(o => o.UnixDate);
|
|
|
|
|
|
|
|
|
|
if (begin != default)
|
|
|
|
|
{
|
|
|
|
|
var unixBegin = (begin - new DateTime(1970, 1, 1)).TotalSeconds;
|
|
|
|
|
operations = operations.Where(o => o.UnixDate >= unixBegin);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (end != default)
|
|
|
|
|
{
|
|
|
|
|
var unixEnd = (end - new DateTime(1970, 1, 1)).TotalSeconds;
|
|
|
|
|
operations = operations.Where(m => (m.UnixDate + m.Duration) <= unixEnd);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result.Count = operations.Count();
|
|
|
|
|
|
|
|
|
|
if (skip > 0)
|
|
|
|
|
operations = operations.Skip(skip);
|
|
|
|
|
|
|
|
|
|
var operationsList = operations.Take(take).ToList();
|
|
|
|
|
|
|
|
|
|
if (operationsList.Count == 0)
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
|
|
foreach(var operation in operations)
|
|
|
|
|
{
|
|
|
|
|
var operationDto = new OperationDto
|
|
|
|
|
{
|
|
|
|
|
Id = operation.Id,
|
|
|
|
|
Name = operation.Operation.Name,
|
|
|
|
|
BeginDate = DateTimeOffset.FromUnixTimeSeconds(operation.UnixDate).DateTime,
|
|
|
|
|
EndDate = DateTimeOffset.FromUnixTimeSeconds(operation.UnixDate + operation.Duration).DateTime,
|
|
|
|
|
StartWellDepth = operation.OperationStartDepth ?? 0.0,
|
|
|
|
|
EndWellDepth = operation.OperationEndDepth ?? 0.0
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
result.Items.Add(operationDto);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2021-07-19 15:31:50 +05:00
|
|
|
|
public IEnumerable<OperationDurationDto> GetOperationsSummary(int wellId,
|
2021-06-22 09:49:53 +05:00
|
|
|
|
DateTime begin = default, DateTime end = default)
|
|
|
|
|
{
|
2021-06-30 10:16:06 +05:00
|
|
|
|
var telemetry = telemetryService.GetTelemetryByWellId(wellId);
|
|
|
|
|
|
|
|
|
|
if (telemetry is null)
|
|
|
|
|
return null;
|
|
|
|
|
|
2021-07-19 15:31:50 +05:00
|
|
|
|
var unixBegin = (begin - new DateTime(1970, 1, 1)).TotalSeconds;
|
|
|
|
|
var unixEnd = (end - new DateTime(1970, 1, 1)).TotalSeconds;
|
|
|
|
|
|
|
|
|
|
var operations = (from a in db.TelemetryAnalysis
|
2021-07-21 16:49:24 +05:00
|
|
|
|
where a.IdTelemetry == telemetry.Id &&
|
|
|
|
|
a.UnixDate > unixBegin && a.UnixDate < unixEnd
|
2021-06-30 10:16:06 +05:00
|
|
|
|
join o in db.Operations on a.IdOperation equals o.Id
|
|
|
|
|
group a by new { a.IdOperation, o.Name } into g
|
|
|
|
|
select new OperationDurationDto
|
|
|
|
|
{
|
2021-07-21 16:49:24 +05:00
|
|
|
|
OperationName = g.Key.Name,
|
2021-06-30 10:16:06 +05:00
|
|
|
|
Duration = g.Where(g => g.Duration > 0)
|
|
|
|
|
.Sum(a => a.Duration)
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
return operations;
|
2021-06-22 09:49:53 +05:00
|
|
|
|
}
|
2021-06-17 15:12:39 +05:00
|
|
|
|
|
2021-06-30 10:16:06 +05:00
|
|
|
|
public IEnumerable<OperationInfoDto> GetOperationsToInterval(int wellId,
|
2021-07-20 11:50:35 +05:00
|
|
|
|
int intervalSeconds, int workBeginSeconds)
|
2021-06-22 09:49:53 +05:00
|
|
|
|
{
|
2021-07-20 11:50:35 +05:00
|
|
|
|
intervalSeconds = intervalSeconds == 0 ? 86400 : intervalSeconds;
|
2021-06-30 10:16:06 +05:00
|
|
|
|
|
|
|
|
|
var telemetry = telemetryService.GetTelemetryByWellId(wellId);
|
|
|
|
|
|
|
|
|
|
if (telemetry is null)
|
|
|
|
|
return null;
|
|
|
|
|
|
|
|
|
|
var timezoneOffset = telemetryService.GetTimezoneOffsetByTelemetryId(telemetry.Id);
|
|
|
|
|
|
2021-07-19 15:31:50 +05:00
|
|
|
|
var operations = (from a in db.TelemetryAnalysis
|
2021-07-21 16:49:24 +05:00
|
|
|
|
where a.IdTelemetry == telemetry.Id
|
2021-06-30 10:16:06 +05:00
|
|
|
|
join o in db.Operations on a.IdOperation equals o.Id
|
2021-07-21 15:29:19 +05:00
|
|
|
|
group a by new
|
|
|
|
|
{
|
2021-07-20 11:50:35 +05:00
|
|
|
|
Interval = Math.Floor((a.UnixDate - workBeginSeconds + timezoneOffset) / intervalSeconds),
|
2021-07-21 15:29:19 +05:00
|
|
|
|
OperationId = a.IdOperation,
|
|
|
|
|
o.Name
|
|
|
|
|
} into g
|
2021-06-30 10:16:06 +05:00
|
|
|
|
select new
|
|
|
|
|
{
|
2021-07-19 15:31:50 +05:00
|
|
|
|
IntervalStart = g.Min(d => d.UnixDate),
|
2021-06-30 10:16:06 +05:00
|
|
|
|
OperationName = g.Key.Name,
|
|
|
|
|
OperationsDuration = g.Sum(an => an.Duration)
|
|
|
|
|
}).ToList();
|
|
|
|
|
|
|
|
|
|
var operationsGroupedByInterval = operations.GroupBy(op => op.IntervalStart)
|
2021-07-21 15:29:19 +05:00
|
|
|
|
.Select(o => new OperationInfoDto
|
|
|
|
|
{
|
|
|
|
|
IntervalBegin = DateTimeOffset.FromUnixTimeSeconds(o.Key),
|
|
|
|
|
Operations = o.Select(opr => new OperationDetailsDto
|
|
|
|
|
{
|
|
|
|
|
OperationName = opr.OperationName,
|
|
|
|
|
Duration = opr.OperationsDuration
|
2021-06-30 10:16:06 +05:00
|
|
|
|
}).ToList()
|
|
|
|
|
})
|
2021-07-21 15:29:19 +05:00
|
|
|
|
.OrderBy(ops => ops.IntervalBegin);
|
2021-06-30 10:16:06 +05:00
|
|
|
|
|
|
|
|
|
return operationsGroupedByInterval;
|
2021-06-17 15:12:39 +05:00
|
|
|
|
}
|
2021-06-24 13:02:31 +05:00
|
|
|
|
|
2021-07-20 11:24:57 +05:00
|
|
|
|
public void SaveAnalytics(DataSaubBase dataSaub)
|
|
|
|
|
{
|
|
|
|
|
saubDataCache.AddData(dataSaub);
|
|
|
|
|
|
|
|
|
|
if (saubDataCache.GetOrCreateCache(dataSaub.IdTelemetry).Count() > 1)
|
|
|
|
|
{
|
|
|
|
|
var dataSaubs = saubDataCache.GetOrCreateCache(dataSaub.IdTelemetry)
|
|
|
|
|
.OrderBy(d => d.Date);
|
|
|
|
|
|
|
|
|
|
var telemetryAnalysis = GetDrillingAnalysis(dataSaubs);
|
|
|
|
|
|
|
|
|
|
if (saubDataCache.CurrentAnalysis is null)
|
|
|
|
|
saubDataCache.CurrentAnalysis = telemetryAnalysis;
|
|
|
|
|
|
|
|
|
|
if (saubDataCache.CurrentAnalysis.IdOperation == telemetryAnalysis.IdOperation)
|
|
|
|
|
{
|
|
|
|
|
saubDataCache.CurrentAnalysis.Duration +=
|
|
|
|
|
telemetryAnalysis.Duration;
|
|
|
|
|
saubDataCache.CurrentAnalysis.OperationEndDepth = dataSaub.WellDepth;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
db.TelemetryAnalysis.Add(saubDataCache.CurrentAnalysis);
|
|
|
|
|
saubDataCache.CurrentAnalysis = telemetryAnalysis;
|
|
|
|
|
saubDataCache.CurrentAnalysis.OperationStartDepth = dataSaub.WellDepth;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private TelemetryAnalysis GetDrillingAnalysis(IEnumerable<DataSaubBase> dataSaubBases)
|
2021-06-24 13:02:31 +05:00
|
|
|
|
{
|
2021-06-28 11:35:52 +05:00
|
|
|
|
var lastSaubDate = dataSaubBases.Last().Date;
|
2021-06-24 13:02:31 +05:00
|
|
|
|
|
2021-06-28 11:35:52 +05:00
|
|
|
|
var saubWellDepths = dataSaubBases.Where(sw => sw.WellDepth is not null)
|
2021-07-21 15:29:19 +05:00
|
|
|
|
.Select(s => (Value: (double)s.WellDepth,
|
2021-06-28 11:35:52 +05:00
|
|
|
|
(s.Date - dataSaubBases.First().Date).TotalSeconds));
|
|
|
|
|
var saubBitDepths = dataSaubBases.Where(sw => sw.BitDepth is not null)
|
2021-07-21 15:29:19 +05:00
|
|
|
|
.Select(s => (Value: (double)s.BitDepth,
|
2021-06-28 11:35:52 +05:00
|
|
|
|
(s.Date - dataSaubBases.First().Date).TotalSeconds));
|
|
|
|
|
var saubBlockPositions = dataSaubBases.Where(sw => sw.BlockPosition is not null)
|
2021-07-21 15:29:19 +05:00
|
|
|
|
.Select(s => (Value: (double)s.BlockPosition,
|
2021-06-28 11:35:52 +05:00
|
|
|
|
(s.Date - dataSaubBases.First().Date).TotalSeconds));
|
|
|
|
|
var saubRotorSpeeds = dataSaubBases.Where(sw => sw.RotorSpeed is not null)
|
2021-07-21 15:29:19 +05:00
|
|
|
|
.Select(s => (Value: (double)s.RotorSpeed,
|
2021-06-28 11:35:52 +05:00
|
|
|
|
(s.Date - dataSaubBases.First().Date).TotalSeconds));
|
|
|
|
|
var saubPressures = dataSaubBases.Where(sw => sw.Pressure is not null)
|
2021-07-21 15:29:19 +05:00
|
|
|
|
.Select(s => (Value: (double)s.Pressure,
|
2021-06-28 11:35:52 +05:00
|
|
|
|
(s.Date - dataSaubBases.First().Date).TotalSeconds));
|
|
|
|
|
var saubHookWeights = dataSaubBases.Where(sw => sw.HookWeight is not null)
|
2021-07-21 15:29:19 +05:00
|
|
|
|
.Select(s => (Value: (double)s.HookWeight,
|
2021-06-28 11:35:52 +05:00
|
|
|
|
(s.Date - dataSaubBases.First().Date).TotalSeconds));
|
|
|
|
|
|
2021-07-20 11:24:57 +05:00
|
|
|
|
var wellDepthChangingIndex = new InterpolationLine(saubWellDepths).GetAForLinearFormula();
|
|
|
|
|
var bitPositionChangingIndex = new InterpolationLine(saubBitDepths).GetAForLinearFormula();
|
|
|
|
|
var blockPositionChangingIndex = new InterpolationLine(saubBlockPositions).GetAForLinearFormula();
|
|
|
|
|
var rotorSpeedChangingIndex = new InterpolationLine(saubRotorSpeeds).GetAForLinearFormula();
|
|
|
|
|
var pressureChangingIndex = new InterpolationLine(saubPressures).GetAForLinearFormula();
|
|
|
|
|
var hookWeightChangingIndex = new InterpolationLine(saubHookWeights).GetAForLinearFormula();
|
2021-06-28 11:35:52 +05:00
|
|
|
|
|
2021-07-20 11:24:57 +05:00
|
|
|
|
var IsBlockRising = InterpolationLine.IsValueDecreases(blockPositionChangingIndex, -0.0001);
|
|
|
|
|
var IsBlockGoesDown = InterpolationLine.IsValueIncreases(blockPositionChangingIndex, 0.0001);
|
|
|
|
|
var IsBlockStandsStill = InterpolationLine.IsValueNotChanges(blockPositionChangingIndex, (0.0001, -0.0001));
|
2021-06-24 13:02:31 +05:00
|
|
|
|
|
2021-07-19 15:31:50 +05:00
|
|
|
|
var drillingAnalysis = new TelemetryAnalysis
|
2021-06-24 13:02:31 +05:00
|
|
|
|
{
|
|
|
|
|
IdTelemetry = dataSaubBases.First().IdTelemetry,
|
2021-07-19 15:31:50 +05:00
|
|
|
|
UnixDate = (long)(lastSaubDate - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds,
|
2021-06-30 10:16:06 +05:00
|
|
|
|
Duration = (int)(dataSaubBases.Last().Date - dataSaubBases.ElementAt(dataSaubBases.Count() - 2).Date).TotalSeconds,
|
2021-07-20 09:36:40 +05:00
|
|
|
|
OperationStartDepth = null,
|
|
|
|
|
OperationEndDepth = null,
|
2021-07-20 11:24:57 +05:00
|
|
|
|
IsWellDepthDecreasing = InterpolationLine.IsValueDecreases(wellDepthChangingIndex, -0.0001),
|
|
|
|
|
IsWellDepthIncreasing = InterpolationLine.IsValueIncreases(wellDepthChangingIndex, 0.0001),
|
|
|
|
|
IsBitPositionDecreasing = InterpolationLine.IsValueDecreases(bitPositionChangingIndex, -0.0001),
|
|
|
|
|
IsBitPositionIncreasing = InterpolationLine.IsValueIncreases(bitPositionChangingIndex, 0.0001),
|
|
|
|
|
IsBitDepthLess20 = InterpolationLine.IsAverageLessThanBound(saubBitDepths, 20),
|
|
|
|
|
IsBlockPositionDecreasing = InterpolationLine.IsValueDecreases(blockPositionChangingIndex, -0.0001),
|
|
|
|
|
IsBlockPositionIncreasing = InterpolationLine.IsValueIncreases(blockPositionChangingIndex, 0.0001),
|
|
|
|
|
IsRotorSpeedLt3 = InterpolationLine.IsAverageLessThanBound(saubRotorSpeeds, 3),
|
|
|
|
|
IsRotorSpeedGt3 = InterpolationLine.IsAverageMoreThanBound(saubRotorSpeeds, 3),
|
|
|
|
|
IsPressureLt20 = InterpolationLine.IsAverageLessThanBound(saubPressures, 20),
|
|
|
|
|
IsPressureGt20 = InterpolationLine.IsAverageMoreThanBound(saubPressures, 20),
|
|
|
|
|
IsHookWeightNotChanges = InterpolationLine.IsValueNotChanges(hookWeightChangingIndex, (0.0001, -0.0001)),
|
|
|
|
|
IsHookWeightLt3 = InterpolationLine.IsAverageLessThanBound(saubHookWeights, 3),
|
2021-06-24 13:02:31 +05:00
|
|
|
|
IdOperation = 1
|
|
|
|
|
};
|
|
|
|
|
|
2021-06-25 15:10:05 +05:00
|
|
|
|
drillingAnalysis.IdOperation = operationDetectorService.DetectOperation(drillingAnalysis).Id;
|
2021-06-24 13:02:31 +05:00
|
|
|
|
|
|
|
|
|
return drillingAnalysis;
|
|
|
|
|
}
|
2021-06-17 15:12:39 +05:00
|
|
|
|
}
|
|
|
|
|
}
|