Инициализировать репозиторий «Дейл»

Первый коммит: модульный монолит ядра (.NET 10) и gRPC-сервисы
ai/ml/telegram, фронтенд Vue 3/Vite/Tailwind, документация (ТЗ,
инструкция пользователя, техдокументация, код-стайл), бэклог,
скрипты развёртывания и архив прототипа LeadRadar.
This commit is contained in:
Rustam Khalimov
2026-09-11 02:50:17 +03:00
commit 9e07568ddd
1402 changed files with 177470 additions and 0 deletions
@@ -0,0 +1,49 @@
using Deal.Contracts.Integrations.Models;
using Deal.Modules.Kanban.Application;
using Deal.Modules.Kanban.Application.Models;
using Deal.Modules.Pipeline.Application.Models;
namespace Deal.Modules.Pipeline.Application;
/// <summary>
/// Создание карточки пайплайна через публичный интерфейс Kanban (план Task 7 L400–402, Ruling 3/4;
/// python _store_lead L483–514: INSERT карточки → UPDATE dedup.lead_id → чтение после записи).
/// </summary>
/// <remarks>
/// Тонкая обёртка создания: генерирует id карточки (<c>c_</c>, <see cref="PrefixId"/>/<see cref="KanbanIdPrefixes"/> —
/// переиспользуем генератор владельца), собирает полный снимок через <see cref="CardComposer"/> и пишет его
/// <see cref="ICardStore.AddCardAsync"/>, после чего связывает заявку дедупа с карточкой
/// (<see cref="IPipelineStore.LinkAsync"/> — порядок AddCard → Link 1:1 с Ruling 4, L512–513) и перечитывает
/// полный <see cref="CardDto"/> (для SSE new_card, Ruling 8/9).
/// <para>
/// Проверку «уже есть карточка по дедупу» обёртка НЕ дублирует — её делает воркер на «new»-проходе pump
/// (Ruling 8, L940–951): к моменту записи сообщение уже прошло дедуп-гвард и его хэш заявлен (LeadId=null).
/// Транзакционности карточка + dedup-link нет (как в прототипе — два отдельных statement'а адаптеров на общем
/// TenantDbContext, Ruling 3/4): сбой LinkAsync оставляет карточку без связи и пробрасывается вызывающему.
/// </para>
/// </remarks>
public sealed class PipelineCardWriter(ICardStore kanjStore, IPipelineStore pipelineStore, CardComposer composer)
{
/// <summary>
/// Создаёт карточку из разобранного сообщения и связывает её с заявкой дедупа.
/// </summary>
/// <param name="parsed">Разбор сообщения (ИИ/локальный путь; колонка разбора пройдёт ContainerAccepts-страховку).</param>
/// <param name="message">Строка очереди с сообщением-источником (метаданные канала/текст/время).</param>
/// <param name="dedupHash">SHA1-hex хэша текста (заявка дедупа уже создана воркером, Ruling 8).</param>
/// <param name="ct">Токен отмены.</param>
/// <returns>Полная карточка (чтение после записи — §4.1, как lead_to_dict после INSERT, L514).</returns>
/// <exception cref="InvalidOperationException">Карточка не прочиталась сразу после создания.</exception>
public async Task<CardDto> CreateCardAsync(
AiParsedCardDto parsed,
QueueItemDto message,
string dedupHash,
CancellationToken ct)
{
string cardId = PrefixId.New(KanbanIdPrefixes.Card);
CardSnapshot snapshot = await composer.BuildAsync(parsed, message, cardId, ct);
await kanjStore.AddCardAsync(snapshot, ct);
await pipelineStore.LinkAsync(dedupHash, cardId, ct);
return await kanjStore.GetCardAsync(cardId, ct)
?? throw new InvalidOperationException("Карточка не прочиталась после создания: " + cardId);
}
}