Вынести магические строки статусов, видов и ключей в каталоги

Литералы статусов/видов/фаз и wire-ключей заменены каталогами: значения сущностей и репозиториев — TenantStatuses/TenantLimitPeriods/InviteStatuses/ContainerKinds/ContainerSpaces/CardIds/Discovery*/PipelineQueueStatuses; контакты и виды вложений — CardContactTypes/CardFileKinds; источники и этапы отсева — PipelineRejectStages/PipelineRejectSources; ключи промптов/провайдеров — SettingsFieldKeys; wire-ключи канбана — KanbanWireKeys; режимы правил — ColumnRuleModes; операции/фазы/виды диалогов/контакты Telegram и общий BoolText — в Deal.Contracts/Deal.SharedKernel; имена метрик — DealMetrics. Значения не менялись.
This commit is contained in:
2026-09-13 20:51:36 +03:00
parent 8dde35de49
commit 1fbe29a838
61 changed files with 907 additions and 167 deletions
@@ -4,7 +4,9 @@ using System.Text.Json.Nodes;
using Deal.Contracts.Integrations.Models;
using Deal.Modules.Kanban.Application.Models;
using Deal.Modules.Kanban.Application.Services;
using Deal.Modules.Pipeline.Application.Models;
using Deal.Modules.Pipeline.Application.Parse;
using Deal.SharedKernel.Utilities;
namespace Deal.Modules.Pipeline.Application.Services;
@@ -19,9 +21,9 @@ public static class AiRawCardMapper
private const string BudgetAmountPattern = @"[0-9.,]+\s*[кkКK]?";
private const string CurrencyFieldName = "currency";
private const string CurrencyFieldNameShort = "cur";
// Строковые значения bool-полей, которые считаются «ложью».
private static readonly IReadOnlySet<string> FalsyAnswerValues =
new HashSet<string>(StringComparer.Ordinal) { "0", BoolText.False, "no", "нет", "null", "none" };
// Регулярное выражение числа бюджета: число + необязательные «к»/валюта-суффиксы.
private static readonly System.Text.RegularExpressions.Regex BudgetNumRe =
@@ -39,7 +41,7 @@ public static class AiRawCardMapper
JsonObject? root = JsonNode.Parse(json) as JsonObject
?? throw new JsonException("Ответ ИИ-классификатора — не JSON-объект (ok=true, но схема нарушена).");
string title = MessageTextCleaner.CleanShort(ReadRawString(root, "title"), MaxTitleCodePoints);
string title = MessageTextCleaner.CleanShort(ReadRawString(root, AiRawCardKeys.Title), MaxTitleCodePoints);
if (title.Length == 0)
{
title = MessageTextCleaner.CleanShort(text, MaxFallbackTextCodePoints); // python L455 fallback
@@ -49,7 +51,7 @@ public static class AiRawCardMapper
AiBudgetDto? budget = ReadBudget(root);
IReadOnlyList<AiContactDto> contacts = ReadContacts(root, text);
string? board = ReadRawString(root, "board").Trim();
string? board = ReadRawString(root, AiRawCardKeys.Board).Trim();
if (board.Length == 0)
{
board = null;
@@ -57,19 +59,19 @@ public static class AiRawCardMapper
return new AiParsedCardDto(
Title: title,
Company: ReadNullableString(root, "company"),
Format: ReadNullableString(root, "format"),
Task: ReadNullableString(root, "task"),
Requirements: ReadItems(root, "requirements"),
Plus: ReadItems(root, "plus"),
Conditions: ReadNullableString(root, "conditions"),
Summary: ReadNullableString(root, "summary"),
Company: ReadNullableString(root, AiRawCardKeys.Company),
Format: ReadNullableString(root, AiRawCardKeys.Format),
Task: ReadNullableString(root, AiRawCardKeys.Task),
Requirements: ReadItems(root, AiRawCardKeys.Requirements),
Plus: ReadItems(root, AiRawCardKeys.Plus),
Conditions: ReadNullableString(root, AiRawCardKeys.Conditions),
Summary: ReadNullableString(root, AiRawCardKeys.Summary),
Stack: stack,
Budget: budget,
Contacts: contacts,
IsVacancy: ReadBool(root, "is_vacancy"),
IsVacancyKnown: ReadBool(root, "is_vacancy_known"), // false — воркер стемпит true после успешного ИИ
IsSpam: ReadBool(root, "is_spam"),
IsVacancy: ReadBool(root, AiRawCardKeys.IsVacancy),
IsVacancyKnown: ReadBool(root, AiRawCardKeys.IsVacancyKnown), // false — воркер стемпит true после успешного ИИ
IsSpam: ReadBool(root, AiRawCardKeys.IsSpam),
Board: board);
}
@@ -114,7 +116,7 @@ public static class AiRawCardMapper
if (value.TryGetValue<string>(out string? text))
{
string lower = (text ?? string.Empty).Trim().ToLowerInvariant();
return lower.Length > 0 && lower is not ("0" or "false" or "no" or "нет" or "null" or "none");
return lower.Length > 0 && !FalsyAnswerValues.Contains(lower);
}
return false;
@@ -147,7 +149,7 @@ public static class AiRawCardMapper
private static IReadOnlyList<string> ReadStack(JsonObject root)
{
if (!root.TryGetPropertyValue("stack", out JsonNode? node) || node is null)
if (!root.TryGetPropertyValue(AiRawCardKeys.Stack, out JsonNode? node) || node is null)
{
return Array.Empty<string>();
}
@@ -161,19 +163,19 @@ public static class AiRawCardMapper
private static AiBudgetDto? ReadBudget(JsonObject root)
{
if (!root.TryGetPropertyValue("budget", out JsonNode? node) || node is not JsonObject budget)
if (!root.TryGetPropertyValue(AiRawCardKeys.Budget, out JsonNode? node) || node is not JsonObject budget)
{
return null;
}
string currency = ReadRawString(budget, CurrencyFieldName);
string currency = ReadRawString(budget, AiRawCardKeys.Currency);
if (currency.Length == 0)
{
currency = ReadRawString(budget, CurrencyFieldNameShort); // ai.py L327: currency или cur
currency = ReadRawString(budget, AiRawCardKeys.CurrencyShort); // ai.py L327: currency или cur
}
double? from = ParseBudgetBound(budget, "from");
double? to = ParseBudgetBound(budget, "to");
double? from = ParseBudgetBound(budget, AiRawCardKeys.From);
double? to = ParseBudgetBound(budget, AiRawCardKeys.To);
CardBudgetDto? normalized = BudgetNormalizer.Normalize(new BudgetRangeDto(from, to, currency));
return normalized is null
? null
@@ -226,7 +228,7 @@ public static class AiRawCardMapper
private static IReadOnlyList<AiContactDto> ReadContacts(JsonObject root, string text)
{
List<string> candidates = new();
if (root.TryGetPropertyValue("contacts", out JsonNode? node) && node is not null)
if (root.TryGetPropertyValue(AiRawCardKeys.Contacts, out JsonNode? node) && node is not null)
{
if (node is JsonValue)
{
@@ -245,7 +247,7 @@ public static class AiRawCardMapper
break;
case JsonObject contactObject:
{
string value = ReadRawString(contactObject, "value"); // python L402403: dict.get("value")
string value = ReadRawString(contactObject, AiRawCardKeys.Value);
if (value.Length > 0)
{
candidates.Add(value);