diff --git a/AsbCloudApp/Data/DetectedOperation/DetectedOperationDto.cs b/AsbCloudApp/Data/DetectedOperation/DetectedOperationDto.cs
index 0386e8b3..2b0fc768 100644
--- a/AsbCloudApp/Data/DetectedOperation/DetectedOperationDto.cs
+++ b/AsbCloudApp/Data/DetectedOperation/DetectedOperationDto.cs
@@ -1,100 +1,85 @@
 using System;
 using System.ComponentModel.DataAnnotations;
 
-namespace AsbCloudApp.Data.DetectedOperation
+namespace AsbCloudApp.Data.DetectedOperation;
+
+/// <summary>
+/// Автоматически определенная операция
+/// </summary>
+public class DetectedOperationDto: IId
 {
+    /// <inheritdoc/>    
+    [Required]
+    public int Id { get; set; }
+
     /// <summary>
-    /// Автоматически определяемая операция
+    /// Id телеметрии
     /// </summary>
-    public class DetectedOperationDto : IId, IWellRelated
-    {
-        /// <inheritdoc/>
-        [Required]
-        public int Id { get; set; }
+    [Required]
+    public int IdTelemetry { get; set; }
 
-        /// <inheritdoc/>
-        [Required]
-        public int IdWell { get; set; }
+    /// <summary>
+    /// Id названия/описания операции
+    /// </summary>
+    [Required]
+    public int IdCategory { get; set; }
 
-        /// <summary>
-        /// Id телеметрии
-        /// </summary>
-        [Required]
-        public int IdTelemetry { get; set; }
+    /// <summary>
+    /// Id пользователя панели на момент начала операции
+    /// </summary>
+    [Required]
+    public int IdUserAtStart { get; set; }
 
-        /// <summary>
-        /// Id названия/описания операции
-        /// </summary>
-        [Required]
-        public int IdCategory { get; set; }
+    /// <summary>
+    /// Дата завершения операции в часовом поясе скважины
+    /// </summary>
+    [Required]
+    public DateTimeOffset DateEnd { get; set; }
 
-        /// <summary>
-        /// Id пользователя панели
-        /// </summary>
-        [Required]
-        public int IdUsersAtStart { get; set; }
+    /// <summary>
+    /// Дата начала операции в часовом поясе скважины
+    /// </summary>
+    [Required]
+    public DateTimeOffset DateStart { get; set; }
 
-        /// <summary>
-        /// Дата начала операции в часовом поясе скважины
-        /// </summary>
-        [Required]
-        public DateTime DateStart { get; set; }
+    /// <summary>
+    /// глубина на завершения операции, м
+    /// </summary>
+    [Required]
+    public double DepthEnd { get; set; }
 
-        /// <summary>
-        /// Дата завершения операции в часовом поясе скважины
-        /// </summary>
-        [Required]
-        public DateTime DateEnd { get; set; }
+    /// <summary>
+    /// глубина на начало операции, м
+    /// </summary>
+    [Required]
+    public double DepthStart { get; set; }
 
-        /// <summary>
-        /// Продолжительность операции в минутах
-        /// </summary>
-        [Required]
-        public double DurationMinutes => (DateEnd - DateStart).TotalMinutes;
+    /// <summary>
+    /// Продолжительность операции в минутах
+    /// </summary>
+    [Required]
+    public double DurationMinutes => (DateEnd - DateStart).TotalMinutes;
 
-        /// <summary>
-        /// глубина на начало операции, м
-        /// </summary>
-        [Required]
-        public double DepthStart { get; set; }
+    /// <summary>
+    /// Флаг включенной подсистемы
+    /// </summary>
+    [Required]
+    public int EnabledSubsystems { get; set; }
 
-        /// <summary>
-        /// глубина на завершения операции, м
-        /// </summary>
-        [Required]
-        public double DepthEnd { get; set; }
+    /// <summary>
+    /// название/описание операции
+    /// </summary>
+    [Required]
+    public WellOperationCategoryDto OperationCategory { get; set; } = null!;
 
-        /// <summary>
-        /// название/описание операции
-        /// </summary>
-        [Required]
-        public WellOperationCategoryDto OperationCategory { get; set; } = null!;
+    /// <summary>
+    /// Пользователь панели оператора
+    /// </summary>
+    public string? TelemetryUserName { get; set; }
 
-        /// <summary>
-        /// Пользователь панели оператора
-        /// </summary>
-        public string? TelemetryUserName { get; set; }
-
-        /// <summary>
-        /// Бурильщик
-        /// </summary>
-        public DrillerDto? Driller { get; set; }
-
-        /// <summary>
-        /// Целевые/нормативные показатели
-        /// </summary>
-        public OperationValueDto? OperationValue { get; set; }
-
-        /// <summary>
-        /// Ключевой параметр операции
-        /// </summary>
-        [Required]
-        public double Value { get; set; }
-
-        /// <summary>
-        /// Флаг включенной подсистемы
-        /// </summary>
-        [Required]
-        public int EnabledSubsystems { get; set; }
-    }
-}
+    /// <summary>
+    /// Ключевой параметр операции
+    /// </summary>
+    [Required]
+    public double Value { get; set; }
+}
\ No newline at end of file
diff --git a/AsbCloudApp/Data/DetectedOperation/DetectedOperationListDto.cs b/AsbCloudApp/Data/DetectedOperation/DetectedOperationListDto.cs
index fef979bb..32dd6040 100644
--- a/AsbCloudApp/Data/DetectedOperation/DetectedOperationListDto.cs
+++ b/AsbCloudApp/Data/DetectedOperation/DetectedOperationListDto.cs
@@ -13,7 +13,7 @@ namespace AsbCloudApp.Data.DetectedOperation
         /// Список всех операций
         /// </summary>
         [Required]
-        public IEnumerable<DetectedOperationDto> Operations { get; set; } = Enumerable.Empty<DetectedOperationDto>();
+        public IEnumerable<DetectedOperationWithDrillerDto> Operations { get; set; } = Enumerable.Empty<DetectedOperationWithDrillerDto>();
 
         /// <summary>
         /// Статистика по бурильщикам
