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

Удалены <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
@@ -15,51 +15,30 @@ using KanbanColumnRules = Deal.Modules.Kanban.Application.ColumnRules.ColumnRule
namespace Deal.Infrastructure.Integrations.Services;
/// <summary>
/// Адаптер ИИ-предложений колонок/ключей — детерминированная эвристика этапа 3 (Ruling 3, план Task 14).
/// Адаптер ИИ-предложений колонок/ключей — детерминированная эвристика.
/// </summary>
/// <remarks>
/// Реализует порт <see cref="IColumnSuggester"/> поверх порта <see cref="ICardStore"/> и чистого ядра
/// <see cref="SuggestHeuristics"/> (модуль Kanban): читает «Неразобранное» (ListInboxWithSourceAsync),
/// считает группы слов-тем и создаёт доски suggested=true (RulesJson {mode:"any", keywords:[…]},
/// note-обоснование, цвет/позицию даёт ContainersService) и раскладывает карточки (is_new=TRUE,
/// prev_col='inbox', matchHits по правилам доски — Ruling 2). Причины отказов — детерминированные
/// строки прототипа/Ruling 3: «мало карточек в «Неразобранном» (нужно от 6)», «похожие колонки уже
/// есть или нечего сгруппировать»; кулдаун повторов — KV-ключ <see cref="SettingsKeys.LastSuggestAt"/>
/// (прототип COOLDOWN_S L51 + «недавно предлагали — подождите» L95). Журнал CardMoves/ML-сигналы при
/// раскладке НЕ пишутся (suggest.py _assign_ids L220239 — это не действие пользователя, а предложение).
/// Suggest-keywords читает карточки вне trash/archive (suggest_domain_keywords L172178).
/// </remarks>
/// <param name="store">Порт хранилища (карточки «Неразобранного», переносы в колонки-доски).</param>
/// <param name="settings">KV-хранилище настроек тенанта (кулдаун lastSuggestAt, как KEY suggest.py L52).</param>
/// <param name="settings">KV-хранилище настроек тенанта.</param>
/// <param name="containersService">Сервис контейнеров: список существующих и создание suggested-колонок с дефолтами.</param>
public sealed class LocalColumnSuggester(
ICardStore store,
ISettingsStore settings,
ContainersService containersService) : IColumnSuggester
{
// ── Кулдаун повторов (suggest.py COOLDOWN_S L51; KEY lastSuggestAt L52) ──
// Как часто можно переспрашивать ИИ-предложения: 20 минут (COOLDOWN_S = 20 * 60, L51).
private const long CooldownSeconds = 20 * 60;
// ── Детерминированные причины (Ruling 3; строки прототипа suggest.py) ──
// Кулдаун: повторный вызов слишком рано (suggest.py L95 «недавно предлагали — подождите»).
private const string CooldownReason = "недавно предлагали — подождите";
// Мало карточек в «Неразобранном»: {0} — порог MIN_INBOX (suggest.py L102).
private const string TooFewCardsReasonFormat = "мало карточек в «Неразобранном» (нужно от {0})";
// Групп не вышло: темы похожи на существующие доски или карточкам нечего разделить (L159).
private const string NothingGroupedReason = "похожие колонки уже есть или нечего сгруппировать";
// Мало карточек для ключей: нужно хотя бы 3 (suggest_domain_keywords L178).
private const string KeywordsTooFewReason = "мало карточек — сначала накопите заявки (нужно хотя бы 3)";
// Повторяющихся слов-маркеров не нашлось (suggest_domain_keywords L187, текст прототипа).
private const string KeywordsEmptyReason = "ИИ не смог выделить ключи — попробуйте ещё раз";
// Режим правил колонки-предложения: «любое из условий» (suggest.py _rules_for L68 mode: any).
private const string RulesModeAny = "any";
/// <inheritdoc />
@@ -80,7 +59,6 @@ ContainersService containersService) : IColumnSuggester
Cooldown: false);
}
// Существующие (suggested=false) колонки: похожие темы не предлагаем (suggest.py L105, L138139).
IReadOnlyList<ContainerDto> containers = await containersService.ListAsync(ContainerSpaces.Dashboard, ct);
IReadOnlyList<string> existingNames = containers
.Where(container => !container.Suggested)
@@ -96,7 +74,6 @@ ContainersService containersService) : IColumnSuggester
int created = await StoreSuggestedColumnsAsync(inbox, plans, ct);
if (created == 0)
{
// Все колонки откатаны: карточки групп разобраны между чтением и раскладкой (suggest.py L153156).
return new SuggestColumnsResultDto(Ok: false, Created: 0, Reason: NothingGroupedReason, Cooldown: false);
}
@@ -107,7 +84,6 @@ ContainersService containersService) : IColumnSuggester
/// <inheritdoc />
public async Task<SuggestKeywordsResultDto> SuggestKeywordsAsync(CancellationToken ct)
{
// Выборка ключей — как suggest_domain_keywords L172176: карточки вне trash/archive с текстом,
// свежие 40 (ListCardsAsync(null) = «все, кроме taken», ORDER BY received_at DESC).
IReadOnlyList<CardDto> cards = await store.ListCardsAsync(new CardsQuery(null), ct);
List<string> texts = cards
@@ -131,22 +107,17 @@ ContainersService containersService) : IColumnSuggester
return new SuggestKeywordsResultDto(Ok: true, Keywords: keywords, Reason: null);
}
// Создаёт доски-предложения по планам и раскладывает карточки (suggest.py L129156).
// inbox: Снимок «Неразобранного» (карточки планов берутся из него).
// plans: Планы колонок (SuggestHeuristics.PlanColumns, ≤4).
// ct: Токен отмены.
// Возвращает: Сколько досок реально создано (0 — все откатаны из-за разобранных карточек).
// Каждая доска — suggested=true c правилами {mode:"any", keywords:[тема]} и note-обоснованием.
// Перед раскладкой перечитывается «Неразобранное»: карточки, ушедшие из inbox между снимком и
// раскладкой (пользователь/тик), пропускаются — 1:1 со страховкой _assign_ids L231233. Если в
// колонку не легло ни одной карточки, пустая доска-предложение откатывается (_rollback_suggested
// L242248). matchHits считаются по правилам созданной доски (Ruling 2); журнал/ML не пишутся.
private async Task<int> StoreSuggestedColumnsAsync(
IReadOnlyList<CardDto> inbox,
IReadOnlyList<SuggestedColumnPlan> plans,
CancellationToken ct)
{
// Свежий снимок inbox — страховка «карточку уже разобрали» (suggest.py _assign_ids L231233).
HashSet<string> inboxIds = (await store.ListInboxWithSourceAsync(ct))
.Select(card => card.Id)
.ToHashSet(StringComparer.Ordinal);
@@ -195,7 +166,6 @@ ContainersService containersService) : IColumnSuggester
if (placed == 0)
{
// Ничего не легло — пустое предложение не нужно (suggest.py L152156).
await containersService.DeleteAsync(container.Id, ct);
continue;
}
@@ -207,8 +177,6 @@ ContainersService containersService) : IColumnSuggester
}
// Сработал ли кулдаун: с последнего успешного предложения прошло меньше 20 минут.
// Повреждённое/отсутствующее значение lastSuggestAt — кулдауна нет (как прототип: значение
// пишется только после успеха, L160–161; битый KV — дефолт «никогда»).
// ct: Токен отмены.
// Возвращает: True — повторный вызов слишком рано (ответ {ok:false, reason, cooldown:true}).
private async Task<bool> WithinCooldownAsync(CancellationToken ct)
@@ -236,7 +204,6 @@ ContainersService containersService) : IColumnSuggester
}
}
// Записывает метку успешного предложения (suggest.py L160: set_setting(KEY, time.time())).
// ct: Токен отмены.
private Task WriteLastSuggestAtAsync(CancellationToken ct) =>
settings.SetAsync(