< Summary

Information
Class: Services.TransactionCommentAi.TransactionCommentAiPromptBuilder
Assembly: Services
File(s): /home/runner/work/Studio2Prod/Studio2Prod/AppRepository/TransactionCommentAi/TransactionCommentAiPromptBuilder.cs
Line coverage
73%
Covered lines: 93
Uncovered lines: 34
Coverable lines: 127
Total lines: 189
Line coverage: 73.2%
Branch coverage
54%
Covered branches: 65
Total branches: 119
Branch coverage: 54.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()50%22100%
BuildSystemPrompt(...)100%11100%
BuildDataContext(...)57.57%866683.33%
AppendOneC(...)66.66%221877.77%
AppendOzonShip(...)87.5%8890%
AppendOzonCancel(...)25%10428.57%
GetTransactionTypeGuidance(...)28.57%1122140.9%

File(s)

/home/runner/work/Studio2Prod/Studio2Prod/AppRepository/TransactionCommentAi/TransactionCommentAiPromptBuilder.cs

#LineLine coverage
 1using System.Globalization;
 2using System.Text;
 3using OzonDomains;
 4
 5namespace Services.TransactionCommentAi;
 6
 7public static class TransactionCommentAiPromptBuilder
 8{
 19    private static readonly CultureInfo Ru = CultureInfo.GetCultureInfo("ru-RU");
 110    private static readonly TimeZoneInfo RussianTimeZone = TimeZoneInfo.FindSystemTimeZoneById(
 111        OperatingSystem.IsWindows() ? "Russian Standard Time" : "Europe/Moscow");
 12
 13    public const string SystemPrompt = """
 14        Ты — операционный ассистент Studio2. Пишешь комментарий к транзакции для журнала заказов.
 15        Правила:
 16        - Используй только факты из контекста, ничего не выдумывай.
 17        - Формат: plain text, без HTML, Markdown, служебных рассуждений и обращения к читателю.
 18        - Язык: русский, деловой стиль.
 19        - Точная структура: «Тип», «Когда», «Пользователь», «Что произошло». Не повторяй ID транзакции.
 20        - В «Что произошло» сначала напиши сводку по количеству, статусам, split и отменам; затем перечисли переданные в
 21        - По каждому заказу сохраняй номер отправления, товар (артикул, наименование, qty), клиента, город, поставщика, 
 22        - Указывай деньги только если они переданы: закупочную цену и диапазон прибыли. Не вычисляй прибыль и не оценива
 23        - Split-часть прямо обозначай как часть split-заказа; строку «К отмене» или «Отменен» — как отмену/подготовку к 
 24        - Для транзакций с 1С явно укажи склады отправителя/получателя, номера документов, факт проведения и все ошибки.
 25        - Для отгрузки или отмены Ozon укажи итоговые счётчики и результаты по отправлениям; ошибки не сглаживай.
 26        - Учитывай смысл типа транзакции: заказ поставщику/реализатору — размещение; отгрузка поставщиком/реализатором/к
 27        - Если факт отсутствует в контексте, не пиши «не указано», не додумывай и не добавляй шаблонных рекомендаций.
 28        - При нескольких заказах: сначала сводка, затем ключевые заказы. Сжимай текст, ориентир — до 4000 символов.
 29        - Не копируй JSON и технические идентификаторы без необходимости.
 30        """;
 31
 32    public const string UserInstruction =
 33        "Сформируй полный комментарий к транзакции строго по системным правилам. Контекст ниже является единственным ист
 34
 35    public static string BuildSystemPrompt(TransactionCommentAiContext context) =>
 1036        SystemPrompt + "\n- Особенность текущей транзакции: " + GetTransactionTypeGuidance(context.TransactionType);
 37
 38    public static string BuildDataContext(TransactionCommentAiContext context, int maxOrders)
 39    {
 740        var sb = new StringBuilder();
 741        var performedLocal = TimeZoneInfo.ConvertTimeFromUtc(
 742            DateTime.SpecifyKind(context.PerformedAtUtc, DateTimeKind.Utc),
 743            RussianTimeZone);
 44
 745        sb.AppendLine($"Тип: {context.TransactionTypeDisplayName} ({context.TransactionType})");
 746        sb.AppendLine(
 747            $"Когда: {context.PerformedAtUtc:dd.MM.yyyy, HH:mm:ss} (UTC) / {performedLocal:HH:mm} (MSK)");
 748        sb.AppendLine($"Пользователь: {context.UserName}");
 749        sb.AppendLine($"ID транзакции: {context.TransactionId}");
 750        sb.AppendLine();
 51
 752        sb.AppendLine("=== Сводка ===");
 753        sb.AppendLine($"Заказов в транзакции: {context.Batch.TotalOrders}");
 754        sb.AppendLine($"Отменено: {context.Batch.CancelledCount}");
 755        sb.AppendLine($"Со split-номером (/1, /2): {context.Batch.SplitCount}");
 756        sb.AppendLine($"Переведено в «К отмене»/отмену: {context.Batch.ToCancellationCount}");
 757        if (!string.IsNullOrWhiteSpace(context.Batch.TargetStatusName))
 558            sb.AppendLine($"Целевой статус: {context.Batch.TargetStatusName}");
 759        if (context.ProcessIn1C)
 060            sb.AppendLine("Проведение в 1С: да");
 761        if (context.ShipInOzonRequested)
 062            sb.AppendLine("Отгрузка в Ozon после проведения: запрошена");
 763        sb.AppendLine();
 64
 765        AppendOneC(sb, context.OneC);
 766        AppendOzonShip(sb, context.OzonShip);
 767        AppendOzonCancel(sb, context.OzonCancelNotes);
 68
 769        var orders = context.Orders;
 770        var shown = orders.Take(Math.Max(1, maxOrders)).ToList();
 771        var hidden = Math.Max(0, orders.Count - shown.Count);
 72
 773        sb.AppendLine("=== Заказы ===");
 2874        foreach (var o in shown)
 75        {
 776            sb.AppendLine($"Заказ: {o.ShipmentNumber ?? $"ID {o.OrderId}"}");
 777            var product = string.Join(" ", new[]
 778            {
 779                o.Article,
 780                !string.IsNullOrWhiteSpace(o.Manufacturer) ? $"({o.Manufacturer})" : null,
 781                !string.IsNullOrWhiteSpace(o.ProductName) ? $"«{o.ProductName}»" : null,
 782                o.Quantity is > 0 ? $"×{o.Quantity}" : null
 3583            }.Where(x => !string.IsNullOrWhiteSpace(x)));
 84
 785            if (!string.IsNullOrWhiteSpace(product))
 586                sb.AppendLine($"  Товар: {product}");
 787            if (!string.IsNullOrWhiteSpace(o.OzonClientName))
 088                sb.AppendLine($"  Клиент: {o.OzonClientName}");
 789            if (!string.IsNullOrWhiteSpace(o.DeliveryCity))
 090                sb.AppendLine($"  Город: {o.DeliveryCity}");
 791            if (!string.IsNullOrWhiteSpace(o.SupplierName))
 192                sb.AppendLine($"  Поставщик: {o.SupplierName}");
 793            if (!string.IsNullOrWhiteSpace(o.ShipmentWarehouseName))
 094                sb.AppendLine($"  Склад отгрузки: {o.ShipmentWarehouseName}");
 795            if (!string.IsNullOrWhiteSpace(o.FromStatusName) || !string.IsNullOrWhiteSpace(o.ToStatusName))
 096                sb.AppendLine($"  Статус: {o.FromStatusName ?? "—"} → {o.ToStatusName ?? "—"}");
 797            if (o.OriginalQuantity is > 0 && o.Quantity != o.OriginalQuantity)
 198                sb.AppendLine($"  ⚠ Количество изменено: было {o.OriginalQuantity}, стало {o.Quantity ?? 0}");
 799            if (o.IsSplitPart)
 0100                sb.AppendLine("  ⚠ Часть split-заказа (номер с /1 или /2)");
 7101            if (o.IsToCancellationPart)
 0102                sb.AppendLine("  ⚠ Строка ушла в отмену / к отмене");
 7103            if (o.PurchasePrice is > 0)
 0104                sb.AppendLine($"  Цена закупки: {o.PurchasePrice.Value.ToString("N2", Ru)} ₽");
 7105            if (o.MinProfit is not null || o.MaxProfit is not null)
 0106                sb.AppendLine($"  Прибыль: {o.MinProfit?.ToString("N2", Ru) ?? "—"} … {o.MaxProfit?.ToString("N2", Ru) ?
 7107            if (o.ChangedFields.Count > 0)
 0108                sb.AppendLine($"  Изменённые поля: {string.Join(", ", o.ChangedFields)}");
 7109            sb.AppendLine();
 110        }
 111
 7112        if (hidden > 0)
 1113            sb.AppendLine($"... и ещё {hidden} заказ(ов) с теми же параметрами транзакции.");
 114
 7115        return sb.ToString().Trim();
 116    }
 117
 118    private static void AppendOneC(StringBuilder sb, TransactionCommentAiOneCSummary? oneC)
 119    {
 7120        if (oneC == null)
 6121            return;
 122
 1123        sb.AppendLine("=== 1С ===");
 1124        if (!string.IsNullOrWhiteSpace(oneC.WarehouseSender))
 1125            sb.AppendLine($"Склад отправителя: {oneC.WarehouseSender}");
 1126        if (!string.IsNullOrWhiteSpace(oneC.WarehouseRecipient))
 0127            sb.AppendLine($"Склад получателя: {oneC.WarehouseRecipient}");
 4128        foreach (var doc in oneC.Documents)
 129        {
 1130            var label = !string.IsNullOrWhiteSpace(doc.DocumentNumber) ? doc.DocumentNumber : doc.TransactionId;
 1131            sb.AppendLine($"Документ: {label}, проведён: {(doc.IsPosted ? "да" : "нет")}");
 132        }
 2133        foreach (var err in oneC.Errors.Distinct())
 0134            sb.AppendLine($"Ошибка 1С: {err}");
 2135        foreach (var err in oneC.PostingErrors.Distinct())
 0136            sb.AppendLine($"Ошибка проведения: {err}");
 2137        foreach (var m in oneC.MissingNomenclature.Distinct())
 0138            sb.AppendLine($"Без номенклатуры: {m}");
 1139        sb.AppendLine();
 1140    }
 141
 142    private static void AppendOzonShip(StringBuilder sb, TransactionCommentAiOzonShipSummary? ozon)
 143    {
 7144        if (ozon == null || ozon.Entries.Count == 0)
 6145            return;
 146
 1147        sb.AppendLine("=== Отгрузка в Ozon ===");
 1148        sb.AppendLine($"Успешно: {ozon.SuccessCount}, ошибок: {ozon.FailedCount}, пропущено: {ozon.SkippedCount}");
 4149        foreach (var e in ozon.Entries.Take(15))
 1150            sb.AppendLine($"- {e.ShipmentNumber}: {e.Message}");
 1151        if (ozon.Entries.Count > 15)
 0152            sb.AppendLine($"... и ещё {ozon.Entries.Count - 15}");
 1153        sb.AppendLine();
 1154    }
 155
 156    private static void AppendOzonCancel(StringBuilder sb, List<string> notes)
 157    {
 7158        if (notes.Count == 0)
 7159            return;
 160
 0161        sb.AppendLine("=== Отмена в Ozon ===");
 0162        foreach (var n in notes.Take(10))
 0163            sb.AppendLine($"- {n}");
 0164        sb.AppendLine();
 0165    }
 166
 10167    private static string GetTransactionTypeGuidance(TransactionType transactionType) => transactionType switch
 10168    {
 5169        TransactionType.OrderedToSupplier => "заказ размещён у поставщика; отрази поставщика, цену и переход в «Заказан 
 0170        TransactionType.OrderedToSeller => "заказ размещён у реализатора; отрази реализатора, цену и переход в «Заказан 
 0171        TransactionType.ShippedBySupplier => "поставщик отгрузил товар; отрази поставщика, складскую/1С-часть и новый ст
 0172        TransactionType.ShippedToSeller => "товар отгружен реализатором; отрази движение, результат 1С и новый статус.",
 0173        TransactionType.AcceptedToWarehouse => "товар принят на склад; отрази оба склада и документы 1С, если они переда
 0174        TransactionType.ShippedToClient => "заказ передан клиенту; отрази отгрузку Ozon и финальный статус только по пер
 0175        TransactionType.ShippedToCommissioner => "товар отправлен комиссионеру/Ozon FBO; отрази складское движение и рез
 1176        TransactionType.ToCancellation => "заказ только помечен к отмене, не называй его отменённым без соответствующего
 0177        TransactionType.Cancelled => "заказ отменён; отрази отмену и связанные с ней части split-заказа или результаты O
 0178        TransactionType.CustomerReturn => "это возврат от покупателя; отрази обратное движение товара и склады.",
 0179        TransactionType.ReturnToSupplier => "товар возвращён поставщику; отрази поставщика, склады и документы 1С.",
 0180        TransactionType.ReturnToWarehouse => "товар возвращён на склад Studio2; отрази склад-получатель и документы 1С."
 1181        TransactionType.WarehouseTransfer => "это внутреннее перемещение между складами; обязательно различай отправител
 0182        TransactionType.WarehouseChange => "изменён склад отгрузки заказа; не описывай это как физическое перемещение бе
 0183        TransactionType.ReturnTransfer => "это внутреннее перемещение после возврата; отрази только фактически переданны
 1184        TransactionType.LostByCommissioner => "товар утрачен реализатором; явно отрази исключительную ситуацию и не назы
 1185        TransactionType.Percentage => "изменены результаты проценки; перечисли только переданные изменённые поля и финан
 1186        TransactionType.ReceiptNote => "создана приходная накладная; отрази документы 1С, проведение и ошибки.",
 0187        _ => "опиши операцию только по типу и фактам из контекста."
 10188    };
 189}