diff --git a/AsbCloudApp/Data/DetectedOperation/DetectedOperationWithDrillerDto.cs b/AsbCloudApp/Data/DetectedOperation/DetectedOperationWithDrillerDto.cs
new file mode 100644
index 00000000..612b079c
--- /dev/null
+++ b/AsbCloudApp/Data/DetectedOperation/DetectedOperationWithDrillerDto.cs
@@ -0,0 +1,18 @@
+namespace AsbCloudApp.Data.DetectedOperation
+{
+    /// <summary>
+    /// Автоматически определяемая операция
+    /// </summary>
+    public class DetectedOperationWithDrillerDto : DetectedOperationDto
+    {
+        /// <summary>
+        /// Бурильщик
+        /// </summary>
+        public DrillerDto? Driller { get; set; }
+
+        /// <summary>
+        /// Целевые/нормативные показатели
+        /// </summary>
+        public OperationValueDto? OperationValue { get; set; }
+    }
+}
diff --git a/AsbCloudApp/Data/SimpleTimezoneDto.cs b/AsbCloudApp/Data/SimpleTimezoneDto.cs
index 3ec75814..5d2e1f8f 100644
--- a/AsbCloudApp/Data/SimpleTimezoneDto.cs
+++ b/AsbCloudApp/Data/SimpleTimezoneDto.cs
@@ -1,25 +1,32 @@
+using System;
+
 namespace AsbCloudApp.Data
 {
     /// <summary>
-    /// ��������� ����
+    /// временная зона
     /// </summary>
     public class SimpleTimezoneDto
     {
         /// <summary>
-        /// �������� � ����� ������������ UTC
+        /// смещение в часах относительно UTC
         /// </summary>
         public double Hours { get; set; }
 
         /// <summary>
-        /// ������������� ������� ����
+        /// идентификатор часовой зоны
         /// </summary>
         public string? TimezoneId { get; set; }
 
         /// <summary>
-        /// ������ �� ���������������
+        /// запрет на переопределение
         /// </summary>
         public bool IsOverride { get; set; }
 
+        /// <summary>
+        /// Смещение часового пояса
+        /// </summary>
+        public TimeSpan Offset => TimeSpan.FromHours(Hours);
+
         /// <inheritdoc/>
         public override bool Equals(object? obj)
         {
diff --git a/AsbCloudApp/Data/TimeDto.cs b/AsbCloudApp/Data/TimeDto.cs
index 6a3620f2..20ffa4ac 100644
--- a/AsbCloudApp/Data/TimeDto.cs
+++ b/AsbCloudApp/Data/TimeDto.cs
@@ -86,6 +86,14 @@ namespace AsbCloudApp.Data
             second = fullDate.Second;
         }
 
+        /// <inheritdoc/>
+        public TimeDto(DateTimeOffset fullDate)
+        {
+            hour = fullDate.Hour;
+            minute = fullDate.Minute;
+            second = fullDate.Second;
+        }
+
         /// <summary>
         /// Makes System.TimeOnly
         /// </summary>
diff --git a/AsbCloudApp/Exceptions/ArgumentInvalidException.cs b/AsbCloudApp/Exceptions/ArgumentInvalidException.cs
index 5db63313..641f18ce 100644
--- a/AsbCloudApp/Exceptions/ArgumentInvalidException.cs
+++ b/AsbCloudApp/Exceptions/ArgumentInvalidException.cs
@@ -14,6 +14,7 @@ namespace AsbCloudApp.Exceptions
         /// </summary>
         public IDictionary<string, string[]> ErrorState { get; } = null!;
 
+        // TODO: swap arguments, inherit from ArgumentException
         /// <summary>
         /// конструктор
         /// </summary>
diff --git a/AsbCloudApp/Repositories/IDetectedOperationRepository.cs b/AsbCloudApp/Repositories/IDetectedOperationRepository.cs
new file mode 100644
index 00000000..5a52b455
--- /dev/null
+++ b/AsbCloudApp/Repositories/IDetectedOperationRepository.cs
@@ -0,0 +1,66 @@
+using AsbCloudApp.Data.DetectedOperation;
+using AsbCloudApp.Requests;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using System.Threading;
+
+namespace AsbCloudApp.Repositories;
+
+/// <summary>
+/// Таблица автоматически определенных операций
+/// </summary>
+public interface IDetectedOperationRepository
+{
+    /// <summary>
+    /// Добавление записей
+    /// </summary>
+    /// <param name="idUser"></param>
+    /// <param name="dtos"></param>
+    /// <param name="token"></param>
+    /// <returns></returns>
+    Task<int> Insert(int? idUser, IEnumerable<DetectedOperationDto> dtos, CancellationToken token);
+
+    /// <summary>
+    /// Получить автоматически определенные операции по телеметрии
+    /// </summary>
+    /// <param name="request"></param>
+    /// <param name="token"></param>
+    /// <returns></returns>
+    Task<IEnumerable<DetectedOperationDto>> Get(DetectedOperationByTelemetryRequest request, CancellationToken token);
+
+    /// <summary>
+    /// Редактирование записей
+    /// </summary>
+    /// <param name="idUser"></param>
+    /// <param name="dtos"></param>
+    /// <param name="token"></param>
+    /// <returns></returns>
+    Task<int> Update(int idUser, IEnumerable<DetectedOperationDto> dtos, CancellationToken token);
+
+    /// <summary>
+    /// Добавляет Dto у которых id == 0, изменяет dto у которых id != 0
+    /// </summary>
+    /// <param name="idUser"></param>
+    /// <param name="dtos"></param>
+    /// <param name="token"></param>
+    /// <returns></returns>
+    Task<int> UpdateOrInsert(int idUser, IEnumerable<DetectedOperationDto> dtos, CancellationToken token);
+
+    /// <summary>
+    /// Удалить операции
+    /// </summary>
+    /// <param name="idUser"></param>
+    /// <param name="request"></param>
+    /// <param name="token"></param>
+    /// <returns></returns>
+    Task<int> Delete(int idUser, DetectedOperationByTelemetryRequest request, CancellationToken token);
+
+    /// <summary>
+    /// Удаление записей
+    /// </summary>
+    /// <param name="idUser"></param>
+    /// <param name="ids"></param>
+    /// <param name="token"></param>
+    /// <returns></returns>
+    Task<int> DeleteRange(int idUser, IEnumerable<int> ids, CancellationToken token);
+}
diff --git a/AsbCloudApp/Repositories/IWellOperationCategoryRepository.cs b/AsbCloudApp/Repositories/IWellOperationCategoryRepository.cs
new file mode 100644
index 00000000..4e18c231
--- /dev/null
+++ b/AsbCloudApp/Repositories/IWellOperationCategoryRepository.cs
@@ -0,0 +1,17 @@
+using AsbCloudApp.Data;
+using System.Collections.Generic;
+
+namespace AsbCloudApp.Repositories
+{
+    /// <summary>
+    /// сервис операций по скважине
+    /// </summary>
+    public interface IWellOperationCategoryRepository
+    {
+        /// <summary>
+        /// список названий операций
+        /// </summary>
+        /// <returns></returns>
+        IEnumerable<WellOperationCategoryDto> Get(bool includeParents);
+    }
+}
\ No newline at end of file
diff --git a/AsbCloudApp/Repositories/IWellOperationRepository.cs b/AsbCloudApp/Repositories/IWellOperationRepository.cs
index 8cb313ac..ff3bfb8e 100644
--- a/AsbCloudApp/Repositories/IWellOperationRepository.cs
+++ b/AsbCloudApp/Repositories/IWellOperationRepository.cs
@@ -13,10 +13,12 @@ namespace AsbCloudApp.Repositories
     /// </summary>
     public interface IWellOperationRepository
     {
+        //TODO: replace all references
         /// <summary>
         /// список названий операций
         /// </summary>
         /// <returns></returns>
+        [Obsolete("use IWellOperationCategoryRepository.GetCategories(bool includeParents)")]
         IEnumerable<WellOperationCategoryDto> GetCategories(bool includeParents);
 
         /// <summary>
diff --git a/AsbCloudApp/Requests/DetectedOperationRequest.cs b/AsbCloudApp/Requests/DetectedOperationRequest.cs
index dcf66aab..243d6e9f 100644
--- a/AsbCloudApp/Requests/DetectedOperationRequest.cs
+++ b/AsbCloudApp/Requests/DetectedOperationRequest.cs
@@ -1,56 +1,114 @@
 using System;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
-using System.Linq;
 
-namespace AsbCloudApp.Requests
+namespace AsbCloudApp.Requests;
+
+/// <summary>
+/// Запрос на получение операций определенных по телеметрии
+/// </summary>
+public class DetectedOperationByTelemetryRequest : DetectedOperationRequest
 {
     /// <summary>
-    /// Параметры запроса на получение операций определенных по телеметрии
+    /// id телеметрии
     /// </summary>
-    public class DetectedOperationRequest : RequestBase
+    [Required] 
+    public int IdTelemetry { get; set; }
+
+    /// <summary>
+    /// Запрос на получение операций определенных по id телеметрии
+    /// </summary>
+    public DetectedOperationByTelemetryRequest()
+    {}
+
+    /// <summary>
+    /// Запрос на получение операций определенных по id телеметрии. Copy
+    /// </summary>
+    /// <param name="idTelemetry"></param>
+    /// <param name="request"></param>
+    public DetectedOperationByTelemetryRequest(int idTelemetry, DetectedOperationRequest request)
+        :base(request)
     {
-        /// <summary>
-        /// категория операций
-        /// </summary>
-        [Required]
-        public int IdWell { get; set; }
-        
-        /// <summary>
-        /// Список id телеметрий
-        /// пустой список - нет фильтрации
-        /// </summary>
-        public IEnumerable<int> IdsTelemetries { get; set; } = Array.Empty<int>();
-
-        /// <summary>
-        /// категории операций
-        /// </summary>
-        public IEnumerable<int> IdsCategories { get; set; } = Array.Empty<int>();
-
-        /// <summary>
-        /// Больше или равно дате
-        /// </summary>
-        public DateTimeOffset? GeDateStart { get; set; }
-
-        /// <summary>
-        /// Меньше или равно дате
-        /// </summary>
-        public DateTimeOffset? LeDateEnd { get; set; }
-
-        /// <summary>
-        /// Больше или равно глубины забоя
-        /// </summary>
-        public double? GeDepth { get; set; }
-
-        /// <summary>
-        /// Меньше или равно глубины забоя
-        /// </summary>
-        public double? LeDepth { get; set; }
-
-        /// <summary>
-        /// Фильтр по пользователю панели
-        /// </summary>
-        public int? IdTelemetryUser { get; set; }
-
+        IdTelemetry = idTelemetry;
+    }
+}
+
+/// <summary>
+/// Запрос на получение операций определенных по id скважины
+/// </summary>
+public class DetectedOperationByWellRequest : DetectedOperationRequest
+{
+    /// <summary>
+    /// id скважины
+    /// </summary>
+    [Required] 
+    public int IdWell { get; set; }
+
+    /// <summary>
+    /// Запрос на получение операций определенных по id скважины
+    /// </summary>
+    public DetectedOperationByWellRequest()
+    {}
+
+    /// <summary>
+    /// Запрос на получение операций определенных по id скважины. Copy
+    /// </summary>
+    public DetectedOperationByWellRequest(int idWell, DetectedOperationRequest request)
+        : base(request)
+    {
+        IdWell = idWell;
+    }
+}
+
+/// <summary>
+/// Запрос на получение операций определенных по телеметрии
+/// </summary>
+public class DetectedOperationRequest : RequestBase
+{
+    /// <summary>
+    /// категории операций
+    /// </summary>
+    public IEnumerable<int> IdsCategories { get; set; }
+
+    /// <summary>
+    /// Больше или равно дате
+    /// </summary>
+    public DateTimeOffset? GeDateStart { get; set; }
+
+    /// <summary>
+    /// Меньше или равно дате
+    /// </summary>
+    public DateTimeOffset? LeDateEnd { get; set; }
+
+    /// <summary>
+    /// Больше или равно глубины забоя
+    /// </summary>
+    public double? GeDepthStart { get; set; }
+
+    /// <summary>
+    /// Меньше или равно глубины забоя
+    /// </summary>
+    public double? LeDepthEnd { get; set; }
+
+    /// <summary>
+    /// Запрос на получение операций определенных по телеметрии
+    /// </summary>
+    public DetectedOperationRequest()
+    {
+        IdsCategories = new List<int>();
+    }
+
+    /// <summary>
+    /// Запрос на получение операций определенных по телеметрии. Copy
+    /// </summary>
+    /// <param name="request"></param>
+    public DetectedOperationRequest(DetectedOperationRequest request)
+        : base(request)
+    {
+        IdsCategories = request.IdsCategories;
+        GeDateStart = request.GeDateStart;
+        LeDateEnd = request.LeDateEnd;
+        GeDepthStart = request.GeDepthStart;
+        LeDepthEnd = request.LeDepthEnd;
     }
 }
diff --git a/AsbCloudApp/Requests/RequestBase.cs b/AsbCloudApp/Requests/RequestBase.cs
index 2ae96e54..c5d70a68 100644
--- a/AsbCloudApp/Requests/RequestBase.cs
+++ b/AsbCloudApp/Requests/RequestBase.cs
@@ -23,5 +23,23 @@ namespace AsbCloudApp.Requests
         /// Указать направление сортировки можно через пробел "asc" или "desc"
         /// </summary>
         public IEnumerable<string>? SortFields { get; set; }
+
+        /// <summary>
+        /// Базовые параметры запроса
+        /// </summary>
+        public RequestBase()
+        {
+        }
+
+        /// <summary>
+        /// Базовые параметры запроса. Копирующий конструктор
+        /// </summary>
+        /// <param name="request"></param>
+        public RequestBase(RequestBase request)
+        {
+            Skip = request.Skip;
+            Take = request.Take;
+            SortFields = request.SortFields;
+        }
     }
 }
diff --git a/AsbCloudApp/Services/IDetectedOperationService.cs b/AsbCloudApp/Services/IDetectedOperationService.cs
index e4f8f201..5109fa56 100644
--- a/AsbCloudApp/Services/IDetectedOperationService.cs
+++ b/AsbCloudApp/Services/IDetectedOperationService.cs
@@ -19,7 +19,7 @@ namespace AsbCloudApp.Services
         /// <param name="idWell"></param>
         /// <param name="token"></param>
         /// <returns></returns>
-        Task<IEnumerable<WellOperationCategoryDto>?> GetCategoriesAsync(int? idWell, CancellationToken token);
+        Task<IEnumerable<WellOperationCategoryDto>> GetCategoriesAsync(int? idWell, CancellationToken token);
 
         /// <summary>
         /// Получить автоматически определенные по телеметрии операции с анализом по бурильщикам
@@ -27,7 +27,7 @@ namespace AsbCloudApp.Services
         /// <param name="request"></param>
         /// <param name="token"></param>
         /// <returns></returns>
-        Task<DetectedOperationListDto?> GetAsync(DetectedOperationRequest request, CancellationToken token);
+        Task<DetectedOperationListDto> GetAsync(DetectedOperationByWellRequest request, CancellationToken token);
         
         /// <summary>
         /// Получить автоматически определенные по телеметрии операции
@@ -35,7 +35,7 @@ namespace AsbCloudApp.Services
         /// <param name="request"></param>
         /// <param name="token"></param>
         /// <returns></returns>
-        Task<IEnumerable<DetectedOperationDto>> GetOperationsAsync(DetectedOperationRequest request, CancellationToken token);
+        Task<IEnumerable<DetectedOperationWithDrillerDto>> GetOperationsAsync(DetectedOperationByWellRequest request, CancellationToken token);
 
         /// <summary>
         /// Удалить операции
@@ -43,7 +43,7 @@ namespace AsbCloudApp.Services
         /// <param name="request"></param>
         /// <param name="token"></param>
         /// <returns></returns>
-        Task<int> DeleteAsync(DetectedOperationRequest request, CancellationToken token);
+        Task<int> DeleteAsync(DetectedOperationByWellRequest request, CancellationToken token);
 
         /// <summary>
         /// Статистика по операциям
@@ -51,6 +51,6 @@ namespace AsbCloudApp.Services
         /// <param name="request"></param>
         /// <param name="token"></param>
         /// <returns></returns>
-        Task<IEnumerable<DetectedOperationStatDto>?> GetOperationsStatAsync(DetectedOperationRequest request, CancellationToken token);
+        Task<IEnumerable<DetectedOperationStatDto>> GetOperationsStatAsync(DetectedOperationByWellRequest request, CancellationToken token);
     }
 }
