SaaS-мониторинг Telegram: ядро (модули Cards/Kanban/Pipeline/Tenants/Settings/ Discovery, Api, Infrastructure), сервисы telegram/ai/ml/storage, фронт Vue, контракты и grpc-hosting, деплой-конфиги (dev/prod/observability/CI-раннер), Gitea Actions CI, документация (ТЗ, техдок, api-map, код-стайл, планы, бэклог). Текущее состояние: все этапы роадмапа 0–12 закрыты, сборка 5 sln 0/0, тесты 1340/130/52/38/9 зелёные.
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
# Дейл — код-стайл (действующие правила)
|
||||
|
||||
> Единый свод правил стиля кода для всего репозитория (core, telegram/ai/ml-сервисы, тесты).
|
||||
> Составлен на основе исходного `Стиль_кода.docx` (перенесён в `archive/style-guide-original/`),
|
||||
> дополнен действующими правилами проекта и `.editorconfig`. Правила обязательны для нового кода;
|
||||
> приведение существующего — в `docs/superpowers/backlog.md` (`TD-COMMENTS-IFACE`, `TD-PROTO-COMMENTS`).
|
||||
|
||||
Пометки:
|
||||
- **[изм.]** — правило дополнено/уточнено относительно исходного документа.
|
||||
- **[отмена]** — правило исходного документа, которое в этом проекте не применяется.
|
||||
|
||||
---
|
||||
|
||||
## 1. Именование
|
||||
|
||||
Используются стандартные соглашения .NET. Венгерская нотация и префиксы типов в именах не применяются.
|
||||
|
||||
- **Классы** — Pascal: `User`.
|
||||
- **Интерфейсы** — Pascal с префиксом `I`: `IDisposable`, `ICardStore`.
|
||||
- **Generic-параметры** — Pascal с `T`: `T`, `TKey`, `TValue`.
|
||||
- **Публичные функции/методы** — Pascal: `Authenticate`.
|
||||
- **Приватные функции/методы** — тоже Pascal: `Authenticate` (не camel).
|
||||
- **Параметры функций** — camel: `userId`.
|
||||
- **Свойства (public/private)** — Pascal: `FirstName`.
|
||||
- **Public-поля** — Pascal: `FirstName`. **[изм.]** Публичное состояние — свойство (§4); публичное поле допускается
|
||||
только для данных-контейнеров без логики и именуется Pascal.
|
||||
- **Private-поля — обязательный префикс `_` + camelCase: `_firstName`.** **[изм.]** Без `_` запрещено.
|
||||
Исключения — только для константоподобных полей: `const` и `static readonly` именуются PascalCase
|
||||
(`MaxRetryCount`, `DefaultTimeout`).
|
||||
- **Локальные переменные** — camel: `user`.
|
||||
- **Константы** — Pascal: `MaxRetryCount` (приватные `const` и `static readonly` — тоже Pascal, без `_`).
|
||||
- **Enum** — Pascal: `UserStatus`; **значения enum** — Pascal: `Active`.
|
||||
- **Exception** — Pascal с суффиксом `Exception`: `UserAuthenticationException`.
|
||||
- **Event** — Pascal: `StatusChanged`.
|
||||
- **Namespace** — Pascal.
|
||||
|
||||
Не использовать сокращения, кроме общепринятых (`id`, `ui`, `http`, `grpc`, `json`, `api`).
|
||||
|
||||
## 2. Организация кода и файлов
|
||||
|
||||
- Один публичный тип — один файл; имя файла = имя типа. **[изм.]** Правило усилено: смешивать типы в
|
||||
одном файле нельзя (небольшие вспомогательные private-классы — исключение).
|
||||
- **`namespace` строго соответствует пути папки** (для тестов — тоже). Файлы группируются по назначению:
|
||||
`Abstractions` (интерфейсы `I*`), `Services` (сервисы/воркеры/исполнители), `Models` (доменные типы,
|
||||
enum/статусы/константные реестры), `Dtos` (`*Dto`/`*Request`/`*Response`/`*Patch`), `Extensions`
|
||||
(`*Extensions`), `Options` (`*Options`), `Exceptions` (`*Exception`), `Registrars` (`*ModuleRegistrar`),
|
||||
`Configurations` (EF-конфигурации), `Entities`, `Repositories`. Feature-папки допустимы и сохраняются
|
||||
(`Endpoints`, `Middleware`, `Hosting`, `Parsing`, `ColumnRules` и т.п.).
|
||||
- **Тестовые проекты** группируются по областям (`Modules/<X>`, `Api`, `Infrastructure`, `Contracts`,
|
||||
`Grpc`, …), общие фейки/хелперы — в `Support`; `namespace` = `<ПроектТестов>.<Область>`.
|
||||
- В одном файле — один `namespace`. File-scoped namespace допустим.
|
||||
- Все `using` — в начале файла; сначала системные, затем сторонние/project.
|
||||
- `using` внутри `namespace` не используются (внешние `using`).
|
||||
- Порядок членов внутри типа: константы → поля → конструкторы → свойства → методы. Члены группируются
|
||||
по назначению.
|
||||
- **[отмена]** Регионы (`#region`) **не используются** — вместо них осмысленный порядок и декомпозиция.
|
||||
- Если у свойства есть backing-поле, поле объявляется **над** свойством:
|
||||
|
||||
```csharp
|
||||
private User _user;
|
||||
public User User { get; set; }
|
||||
```
|
||||
|
||||
## 3. Форматирование
|
||||
|
||||
- Стандартные настройки форматирования Visual Studio / `.editorconfig`.
|
||||
- Фигурные скобки — всегда на отдельной строке (Allman).
|
||||
- В `if`/`else` фигурные скобки используются **всегда**, даже для одной инструкции.
|
||||
- Отступ — 4 пробела (символ табуляции в историческом документе; в проекте — пробелы).
|
||||
- Длина строки — желательно не более 100 символов; при переносе продолжение сдвигается вправо на один
|
||||
уровень отступа.
|
||||
- Каждая переменная объявляется на отдельной строке.
|
||||
- Если `get`/`set` свойства состоит из одной операции, допускается размещение на одной строке:
|
||||
|
||||
```csharp
|
||||
public User
|
||||
{
|
||||
get { return user; }
|
||||
}
|
||||
```
|
||||
- Модификаторы доступа указываются **всегда**, включая явный `private`.
|
||||
|
||||
## 4. Проектные соглашения .NET
|
||||
|
||||
Машиночитаемая часть правил форматирования/анализа — в `.editorconfig` и `Directory.Build.props`
|
||||
(`Nullable=enable`, `TreatWarningsAsErrors=true`, `EnforceCodeStyleInBuild=true`). Ниже — соглашения
|
||||
уровня кода, которые этими файлами не выражаются.
|
||||
|
||||
- **Публичные члены — только свойства** (`{ get; init; }` / `{ get; set; }`), **не публичные поля**.
|
||||
**[изм.]** Отменяет исходное правило о публичных полях: публичное состояние — свойство.
|
||||
- Приватное/внутреннее состояние без дополнительной логики — поле (см. §6); с логикой — свойство.
|
||||
- Зависимости — через конструктор (DI). Настройки — через `IOptions<T>` / `IOptionsSnapshot<T>`;
|
||||
прямое чтение `IConfiguration` в бизнес-коде не допускается.
|
||||
- `var` не использовать для встроенных типов и когда тип неочевиден — предпочитать явный тип
|
||||
(см. `.editorconfig`, `csharp_style_var_* = false`).
|
||||
- **Время**: `DateTimeOffset` в UTC внутри домена; на wire — epoch-миллисекунды. Локальное время —
|
||||
только на границе представления (UI).
|
||||
- **JSON на wire** — camelCase; ошибки API — объект `{ "detail": ... }`.
|
||||
- **Идентификаторы** — с префиксом сущности/типа (напр. `card_...`, `board_...`), без «сырых» чисел.
|
||||
- `this.` для обращения к членам **запрещён** (`dotnet_style_qualification_* = false:warning`).
|
||||
К приватным полям обращаемся по имени с `_` (`_logger.Info(...)`), к свойствам/методам — без
|
||||
квалификации. Запрет распространяется на поля, свойства, методы и события. **[изм.]**
|
||||
- Асинхронность: суффикс `Async`, `CancellationToken` пробрасывать до конца; `.Result` / `.Wait()`
|
||||
запрещены — только `await`.
|
||||
|
||||
## 5. Комментирование кода
|
||||
|
||||
Все комментарии — на русском языке.
|
||||
|
||||
- **Комментируем то, что видно снаружи.** XML-doc (`///`) — на **public/protected** члены, типы и
|
||||
интерфейсы. **[изм.]** Приватные/внутренние детали реализации комментариями не «обвешиваем» — только
|
||||
там, где неочевидна причина/ограничение (короткий обычный комментарий).
|
||||
- **Кратко.** Комментарий объясняет **зачем и что**, а не пересказывает код. Не дублировать имя и
|
||||
сигнатуру словами.
|
||||
- **`<summary>` — короткое описание (одна фраза).** Это назначение типа/члена, а **не** «как оно
|
||||
работает» и не пояснения/детали реализации. Несколько предложений в summary не пишем.
|
||||
- **`<remarks>` не используем** — подробные пояснения «как устроено» не нужны; rationale — только если
|
||||
поведение действительно неочевидно, коротким обычным комментарием.
|
||||
- **Никаких упоминаний процесса:** в комментариях запрещены ссылки на таски/этапы/рулинги/планы и
|
||||
прототип (`Task N`, `Ruling N`, `этап N`, `python L…`, `main.py`, `прототип`, `LEADRADAR_*` и т.п.).
|
||||
- **Внутренние `//`-комментарии — только для неочевидного поведения** (причина, ограничение, подвох).
|
||||
Пересказ кода, пошаговая навигация и «что делает следующая строка» — удалять.
|
||||
- **`<param>`/`<returns>`** — только если смысл не очевиден из имени/типа; не переписывать сигнатуру.
|
||||
- **`<summary>` — только блочный.** Открывающий `<summary>` и закрывающий `</summary>` — **каждый на
|
||||
своей строке**; запись в одну строку (`/// <summary>текст</summary>`) **не допускается**. **[изм.]**
|
||||
|
||||
Правильно:
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// Краткое описание назначения.
|
||||
/// </summary>
|
||||
public void DoWork() { }
|
||||
```
|
||||
|
||||
Неправильно:
|
||||
```csharp
|
||||
/// <summary>Краткое описание.</summary>
|
||||
public void DoWork() { }
|
||||
```
|
||||
- Прочие теги (`<param>`, `<returns>`, `<remarks>`, `<inheritdoc/>`) — по необходимости; `<param>`/`<returns>`
|
||||
можно однострочно, `<remarks>` — блоком.
|
||||
- Для функций, создающих исключения, возможные исключения указывать в `<exception>`.
|
||||
- Для примеров использования — `<example>`, `<remarks>`, `<code>`.
|
||||
- Для ссылок в документации — `<see cref="..."/>`, `<seeAlso cref="..."/>`.
|
||||
- Спецсимволы XML в тексте комментария — через `CDATA`.
|
||||
- Для сложных/неочевидных алгоритмов — пояснение каждого шага прямо в коде.
|
||||
- **[изм.]** При изменении критичных участков/ядра — комментарий: кто, когда, почему.
|
||||
- Временные заплатки — с `//TODO:` и указанием, что и когда должно быть исправлено.
|
||||
- Неочевидные межкомпонентные зависимости (не ловятся компилятором) — описывать подробно.
|
||||
|
||||
## 6. Переменные и типы
|
||||
|
||||
- Свойство использовать только когда есть смысл. Если при получении/сохранении дополнительной логики
|
||||
нет — использовать поле.
|
||||
- Использовать максимально простой достаточный тип (`int`, а не `long`, когда `int` хватает).
|
||||
- Константы — только для простых типов; для сложных — `static readonly`-поля.
|
||||
- `object` — только когда действительно необходимо; в остальных случаях generic-и. `Hashtable` → `Dictionary<>`,
|
||||
`ArrayList` → `List<>`.
|
||||
- Boxing/unboxing value-типов — только при необходимости.
|
||||
- При задании нецелых значений — минимум одна цифра до и после точки.
|
||||
- Использовать имена типов C# (`int`, `string`), а не CTS (`Int32`, `String`).
|
||||
- Поля и переменные инициализировать при объявлении, когда возможно.
|
||||
- Конструктор по умолчанию, если класс требует параметров инициализации, делать `private`, чтобы клиент
|
||||
не создал неинициализированный объект.
|
||||
- Magic numbers для статусов/состояний запрещены — только константы/enum:
|
||||
|
||||
```csharp
|
||||
// плохо
|
||||
public User GetUserByStatus(int statusId);
|
||||
// хорошо
|
||||
public User GetUserByStatus(UserStatus userStatus);
|
||||
```
|
||||
- Если `get`/`set` содержит сложные вычисления, преобразование, побочный эффект или долго выполняется —
|
||||
заменить свойством на метод.
|
||||
- Свойство не должно менять значение от вызова к вызову при неизменном состоянии объекта.
|
||||
- Внутри `get`/`set` не должно быть обращений к коду, не связанному напрямую с получением/сохранением значения.
|
||||
- Настройки, влияющие на работу приложения, не хардкодить — выносить в конфигурацию. Значения по умолчанию
|
||||
прописывать; если default невозможен и ключ отсутствует — выбрасывать исключение.
|
||||
|
||||
## 7. Функции
|
||||
|
||||
- Функции, возвращающие массив/коллекцию, всегда возвращают массив/коллекцию: если данных нет — пустой
|
||||
экземпляр, но не `null`.
|
||||
- Не более 7 параметров у функции. Больше — объединять в класс/DTO.
|
||||
- **Перенос параметров:** если параметров **больше двух** — каждый на **отдельной строке** (открывающая `(` — в конце первой строки, закрывающая `)` — на отдельной строке с отступом объявления); если **два или меньше** — все параметры **в одну строку**.
|
||||
|
||||
Больше двух:
|
||||
```csharp
|
||||
public async Task<CardMoveResultDto> MoveAsync(
|
||||
string cardId,
|
||||
string toContainerId,
|
||||
TransitionContext ctx,
|
||||
CancellationToken ct)
|
||||
```
|
||||
|
||||
Два или меньше:
|
||||
```csharp
|
||||
public User FindUser(string login, CancellationToken ct) { }
|
||||
```
|
||||
|
||||
## 8. Управление выполнением программы
|
||||
|
||||
- При `foreach` по коллекции саму коллекцию модифицировать нельзя (не добавлять и не удалять элементы).
|
||||
- Если задача решается и рекурсией, и циклом — предпочитать цикл; рекурсия — только когда цикл сложнее.
|
||||
- Тернарный оператор — только для простых проверок; сложные условия — через `if`/`else`.
|
||||
- Сложные составные условия разбивать на простые, сохраняя промежуточные результаты в `bool`-переменные.
|
||||
- Типы, реализующие `IDisposable`, создавать в `using`:
|
||||
|
||||
```csharp
|
||||
using (SqlConnection sqlConnection = new SqlConnection(...)) { }
|
||||
```
|
||||
|
||||
## 9. События, делегаты, потоки
|
||||
|
||||
- Перед вызовом делегата/события — всегда проверка на `null`.
|
||||
- Для простых event-ов использовать `EventHandler`/`EventArgs`.
|
||||
- Для сложных event-ов — наследники `EventArgs`.
|
||||
- Для блокировок использовать `lock`, а не класс `Monitor`.
|
||||
|
||||
## 10. Исключения и их обработка
|
||||
|
||||
- `try-catch` — только для непредвиденных ошибок, не для управления ходом программы.
|
||||
- При пробрасывании выше — `throw;`, а **не** `throw ex;`.
|
||||
- Свои исключения наследовать от `Exception`.
|
||||
- Исключение создавать всегда, когда функция не может быть выполнена (неверные параметры, нет доступа к
|
||||
БД, неизвестные идентификаторы и т.п.).
|
||||
- Все исключения должны быть залогированы или показаны пользователю; пустые `catch` запрещены.
|
||||
- В лог об ошибке, как правило, писать `StackTrace`.
|
||||
|
||||
## 11. Интерфейсы
|
||||
|
||||
- **Не дублировать `<summary>` интерфейса в реализации.** Если член объявлен в интерфейсе с XML-doc,
|
||||
в классе-реализации достаточно `/// <inheritdoc/>` (или вообще ничего, если doc наследуется настройкой).
|
||||
Текст описания пишется **один раз** — у интерфейса.
|
||||
- **Явная реализация интерфейсов — по умолчанию** (`Task ICardStore.GetAsync(...)`). **[изм. 2026-09-11,
|
||||
решение владельца]** Классы напрямую не вызываются — только через интерфейсы; исключения: DTO/модели
|
||||
(напр. `Card` и семейство `I*Card`), хелперы, extension-классы. Весь прод-код уже переведён на явные
|
||||
реализации (codemod `scripts/make_explicit.py`, идемпотентный).
|
||||
- Один публичный тип интерфейса = один файл (как и для классов); имя файла = имя типа.
|
||||
- **Маркерные классы не используются** — если нужен маркер, это маркерный интерфейс
|
||||
(`IKanbanModule`, `ISharedKernel` и т.п.). **[изм. 2026-09-11]**
|
||||
- **Тесты: моки — через NSubstitute** (`Substitute.For<IPasswordHasher>()`), тестовые переменные
|
||||
типизируются интерфейсом. Новые hand-written фейк-классы не заводить; существующие мигрируются
|
||||
поэтапно (план — `backlog.md`, `TD-TESTS-NSUBSTITUTE`). **[изм. 2026-09-11]**
|
||||
|
||||
## 12. Приложение: сводная таблица правил именования
|
||||
|
||||
| Идентификатор | Регистр | Пример |
|
||||
| --- | --- | --- |
|
||||
| Класс | Pascal | `User` |
|
||||
| Локальная переменная | camel | `user` |
|
||||
| Интерфейс | Pascal (`I`) | `IDisposable` |
|
||||
| Generic | Pascal (`T`) | `T`, `TKey`, `TValue` |
|
||||
| Публичная функция | Pascal | `Authenticate` |
|
||||
| Приватная функция | Pascal | `Authenticate` |
|
||||
| Параметр функции | camel | `userId` |
|
||||
| Публичное свойство | Pascal | `FirstName` |
|
||||
| Приватное свойство | Pascal | `FirstName` |
|
||||
| Публичное поле | Pascal | `FirstName` |
|
||||
| Приватное поле | `_` + camel | `_firstName` |
|
||||
| Приватное `const` / `static readonly` | Pascal | `MaxRetryCount` |
|
||||
| Константа | Pascal | `MaxRetryCount` |
|
||||
| Enum | Pascal | `UserStatus` |
|
||||
| Значение enum | Pascal | `Active` |
|
||||
| Exception | Pascal (+`Exception`) | `UserAuthenticationException` |
|
||||
| Event | Pascal | `StatusChanged` |
|
||||
| Namespace | Pascal | `Deal.Core.Cards` |
|
||||
|
||||
## 13. Автоматизация
|
||||
|
||||
- **Служебные скрипты (codemod'ы, скрипты сборки/тестов/бэкапов) в репозиторий не входят** —
|
||||
правило владельца: в репе только код. Актуальные копии живут локально, вне кода.
|
||||
- **Проверка на новом коде**: правила `<summary>`-блока и «комментарии только на public» проверяемы
|
||||
статически; задел — линтер (по аналогии с `scripts/i18n-lint.mjs`) и/или анализаторы Roslyn/StyleCop в
|
||||
`Directory.Build.props`.
|
||||
- Открытые пункты аудита и решения по ним — `docs/spec/Код-стайл-аудит-2026-09-11.md`.
|
||||
@@ -0,0 +1,62 @@
|
||||
# Аудит кода на соответствие код-стайлу «Дейл» (2026-09-11)
|
||||
|
||||
> Отчёт прохода по всему C#-коду (`src/**/*.cs`, 928 файлов, без `bin/obj`).
|
||||
> Правила — `docs/spec/Код-стайл-Дейл.md`. Проверка: сборка 4 решений + все тесты.
|
||||
|
||||
## 1. Исправлено (применено и проверено)
|
||||
|
||||
| Пункт | Правило | Было | Стало | Инструмент |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Блочный `<summary>` | §5 | 5286 однострочных/инлайн (833 файла) | 0 | `scripts/fix_summary_blocks.py --apply` |
|
||||
| XML-док на private/internal | §5 | 2028 блоков (359 файлов) | 0 (понижены до `//`) | `scripts/fix_private_docs.py --apply` |
|
||||
| Квалификация `this.` | §4 | 124 | **0** | разовый Roslyn-инструмент (семантический) |
|
||||
| Приватные instance-поля | §1 | camelCase (`logger`) | `_camelCase` (`_logger`) | разовый Roslyn-инструмент |
|
||||
| Приватные `static readonly`/`const` | §1 | — | Pascal (`DefaultTimeout`) | разовый Roslyn-инструмент |
|
||||
|
||||
Дополнительно в `.editorconfig` включены машинные правила, теперь ломающие сборку при нарушении
|
||||
(`TreatWarningsAsErrors=true`, `EnforceCodeStyleInBuild=true`):
|
||||
- `dotnet_style_qualification_for_{field,property,method,event} = false:warning` — запрет `this.`;
|
||||
- правила именования `IDE1006`: приватные instance-поля `_camelCase`, `const`/`static readonly` — Pascal.
|
||||
|
||||
Также проверено и **не требует правок**: `#region` нет; trailing whitespace нет; все файлы заканчиваются
|
||||
переводом строки; кодировка UTF-8; настоящих public-полей нет (публичные члены — свойства); явные
|
||||
модификаторы доступа соблюдены.
|
||||
|
||||
### Проверка после правок
|
||||
|
||||
- `dotnet build` — `Deal.sln`, `Deal.Telegram.sln`, `Deal.Ai.sln`, `Deal.Ml.sln`: 0 ошибок / 0 предупреждений.
|
||||
- Тесты: core **1275/1275**, telegram **125/125**, ai **52/52**, ml **38/38** — все пройдены.
|
||||
- Повторный прогон renamer: `this.` — 0, полей к переименованию — 0 (идемпотентно).
|
||||
|
||||
## 2. Остатки — решения (закрыто 2026-09-11, вечер)
|
||||
|
||||
1. **`var` — закрыто.** В `.editorconfig` включён ломающий сборку гейт `csharp_style_var_for_built_in_types = false:warning`
|
||||
(запрет только для встроенных типов — как в §4); режимы «очевидный тип» и «прочие» оставлены `silent`
|
||||
осознанно: правка субъективна и потребовала бы ~1600 механических замен. Остаток встроенных типов
|
||||
выправлен `dotnet format style --diagnostics IDE0008` по всем 5 решениям (51 файл); сборка 5 sln — 0/0.
|
||||
2. **Явная реализация интерфейсов (§11) — выполнено (вечер, решение владельца, вариант A).** 161 член
|
||||
в 30 прод-файлах конвертирован codemod'ом `scripts/make_explicit.py` (частичные классы и многострочные
|
||||
сигнатуры учтены); потребители конкретных типов перетипизированы на интерфейсы (8 мест в проде,
|
||||
17 тест-файлов); `Card`/семейство `I*Card` оставлены implicit — это DTO, их члены и есть публичный API.
|
||||
Правило закреплено в §11 код-стайла: классы напрямую не вызываем (DTO/хелперы/экстеншены — исключения).
|
||||
3. **Дедупликация `<summary>` через `<inheritdoc/>` — закрыто: дублей нет.** Проверено двумя независимыми
|
||||
сканами (сопоставление по тексту и по имени члена интерфейса: 39 интерфейсов, 229 задокументированных
|
||||
членов) — реализаций, дублирующих summary интерфейсного члена, в продакшн-коде нет; случаев
|
||||
«`<param>` + дублирующий summary» не существует.
|
||||
4. **Переводы строк — решено: LF.** Обоснование: инструменты проекта (Python/Node-скрипты, codemod'ы) пишут LF;
|
||||
shell-скрипты с CRLF не работают на Linux CI (`sh scripts/ci.sh` в GitHub Actions); фактическое большинство
|
||||
файлов уже было LF. Применено: `.gitattributes` (`* text=auto eol=lf` + бинарные исключения),
|
||||
`.editorconfig` → `end_of_line = lf`, конвертировано 1029 трекаемых файлов, `git add --renormalize`.
|
||||
Побочный эффект: починены 42 CRLF-.sh (9 в `scripts/` — до этого первый удалённый прогон CI падал бы).
|
||||
|
||||
### Попутно исправлено (2026-09-11, вечер)
|
||||
|
||||
- Повторный прогон `scripts/fix_private_docs.py --apply`: понижено 12 XML-доков на private/internal (extension-файлы).
|
||||
- Добавлены недостающие `<summary>`: `IContainerRules.Keywords`/`Stack`, `ITenantContext.TenantId`/`HasTenant`.
|
||||
- Переведены на русский англоязычные `//`-комментарии (3 шт. из 18 найденных; остальные — имена
|
||||
сущностей/заголовки секций тестов, не англоязычный текст).
|
||||
- Из индекса убраны случайно закоммиченные `archive/**/__pycache__/*.pyc` (2 шт., уже в `.gitignore`).
|
||||
- STATUS.md: удалён устаревший блок «Осталось (в backlog)» в шапке (пункты закрыты generic-контрактом источника).
|
||||
- Дочистка по контрольному скану краткости: удалены 73 очевидных `<param name="ct|cancellationToken">`
|
||||
(«Токен отмены.» — пересказ сигнатуры, §5) в 17 файлах; ужаты 3 summary (2 многосентенционных, 1 длинное).
|
||||
Контроль: `<remarks>` — 0, inline-`<summary>` — 0, многосентенционных summary — 0, TODO — 0.
|
||||
@@ -0,0 +1,257 @@
|
||||
# Дейл (Deal) — Техническое задание на новую архитектуру
|
||||
|
||||
> Версия: 1.0 (отражает этапы 0–12)
|
||||
> Дата: 2026-09-10
|
||||
> Связанные документы: `docs/architecture/2026-09-05-deal-architecture-design.md`,
|
||||
> `docs/architecture/2026-09-10-unified-api-contract.md`,
|
||||
> `docs/architecture/2026-09-10-operator-analytics-contract.md`,
|
||||
> исходное ТЗ прототипа LeadRadar V1.2 — `archive/leadradar-legacy/ТЗ-LeadRadar-v1.2.md`.
|
||||
|
||||
---
|
||||
|
||||
## 1. О продукте
|
||||
|
||||
«Дейл» — SaaS-сервис мониторинга Telegram-каналов и групп. Клиент подключает свой
|
||||
Telegram-аккаунт, выбирает каналы/группы для мониторинга, а система:
|
||||
|
||||
1. получает сообщения из источников в реальном времени;
|
||||
2. отсеивает мусор: рекламу, скам, служебные сообщения, дубликаты, устаревшее;
|
||||
3. структурирует оставшееся в **карточки** (заказ/вакансия/услуга) по профилю клиента
|
||||
(сфера, стек, бюджет, локация);
|
||||
4. раскладывает карточки по **колонкам-фильтрам** клиента;
|
||||
5. обучается на действиях клиента (ML) и всё больше обрабатывает поток сама;
|
||||
6. помогает искать и подключать новые источники (Discovery).
|
||||
|
||||
**Целевая аудитория:** специалисты и мастера в разных сферах (разработчики, дизайнеры,
|
||||
риелторы, строители и т.д.), которые ищут реальные заказы и клиентов в Telegram.
|
||||
|
||||
**Ключевая ценность:** видеть реальные заказы и клиентов, а не кучу дубликатов и рекламы.
|
||||
|
||||
---
|
||||
|
||||
## 2. Термины
|
||||
|
||||
- **Тенант** — клиент SaaS. Владеет схемой БД, настройками обработки, ML-моделью.
|
||||
- **Аккаунт (Telegram)** — личный Telegram-аккаунт тенанта, подключённый к системе.
|
||||
- **Источник** — откуда система получает записи. Сейчас это Telegram-канал/группа/чат (тема форума);
|
||||
контракт источника универсален, поэтому позже сюда добавляются другие сервисы (WhatsApp, сайты,
|
||||
файлы/таблицы) без изменения ядра.
|
||||
- **Сырое сообщение** — оригинальное сообщение из источника до обработки (входные данные пайплайна).
|
||||
- **Карточка** — единая сущность системы: ядро (id, заголовок, источник) + опциональные модули
|
||||
(содержимое, бюджет, контакты, атрибуты, комментарии, ссылки, файлы, ТЗ, история, напоминания,
|
||||
размещение в контейнере). Создаётся из прошедшего фильтры сообщения либо вручную; переезжает между
|
||||
дашбордами/контейнерами без смены сущности. Термин «лид» не используется — это лишь входное сообщение.
|
||||
- **Источник (Source)** — откуда пришла карточка: локально/вручную, ссылка на сайт, файл, Telegram
|
||||
(канал/группа/чат, тема форума), колонка импортированных данных, внешний API, ИИ (провайдер+модель),
|
||||
составной «первоисточник + цепочка обработки».
|
||||
- **Контейнер** — общая база колонок/стадий/зон: пользовательские колонки дашборда (набор фильтров),
|
||||
стадии «Выбранных», «Неразобранное», архив, корзина, терминальные зоны. У каждого контейнера —
|
||||
политика (что можно/нельзя, автоочистка, терминальность).
|
||||
- **Отсев** — сообщения, отклонённые пайплайном (с причиной).
|
||||
|
||||
---
|
||||
|
||||
## 3. Роли и доступ
|
||||
|
||||
| Роль | Возможности |
|
||||
|---|---|
|
||||
| **Оператор (владелец SaaS)** | Создаёт тенантов и инвайты; управляет лимитами; видит health; impersonation с аудитом |
|
||||
| **Тенант (клиент)** | Входит по инвайту, задаёт пароль; подключает свой Telegram-аккаунт; настраивает обработку; работает с дашбордом |
|
||||
|
||||
- Регистрация — **только по инвайту** (ссылка/код от оператора).
|
||||
- Логин: email + пароль; email уникален в масштабе SaaS; `tenantId` — в сессии/JWT.
|
||||
- Вход оператора — отдельный, изолированный от тенантов.
|
||||
|
||||
---
|
||||
|
||||
## 4. Подключение Telegram-аккаунта
|
||||
|
||||
1. Оператор один раз задаёт ключи приложения Telegram (`api_id`/`api_hash`) — глобально.
|
||||
2. Тенант в UI: «Добавить аккаунт» → QR-код (или телефон + код подтверждения).
|
||||
3. Система сохраняет сессию аккаунта (в telegram-service) и показывает статус подключения.
|
||||
4. **1 аккаунт на тенанта** на старте (схема допускает расширение).
|
||||
5. При первом подключении система подтягивает список диалогов аккаунта (каналы/группы/чаты)
|
||||
и обновляет его при каждом входе на экран каналов и в фоне (появление/исчезновение
|
||||
источников отслеживается автоматически).
|
||||
|
||||
### Мониторинг источников
|
||||
- Тенант включает/выключает мониторинг по каждому источнику из списка его диалогов.
|
||||
- Настройка «новый чат → мониторинг автоматически» (вкл/выкл).
|
||||
- Источники, удалённые/покинутые вне системы, исчезают из списка.
|
||||
- Кнопка «Перечитать»: догон последних ~10 сообщений всех включённых источников
|
||||
(с паузами, анти-бан).
|
||||
- Полученные сообщения **сразу помечаются прочитанными** в Telegram.
|
||||
|
||||
### Discovery (поиск и подключение источников)
|
||||
- Тенант создаёт **задачу поиска**: описание цели → ИИ генерирует ключевые слова.
|
||||
- Система ищет каналы/группы/форумы, в которых аккаунт **не состоит** (глобальное правило).
|
||||
- Каскад фильтров: участники → язык → содержание (по темам, порог ≥40%).
|
||||
- Кандидаты показываются «на рассмотрение» с метаданными (тип, участники, fit «X из N»,
|
||||
темы форума, метки: закрытая группа и т.п.).
|
||||
- Действия: «Вступить и мониторить» (вручную) или авто-вступление с квотами
|
||||
(50/сутки общий, паузы 50–70 с), «Отклонить» → чёрный список.
|
||||
- Чёрный список исключает источник во всех задачах; снимается вручную.
|
||||
|
||||
---
|
||||
|
||||
## 5. Обработка входящих (пайплайн)
|
||||
|
||||
Путь сообщения: **источник → очередь → стоп-лист → дедуп → ML → ИИ → карточка**.
|
||||
Всё, что отсеяно, — в «Отсеве» с причиной. Настройки обработки — **per-tenant**.
|
||||
|
||||
### Этап 1 (без ИИ, дёшево)
|
||||
1. Минимальная длина текста.
|
||||
2. **Стоп-фразы** (настраиваемый список).
|
||||
3. Отсев резюме соискателей (настройка).
|
||||
4. Тип заявки (только вакансии / только заказы) по контексту.
|
||||
5. **Дедуп**: одинаковый текст (нормализованный хэш) уже в системе → отсев «повтор».
|
||||
6. Устаревшее сообщение (старше срока архивации) → отсев.
|
||||
|
||||
### ML-слой
|
||||
- Если ML-модель тенанта уверена — решает сама: спам → отсев; колонка → карточка сразу.
|
||||
- Не уверена → сообщение уходит на ИИ.
|
||||
- Возврат из отсева (force) идёт мимо ML к ИИ-классификации.
|
||||
|
||||
### ИИ-слой (если включён)
|
||||
- ИИ-фильтр: сообщение не про заявки/интересы тенанта → отсев.
|
||||
- Классификация: структурированный разбор (компания, формат, о задаче, требования,
|
||||
плюсы, условия, бюджет, стек, контакты, тип заявки).
|
||||
- Назначение колонки с проверкой её правил.
|
||||
|
||||
### Глобальные фильтры
|
||||
- «Не создавать карточку без суммы» — отдельно для вакансий и для заказов.
|
||||
- Исключения по ключевым словам/технологиям/бюджету/локации (стоп на уровне фильтров).
|
||||
|
||||
### Карточка
|
||||
- Единая сущность: ядро (id, заголовок, источник) + опциональные модули. Вид карточки — композиция
|
||||
модулей, не отдельный класс/таблица; третий дашборд работает с той же карточкой.
|
||||
- Реализация (этап 9): карточка — **одна строка одной таблицы `Cards`** во всех дашбордах; таблица
|
||||
`ProjectCards` упразднена. Модули — колонки той же строки (`StackJson`/`LinksJson`/`FilesJson`/
|
||||
`HistoryJson`/`TzText`/напоминание), комментарии — общая таблица `LeadComments`. Колонки/стадии/зоны —
|
||||
единый реестр контейнеров; пространства не пересекаются (карточка не может быть одновременно
|
||||
в дашборде и в «Выбранных»), «взять в работу» — смена контейнера, а не клон.
|
||||
- Модули: содержимое (единая структура «О заявке»: Компания → Формат → О задаче → Требования →
|
||||
Будет плюсом → Условия), бюджет (from/to/валюта), контакты (квалифицированные: tg/phone/email/
|
||||
linkedin/site), атрибуты (стек/грейд/локация/сроки — настраиваются тенантом в UI, не зашиты),
|
||||
комментарии, ссылки, файлы, ТЗ, история движения, напоминание, размещение в контейнере.
|
||||
- Исходное сообщение карточки хранится и доступно: текст структурируется и показывается в карточке,
|
||||
вложения/ссылки/контакты — отдельными блоками; кнопка «Обновить из источника» догружает оригинал
|
||||
у сервиса-владельца источника (для Telegram — по id сообщения), если он доступен.
|
||||
|
||||
---
|
||||
|
||||
## 6. Дашборд (канбан)
|
||||
|
||||
- Колонки: «Неразобранное», пользовательские колонки (набор фильтров), «Архив», «Корзина».
|
||||
- Пользовательские колонки создаёт пользователь; ИИ может **предлагать** колонки с
|
||||
обоснованием (по каким критериям), пользователь принимает/отклоняет/переименовывает.
|
||||
- Колонка = сложный набор опциональных фильтров: ключевые слова/стек/грейд/уровень/цена/
|
||||
бюджет/локация/тип + отрицательные фильтры («чтобы не попадало»).
|
||||
- При помещении карточки в колонку указывается, **по каким критериям** она попала.
|
||||
- Карточки в колонке: свежие сверху. Drag&drop между колонками (с обучением ML).
|
||||
- Быстрые действия на карточке: комментарий, корзина, контакт, «открыть исходник».
|
||||
- Виджеты-счётчики свёрнутых колонок; колонки можно двигать, менять размер/ширину.
|
||||
- **Архив**: карточки старше N дней (настройка 1–30); очистка архива через 90 дней.
|
||||
- **Корзина**: очистка раз в 7 дней; из архива/корзины карточку можно вернуть (на канбан).
|
||||
|
||||
### «Выбранные» (пространство стадий)
|
||||
- То же пространство карточек: **те же карточки** в контейнерах-стадиях
|
||||
(Запланировано → Отклик → Согласование → В работе → Проверка → Готово / Отложено и др.).
|
||||
«Взять в работу» — переход карточки в контейнер, а не создание второй сущности.
|
||||
- У карточки наполняются модули работы: комментарии, изменение суммы/стека/контактов,
|
||||
прикрепление ссылок, ТЗ, **файлов** (медиа/документы; тип определяется автоматически;
|
||||
хранение в S3/MinIO; на карточке значки количества файлов и ссылок).
|
||||
- Отложенные: напоминания (через срок + в заданное время, выбор даты в календаре);
|
||||
настройка в общих настройках; если напоминания выключены — окно не показывается и
|
||||
установленные не срабатывают.
|
||||
- История движения карточки (статус, дата, время) — под спойлером в карточке.
|
||||
- Ручное создание карточки с тем же набором полей (пометка «создано локально»).
|
||||
- В архив/корзину дашборда карточки «Выбранных» не попадают; свои терминальные зоны:
|
||||
«Отклонено», «Выполнено» (политики контейнеров).
|
||||
|
||||
---
|
||||
|
||||
## 7. Вкладка «Обработка»
|
||||
|
||||
- **Очередь**: сырые сообщения, ожидающие обработки (этап 1 / ожидают ИИ), с автопрокруткой.
|
||||
- **Отсев**: отклонённые сообщения с причиной и источником решения
|
||||
(правила / ML / ИИ / система), включая конкретное стоп-слово/фразу.
|
||||
- У записи: метаданные (источник, подпись, вид, время), «показать исходное сообщение» с форматированием,
|
||||
кнопка обновления исходника у сервиса-владельца источника.
|
||||
- Поиск по отсеву — полнотекстовый.
|
||||
- Возврат из отсева в обработку: причины отсева игнорируются, ML/ИИ обучаются на решении;
|
||||
можно указать причину возврата.
|
||||
- Автоочистка отсева: раз в 3 дня; ручная очистка.
|
||||
- Вкладка показывает счётчик обработки (в боковой панели отсев не показывается).
|
||||
|
||||
---
|
||||
|
||||
## 8. Настройки тенанта
|
||||
|
||||
- Telegram: ключи приложения (оператор), подключение аккаунта, авто-мониторинг новых.
|
||||
- ИИ: провайдер (один; включая локальные), модель, ключ (хранится зашифрованно),
|
||||
промпты (базовый + свой), библиотека готовых промптов по сферам + «мои промпты»,
|
||||
вкл/выкл ИИ, вкл/выкл ИИ-фильтр.
|
||||
- ML: вкл/выкл, обучение на действиях, проверка на сообщении/канале, сброс, самооценка
|
||||
(«ML справляется с последними N сообщениями — ИИ можно отключить»).
|
||||
- Обработка: стоп-фразы, длина, резюме, тип заявки, домен/ключи, маркеры найма/заказа.
|
||||
- Проверка текста: сухой прогон по цепочке (стоп-правила → глобальные исключения → ML → ИИ →
|
||||
«без суммы») без создания карточки — показывает этапы, причину отсева и куда попала бы карточка.
|
||||
- Колонки: набор, правила, отрицательные фильтры, исключения.
|
||||
- Валюта: целевая валюта отображения, источник курсов (4 запроса/сутки), конвертация
|
||||
при приходе данных + пересчёт старых карточек (кроме архива/корзины); USDT = USD.
|
||||
- Хранение: срок архивации (1–30 дней), очистка архива/корзины.
|
||||
- Уведомления и напоминания (общие; отложенные — отдельно).
|
||||
- Звук, внешний вид.
|
||||
|
||||
---
|
||||
|
||||
## 9. Лимиты (бюджет токенов)
|
||||
|
||||
- Каждый тенант имеет **бюджет токенов** на LLM-вызовы (период — настраивается).
|
||||
- ai-service оценивает каждый вызов в токенах и списывает с бюджета.
|
||||
- При исчерпании: AI-обработка переключается на fallback (ML/локальный разбор),
|
||||
тенант получает уведомление; приём и базовая обработка сообщений не блокируются.
|
||||
- Оператор видит расход по тенантам в админке и может менять бюджет.
|
||||
|
||||
---
|
||||
|
||||
## 10. Админка оператора
|
||||
|
||||
- Тенанты: создание, инвайты, статус, лимиты/бюджеты, приостановка.
|
||||
- Health всех сервисов и очередей.
|
||||
- Аудит: входы/выходы, инвайты, impersonation, действия оператора и пользователей тенанта
|
||||
(создание/перенос/удаление карточек, комментарии, контейнеры, настройки, каналы).
|
||||
- Аналитика: расход токенов (по дню/тенанту/провайдеру/модели) и лента действий с фильтрами.
|
||||
- Подозрительная активность (по логам безопасности) и метрики сервисов (Prometheus/Grafana).
|
||||
- UI: оператор-консоль (`#/operator`) и страница активации инвайта (`#/join`).
|
||||
|
||||
---
|
||||
|
||||
## 11. Нефункциональные требования
|
||||
|
||||
- **Безопасность**: TLS, mTLS между сервисами, параметризованный SQL, защита от
|
||||
IDOR/XSS/SSRF/CSRF, Argon2id, rate limiting (прокси + приложение; счётчики — распределённые,
|
||||
в БД, работают при нескольких инстансах), Cloudflare.
|
||||
- **Надёжность**: ежедневные бэкапы (Postgres, файлы, сессии), outbox для событий;
|
||||
авто-очистки (retention аудита, лимитов, окон rate-limit); мгновенный разлогин suspended-сессий.
|
||||
- **Наблюдаемость**: структурированные логи → Loki, метрики (OpenTelemetry → Prometheus) → Grafana
|
||||
+ правила алертов; история расхода токенов (`token_usage_events`).
|
||||
- **Масштабируемость**: модульный монолит + отдельные сервисы (ml/ai/telegram);
|
||||
горизонтальное масштабирование сервисов; k8s — позже.
|
||||
- **Производительность**: пайплайн обрабатывает поток без потерь; анти-бан-паузы
|
||||
Telegram не блокируют обработку.
|
||||
- **Локализация (i18n)**: весь интерфейс — на русском; все пользовательские строки вынесены в ресурсы
|
||||
(без хардкода в компонентах), включая тексты ошибок; фолбэк — русский. Переключатель языка и второй
|
||||
язык — **в бэклоге**: делаем, когда возникнет потребность (основа в ресурсах уже готова).
|
||||
Область — основное приложение и оператор-консоль. (Этап 11 roadmap.)
|
||||
|
||||
---
|
||||
|
||||
## 12. Ограничения и допущения
|
||||
|
||||
- Фронтенд (Vue 3 + Vite + Tailwind) переезжает из LeadRadar; с этапа 9 контракт карточек/колонок — единый
|
||||
(`/api/cards` + `/api/containers`, см. `docs/architecture/2026-09-10-unified-api-contract.md`).
|
||||
- Данные текущего LeadRadar тестовые — не мигрируются.
|
||||
- Kafka, k8s, биллинг-провайдер, саморегистрация — вне рамок текущего этапа.
|
||||
- 1 Telegram-аккаунт на тенанта; несколько аккаунтов — позже (схема готова).
|
||||
Reference in New Issue
Block a user