Почистить комментарии от упоминаний процесса

Удалены <remarks>, <summary> сжаты до короткой фразы, вырезаны
ссылки на Task/Ruling/этап/python/прототип; //-комментарии со ссылками
на процесс удалены; то же в .proto. Правила обновлены в
docs/spec/Код-стайл-Дейл.md. Строк комментариев 27210 -> ~19100.
This commit is contained in:
Rustam Khalimov
2026-09-11 13:39:39 +03:00
parent 5f5538d33b
commit b053d58335
902 changed files with 3902 additions and 12074 deletions
@@ -17,43 +17,22 @@ using Microsoft.Extensions.Logging;
namespace Deal.Infrastructure.Integrations.Services;
/// <summary>
/// gRPC-адаптер порта IMlClient к автономному ml-service (Ruling 4/6, план Task 16 L438445).
/// gRPC-адаптер порта IMlClient к автономному ml-service.
/// </summary>
/// <remarks>
/// Регистрируется вместо Local-заглушки при <c>Services:Ml:UseLocal=false</c> (выбор на старте, Ruling 6).
/// Поведение 1:1 с <c>backend/app/services/ml_client.py</c> и ml.proto:
/// <list type="bullet">
/// <item><see cref="StatusAsync"/> — статус модели из ml-service (RPC Status, deadline 10 с) с кэшем 15 с
/// (<see cref="MlStatusCache"/>, python L3031/127135) + локальная статистика тенанта из KV/таблиц
/// (счётчики ml/ai, learning = count(CardMoves), outbox = count(MlOutbox)); сервис недоступен — старые
/// данные кэша (или «не готова») и <c>reachable=false</c>;</item>
/// <item><see cref="PredictAsync"/> — RPC Predict (deadline 5 с); сбой/недоступность → фиксированный «не
/// уверен» (python L101–107: решит ИИ/локальный путь воркера);</item>
/// <item><see cref="ResetAsync"/> — RPC Reset (deadline 10 с); при успехе — очистка своей очереди
/// MlOutbox (reset_model L110124) и инвалидация кэша статуса; сбой — мягкий <c>{ok:false,error}</c>,
/// очередь не трогается;</item>
/// <item><see cref="PushAsync"/> — ВСЕГДА запись в MlOutbox через <see cref="IMlLearningStore"/> (Ruling 6:
/// обучение гарантированно и локально; отправку батчами делает <c>MlOutboxFlushScheduler</c>);</item>
/// <item><see cref="TrainBatchAsync"/> (IMlTrainClient) — RPC TrainBatch (deadline 30 с) для фонового флашера.</item>
/// </list>
/// Каждый вызов несёт metadata tenant-id + service-token (<see cref="MlGrpcConnection"/>, Ruling 1). Scoped:
/// локальная статистика читает KV-настройки и таблицы тенанта (ISettingsStore/IMlLearningStore → scoped
/// TenantDbContext), как LocalMlClient.
/// </remarks>
public sealed class GrpcMlClient : IMlClient, IMlTrainClient
{
/// <summary>
/// Deadline Predict — 5 с (README контрактов: локальная модель).
/// Deadline Predict — 5 с
/// </summary>
public const int PredictDeadlineSeconds = 5;
/// <summary>
/// Deadline Status/Reset — 10 с (README контрактов).
/// Deadline Status/Reset — 10 с
/// </summary>
public const int StatusDeadlineSeconds = 10;
/// <summary>
/// Deadline TrainBatch — 30 с (README контрактов: батч ≤100, 1 транзакция).
/// Deadline TrainBatch — 30 с
/// </summary>
public const int TrainBatchDeadlineSeconds = 30;
@@ -78,7 +57,6 @@ public sealed class GrpcMlClient : IMlClient, IMlTrainClient
// Кэш статуса сервиса на тенанта (15 с).
private readonly MlStatusCache _statusCache;
// Recorder истории расхода (ML-событие расхода, этап 10, T2): оценка токенов входного текста.
private readonly TokenUsageRecorder _usageRecorder;
// Логгер сбоев вызовов ml-service.
@@ -92,7 +70,7 @@ public sealed class GrpcMlClient : IMlClient, IMlTrainClient
/// <param name="learningStore">Хранилище обучения ML (очередь MlOutbox + журнал).</param>
/// <param name="connection">Транспорт ml-service (singleton-канал + service-token).</param>
/// <param name="statusCache">Кэш статуса сервиса на тенанта (singleton).</param>
/// <param name="usageRecorder">Recorder истории расхода (ML-событие predict, этап 10, T2).</param>
/// <param name="usageRecorder">Recorder истории расхода.</param>
/// <param name="logger">Логгер сбоев.</param>
public GrpcMlClient(
ITenantContext tenantContext,
@@ -155,14 +133,12 @@ public sealed class GrpcMlClient : IMlClient, IMlTrainClient
new PredictRequest { Text = text ?? string.Empty },
CallOptions(tenantId.Value, TimeSpan.FromSeconds(PredictDeadlineSeconds), ct));
// История расхода (этап 10, T2): ML-ответ токенов не несёт — оценка входного текста (≈chars/4),
// бюджет/lifetime AI-счётчик не затрагиваются (локальная модель бесплатна).
await _usageRecorder.AddEstimatedAsync(text, TokenUsageSources.Local, TokenUsageSources.Ml, ct);
return MapPredict(reply);
}
catch (Exception exception) when (exception is RpcException or OperationCanceledException or HttpRequestException)
{
// Сервис недоступен/таймаут/отмена — «не уверен» (python predict L101107): решит ИИ/локальный путь.
_logger.LogDebug(exception, "ML predict недоступен (тенант {TenantId})", tenantId.Value);
return NotReadyPrediction;
}
@@ -182,7 +158,6 @@ public sealed class GrpcMlClient : IMlClient, IMlTrainClient
}
catch (Exception exception) when (exception is RpcException or OperationCanceledException or HttpRequestException)
{
// Мягкая ошибка реального сервиса (python reset_model L117121): ok:false + текст; outbox не трогаем.
_logger.LogWarning(exception, "ML reset не удался (тенант {TenantId})", tenantId.Value);
return new MlResetResultDto(Ok: false, Error: ErrorText(exception));
}
@@ -192,7 +167,6 @@ public sealed class GrpcMlClient : IMlClient, IMlTrainClient
return new MlResetResultDto(Ok: false, Error: reply.HasError ? reply.Error : DefaultResetError);
}
// 1:1 reset_model L122123: после успешного сброса сервиса — очистка своей очереди + свежий статус.
await _learningStore.ClearOutboxAsync(ct);
_statusCache.Invalidate(tenantId.Value);
return new MlResetResultDto(Ok: true, Error: null);
@@ -205,8 +179,6 @@ public sealed class GrpcMlClient : IMlClient, IMlTrainClient
double delta,
CancellationToken ct)
{
// Обучение гарантированно и локально (Ruling 6): сигнал всегда пишется в MlOutbox, отправку батчами
// делает MlOutboxFlushScheduler — и в Local-, и в gRPC-режиме (ml_client.py L67).
await MlOutboxQueue.PushAsync(_learningStore, text, label, delta, ct);
}
@@ -251,7 +223,6 @@ public sealed class GrpcMlClient : IMlClient, IMlTrainClient
return fresh;
}
// Последние известные данные (при сбое refresh останутся они — python refresh_status L132135).
_statusCache.TryGet(tenantId.Value, out MlStatusCache.Snapshot stale);
MlServiceStatusDto previous = stale?.Service ?? NotReadyServiceStatus;
@@ -275,7 +246,6 @@ public sealed class GrpcMlClient : IMlClient, IMlTrainClient
}
}
// Маппит ответ Status в контрактный статус модели (поля 1:1 с MlServiceStatusDto).
// reply: Ответ ml-service.
// Возвращает: DTO статуса модели.
private static MlServiceStatusDto MapStatus(StatusReply reply)
@@ -290,7 +260,6 @@ public sealed class GrpcMlClient : IMlClient, IMlTrainClient
Accuracy: reply.Eval?.Accuracy ?? 0.0));
}
// Маппит ответ Predict в контрактный результат (поля 1:1 с MlPredictResultDto).
// reply: Ответ ml-service.
// Возвращает: DTO предсказания.
private static MlPredictResultDto MapPredict(PredictReply reply)
@@ -320,7 +289,6 @@ public sealed class GrpcMlClient : IMlClient, IMlTrainClient
Margin: decision.Margin);
}
// CallOptions вызова: metadata tenant-id/service-token + deadline + токен отмены (Ruling 1).
// tenantId: Id тенанта (формат N).
// deadline: Лимит времени вызова.
// ct: Токен отмены вызова.
@@ -342,7 +310,6 @@ public sealed class GrpcMlClient : IMlClient, IMlTrainClient
? "ML-сервис недоступен"
: "ML-сервис не ответил — повторите попытку через несколько секунд";
// Фиксированный ответ неготовой/недоступной модели: «не уверен» (Ruling 5, ml.proto L2123).
private static MlPredictResultDto NotReadyPrediction => new(
Take: false,
Label: null,
@@ -360,7 +327,6 @@ public sealed class GrpcMlClient : IMlClient, IMlTrainClient
Learned: 0,
Eval: new MlEvalDto(Count: 0, Correct: 0, Accuracy: 0.0));
// Читает выключатель mlEnabled: «не false» (ml_routes.py L71) — false только при сохранённом JSON-false.
// ct: Токен отмены.
// Возвращает: True, если ключ отсутствует, повреждён или хранит JSON-true.
private async Task<bool> ReadMlEnabledAsync(CancellationToken ct)