using System.Globalization; using System.Text.Json; using Deal.Contracts.Integrations; using Deal.Contracts.Integrations.Models; using Deal.Modules.Kanban.Application; using Deal.Modules.Kanban.Application.ColumnRules; using Deal.Modules.Kanban.Application.Models; using Deal.Modules.Pipeline.Application.Models; using Deal.Modules.Pipeline.Application.Parse; using Deal.Modules.Settings.Application; using Deal.Modules.Settings.Application.Models; namespace Deal.Modules.Pipeline.Application; /// /// Снимок настроек прохода — partial-часть (C32: выделено из общего /// файла, поведение не менялось): WorkerRunSettings + один GetAllAsync на pump и резолв дефолтов. /// public sealed partial class PipelineWorkerService { // ── Снимок настроек на проход pump (Ruling 8/C30: один GetAllAsync вместо чтений на каждое сообщение) ── // Снимок настроек воркера на один проход pump: значения прочитаны ОДИН раз (LoadRunSettingsAsync) // и передаются во все проверки сообщений батча и в LocalFieldsParser — раньше настройки читались на каждое // сообщение (ReadBool/ReadInt + GetAllAsync парсера — до 16 чтений таблицы за pump). Значения — из // типизированного снимка TenantSettingsSnapshot (C30). // Settings: Типизированный снимок переопределений тенанта (передаётся LocalFieldsParser). // AutoArchive: Флаг автоархива (срок актуальности сообщения). // ArchiveAfterDays: Срок до автоархива, сутки (stale/причина отсева). // MlEnabled: ML-слот включён (не false — прогноз/решения). // AiEnabled: ИИ-слот включён (классификация/фильтр). // AiFilterEnabled: ИИ-фильтр включён (этап 2). // BudgetRequiredHire: Фильтр «без суммы» для найма. // BudgetRequiredOrder: Фильтр «без суммы» для разовых заказов. // WantedType: Тип заявок «что собираем» (both|vacancy|freelance). // GlobalExclusions: Снимок глобальных исключений (стоп до ML/ИИ, §5.14). private sealed record WorkerRunSettings( TenantSettingsSnapshot Settings, bool AutoArchive, int ArchiveAfterDays, bool MlEnabled, bool AiEnabled, bool AiFilterEnabled, bool BudgetRequiredHire, bool BudgetRequiredOrder, string WantedType, GlobalExcludeSettings GlobalExclusions); // Читает настройки воркера одним запросом (GetAllAsync) и резолвит значения с дефолтами // SettingsDefaults через типизированный снимок (C30). // settingsStore: KV-хранилище настроек тенанта. // ct: Токен отмены. // Возвращает: Снимок на проход pump (один на PumpOnce, передаётся в проверки обоих проходов). private static async Task LoadRunSettingsAsync(ISettingsStore settingsStore, CancellationToken ct) { TenantSettingsSnapshot settings = await TenantSettingsSnapshot.LoadAsync(settingsStore, ct); return new WorkerRunSettings( settings, settings.GetBool(SettingsKeys.AutoArchive, SettingsDefaults.AutoArchive), settings.GetInt(SettingsKeys.ArchiveAfterDays, SettingsDefaults.ArchiveAfterDays), settings.GetBool(SettingsKeys.MlEnabled, SettingsDefaults.MlEnabled), settings.GetBool(SettingsKeys.AiEnabled, SettingsDefaults.AiEnabled), settings.GetBool(SettingsKeys.AiFilterEnabled, SettingsDefaults.AiFilterEnabled), settings.GetBool(SettingsKeys.BudgetRequiredHire, SettingsDefaults.BudgetRequiredHire), settings.GetBool(SettingsKeys.BudgetRequiredOrder, SettingsDefaults.BudgetRequiredOrder), NormalizeWantedType(settings.GetString(SettingsKeys.WantedType, SettingsDefaults.WantedType)), LoadGlobalExclusions(settings)); } // Собирает снимок глобальных исключений тенанта (§5.14): списки слов/локаций/типов и границы бюджета. // Граница бюджета 0 в настройках трактуется как «не задана» (null); сравнение сумм — числовое, // без конвертации валют (у настройки нет валюты). // settings: Типизированный снимок настроек тенанта. // Возвращает: Снимок исключений для чистой проверки сообщений батча. private static GlobalExcludeSettings LoadGlobalExclusions(TenantSettingsSnapshot settings) { int from = settings.GetInt(SettingsKeys.ExcludeBudgetFrom, SettingsDefaults.ExcludeBudgetFrom); int to = settings.GetInt(SettingsKeys.ExcludeBudgetTo, SettingsDefaults.ExcludeBudgetTo); return new GlobalExcludeSettings( Keywords: settings.GetStringList(SettingsKeys.ExcludeKeywords, SettingsDefaults.ExcludeKeywords), Locations: settings.GetStringList(SettingsKeys.ExcludeLocations, SettingsDefaults.ExcludeLocations), Types: settings.GetStringList(SettingsKeys.ExcludeTypes, SettingsDefaults.ExcludeTypes), BudgetFrom: from > 0 ? from : null, BudgetTo: to > 0 ? to : null); } // wantedType: тип заявок (both|vacancy|freelance) в нижнем регистре (python L1030); пустая/отсут- // ствующая/повреждённая строка → дефолт «both» (даёт GetString с дефолтом + нормализация). // value: Значение настройки wantedType (JSON-строка либо дефолт из SettingsDefaults). // Возвращает: Значение в нижнем регистре (пустое хранимое значение остаётся пустым — как wantedType=""). private static string NormalizeWantedType(string value) { return value.Trim().ToLowerInvariant(); } }