diff --git a/AsbCloudApp/Services/ITelemetryDataSaubService.cs b/AsbCloudApp/Services/ITelemetryDataSaubService.cs
index a0bb271d..e13240c0 100644
--- a/AsbCloudApp/Services/ITelemetryDataSaubService.cs
+++ b/AsbCloudApp/Services/ITelemetryDataSaubService.cs
@@ -12,6 +12,18 @@ namespace AsbCloudApp.Services
     /// </summary>
     public interface ITelemetryDataSaubService : ITelemetryDataService<TelemetryDataSaubDto> 
     {
+        /// <summary>
+        /// Получение телеметрии для РТК статистики
+        /// </summary>
+        /// <param name="idTelemetry"></param>
+        /// <param name="isBitOnBottom"></param>
+        /// <param name="geDate"></param>
+        /// <param name="leDate"></param>
+        /// <param name="take"></param>
+        /// <param name="token"></param>
+        /// <returns></returns>
+        Task<IEnumerable<TelemetryDataSaubDto>> Get(int idTelemetry, bool isBitOnBottom, DateTimeOffset geDate, DateTimeOffset leDate, int take, CancellationToken token);
+
         /// <summary>
         /// усредненная статистика по 1м за весь период
         /// <para>
diff --git a/AsbCloudDb/EFExtensionsExceptionHandling.cs b/AsbCloudDb/EFExtensionsExceptionHandling.cs
new file mode 100644
index 00000000..aa1996b5
--- /dev/null
+++ b/AsbCloudDb/EFExtensionsExceptionHandling.cs
@@ -0,0 +1,34 @@
+using AsbCloudDb.Model;
+using Microsoft.EntityFrameworkCore;
+using Npgsql;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace AsbCloudDb
+{
+    public static class EFExtensionsExceptionHandling
+    {
+        public static async Task<int> SaveChangesWithExceptionHandling(this IAsbCloudDbContext db, CancellationToken token)
+        {
+            try
+            {
+                var result = await db.SaveChangesAsync(token);
+                return result;
+            }
+            catch (DbUpdateException ex)
+            {
+                if (ex.InnerException is PostgresException pgException)
+                    TryConvertPostgresExceptionToValidateException(pgException);
+                throw;
+            }
+        }
+
+        private static void TryConvertPostgresExceptionToValidateException(PostgresException pgException)
+        {
+            if (pgException.SqlState == PostgresErrorCodes.ForeignKeyViolation)
+                // TODO: replace ArgumentException by new Exception
+                throw new ArgumentException(pgException.Message + "\r\n" + pgException.Detail, "dtos");
+        }
+    }
+}
diff --git a/AsbCloudDb/EFExtentionOrderBy.cs b/AsbCloudDb/EFExtensionsSortBy.cs
similarity index 99%
rename from AsbCloudDb/EFExtentionOrderBy.cs
rename to AsbCloudDb/EFExtensionsSortBy.cs
index 0b46c29b..933281c7 100644
--- a/AsbCloudDb/EFExtentionOrderBy.cs
+++ b/AsbCloudDb/EFExtensionsSortBy.cs
@@ -7,7 +7,7 @@ using System.Reflection;
 
 namespace AsbCloudDb
 {
-    public static class EFExtentionsSortBy
+    public static class EFExtensionsSortBy
     {
         struct TypeAcessor
         {
diff --git a/AsbCloudInfrastructure/Background/PeriodicWorks/WorkDataSaubStat.cs b/AsbCloudInfrastructure/Background/PeriodicWorks/WorkDataSaubStat.cs
index bc942353..d7daf9ad 100644
--- a/AsbCloudInfrastructure/Background/PeriodicWorks/WorkDataSaubStat.cs
+++ b/AsbCloudInfrastructure/Background/PeriodicWorks/WorkDataSaubStat.cs
@@ -1,10 +1,11 @@
 using AsbCloudApp.Data;
+using AsbCloudApp.Data.DetectedOperation;
 using AsbCloudApp.Data.SAUB;
 using AsbCloudApp.Repositories;
 using AsbCloudApp.Requests;
+using AsbCloudApp.Services;
 using AsbCloudDb.Model;
-using AsbCloudInfrastructure.Services.DetectOperations.Detectors;
-using Microsoft.EntityFrameworkCore;
+using AsbCloudInfrastructure.Services.DetectOperations;
 using Microsoft.Extensions.DependencyInjection;
 using System;
 using System.Collections.Generic;
@@ -19,7 +20,6 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks
     /// </summary>
     internal class WorkDataSaubStat : Work
     {
-        private int MechanicalDrillingCategoryId = 4001;
         private int Gap = 60;
 
         public WorkDataSaubStat() : base("Generate DataSaubStat entries and save them into Db")
@@ -29,7 +29,6 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks
 
         protected override async Task Action(string id, IServiceProvider services, Action<string, double?> onProgressCallback, CancellationToken token)
         {
-            using var db = services.GetRequiredService<IAsbCloudDbContext>();
 
             var telemetryDataCache = services.GetRequiredService<ITelemetryDataCache<TelemetryDataSaubDto>>();
 
@@ -43,96 +42,102 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks
                 return;
 
             var dataSaubStatRepo = services.GetRequiredService<IDataSaubStatRepository>();
+            var dataSaubService = services.GetRequiredService<ITelemetryDataSaubService>();
+            var detectedOperationRepository = services.GetRequiredService<IDetectedOperationRepository>();
             var stats = await dataSaubStatRepo.GetLastsAsync(idTelemetries, token);
 
             for( var i =0; i < idTelemetries.Length; i++)
             {
                 var idTelemetry = idTelemetries[i];
-                var lastDate = stats.FirstOrDefault(s => s.IdTelemetry == idTelemetry)?.DateEnd ?? DateTimeOffset.UnixEpoch;
-                var statsCount = await CreateStatForTelemetryFromDate(db, idTelemetry, lastDate, dataSaubStatRepo, token);
+                var lastDate = stats.FirstOrDefault(s => s.IdTelemetry == idTelemetry)?.DateEnd.ToUniversalTime() ?? DateTimeOffset.UnixEpoch;
+                var statsCount = await CreateStatForTelemetryFromDate(idTelemetry, lastDate, dataSaubService, dataSaubStatRepo, detectedOperationRepository, token);
                 onProgressCallback($"Calculate stat for telemetry: {idTelemetry}; from {lastDate}; results count: {statsCount};", 100*i / idTelemetries.Length);
             }
         }
 
-        private async Task<int> CreateStatForTelemetryFromDate(IAsbCloudDbContext db, int idTelemetry, DateTimeOffset begin, IDataSaubStatRepository dataSaubStatRepo, CancellationToken token)
+        private static async Task<int> CreateStatForTelemetryFromDate(
+            int idTelemetry, 
+            DateTimeOffset begin, 
+            ITelemetryDataSaubService dataSaubService, 
+            IDataSaubStatRepository dataSaubStatRepo,
+            IDetectedOperationRepository detectedOperationRepository,
+            CancellationToken token)
         {
-            var detectedOperations = await db.Set<DetectedOperation>()
-                .Where(o => o.IdTelemetry == idTelemetry)
-                .Where(o => o.DateStart > begin)
-                .Where(o => o.OperationCategory.IdParent == MechanicalDrillingCategoryId)
-                .OrderBy(o => o.DateStart)
-                .Take(250)
-                .ToArrayAsync(token);
+            var detectedOperationRequest = new DetectedOperationByTelemetryRequest { 
+                GeDateStart = begin,
+                IdTelemetry = idTelemetry,
+                IdsCategories = WellOperationCategory.MechanicalDrillingSubIds,
+                SortFields = new[] {nameof(DetectedOperation.DateStart) },
+                Take = 250,
+            };
+
+            var detectedOperations = await detectedOperationRepository.Get(detectedOperationRequest, token);
 
             if (!detectedOperations.Any())
                 return 0;
 
-            var minDate = detectedOperations.First().DateStart;
-            var maxDate = detectedOperations.OrderByDescending(d => d.DateEnd).First().DateEnd;
+            var geDate = detectedOperations.First().DateStart;
+            var leDate = detectedOperations.OrderByDescending(d => d.DateEnd).First().DateEnd;
 
-            var telemetryDataSaub = await db.Set<TelemetryDataSaub>()
-                .Where(t => t.IdTelemetry == idTelemetry)
-                .Where(t => t.DateTime >= minDate)
-                .Where(t => t.DateTime <= maxDate)
-                .Where(t => Math.Abs(t.BitDepth - t.WellDepth) < 0.0001)
-                .OrderBy(t => t.DateTime)
-                .Take(100_000)
-                .ToArrayAsync(token);
+            var dataSaub = await dataSaubService.Get(idTelemetry, true, geDate, leDate, 100_000, token);
 
-            if (!telemetryDataSaub.Any())
+            if (!dataSaub.Any())
                 return 0;
 
-            var dataSaubStats = CreateDataSaubStat(detectedOperations, telemetryDataSaub);
+            if(dataSaub is not TelemetryDataSaubDto[] dataSaubArray)
+                dataSaubArray = dataSaub.ToArray();
+
+            var dataSaubStats = CreateDataSaubStat(detectedOperations, dataSaubArray);
 
             return await dataSaubStatRepo.InsertRangeAsync(dataSaubStats, token);
         }
 
-        private static IEnumerable<DataSaubStatDto> CreateDataSaubStat(IEnumerable<DetectedOperation> detectedOperations, TelemetryDataSaub[] telemetryDataSaub)
+        private static IEnumerable<DataSaubStatDto> CreateDataSaubStat(IEnumerable<DetectedOperationDto> detectedOperations, TelemetryDataSaubDto[] dataSaub)
         {
             var indexStart = 0;
             var indexEnd = 0;
             var result = new List<DataSaubStatDto>();
 
-            if (!telemetryDataSaub.Any())
+            if (!dataSaub.Any())
                 return result;
 
             foreach (var operation in detectedOperations)
             {
-                indexStart = Array.FindIndex(telemetryDataSaub, indexEnd, t => t.DateTime >= operation.DateStart);
+                indexStart = Array.FindIndex(dataSaub, indexEnd, t => t.DateTime >= operation.DateStart);
                 if (indexStart < 0)
                     break;
 
-                indexEnd = Array.FindIndex(telemetryDataSaub, indexStart, t => t.DateTime > operation.DateEnd);
+                indexEnd = Array.FindIndex(dataSaub, indexStart, t => t.DateTime > operation.DateEnd);
 
                 if (indexEnd < 0)
-                    indexEnd = telemetryDataSaub.Length - 1;
+                    indexEnd = dataSaub.Length - 1;
 
                 if (indexEnd == indexStart)
                     continue;
 
                 var length = indexEnd - indexStart;
 
-                var subset = telemetryDataSaub.AsSpan(indexStart, length);
+                var subset = dataSaub.AsSpan(indexStart, length);
                 var stats = CalcStats(operation, subset);
                 result.AddRange(stats);
             }
             return result;
         }
 
-        private static IEnumerable<DataSaubStatDto> CalcStats(DetectedOperation operation, Span<TelemetryDataSaub> telemetryDataSaub)
+        private static IEnumerable<DataSaubStatDto> CalcStats(DetectedOperationDto operation, Span<TelemetryDataSaubDto> dataSaub)
         {
             var result = new List<DataSaubStatDto>();
 
             var indexStart = 0;
-            for (var i = 1; i < telemetryDataSaub.Length; i++)
+            for (var i = 1; i < dataSaub.Length; i++)
             {
-                var previous = telemetryDataSaub[i - 1];
-                var current = telemetryDataSaub[i];
+                var previous = dataSaub[i - 1];
+                var current = dataSaub[i];
 
-                if (IsNewCacheItem(previous, current) || i == telemetryDataSaub.Length - 1)
+                if (IsNewCacheItem(previous, current) || i == dataSaub.Length - 1)
                 {
                     var length = i - indexStart;
-                    var span = telemetryDataSaub.Slice(indexStart, length);
+                    var span = dataSaub.Slice(indexStart, length);
                     indexStart = i;
                     if (length <= 2 || (span[^1].WellDepth - span[0].WellDepth) < 0.001)
                         continue; // мелкие выборки не учитываем.
@@ -144,10 +149,9 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks
             return result;
         }
 
-        private static DataSaubStatDto CalcStat(DetectedOperation operation, Span<TelemetryDataSaub> span)
+        private static DataSaubStatDto CalcStat(DetectedOperationDto operation, Span<TelemetryDataSaubDto> span)
         {
-            var hasOscillation = operation.ExtraData.TryGetValue(DetectorDrilling.ExtraDataKeyHasOscillation, out object? hasOscillationObject)
-                && hasOscillationObject is true;
+            var hasOscillation = EnabledSubsystemsFlags.AutoOscillation.HasEnabledSubsystems(operation.EnabledSubsystems);
 
             var aggregatedValues = CalcAggregate(span);
             var processMapDrillingCacheItem = new DataSaubStatDto
@@ -184,7 +188,7 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks
             double RotorTorque,
             double RotorSpeed,
             double Flow
-            ) CalcAggregate(Span<TelemetryDataSaub> span)
+            ) CalcAggregate(Span<TelemetryDataSaubDto> span)
         {
             var sumPressure = 0.0;
             var sumAxialLoad = 0.0;
@@ -210,7 +214,7 @@ namespace AsbCloudInfrastructure.Background.PeriodicWorks
             );
         }
 
-        private static bool IsNewCacheItem(TelemetryDataSaub previous, TelemetryDataSaub current)
+        private static bool IsNewCacheItem(TelemetryDataSaubDto previous, TelemetryDataSaubDto current)
         {
             return !(current.Mode == previous.Mode)
                  || !(current.WellDepth >= previous.WellDepth)
diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs
index e940e12a..5e1ea903 100644
--- a/AsbCloudInfrastructure/DependencyInjection.cs
+++ b/AsbCloudInfrastructure/DependencyInjection.cs
@@ -331,6 +331,8 @@ namespace AsbCloudInfrastructure
             services.AddTransient<IProcessMapPlanService<ProcessMapPlanWellReamDto>, ProcessMapPlanService<ProcessMapPlanWellReamDto>>();
 
             services.AddTransient<IWellSectionPlanRepository, WellSectionPlanRepository>();
+            services.AddTransient<IWellOperationCategoryRepository, WellOperationCategoryRepository>();
+            services.AddTransient<IDetectedOperationRepository, DetectedOperationRepository>();
 
             services.AddSingleton<ParserServiceFactory>();
             
diff --git a/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs b/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs
index 64208a8e..f9265ba8 100644
--- a/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs
+++ b/AsbCloudInfrastructure/Repository/ChangeLogRepositoryAbstract.cs
@@ -18,11 +18,11 @@ public abstract class ChangeLogRepositoryAbstract<TDto, TEntity, TRequest> : ICh
     where TEntity : ChangeLogAbstract
     where TRequest : ChangeLogBaseRequest
 {
-    protected readonly IAsbCloudDbContext context;
+    protected readonly IAsbCloudDbContext db;
 
-    public ChangeLogRepositoryAbstract(IAsbCloudDbContext context)
+    public ChangeLogRepositoryAbstract(IAsbCloudDbContext db)
     {
-        this.context = context;
+        this.db = db;
     }
 
     public async Task<int> InsertRange(int idUser, IEnumerable<TDto> dtos, CancellationToken token)
@@ -30,9 +30,10 @@ public abstract class ChangeLogRepositoryAbstract<TDto, TEntity, TRequest> : ICh
         var result = 0;
         if (dtos.Any())
         {
+            using var transaction = await db.Database.BeginTransactionAsync(token);
             var entities = dtos.Select(Convert);
             var creation = DateTimeOffset.UtcNow;
-            var dbSet = context.Set<TEntity>();
+            var dbSet = db.Set<TEntity>();
             foreach (var entity in entities)
             {
                 entity.Id = default;
@@ -46,6 +47,7 @@ public abstract class ChangeLogRepositoryAbstract<TDto, TEntity, TRequest> : ICh
             }
 
             result += await SaveChangesWithExceptionHandling(token);
+            await transaction.CommitAsync(token);
         }
         return result;
     }
@@ -62,9 +64,8 @@ public abstract class ChangeLogRepositoryAbstract<TDto, TEntity, TRequest> : ICh
 
         var ids = dtos.Select(d => d.Id);
 
-        using var transaction = context.Database.BeginTransaction();
         var result = 0;
-        var dbSet = context
+        var dbSet = db
             .Set<TEntity>();
 
         var entitiesToDelete = await dbSet
@@ -80,13 +81,14 @@ public abstract class ChangeLogRepositoryAbstract<TDto, TEntity, TRequest> : ICh
             throw new ArgumentInvalidException(nameof(dtos), $"записи с id:[{stringnotFoundIds}] не найдены, или не актуальны.");
         }
 
+        using var transaction = db.Database.BeginTransaction();
         foreach (var entity in entitiesToDelete)
         {
             entity.IdState = ChangeLogAbstract.IdStateReplaced;
             entity.Obsolete = updateTime;
             entity.IdEditor = idUser;
         }
-        result += await context.SaveChangesAsync(token);
+        result += await db.SaveChangesAsync(token);
 
         var entitiesNew = dtos.Select(Convert);
         foreach (var entity in entitiesNew)
@@ -143,7 +145,7 @@ public abstract class ChangeLogRepositoryAbstract<TDto, TEntity, TRequest> : ICh
     public async Task<int> ClearAndInsertRange(int idUser, TRequest request, IEnumerable<TDto> dtos, CancellationToken token)
     {
         var result = 0;
-        var transaction = await context.Database.BeginTransactionAsync(token);
+        using var transaction = await db.Database.BeginTransactionAsync(token);
         result += await Clear(idUser, request, token);
         result += await InsertRange(idUser, dtos, token);
         await transaction.CommitAsync(token);
@@ -153,7 +155,7 @@ public abstract class ChangeLogRepositoryAbstract<TDto, TEntity, TRequest> : ICh
     public async Task<int> DeleteRange(int idUser, IEnumerable<int> ids, CancellationToken token)
     {
         var updateTime = DateTimeOffset.UtcNow;
-        var query = context.Set<TEntity>()
+        var query = db.Set<TEntity>()
             .Where(e => ids.Contains(e.Id))
             .Where(e => e.Obsolete == null);
 
@@ -269,7 +271,7 @@ public abstract class ChangeLogRepositoryAbstract<TDto, TEntity, TRequest> : ICh
     {
         try
         {
-            var result = await context.SaveChangesAsync(token);
+            var result = await db.SaveChangesAsync(token);
             return result;
         }
         catch (DbUpdateException ex)
diff --git a/AsbCloudInfrastructure/Repository/DetectedOperationRepository.cs b/AsbCloudInfrastructure/Repository/DetectedOperationRepository.cs
new file mode 100644
index 00000000..50ec6c9e
--- /dev/null
+++ b/AsbCloudInfrastructure/Repository/DetectedOperationRepository.cs
@@ -0,0 +1,191 @@
+using AsbCloudApp.Data.DetectedOperation;
+using AsbCloudApp.Exceptions;
+using AsbCloudApp.Repositories;
+using AsbCloudApp.Requests;
+using AsbCloudApp.Services;
+using AsbCloudDb;
+using AsbCloudDb.Model;
+using Mapster;
+using Microsoft.EntityFrameworkCore;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace AsbCloudInfrastructure.Repository;
+
+public class DetectedOperationRepository : IDetectedOperationRepository
+{
+    private readonly IAsbCloudDbContext db;
+    private readonly ITelemetryService telemetryService;
+
+    public DetectedOperationRepository(
+        IAsbCloudDbContext db, 
+        ITelemetryService telemetryService)
+    {
+        this.db = db;
+        this.telemetryService = telemetryService;
+    }
+
+    public async Task<int> Delete(int idUser, DetectedOperationByTelemetryRequest request, CancellationToken token)
+    {
+        var query = BuildQuery(request);
+        db.Set<DetectedOperation>().RemoveRange(query);
+        return await db.SaveChangesAsync(token);
+    }
+
+    public async Task<int> DeleteRange(int idUser, IEnumerable<int> ids, CancellationToken token)
+    {
+        var query = db.Set<DetectedOperation>()
+            .Where(e => ids.Contains( e.Id));
+
+        db.Set<DetectedOperation>()
+            .RemoveRange(query);
+
+        return await db.SaveChangesAsync(token);
+    }
+
+    public async Task<IEnumerable<DetectedOperationDto>> Get(DetectedOperationByTelemetryRequest request, CancellationToken token)
+    {
+        var query = BuildQuery(request)
+            .Include(o => o.OperationCategory);
+        var entities = await query.ToArrayAsync(token);
+        var offset = telemetryService.GetTimezone(request.IdTelemetry).Offset;
+        var dtos = entities.Select(o => Convert(o, offset));
+
+        return dtos;
+    }
+
+    public async Task<int> Insert(int? idUser, IEnumerable<DetectedOperationDto> dtos, CancellationToken token)
+    {
+        if(!dtos.Any())
+            return 0;
+
+        var entities = dtos.Select(Convert);
+        var dbset = db.Set<DetectedOperation>();
+        foreach(var entity in entities)
+        {
+            entity.Id = default;
+            dbset.Add(entity);
+        }
+
+        return await db.SaveChangesWithExceptionHandling(token);
+    }
+
+    public async Task<int> Update(int idUser, IEnumerable<DetectedOperationDto> dtos, CancellationToken token)
+    {
+        if (!dtos.Any())
+            return 0;
+
+        var ids = dtos
+            .Select(o => o.Id)
+            .Distinct()
+            .ToArray();
+
+        if (ids.Any(id => id == default))
+            throw new ArgumentInvalidException(nameof(dtos), "Все записи должны иметь Id");
+
+        if (ids.Length != dtos.Count())
+            throw new ArgumentInvalidException(nameof(dtos), "Все записи должны иметь уникальные Id");
+
+        var dbSet = db.Set<DetectedOperation>();
+        
+        var existingEntitiesCount = await dbSet
+            .Where(o => ids.Contains(o.Id))
+            .CountAsync(token);
+
+        if (ids.Length != existingEntitiesCount)
+            throw new ArgumentInvalidException(nameof(dtos), "Все записи должны существовать в БД");
+
+        var entities = dtos
+            .Select(Convert)
+            .ToArray();
+
+        var entries = new Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry<DetectedOperation>[entities.Length];
+        for(var i = 0; i < entities.Length; i++)
+            entries[i] = dbSet.Update(entities[i]);       
+
+        var result = await db.SaveChangesWithExceptionHandling(token);
+        
+        for (var i = 0; i < entries.Length; i++)
+            entries[i].State = EntityState.Detached;
+
+        return result;
+    }
+
+    public async Task<int> UpdateOrInsert(int idUser, IEnumerable<DetectedOperationDto> dtos, CancellationToken token)
+    {
+        var result = 0;
+
+        var itemsToInsert = dtos.Where(e => e.Id == 0);
+        if (itemsToInsert.Any())
+            result += await Insert(idUser, itemsToInsert, token);
+
+        var itemsToUpdate = dtos.Where(e => e.Id != 0);
+        if (itemsToUpdate.Any())
+            result += await Update(idUser, itemsToUpdate, token);
+
+        return result;
+    }
+
+    private IQueryable<DetectedOperation> BuildQuery(DetectedOperationByTelemetryRequest request)
+    {
+        var query = db.Set<DetectedOperation>()
+            .Where(o => o.IdTelemetry == request.IdTelemetry);
+
+        if (request.IdsCategories.Any())
+            query = query.Where(o => request.IdsCategories.Contains(o.IdCategory));
+
+        if (request.GeDepthStart is not null)
+            query = query.Where(o => o.DepthStart >= request.GeDepthStart);
+
+        if (request.LeDepthEnd is not null)
+            query = query.Where(o => o.DepthEnd <= request.LeDepthEnd);
+
+        if (request.GeDateStart is not null)
+        {
+            var geDate = request.GeDateStart.Value.ToUniversalTime();
+            query = query.Where(o => o.DateStart >= geDate);
+        }
+        
+        if (request.LeDateEnd is not null)
+        {
+            var leDate = request.LeDateEnd.Value.ToUniversalTime();
+            query = query.Where(o => o.DateEnd <= leDate);
+        }
+
+        if (request.SortFields?.Any() == true)
+        {
+            query = query.SortBy(request.SortFields);
+        }
+        else
+            query = query
+                .OrderBy(o => o.DateStart)
+                .ThenBy(o => o.DepthStart);
+
+        if (request.Skip.HasValue)
+            query = query.Skip((int)request.Skip);
+
+        if (request.Take.HasValue)
+            query = query.Take((int)request.Take);
+
+        return query;
+    }
+
+    private static DetectedOperationDto Convert(DetectedOperation entity, TimeSpan offset)
+    {
+        var dto = entity.Adapt<DetectedOperationDto>();
+        dto.DateStart = entity.DateStart.ToOffset(offset);
+        dto.DateEnd = entity.DateEnd.ToOffset(offset);
+        return dto;
+    }
+
+    private static DetectedOperation Convert(DetectedOperationDto dto)
+    {
+        var entity = dto.Adapt<DetectedOperation>();
+        entity.DateStart = dto.DateStart.ToUniversalTime();
+        entity.DateEnd = dto.DateEnd.ToUniversalTime();
+        return entity;
+    }
+}
diff --git a/AsbCloudInfrastructure/Repository/ProcessMapPlanBaseRepository.cs b/AsbCloudInfrastructure/Repository/ProcessMapPlanBaseRepository.cs
index 0133b527..7da062ac 100644
--- a/AsbCloudInfrastructure/Repository/ProcessMapPlanBaseRepository.cs
+++ b/AsbCloudInfrastructure/Repository/ProcessMapPlanBaseRepository.cs
@@ -23,7 +23,7 @@ public class ProcessMapPlanBaseRepository<TDto, TEntity> : ChangeLogRepositoryAb
 
     protected override IQueryable<TEntity> BuildQuery(ProcessMapPlanBaseRequestWithWell request)
     {
-        var query = context
+        var query = db
             .Set<TEntity>()
             .Include(e => e.Author)
             .Include(e => e.Editor)
diff --git a/AsbCloudInfrastructure/Repository/WellOperationCategoryRepository.cs b/AsbCloudInfrastructure/Repository/WellOperationCategoryRepository.cs
new file mode 100644
index 00000000..84da5f3d
--- /dev/null
+++ b/AsbCloudInfrastructure/Repository/WellOperationCategoryRepository.cs
@@ -0,0 +1,44 @@
+using AsbCloudApp.Data;
+using AsbCloudApp.Repositories;
+using AsbCloudDb.Model;
+using Mapster;
+using Microsoft.Extensions.Caching.Memory;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace AsbCloudInfrastructure.Repository;
+
+public class WellOperationCategoryRepository : IWellOperationCategoryRepository
+{
+    private readonly IAsbCloudDbContext db;
+    private readonly IMemoryCache memoryCache;
+
+    public WellOperationCategoryRepository(IAsbCloudDbContext db, IMemoryCache memoryCache)
+    {
+        this.db = db;
+        this.memoryCache = memoryCache;
+    }
+
+    public IEnumerable<WellOperationCategoryDto> Get(bool includeParents)
+    {
+        var categories = memoryCache
+            .GetOrCreateBasic(db.Set<WellOperationCategory>());
+
+        if (!includeParents)
+        {
+            var parentIds = categories
+                .Select(o => o.IdParent)
+                .Distinct();
+
+            categories = categories
+                .Where(o => !parentIds.Contains(o.Id));
+        }
+
+        var result = categories
+            .OrderBy(o => o.Name)
+            .Adapt<IEnumerable<WellOperationCategoryDto>>();
+
+        return result;
+    }
+}
diff --git a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs
index c9d3c7fc..1330a282 100644
--- a/AsbCloudInfrastructure/Repository/WellOperationRepository.cs
+++ b/AsbCloudInfrastructure/Repository/WellOperationRepository.cs
@@ -28,35 +28,20 @@ public class WellOperationRepository : IWellOperationRepository
     private readonly IAsbCloudDbContext db;
     private readonly IMemoryCache memoryCache;
     private readonly IWellService wellService;
+    private readonly IWellOperationCategoryRepository wellOperationCategoryRepository;
 
-    public WellOperationRepository(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService)
+    public WellOperationRepository(IAsbCloudDbContext db, IMemoryCache memoryCache, IWellService wellService, IWellOperationCategoryRepository wellOperationCategoryRepository)
     {
         this.db = db;
         this.memoryCache = memoryCache;
         this.wellService = wellService;
+        this.wellOperationCategoryRepository = wellOperationCategoryRepository;
     }
 
     /// <inheritdoc/>
     public IEnumerable<WellOperationCategoryDto> GetCategories(bool includeParents)
     {
-        var categories = memoryCache
-            .GetOrCreateBasic(db.Set<WellOperationCategory>());
-
-        if (!includeParents)
-        {
-            var parentIds = categories
-                .Select(o => o.IdParent)
-                .Distinct();
-
-            categories = categories
-                .Where(o => !parentIds.Contains(o.Id));
-        }
-
-        var result = categories
-            .OrderBy(o => o.Name)
-            .Adapt<IEnumerable<WellOperationCategoryDto>>();
-
-        return result;
+        return wellOperationCategoryRepository.Get(includeParents);
     }
 
     /// <inheritdoc/>
diff --git a/AsbCloudInfrastructure/Services/DailyReport/DailyReportService.cs b/AsbCloudInfrastructure/Services/DailyReport/DailyReportService.cs
index e4271c8f..87108fd2 100644
--- a/AsbCloudInfrastructure/Services/DailyReport/DailyReportService.cs
+++ b/AsbCloudInfrastructure/Services/DailyReport/DailyReportService.cs
@@ -282,7 +282,7 @@ public class DailyReportService : IDailyReportService
 			var leDateEnd = dailyReport.Date.AddDays(1).ToDateTime(TimeOnly.MinValue, DateTimeKind.Unspecified);
 			
 			dailyReport.TimeBalanceBlock.WellOperationSlipsTimeCount = (await detectedOperationService.GetAsync(
-				new DetectedOperationRequest
+				new DetectedOperationByWellRequest
 				{
 					IdsCategories = new[] { idWellOperationSlipsTime },
 					IdWell = dailyReport.IdWell,
diff --git a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationExportService.cs b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationExportService.cs
index ef781a1e..987f274f 100644
--- a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationExportService.cs
+++ b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationExportService.cs
@@ -1,6 +1,5 @@
 using AsbCloudDb.Model;
 using ClosedXML.Excel;
-using Microsoft.EntityFrameworkCore;
 using System;
 using System.Collections.Generic;
 using System.IO;
@@ -9,22 +8,21 @@ using System.Reflection;
 using System.Threading;
 using System.Threading.Tasks;
 using AsbCloudApp.Data.DetectedOperation;
-using AsbCloudInfrastructure.Services.DetectOperations.Detectors;
 using AsbCloudApp.Repositories;
 using Microsoft.AspNetCore.Http.Extensions;
 using AsbCloudApp.Exceptions;
+using AsbCloudApp.Services;
+using AsbCloudApp.Data;
+using AsbCloudInfrastructure.Services.DetectOperations.Detectors;
 
 namespace AsbCloudInfrastructure.Services.DetectOperations;
 
 public class DetectedOperationExportService
 {
-	private readonly DetectorAbstract[] detectors =
-	{
-		new DetectorDrilling(),
-		new DetectorSlipsTime()
-	};
-
-	private const int headerRowsCount = 1;
+    private readonly IAsbCloudDbContext db;
+    private readonly IWellService wellService;
+    private readonly IWellOperationCategoryRepository wellOperationCategoryRepository;
+    private const int headerRowsCount = 1;
 
 	private const string cellDepositName = "B1";
 	private const string cellClusterName = "B2";
@@ -42,14 +40,14 @@ public class DetectedOperationExportService
 	private const int columnIdReasonOfEnd = 9;
 	private const int columnComment = 10;
 
-	//TODO: удалить неиспользуемую зависимость
-	private readonly IAsbCloudDbContext dbContext;
-	private readonly IWellOperationRepository wellOperationRepository;
-
-    public DetectedOperationExportService(IAsbCloudDbContext dbContext, IWellOperationRepository wellOperationRepository)
+    public DetectedOperationExportService(
+		IAsbCloudDbContext db,
+		IWellService wellService,
+		IWellOperationCategoryRepository wellOperationCategoryRepository)
 	{
-		this.dbContext = dbContext;
-        this.wellOperationRepository = wellOperationRepository;
+        this.db = db;
+        this.wellService = wellService;
+        this.wellOperationCategoryRepository = wellOperationCategoryRepository;
     }
 
     /// <summary>
@@ -57,15 +55,12 @@ public class DetectedOperationExportService
     /// </summary>
     /// <param name="idWell">ключ скважины</param>
     /// <param name="host">хост</param>
-    /// <param name="cancellationToken"></param>
+    /// <param name="token"></param>
     /// <returns></returns>
     /// <exception cref="ArgumentInvalidException"></exception>
-    public async Task<Stream> ExportAsync(int idWell, string host, CancellationToken cancellationToken)
+    public async Task<Stream> ExportAsync(int idWell, string host, CancellationToken token)
 	{
-		var well = await dbContext.Set<Well>()
-			.Include(w => w.Cluster)
-			.ThenInclude(c => c.Deposit)
-			.FirstOrDefaultAsync(w => w.Id == idWell, cancellationToken);
+		var well = await wellService.GetOrDefaultAsync(idWell, token);
 
 		if (well is null)
 			throw new ArgumentInvalidException(nameof(idWell), $"Well {idWell} does not exist");
@@ -73,19 +68,19 @@ public class DetectedOperationExportService
 		if (!well.IdTelemetry.HasValue)
             throw new ArgumentInvalidException(nameof(idWell), $"Well {idWell} has no telemetry");
 
-        var operations = await DetectOperationsAsync(well.IdTelemetry.Value, DateTime.UnixEpoch, cancellationToken);
+        var operations = await WorkOperationDetection.DetectOperationsAsync(well.IdTelemetry.Value, DateTime.UnixEpoch, db, token);
 
-		return await GenerateExcelFileStreamAsync(well, host, operations, cancellationToken);
+		return await GenerateExcelFileStreamAsync(well, host, operations, token);
 	}
 
-	private async Task<Stream> GenerateExcelFileStreamAsync(Well well, string host, IEnumerable<OperationDetectorResult> operationDetectorResults,
+	private async Task<Stream> GenerateExcelFileStreamAsync(WellDto well, string host, IEnumerable<DetectedOperation> operationDetectorResults,
 		CancellationToken cancellationToken)
 	{
 		using var excelTemplateStream = await GetExcelTemplateStreamAsync(cancellationToken);
 
 		using var workbook = new XLWorkbook(excelTemplateStream);
 
-		await AddToWorkbookAsync(workbook, well, host, operationDetectorResults, cancellationToken);
+		AddToWorkbook(workbook, well, host, operationDetectorResults);
 
 		MemoryStream memoryStream = new MemoryStream();
 		workbook.SaveAs(memoryStream, new SaveOptions { });
@@ -93,36 +88,34 @@ public class DetectedOperationExportService
 		return memoryStream;
 	}
 
-	private async Task AddToWorkbookAsync(XLWorkbook workbook, Well well, string host, IEnumerable<OperationDetectorResult> operationDetectorResults,
-		CancellationToken cancellationToken)
+	private void AddToWorkbook(XLWorkbook workbook, WellDto well, string host, IEnumerable<DetectedOperation> operations)
 	{
 		const string sheetName = "Операции";
 
-		if (!operationDetectorResults.Any())
+		if (!operations.Any())
 			return;
 
 		var sheet = workbook.GetWorksheet(sheetName);
 
-		await AddToSheetAsync(sheet, well, host, operationDetectorResults
-				.OrderBy(x => x.Operation.DateStart).ThenBy(x => x.Operation.DepthStart).ToArray(),
-			cancellationToken);
+		var orderedOperations = operations
+				.OrderBy(x => x.DateStart)
+				.ThenBy(x => x.DepthStart).ToArray();
+
+        AddToSheet(sheet, well, host, orderedOperations);
 	}
 
-	private async Task AddToSheetAsync(IXLWorksheet sheet, Well well, string host, IList<OperationDetectorResult> operationDetectorResults,
-		CancellationToken cancellationToken)
+	private void AddToSheet(IXLWorksheet sheet, WellDto well, string host, IList<DetectedOperation> operations)
 	{
-		var wellOperationCategories = await dbContext.WellOperationCategories.ToListAsync(cancellationToken);
+		var wellOperationCategories = wellOperationCategoryRepository.Get(true);
 
-		sheet.Cell(cellDepositName).SetCellValue(well.Cluster.Deposit.Caption);
-		sheet.Cell(cellClusterName).SetCellValue(well.Cluster.Caption);
+		sheet.Cell(cellDepositName).SetCellValue(well.Deposit);
+		sheet.Cell(cellClusterName).SetCellValue(well.Cluster);
 		sheet.Cell(cellWellName).SetCellValue(well.Caption);
-		sheet.Cell(cellDeltaDate).SetCellValue(operationDetectorResults.Max(o => o.Operation.DateEnd) - operationDetectorResults.Min(o => o.Operation.DateStart));
+		sheet.Cell(cellDeltaDate).SetCellValue((TimeSpan)(Enumerable.Max<DetectedOperation, DateTimeOffset>(operations, (Func<DetectedOperation, DateTimeOffset>)(o => o.DateEnd)) - Enumerable.Min<DetectedOperation, DateTimeOffset>(operations, (Func<DetectedOperation, DateTimeOffset>)(o => o.DateStart))));
 
-		var detectedOperations = operationDetectorResults.Select(o => o.Operation).ToArray();
-
-		for (int i = 0; i < operationDetectorResults.Count; i++)
+		for (int i = 0; i < operations.Count; i++)
 		{
-			var current = detectedOperations[i];
+			var current = operations[i];
             var dateStart = current.DateStart.ToRemoteDateTime(well.Timezone.Hours);
 			var dateEnd = current.DateEnd.ToRemoteDateTime(well.Timezone.Hours);
 
@@ -153,17 +146,18 @@ public class DetectedOperationExportService
             var link = $"{host}/well/{well.Id}/telemetry/monitoring{query}";
 			row.Cell(columnDateStart).SetHyperlink(link);
 			
-			var deltaDepth = i > 0 && i + 1 < detectedOperations.Length
-				? current.DepthStart - detectedOperations[i - 1].DepthEnd
+			var deltaDepth = i > 0 && i + 1 < operations.Count
+                ? current.DepthStart - operations[i - 1].DepthEnd
 				: 0;
+
 			row.Cell(columnDeltaDepth).SetCellValue(deltaDepth);
 
-			var comment = CreateComment(operationDetectorResults[i]);
+			var comment = CreateComment(operations[i]);
 			row.Cell(columnComment).SetCellValue(comment);
 		}
 	}
 
-    private static string GetCategoryName(IEnumerable<WellOperationCategory> wellOperationCategories, DetectedOperation current)
+    private static string GetCategoryName(IEnumerable<WellOperationCategoryDto> wellOperationCategories, DetectedOperation current)
     {
 		var idCategory = current.IdCategory;
 		if (idCategory == WellOperationCategory.IdSlide &&
@@ -204,9 +198,8 @@ public class DetectedOperationExportService
 		return memoryStream;
 	}
 	
-	private static string CreateComment(OperationDetectorResult operationDetectorResult)
+	private static string CreateComment(DetectedOperation operation)
 	{
-		var operation = operationDetectorResult.Operation;
 		switch (operation.IdCategory)
 		{
 			case WellOperationCategory.IdRotor:
@@ -224,69 +217,4 @@ public class DetectedOperationExportService
 				return string.Empty;
 		}
 	}
-
-	private async Task<IEnumerable<OperationDetectorResult>> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin,
-		CancellationToken token)
-	{
-		var query = dbContext.TelemetryDataSaub
-			.AsNoTracking()
-			.Where(d => d.IdTelemetry == idTelemetry)
-			.Where(d => d.BlockPosition >= 0)
-			.Select(d => new DetectableTelemetry
-			{
-				DateTime = d.DateTime,
-				IdUser = d.IdUser,
-				WellDepth = d.WellDepth,
-				Pressure = d.Pressure,
-				HookWeight = d.HookWeight,
-				BlockPosition = d.BlockPosition,
-				BitDepth = d.BitDepth,
-				RotorSpeed = d.RotorSpeed,
-			})
-			.OrderBy(d => d.DateTime);
-
-		var startDate = begin;
-		var detectedOperationResults = new List<OperationDetectorResult>(8);
-		DetectedOperation? lastDetectedOperation = null;
-		const int minOperationLength = 5;
-		const int maxDetectorsInterpolationFrameLength = 30;
-		const int gap = maxDetectorsInterpolationFrameLength + minOperationLength;
-
-		while (true)
-		{
-			var data = await query
-				.Where(d => d.DateTime > startDate)
-				.ToArrayAsync(token);
-
-			if (data.Length < gap)
-				break;
-
-			var isDetected = false;
-			var positionBegin = 0;
-			var positionEnd = data.Length - gap;
-			while (positionEnd > positionBegin)
-			{
-				foreach (var detector in detectors)
-				{
-					if (!detector.TryDetect(idTelemetry, data, positionBegin, positionEnd, lastDetectedOperation, out var result))
-						continue;
-					
-					detectedOperationResults.Add(result!);
-					lastDetectedOperation = result!.Operation;
-					isDetected = true;
-					positionBegin = result.TelemetryEnd;
-					break;
-				}
-				
-				positionBegin += 1;
-			}
-
-			if (isDetected)
-				startDate = lastDetectedOperation!.DateEnd;
-			else
-				startDate = data[positionEnd].DateTime;
-		}
-
-		return detectedOperationResults;
-	}
 }
\ No newline at end of file
diff --git a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs
index 23a5d20b..bd8fba77 100644
--- a/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs
+++ b/AsbCloudInfrastructure/Services/DetectOperations/DetectedOperationService.cs
@@ -1,5 +1,6 @@
 using AsbCloudApp.Data;
 using AsbCloudApp.Data.DetectedOperation;
+using AsbCloudApp.Repositories;
 using AsbCloudApp.Requests;
 using AsbCloudApp.Services;
 using AsbCloudDb;
@@ -11,274 +12,200 @@ using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 
-namespace AsbCloudInfrastructure.Services.DetectOperations
+namespace AsbCloudInfrastructure.Services.DetectOperations;
+
+public class DetectedOperationService : IDetectedOperationService
 {
+    private readonly IDetectedOperationRepository operationRepository;
+    private readonly IWellOperationCategoryRepository wellOperationCategoryRepository;
+    private readonly IWellService wellService;
+    private readonly IRepositoryWellRelated<OperationValueDto> operationValueService;
+    private readonly IScheduleRepository scheduleService;
 
-    public class DetectedOperationService : IDetectedOperationService
+    public DetectedOperationService(
+        IDetectedOperationRepository operationRepository,
+        IWellOperationCategoryRepository wellOperationCategoryRepository,
+        IWellService wellService,
+        IRepositoryWellRelated<OperationValueDto> operationValueRepository, 
+        IScheduleRepository scheduleRepository)
     {
-        private readonly IAsbCloudDbContext db;
-        private readonly IWellService wellService;
-        private readonly IRepositoryWellRelated<OperationValueDto> operationValueService;
-        private readonly IScheduleRepository scheduleService;
+        this.operationRepository = operationRepository;
+        this.wellOperationCategoryRepository = wellOperationCategoryRepository;
+        this.wellService = wellService;
+        this.operationValueService = operationValueRepository;
+        this.scheduleService = scheduleRepository;
+    }
 
-        public DetectedOperationService(IAsbCloudDbContext db, IWellService wellService,
-            IRepositoryWellRelated<OperationValueDto> operationValueService, IScheduleRepository scheduleService)
+    public async Task<DetectedOperationListDto> GetAsync(DetectedOperationByWellRequest request, CancellationToken token)
+    {
+        var dtos = await GetOperationsAsync(request, token);
+        if (dtos?.Any() != true)
+            return new DetectedOperationListDto();
+
+        var stats = GetOperationsDrillersStat(dtos);
+        var result = new DetectedOperationListDto
         {
-            this.db = db;
-            this.wellService = wellService;
-            this.operationValueService = operationValueService;
-            this.scheduleService = scheduleService;
+            Operations = dtos,
+            Stats = stats
+        };
+        return result;
+    }
+
+    public async Task<IEnumerable<DetectedOperationWithDrillerDto>> GetOperationsAsync(DetectedOperationByWellRequest request, CancellationToken token)
+    {
+        var well = await wellService.GetOrDefaultAsync(request.IdWell, token);
+        if (well?.IdTelemetry is null)
+            return Enumerable.Empty<DetectedOperationWithDrillerDto>();
+
+        var requestByTelemetry = new DetectedOperationByTelemetryRequest(well.IdTelemetry.Value, request);
+        var data = await operationRepository.Get(requestByTelemetry, token);
+
+        var operationValues = await operationValueService.GetByIdWellAsync(request.IdWell, token);
+        var schedules = await scheduleService.GetByIdWellAsync(request.IdWell, token);
+        var dtos = data.Select(o => Convert(o, operationValues, schedules));
+        return dtos;
+    }
+
+    public async Task<IEnumerable<WellOperationCategoryDto>> GetCategoriesAsync(int? idWell, CancellationToken token)
+    {
+        if(idWell is null)
+        {
+            return wellOperationCategoryRepository.Get(false);
         }
-
-        public async Task<DetectedOperationListDto?> GetAsync(DetectedOperationRequest request, CancellationToken token)
+        else
         {
-            var dtos = await GetOperationsAsync(request, token);
-            if (dtos?.Any() != true)
-                return null;
-
-            var stats = GetOperationsDrillersStat(dtos);
-            var result = new DetectedOperationListDto
-            {
-                Operations = dtos,
-                Stats = stats
-            };
-            return result;
-        }
-
-        public async Task<IEnumerable<DetectedOperationDto>> GetOperationsAsync(DetectedOperationRequest request, CancellationToken token)
-        {
-            var well = await wellService.GetOrDefaultAsync(request.IdWell, token);
+            var well = await wellService.GetOrDefaultAsync((int )idWell, token);
             if (well?.IdTelemetry is null)
-                return Enumerable.Empty<DetectedOperationDto>();
+                return Enumerable.Empty<WellOperationCategoryDto>();
 
-            var query = BuildQuery(well, request).AsNoTracking();
-
-            var data = await query.ToListAsync(token);
-
-            var operationValues = await operationValueService.GetByIdWellAsync(request.IdWell, token);
-            var schedules = await scheduleService.GetByIdWellAsync(request.IdWell, token);
-            var dtos = data.Select(o => Convert(o, well, operationValues, schedules));
-            return dtos;
-        }        
-
-        private static IEnumerable<DetectedOperationDrillersStatDto> GetOperationsDrillersStat(IEnumerable<DetectedOperationDto> operations)
-        {
-            var groups = operations.GroupBy(o => o.Driller);
-
-            var stats = new List<DetectedOperationDrillersStatDto>(groups.Count());
-            foreach (var group in groups)
-            {
-                var itemsWithTarget = group.Where(i => i.OperationValue is not null);
-                var stat = new DetectedOperationDrillersStatDto
-                {
-                    Driller = group.Key,
-                    AverageValue = group.Sum(e => e.Value) / group.Count(),
-                    Count = group.Count(),
-                };
-                if (itemsWithTarget.Any())
-                {
-                    var itemsOutOfTarget = itemsWithTarget.Where(o => !IsTargetOk(o));
-                    stat.AverageTargetValue = itemsWithTarget.Average(e => e.OperationValue?.TargetValue);
-                    stat.Efficiency = 100d * itemsOutOfTarget.Count() / itemsWithTarget.Count();
-                    stat.Loss = itemsOutOfTarget.Sum(DeltaToTarget);
-                }
-
-                stats.Add(stat);
-            }
-            return stats;
-        }
-
-        public async Task<IEnumerable<DetectedOperationStatDto>?> GetOperationsStatAsync(DetectedOperationRequest request, CancellationToken token)
-        {
-            var well = await wellService.GetOrDefaultAsync(request.IdWell, token);
-            if (well?.IdTelemetry is null || well.Timezone is null)
-                return null;
-
-            var query = BuildQuery(well, request)
-                ?.AsNoTracking();
-
-            if (query is null)
-                return null;
-
-            var entities = await query
-                .Select(o => new {
-                    o.IdCategory,
-                    DurationMinutes = (o.DateEnd - o.DateStart).TotalMinutes,
-                    o.Value,
-                })
-                .ToListAsync(token);
-
-            if (!entities.Any())
-                return null;
-
-            var operationValues = await operationValueService.GetByIdWellAsync(request.IdWell, token);
-            var categories = await query
-                .Select(o => new {o.IdCategory, o.OperationCategory.Name })
-                .Distinct()
-                .ToDictionaryAsync(c=>c.IdCategory, c=>c.Name, token);
-
-            var dtos = entities
-                .GroupBy(o => o.IdCategory)
-                .OrderBy(g => g.Key)
-                .Select(g => new DetectedOperationStatDto{
-                    IdCategory = g.Key,
-                    Category = categories[g.Key],
-                    Count = g.Count(),
-                    MinutesAverage = g.Average(o => o.DurationMinutes),
-                    MinutesMin = g.Min(o => o.DurationMinutes),
-                    MinutesMax = g.Max(o => o.DurationMinutes),
-                    MinutesTotal = g.Sum(o => o.DurationMinutes),
-                    ValueAverage = g.Average(o => o.Value),
-                    ValueMax = g.Max(o => o.Value),
-                    ValueMin = g.Min(o => o.Value),
-                });
-
-            return dtos;
-        }
-
-        public async Task<int> DeleteAsync(DetectedOperationRequest request, CancellationToken token)
-        {
-            var well = await wellService.GetOrDefaultAsync(request.IdWell, token);
-            if (well?.IdTelemetry is null || well.Timezone is null)
-                return 0;
-
-            var query = BuildQueryBase(well, request);
-
-            if (query is null)
-                return 0;
-
-            db.DetectedOperations.RemoveRange(query);
-            return await db.SaveChangesAsync(token);
-        }
-
-        private static bool IsTargetOk(DetectedOperationDto op)
-        {
-            return (op.IdCategory) switch
-            {
-                WellOperationCategory.IdRotor => op.Value > op.OperationValue?.TargetValue,
-                WellOperationCategory.IdSlide => op.Value > op.OperationValue?.TargetValue,
-                WellOperationCategory.IdSlipsTime => op.Value > op.OperationValue?.TargetValue,
-                _ => op.Value > op.OperationValue?.TargetValue,
+            var request = new DetectedOperationByTelemetryRequest() 
+            { 
+                IdTelemetry = well.IdTelemetry.Value 
             };
-        }
 
-        private static double DeltaToTarget(DetectedOperationDto op)
-        {
-            return (op.IdCategory) switch
-            {
-                WellOperationCategory.IdRotor => 0,
-                WellOperationCategory.IdSlide => 0,
-                WellOperationCategory.IdSlipsTime => op.Value - op.OperationValue?.TargetValue??0,
-                _ => 0,
-            };
-        }
-
-        private IQueryable<DetectedOperation> BuildQueryBase(WellDto well, DetectedOperationRequest request)
-        {
-            var query = db.Set<DetectedOperation>()
-                .Where(o => o.IdTelemetry == well.IdTelemetry);
-
-            if (request.IdsTelemetries.Any())
-            {
-                query = query
-                    .Union(db.Set<DetectedOperation>().Where(o => request.IdsTelemetries.Contains(o.IdTelemetry)));
-            }                
-                
-            if (request.IdsCategories.Any())
-                query = query.Where(o => request.IdsCategories.Contains(o.IdCategory));
-
-            if (request.GeDateStart is not null)
-                query = query.Where(o => o.DateStart >= request.GeDateStart.Value.Date.ToUtcDateTimeOffset(well.Timezone.Hours));
-
-            if (request.LeDateEnd is not null)
-                query = query.Where(o => o.DateEnd <= request.LeDateEnd.Value.Date.ToUtcDateTimeOffset(well.Timezone.Hours));
-
-            if (request.GeDepth is not null)
-                query = query.Where(o => o.DepthStart >= request.GeDepth);
-
-            if (request.LeDepth is not null)
-                query = query.Where(o => o.DepthEnd <= request.LeDepth);
-
-            if (request.IdTelemetryUser is not null)
-                query = query.Where(o => o.IdUsersAtStart == request.IdTelemetryUser);
-
-            return query;
-        }
-
-        private IQueryable<DetectedOperation> BuildQuery(WellDto well, DetectedOperationRequest request)
-        {
-            IQueryable<DetectedOperation> query = BuildQueryBase(well, request)
-                .Include(o => o.OperationCategory);
-
-            if (request.SortFields?.Any() == true)
-            {
-                query = query.SortBy(request.SortFields);
-            }
-            else
-                query = query
-                    .OrderBy(o => o.DateStart)
-                    .ThenBy(o => o.DepthStart);
-
-            if (request.Skip.HasValue)
-                query = query.Skip((int)request.Skip);
-
-            if (request.Take.HasValue)
-                query = query.Take((int)request.Take);
-
-            return query;
-        }
-
-        private static DetectedOperationDto Convert(DetectedOperation operation, WellDto well, IEnumerable<OperationValueDto> operationValues, IEnumerable<ScheduleDto> schedules)
-        {
-            var dto = operation.Adapt<DetectedOperationDto>();
-            dto.IdWell = well.Id;
-            var dateStart = operation.DateStart.ToRemoteDateTime(well.Timezone.Hours);
-            dto.DateStart = dateStart;
-            dto.DateEnd = operation.DateEnd.ToRemoteDateTime(well.Timezone.Hours);
-            dto.OperationValue = operationValues.FirstOrDefault(v => v.IdOperationCategory == dto.IdCategory
-                  && v.DepthStart <= dto.DepthStart
-                  && v.DepthEnd > dto.DepthStart);
-
-            var timeStart = new TimeDto(dateStart);
-            var driller = schedules.FirstOrDefault(s =>
-                s.DrillStart <= dateStart &&
-                s.DrillEnd > dateStart && (
-                s.ShiftStart > s.ShiftEnd
-                ) ^ (s.ShiftStart <= timeStart &&
-                s.ShiftEnd > timeStart
-                ))
-                ?.Driller;
-            dto.Driller = driller;
-            return dto;
-        }
-
-        public async Task<IEnumerable<WellOperationCategoryDto>?> GetCategoriesAsync(int? idWell, CancellationToken token)
-        {
-            IQueryable<WellOperationCategory> query;
-            if(idWell is null)
-            {
-                query = db.WellOperationCategories;
-            }
-            else
-            {
-                var well = await wellService.GetOrDefaultAsync((int )idWell, token);
-                if (well?.IdTelemetry is null)
-                    return null;
-                var idTelemetry = (int)well.IdTelemetry;
-                query = db.DetectedOperations
-                    .Include(o => o.OperationCategory)
-                    .Where(o => o.IdTelemetry == idTelemetry)
-                    .Select(o => o.OperationCategory)
-                    .Distinct();
-            }
-
-            var result = await query
-                .Where(c => c.Id < 1000)
-                .AsNoTracking()
-                .Select(c => c.Adapt<WellOperationCategoryDto>())
-                .ToArrayAsync(token);
-            return result;
+            var operations = await operationRepository.Get(request, token);
+            var categories = operations
+                .Select(o => o.OperationCategory)
+                .Distinct();
+            return categories;
         }
     }
 
+    public async Task<IEnumerable<DetectedOperationStatDto>> GetOperationsStatAsync(DetectedOperationByWellRequest request, CancellationToken token)
+    {
+        var well = await wellService.GetOrDefaultAsync(request.IdWell, token);
+        if (well?.IdTelemetry is null || well.Timezone is null)
+            return Enumerable.Empty<DetectedOperationStatDto>();
+
+        var requestByTelemetry = new DetectedOperationByTelemetryRequest(well.IdTelemetry.Value, request);
+
+        var operations = await operationRepository.Get(requestByTelemetry, token);
+
+        if (!operations.Any())
+            return Enumerable.Empty<DetectedOperationStatDto>();
+
+        var operationValues = await operationValueService.GetByIdWellAsync(request.IdWell, token);
+
+        var dtos = operations
+            .GroupBy(o => (o.IdCategory, o.OperationCategory.Name))
+            .OrderBy(g => g.Key)
+            .Select(g => new DetectedOperationStatDto
+            {
+                IdCategory = g.Key.IdCategory,
+                Category = g.Key.Name,
+                Count = g.Count(),
+                MinutesAverage = g.Average(o => o.DurationMinutes),
+                MinutesMin = g.Min(o => o.DurationMinutes),
+                MinutesMax = g.Max(o => o.DurationMinutes),
+                MinutesTotal = g.Sum(o => o.DurationMinutes),
+                ValueAverage = g.Average(o => o.Value),
+                ValueMax = g.Max(o => o.Value),
+                ValueMin = g.Min(o => o.Value),
+            });
+
+        return dtos;
+    }
+
+    public async Task<int> DeleteAsync(DetectedOperationByWellRequest request, CancellationToken token)
+    {
+        var well = await wellService.GetOrDefaultAsync(request.IdWell, token);
+        if (well?.IdTelemetry is null || well.Timezone is null)
+            return 0;
+
+        var requestByTelemetry = new DetectedOperationByTelemetryRequest(well.IdTelemetry.Value, request);
+        var result = await operationRepository.Delete(-1, requestByTelemetry, token);
+        return result;
+    }
+
+    private static IEnumerable<DetectedOperationDrillersStatDto> GetOperationsDrillersStat(IEnumerable<DetectedOperationWithDrillerDto> operations)
+    {
+        var groups = operations.GroupBy(o => o.Driller);
+
+        var stats = new List<DetectedOperationDrillersStatDto>(groups.Count());
+        foreach (var group in groups)
+        {
+            var itemsWithTarget = group.Where(i => i.OperationValue is not null);
+            var stat = new DetectedOperationDrillersStatDto
+            {
+                Driller = group.Key,
+                AverageValue = group.Sum(e => e.Value) / group.Count(),
+                Count = group.Count(),
+            };
+            if (itemsWithTarget.Any())
+            {
+                var itemsOutOfTarget = itemsWithTarget.Where(o => !IsTargetOk(o));
+                stat.AverageTargetValue = itemsWithTarget.Average(e => e.OperationValue?.TargetValue);
+                stat.Efficiency = 100d * itemsOutOfTarget.Count() / itemsWithTarget.Count();
+                stat.Loss = itemsOutOfTarget.Sum(DeltaToTarget);
+            }
+
+            stats.Add(stat);
+        }
+        return stats;
+    }
+
+    private static bool IsTargetOk(DetectedOperationWithDrillerDto op)
+    {
+        return (op.IdCategory) switch
+        {
+            WellOperationCategory.IdRotor => op.Value > op.OperationValue?.TargetValue,
+            WellOperationCategory.IdSlide => op.Value > op.OperationValue?.TargetValue,
+            WellOperationCategory.IdSlipsTime => op.Value > op.OperationValue?.TargetValue,
+            _ => op.Value > op.OperationValue?.TargetValue,
+        };
+    }
+
+    private static double DeltaToTarget(DetectedOperationWithDrillerDto op)
+    {
+        return (op.IdCategory) switch
+        {
+            WellOperationCategory.IdRotor => 0,
+            WellOperationCategory.IdSlide => 0,
+            WellOperationCategory.IdSlipsTime => op.Value - op.OperationValue?.TargetValue??0,
+            _ => 0,
+        };
+    }
+
+
+    private static DetectedOperationWithDrillerDto Convert(DetectedOperationDto operation, IEnumerable<OperationValueDto> operationValues, IEnumerable<ScheduleDto> schedules)
+    {
+        var dto = operation.Adapt<DetectedOperationWithDrillerDto>();
+        dto.OperationValue = operationValues.FirstOrDefault(v => v.IdOperationCategory == dto.IdCategory
+              && v.DepthStart <= dto.DepthStart
+              && v.DepthEnd > dto.DepthStart);
+        
+        var dateStart = dto.DateStart;
+        var timeStart = new TimeDto(dateStart);
+        var driller = schedules.FirstOrDefault(s =>
+            s.DrillStart <= dateStart &&
+            s.DrillEnd > dateStart && (
+            s.ShiftStart > s.ShiftEnd
+            ) ^ (s.ShiftStart <= timeStart &&
+            s.ShiftEnd > timeStart
+            ))
+            ?.Driller;
+        dto.Driller = driller;
+
+        return dto;
+    }
 }
diff --git a/AsbCloudInfrastructure/Services/DetectOperations/WorkOperationDetection.cs b/AsbCloudInfrastructure/Services/DetectOperations/WorkOperationDetection.cs
index 2dc09b02..aece226e 100644
--- a/AsbCloudInfrastructure/Services/DetectOperations/WorkOperationDetection.cs
+++ b/AsbCloudInfrastructure/Services/DetectOperations/WorkOperationDetection.cs
@@ -86,7 +86,8 @@ public class WorkOperationDetection: Work
         }
     }
 
-    private static async Task<IEnumerable<DetectedOperation>> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token)
+    //todo: move this logic to DetectedOperationsService
+    internal static async Task<IEnumerable<DetectedOperation>> DetectOperationsAsync(int idTelemetry, DateTimeOffset begin, IAsbCloudDbContext db, CancellationToken token)
     {
         var query = db.TelemetryDataSaub
             .AsNoTracking()
diff --git a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataSaubService.cs b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataSaubService.cs
index d918f384..fdced02c 100644
--- a/AsbCloudInfrastructure/Services/SAUB/TelemetryDataSaubService.cs
+++ b/AsbCloudInfrastructure/Services/SAUB/TelemetryDataSaubService.cs
@@ -1,5 +1,4 @@
-using AsbCloudApp.Data;
-using AsbCloudApp.Data.SAUB;
+using AsbCloudApp.Data.SAUB;
 using AsbCloudApp.Exceptions;
 using AsbCloudApp.Repositories;
 using AsbCloudApp.Services;
@@ -11,142 +10,163 @@ using System.Collections.Generic;
 using System.IO;
 using System.IO.Compression;
 using System.Linq;
-using System.Text;
 using System.Text.Csv;
 using System.Threading;
 using System.Threading.Tasks;
 
-namespace AsbCloudInfrastructure.Services.SAUB
+namespace AsbCloudInfrastructure.Services.SAUB;
+
+public class TelemetryDataSaubService : TelemetryDataBaseService<TelemetryDataSaubDto, TelemetryDataSaub>, ITelemetryDataSaubService
 {
+    private readonly ITelemetryUserService telemetryUserService;
 
-    public class TelemetryDataSaubService : TelemetryDataBaseService<TelemetryDataSaubDto, TelemetryDataSaub>, ITelemetryDataSaubService
+    public TelemetryDataSaubService(
+        IAsbCloudDbContext db,
+        ITelemetryService telemetryService,
+        ITelemetryUserService telemetryUserService,
+        ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache)
+        : base(db, telemetryService, telemetryDataCache)
     {
-        private readonly ITelemetryUserService telemetryUserService;
-
-        public TelemetryDataSaubService(
-            IAsbCloudDbContext db,
-            ITelemetryService telemetryService,
-            ITelemetryUserService telemetryUserService,
-            ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache)
-            : base(db, telemetryService, telemetryDataCache)
-        {
-            this.telemetryUserService = telemetryUserService;
-        }
-
-        public async Task<IEnumerable<TelemetryDataSaubStatDto>> GetTelemetryDataStatAsync(int idTelemetry, CancellationToken token)
-        {
-            var timezone = telemetryService.GetTimezone(idTelemetry);
-            var timezoneOffset = TimeSpan.FromHours(timezone.Hours);
-            int[] modes = new int[] { 0, 1, 3 };
-
-            db.Database.SetCommandTimeout(TimeSpan.FromMinutes(1.5));
-
-            var query = db.Set<TelemetryDataSaub>()
-                .Where(t => t.IdTelemetry == idTelemetry)
-                .Where(t => t.BlockPosition > 0.0001)
-                .Where(t => t.WellDepth > 0.0001)
-                .Where(t => modes.Contains(t.Mode))
-                .Where(t => t.WellDepth - t.BitDepth < 0.01)
-                .GroupBy(t => new { 
-                    t.DateTime.Hour,
-                    WellDepthX10 = Math.Truncate(t.WellDepth * 10),  
-                    t.Mode, 
-                    t.IdFeedRegulator})
-                .Select(g => new TelemetryDataSaubStatDto
-                {
-                    Count = g.Count(),
-                    IdMode = g.Key.Mode,
-                    IdFeedRegulator = g.Key.IdFeedRegulator,
-
-                    DateMin = DateTime.SpecifyKind(g.Min(t => t.DateTime.UtcDateTime) + timezoneOffset, DateTimeKind.Unspecified),
-                    DateMax = DateTime.SpecifyKind(g.Max(t => t.DateTime.UtcDateTime) + timezoneOffset, DateTimeKind.Unspecified),
-
-                    WellDepthMin = g.Min(t => t.WellDepth),
-                    WellDepthMax = g.Max(t => t.WellDepth),
-
-                    Pressure = g.Average(t => t.Pressure),
-                    PressureSp = g.Average(t => t.PressureSp!.Value),
-                    PressureIdle = g.Average(t => t.PressureIdle!.Value),
-                    PressureDeltaLimitMax = g.Average(t => t.PressureDeltaLimitMax!.Value),
-                    PressureDelta = g.Average(t => t.Pressure - t.PressureIdle!.Value),
-                    PressureSpDelta = g.Average(t => t.PressureSp!.Value - t.PressureIdle!.Value),
-
-                    AxialLoad = g.Average(t => t.AxialLoad),
-                    AxialLoadSp = g.Average(t => t.AxialLoadSp!.Value),
-                    AxialLoadLimitMax = g.Average(t => t.AxialLoadLimitMax!.Value),
-
-                    RotorTorque = g.Average(t => t.RotorTorque),
-                    RotorTorqueSp = g.Average(t => t.RotorTorqueSp!.Value),
-                    RotorTorqueLimitMax = g.Average(t => t.RotorTorqueLimitMax!.Value),
-
-                    BlockSpeed = g.Average(t => t.BlockSpeed!.Value),
-                    BlockSpeedSp = g.Average(t => t.BlockSpeedSp!.Value),
-                })
-                .Where(s => s.WellDepthMin != s.WellDepthMax)
-                .Where(s => s.Count > 3)
-                .OrderBy(t => t.DateMin);
-
-            return await query.ToArrayAsync(token);
-        }
-
-        public override TelemetryDataSaub Convert(TelemetryDataSaubDto src, double timezoneOffset)
-        {
-            var entity = src.Adapt<TelemetryDataSaub>();
-            var telemetryUser = telemetryUserService
-                .GetUsers(src.IdTelemetry, u => (u.Name == src.User || u.Surname == src.User))
-                .FirstOrDefault();
-            entity.IdUser = telemetryUser?.Id;
-            entity.DateTime = src.DateTime.ToUtcDateTimeOffset(timezoneOffset);
-            return entity;
-        }
-
-        public override TelemetryDataSaubDto Convert(TelemetryDataSaub src, double timezoneOffset)
-        {
-            var dto = src.Adapt<TelemetryDataSaubDto>();
-            var telemetryUser = telemetryUserService.GetOrDefault(src.IdTelemetry, src.IdUser??int.MinValue);
-            dto.User = telemetryUser?.MakeDisplayName();
-            dto.DateTime = src.DateTime.ToRemoteDateTime(timezoneOffset);
-            dto.BitDepth = src.BitDepth <= src.WellDepth
-                ? src.BitDepth
-                : src.WellDepth;
-            return dto;
-        }
-
-        public async Task<Stream> GetZippedCsv(int idWell, DateTime beginDate, DateTime endDate, CancellationToken token)
-        {
-            double intervalSec = (endDate - beginDate).TotalSeconds;
-            if (intervalSec > 60*60*24*3)
-                throw new ArgumentInvalidException(nameof(endDate), "Слишком большой диапазон");
-
-            var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell)
-                ?? throw new ArgumentInvalidException(nameof(idWell), $"Скважина id:{idWell} не содержит телеметрии");
-
-            var approxPointsCount = intervalSec switch
-            {
-                < 2048 => 2048, 
-                < 8_192 => 4_096,
-                < 131_072 => 16_384,
-                _ => 32_768
-            };
-
-            var data = await GetAsync(idWell, beginDate, intervalSec, approxPointsCount, token );
-
-            var fileName = $"DataSaub idWell{idWell}";
-            if (telemetry.Info is not null)
-                fileName += $" {telemetry.Info?.Cluster}, {telemetry.Info?.Well}";
-            fileName += $" {beginDate:yyyy-MM-DDTHH-mm} - {endDate:yyyy-MM-DDTHH-mm}.csv";
-
-            var outStream = new MemoryStream();
-            using (var archive = new ZipArchive(outStream, ZipArchiveMode.Create, true))
-            {
-                var entryFile = archive.CreateEntry(fileName, CompressionLevel.Optimal);
-                using var entryStream = entryFile.Open();
-                var serializer = new CsvSerializer<TelemetryDataSaubDto>();
-                serializer.Serialize(data, entryStream);
-            }
-            outStream.Seek(0, SeekOrigin.Begin);
-            return outStream;
-        }
+        this.telemetryUserService = telemetryUserService;
     }
 
+    public async Task<IEnumerable<TelemetryDataSaubDto>> Get(int idTelemetry, bool isBitOnBottom, DateTimeOffset geDate, DateTimeOffset leDate, int take, CancellationToken token)
+    {
+        var offset = telemetryService.GetTimezone(idTelemetry).Offset;
+        var geDateUtc = geDate.ToUniversalTime();
+        var leDateUtc = leDate.ToUniversalTime();
+
+        var query = db.Set<TelemetryDataSaub>()
+            .Where(t => t.IdTelemetry == idTelemetry)
+            .Where(t => t.DateTime >= geDateUtc)
+            .Where(t => t.DateTime <= leDateUtc);
+
+        if (isBitOnBottom)
+            query = query.Where(t => Math.Abs(t.BitDepth - t.WellDepth) < 0.0001);
+
+        query = query
+            .OrderBy(t => t.DateTime)
+            .Take(take);
+
+        var entities = await query.ToArrayAsync(token);
+        var dtos = entities.Select(e => Convert(e, offset.TotalHours));
+        return dtos;
+    }
+
+    public async Task<IEnumerable<TelemetryDataSaubStatDto>> GetTelemetryDataStatAsync(int idTelemetry, CancellationToken token)
+    {
+        var timezone = telemetryService.GetTimezone(idTelemetry);
+        var timezoneOffset = TimeSpan.FromHours(timezone.Hours);
+        int[] modes = new int[] { 0, 1, 3 };
+
+        db.Database.SetCommandTimeout(TimeSpan.FromMinutes(1.5));
+
+        var query = db.Set<TelemetryDataSaub>()
+            .Where(t => t.IdTelemetry == idTelemetry)
+            .Where(t => t.BlockPosition > 0.0001)
+            .Where(t => t.WellDepth > 0.0001)
+            .Where(t => modes.Contains(t.Mode))
+            .Where(t => t.WellDepth - t.BitDepth < 0.01)
+            .GroupBy(t => new
+            {
+                t.DateTime.Hour,
+                WellDepthX10 = Math.Truncate(t.WellDepth * 10),
+                t.Mode,
+                t.IdFeedRegulator
+            })
+            .Select(g => new TelemetryDataSaubStatDto
+            {
+                Count = g.Count(),
+                IdMode = g.Key.Mode,
+                IdFeedRegulator = g.Key.IdFeedRegulator,
+
+                DateMin = DateTime.SpecifyKind(g.Min(t => t.DateTime.UtcDateTime) + timezoneOffset, DateTimeKind.Unspecified),
+                DateMax = DateTime.SpecifyKind(g.Max(t => t.DateTime.UtcDateTime) + timezoneOffset, DateTimeKind.Unspecified),
+
+                WellDepthMin = g.Min(t => t.WellDepth),
+                WellDepthMax = g.Max(t => t.WellDepth),
+
+                Pressure = g.Average(t => t.Pressure),
+                PressureSp = g.Average(t => t.PressureSp!.Value),
+                PressureIdle = g.Average(t => t.PressureIdle!.Value),
+                PressureDeltaLimitMax = g.Average(t => t.PressureDeltaLimitMax!.Value),
+                PressureDelta = g.Average(t => t.Pressure - t.PressureIdle!.Value),
+                PressureSpDelta = g.Average(t => t.PressureSp!.Value - t.PressureIdle!.Value),
+
+                AxialLoad = g.Average(t => t.AxialLoad),
+                AxialLoadSp = g.Average(t => t.AxialLoadSp!.Value),
+                AxialLoadLimitMax = g.Average(t => t.AxialLoadLimitMax!.Value),
+
+                RotorTorque = g.Average(t => t.RotorTorque),
+                RotorTorqueSp = g.Average(t => t.RotorTorqueSp!.Value),
+                RotorTorqueLimitMax = g.Average(t => t.RotorTorqueLimitMax!.Value),
+
+                BlockSpeed = g.Average(t => t.BlockSpeed!.Value),
+                BlockSpeedSp = g.Average(t => t.BlockSpeedSp!.Value),
+            })
+            .Where(s => s.WellDepthMin != s.WellDepthMax)
+            .Where(s => s.Count > 3)
+            .OrderBy(t => t.DateMin);
+
+        return await query.ToArrayAsync(token);
+    }
+
+    public override TelemetryDataSaub Convert(TelemetryDataSaubDto src, double timezoneOffset)
+    {
+        var entity = src.Adapt<TelemetryDataSaub>();
+        var telemetryUser = telemetryUserService
+            .GetUsers(src.IdTelemetry, u => (u.Name == src.User || u.Surname == src.User))
+            .FirstOrDefault();
+        entity.IdUser = telemetryUser?.Id;
+        entity.DateTime = src.DateTime.ToUtcDateTimeOffset(timezoneOffset);
+        return entity;
+    }
+
+    public override TelemetryDataSaubDto Convert(TelemetryDataSaub src, double timezoneOffset)
+    {
+        var dto = src.Adapt<TelemetryDataSaubDto>();
+        var telemetryUser = telemetryUserService.GetOrDefault(src.IdTelemetry, src.IdUser ?? int.MinValue);
+        dto.User = telemetryUser?.MakeDisplayName();
+        dto.DateTime = src.DateTime.ToRemoteDateTime(timezoneOffset);
+        dto.BitDepth = src.BitDepth <= src.WellDepth
+            ? src.BitDepth
+            : src.WellDepth;
+        return dto;
+    }
+
+    public async Task<Stream> GetZippedCsv(int idWell, DateTime beginDate, DateTime endDate, CancellationToken token)
+    {
+        double intervalSec = (endDate - beginDate).TotalSeconds;
+        if (intervalSec > 60 * 60 * 24 * 3)
+            throw new ArgumentInvalidException(nameof(endDate), "Слишком большой диапазон");
+
+        var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell)
+            ?? throw new ArgumentInvalidException(nameof(idWell), $"Скважина id:{idWell} не содержит телеметрии");
+
+        var approxPointsCount = intervalSec switch
+        {
+            < 2048 => 2048,
+            < 8_192 => 4_096,
+            < 131_072 => 16_384,
+            _ => 32_768
+        };
+
+        var data = await GetAsync(idWell, beginDate, intervalSec, approxPointsCount, token);
+
+        var fileName = $"DataSaub idWell{idWell}";
+        if (telemetry.Info is not null)
+            fileName += $" {telemetry.Info?.Cluster}, {telemetry.Info?.Well}";
+        fileName += $" {beginDate:yyyy-MM-DDTHH-mm} - {endDate:yyyy-MM-DDTHH-mm}.csv";
+
+        var outStream = new MemoryStream();
+        using (var archive = new ZipArchive(outStream, ZipArchiveMode.Create, true))
+        {
+            var entryFile = archive.CreateEntry(fileName, CompressionLevel.Optimal);
+            using var entryStream = entryFile.Open();
+            var serializer = new CsvSerializer<TelemetryDataSaubDto>();
+            serializer.Serialize(data, entryStream);
+        }
+        outStream.Seek(0, SeekOrigin.Begin);
+        return outStream;
+    }
 }
diff --git a/AsbCloudInfrastructure/Services/Subsystems/SubsystemService.cs b/AsbCloudInfrastructure/Services/Subsystems/SubsystemService.cs
index fd27efdf..93c5ab2f 100644
--- a/AsbCloudInfrastructure/Services/Subsystems/SubsystemService.cs
+++ b/AsbCloudInfrastructure/Services/Subsystems/SubsystemService.cs
@@ -45,17 +45,16 @@ internal class SubsystemService : ISubsystemService
 		if(!well.IdTelemetry.HasValue)
 			return Enumerable.Empty<SubsystemStatDto>();
 		
-		var detectedOperationSummaryRequest = new DetectedOperationRequest
-		{
+		var detectedOperationSummaryRequest = new DetectedOperationByWellRequest
+        {
 			IdWell = request.IdWell,
-			IdsTelemetries = new[] { well.IdTelemetry.Value },
 			IdsCategories = WellOperationCategory.MechanicalDrillingSubIds,
 
 			GeDateStart = request.GeDate,
 			LeDateEnd = request.LeDate,
 
-			GeDepth = request.GeDepth,
-			LeDepth = request.LeDepth,
+			GeDepthStart = request.GeDepth,
+			LeDepthEnd = request.LeDepth,
 		};
 
 		var operations = await detectedOperationService.GetOperationsAsync(detectedOperationSummaryRequest,
@@ -78,7 +77,7 @@ internal class SubsystemService : ISubsystemService
 		return result;
 	}
 	
-	private async Task<IEnumerable<SubsystemStatDto>> CalcStatAsync(IEnumerable<DetectedOperationDto> operations, CancellationToken token)
+	private async Task<IEnumerable<SubsystemStatDto>> CalcStatAsync(IEnumerable<DetectedOperationWithDrillerDto> operations, CancellationToken token)
 	{
 		if (!subsystems.Any())
 			subsystems = (await subsystemRepository.GetAllAsync(token)).ToDictionary(s => s.Id, s => s);
@@ -92,7 +91,7 @@ internal class SubsystemService : ISubsystemService
 		return stat;
 	}
 
-	private SubsystemStatDto CalcOscillationStat(IEnumerable<DetectedOperationDto> operations)
+	private SubsystemStatDto CalcOscillationStat(IEnumerable<DetectedOperationWithDrillerDto> operations)
 	{
 		operations = operations.Where(o => o.IdCategory == WellOperationCategory.IdSlide);
 
@@ -114,7 +113,7 @@ internal class SubsystemService : ISubsystemService
 		return oscillationStat;
 	}
 
-	private IEnumerable<SubsystemStatDto> CalcApdStat(IEnumerable<DetectedOperationDto> operations)
+	private IEnumerable<SubsystemStatDto> CalcApdStat(IEnumerable<DetectedOperationWithDrillerDto> operations)
 	{
 		var apdRotorAndSlide = operations
 			.Where(o => WellOperationCategory.MechanicalDrillingSubIds.Contains(o.IdCategory))
@@ -169,7 +168,7 @@ internal class SubsystemService : ISubsystemService
 	}
 
 	private static (double SumDepthInterval, double UsedTimeHours, int Count) AggregateOperations(int idSubsystem,
-		IEnumerable<DetectedOperationDto> operations) =>
+		IEnumerable<DetectedOperationWithDrillerDto> operations) =>
 		idSubsystem switch
 		{
 			IdSubsystemAPDRotor => CalcOperationsByEnableSubsystems(operations, EnabledSubsystemsFlags.AutoRotor),
@@ -180,7 +179,7 @@ internal class SubsystemService : ISubsystemService
 		};
 
 	private static (double SumDepthInterval, double UsedTimeHours, int OperationCount) CalcOperationsByEnableSubsystems(
-		IEnumerable<DetectedOperationDto> operations,
+		IEnumerable<DetectedOperationWithDrillerDto> operations,
 		EnabledSubsystemsFlags enabledSubsystems)
 	{
 		var filtered = operations.Where(o => enabledSubsystems.HasEnabledSubsystems(o.EnabledSubsystems));
@@ -215,7 +214,7 @@ internal class SubsystemService : ISubsystemService
 
 			var leDateUtc = leDate?.ToUtcDateTimeOffset(hoursOffset);
 
-			var request = new DetectedOperationRequest
+			var request = new DetectedOperationByWellRequest
 			{
 				IdWell = well.Id,
 				IdsCategories = WellOperationCategory.MechanicalDrillingSubIds,
diff --git a/AsbCloudInfrastructure/Services/WellService.cs b/AsbCloudInfrastructure/Services/WellService.cs
index befa8851..4d6ec10a 100644
--- a/AsbCloudInfrastructure/Services/WellService.cs
+++ b/AsbCloudInfrastructure/Services/WellService.cs
@@ -36,13 +36,18 @@ namespace AsbCloudInfrastructure.Services
                 .ThenInclude(r => r.Company)
                 .AsNoTracking();
 
-        public WellService(IAsbCloudDbContext db, IMemoryCache memoryCache, ITelemetryService telemetryService, ITimezoneService timezoneService, WellInfoService wellInfoService)
+        public WellService(IAsbCloudDbContext db,
+                           IMemoryCache memoryCache,
+                           ITelemetryService telemetryService,
+                           ITimezoneService timezoneService,
+                           WellInfoService wellInfoService,
+                           IWellOperationCategoryRepository wellOperationCategoryRepository)
             : base(db, memoryCache, MakeQueryWell)
         {
             this.telemetryService = telemetryService;
             this.timezoneService = timezoneService;
             this.wellInfoService = wellInfoService;
-            this.wellOperationRepository = new WellOperationRepository(db, memoryCache, this);
+            this.wellOperationRepository = new WellOperationRepository(db, memoryCache, this, wellOperationCategoryRepository);
             companyTypesService = new CrudCacheRepositoryBase<CompanyTypeDto, CompanyType>(dbContext, memoryCache);
         }
 
diff --git a/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs b/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs
index 02f2d4cb..c6bd6dda 100644
--- a/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs
+++ b/AsbCloudWebApi/Controllers/SAUB/DetectedOperationController.cs
@@ -5,7 +5,6 @@ using AsbCloudApp.Services;
 using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
 using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
 using System.Threading;
 using System.Threading.Tasks;
 using AsbCloudInfrastructure.Services.DetectOperations;
@@ -57,7 +56,7 @@ namespace AsbCloudWebApi.Controllers.SAUB
         [HttpGet]
         [ProducesResponseType(typeof(DetectedOperationListDto), (int)System.Net.HttpStatusCode.OK)]
         public async Task<IActionResult> GetAsync(
-            [FromQuery] DetectedOperationRequest request,
+            [FromQuery] DetectedOperationByWellRequest request,
             CancellationToken token)
         {
             if (!await UserHasAccessToWellAsync(request.IdWell, token))
@@ -76,7 +75,7 @@ namespace AsbCloudWebApi.Controllers.SAUB
         [HttpGet("stat")]
         [ProducesResponseType(typeof(IEnumerable<DetectedOperationStatDto>), (int)System.Net.HttpStatusCode.OK)]
         public async Task<IActionResult> GetStatAsync(
-            [FromQuery] DetectedOperationRequest request,
+            [FromQuery] DetectedOperationByWellRequest request,
             CancellationToken token)
         {
             if (!await UserHasAccessToWellAsync(request.IdWell, token))
@@ -98,7 +97,7 @@ namespace AsbCloudWebApi.Controllers.SAUB
         [Permission]
         [ProducesResponseType(typeof(int), (int)System.Net.HttpStatusCode.OK)]
         public async Task<IActionResult> DeleteAsync(
-            [FromQuery] DetectedOperationRequest request,
+            [FromQuery] DetectedOperationByWellRequest request,
             CancellationToken token)
         {
             if (!await UserHasAccessToWellAsync(request.IdWell, token))