Перенести настройки ИИ-провайдера в консоль оператора
+9
-10
@@ -138,17 +138,19 @@
|
|||||||
| `POST /dialogs/{dialog_id}/backfill` | Догнать сообщения одного диалога | — | `{ok: true, processed: int}` *(фронт не вызывает — только сервер)* |
|
| `POST /dialogs/{dialog_id}/backfill` | Догнать сообщения одного диалога | — | `{ok: true, processed: int}` *(фронт не вызывает — только сервер)* |
|
||||||
| `POST /dialogs/preview` | Последние сообщения диалога (свежие из TG, старые из БД) | `{dialogId, limit?=24 (clamp 1..50)}` | `{items: [§4.11 сообщение]}` |
|
| `POST /dialogs/preview` | Последние сообщения диалога (свежие из TG, старые из БД) | `{dialogId, limit?=24 (clamp 1..50)}` | `{items: [§4.11 сообщение]}` |
|
||||||
|
|
||||||
### 3.4 Settings / rates / meta (settings_routes.py) — 6
|
### 3.4 Settings / rates / meta (settings_routes.py) — 5
|
||||||
|
|
||||||
| METHOD /api/… | Назначение | Request body | Response |
|
| METHOD /api/… | Назначение | Request body | Response |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `GET /settings` | Публичные настройки (секреты замаскированы) | — | §4.6 (полный settings) |
|
| `GET /settings` | Публичные настройки (секреты замаскированы) | — | §4.6 (полный settings) |
|
||||||
| `PATCH /settings` | Частичное обновление (см. §4.6 список ключей). Инварианты: `archiveAfterDays` 1..30, `minLen` 10..500, `discJoinLimit` 1..200, `discJoinDelayMin/Max` 5..600 (min≤max), `discEvalSample` 3..30, `discEvalThreshold` 1..100; `aiConfigs` apiKey ≥8 → шифруется; `myPrompts` ≤100. ⚠ `tgKeys` удалён из настроек тенанта — ключи Telegram задаёт оператор глобально. ⚠ Ответ — **весь** public settings (фронт затирает локальное состояние ответом) | произвольный dict из публичных ключей | §4.6 |
|
| `PATCH /settings` | Частичное обновление (см. §4.6 список ключей). Инварианты: `archiveAfterDays` 1..30, `minLen` 10..500, `discJoinLimit` 1..200, `discJoinDelayMin/Max` 5..600 (min≤max), `discEvalSample` 3..30, `discEvalThreshold` 1..100; `myPrompts` ≤100. ⚠ `tgKeys`, `aiProvider` и `aiConfigs` удалены из настроек тенанта — ключи Telegram и конфигурацию ИИ задаёт оператор глобально. ⚠ Ответ — **весь** public settings (фронт затирает локальное состояние ответом) | произвольный dict из публичных ключей | §4.6 |
|
||||||
| `POST /ai/check` | Проверка подключения AI-провайдера | — | `{ok: bool, message: str}` + поля статуса провайдера |
|
|
||||||
| `GET /rates` | Курсы валют | — | `{base: "RUB", rates: {CODE: num}, source: "cbr"\|"mock", updatedAt: ms\|null}` |
|
| `GET /rates` | Курсы валют | — | `{base: "RUB", rates: {CODE: num}, source: "cbr"\|"mock", updatedAt: ms\|null}` |
|
||||||
| `POST /rates/refresh` | Принудительно обновить курсы (ЦБ/мок) | — | `{ok: bool, rates: {base, rates, source, updatedAt}}` — ⚠ фронт передаёт `r.rates` в `applyRates` |
|
| `POST /rates/refresh` | Принудительно обновить курсы (ЦБ/мок) | — | `{ok: bool, rates: {base, rates, source, updatedAt}}` — ⚠ фронт передаёт `r.rates` в `applyRates` |
|
||||||
| `GET /meta/constants` | Валюты/стадии/палитра | — | `{currencies: [{code,name,symbol}], stages: [§4.4], palette: ["#…"]}` *(фронт не вызывает — зашиты в data.js)* |
|
| `GET /meta/constants` | Валюты/стадии/палитра | — | `{currencies: [{code,name,symbol}], stages: [§4.4], palette: ["#…"]}` *(фронт не вызывает — зашиты в data.js)* |
|
||||||
|
|
||||||
|
⚠ `POST /ai/check` удалён: проверка связи с провайдером ИИ теперь операторская —
|
||||||
|
`POST /api/operator/settings/ai-config/check` (раздел «ИИ» консоли оператора).
|
||||||
|
|
||||||
### 3.5 Детальные операции карточки (бывший Projects, projects_routes.py) — 15
|
### 3.5 Детальные операции карточки (бывший Projects, projects_routes.py) — 15
|
||||||
|
|
||||||
Все операции — над ресурсом `/api/cards/{cardId}` (см. §4.1); отдельного `/api/projects` больше нет.
|
Все операции — над ресурсом `/api/cards/{cardId}` (см. §4.1); отдельного `/api/projects` больше нет.
|
||||||
@@ -349,14 +351,12 @@ links/files/history/tzText/reminder` — модули; `isNew/prevCol/isVacancy/
|
|||||||
"discJoinLimit": 50, "discJoinDelayMin": 50, "discJoinDelayMax": 70,
|
"discJoinLimit": 50, "discJoinDelayMin": 50, "discJoinDelayMax": 70,
|
||||||
"discEvalSample": 10, "discEvalThreshold": 40, // (не используется фронтом)
|
"discEvalSample": 10, "discEvalThreshold": 40, // (не используется фронтом)
|
||||||
"discPaused": false, "colState": {}, // colState — то же, что GET /columns/state
|
"discPaused": false, "colState": {}, // colState — то же, что GET /columns/state
|
||||||
"aiProvider": "deepseek",
|
|
||||||
"aiConfigs": { "deepseek": {"baseUrl": "https://api.deepseek.com", "model": "…", "keySet": true, "keyMasked": "sk-12…3456"} },
|
|
||||||
"providers": [{"id":"deepseek","name":"DeepSeek","base":"…","local":false,"models":[…]}, …]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Ключи, которые фронт шлёт в PATCH (по одному/группами): `aiProvider`, `aiConfigs{<id>:{baseUrl,model,apiKey?}}`, `aiPrompt`, `cardPrompt`, `aiFilterPrompt`, `stopPhrases`, `domainDescription`, `domainKeywords`, `hireMarkers`, `levelTerms`, `resumeMarkers`, `blockResumes`, `myPrompts`, `autoArchive`, `archiveAfterDays`, `aiEnabled`, `aiFilterEnabled`, `minLen`, `conversionOn`, `targetCurrency`, `rateSource`, `remindersEnabled`, `mlEnabled`, `wantedType`, `budgetRequiredHire`, `budgetRequiredOrder`, `hireLabel`, `orderLabel`, `autoMonitorNew`, `discJoinLimit`, `discJoinDelayMin`, `discJoinDelayMax`, `discPaused`.
|
Ключи, которые фронт шлёт в PATCH (по одному/группами): `aiPrompt`, `cardPrompt`, `aiFilterPrompt`, `stopPhrases`, `domainDescription`, `domainKeywords`, `hireMarkers`, `levelTerms`, `resumeMarkers`, `blockResumes`, `myPrompts`, `autoArchive`, `archiveAfterDays`, `aiEnabled`, `aiFilterEnabled`, `minLen`, `conversionOn`, `targetCurrency`, `rateSource`, `remindersEnabled`, `mlEnabled`, `wantedType`, `budgetRequiredHire`, `budgetRequiredOrder`, `hireLabel`, `orderLabel`, `autoMonitorNew`, `discJoinLimit`, `discJoinDelayMin`, `discJoinDelayMax`, `discPaused`.
|
||||||
⚠ Ответ PATCH — **полный** settings: `schedulePersist`/`saveAiSettings`/`saveDiscQuota` применяют его целиком к локальному state (источник истины после клампов).
|
⚠ Ответ PATCH — **полный** settings: `schedulePersist`/`saveDiscQuota` применяют его целиком к локальному state (источник истины после клампов).
|
||||||
⚠ **Изменение (решение владельца, вариант A):** ключей Telegram (`api_id`/`api_hash`) в настройках тенанта больше нет — они задаются оператором глобально (ТЗ §4.1/§8.1), см. `docs/architecture/2026-09-10-operator-analytics-contract.md` (раздел «Операторские настройки»). Вкладка Telegram у тенанта остаётся (подключение аккаунта, `GET /api/tg/status`).
|
⚠ **Изменение (решение владельца, вариант A):** ключей Telegram (`api_id`/`api_hash`) в настройках тенанта больше нет — они задаются оператором глобально (ТЗ §4.1/§8.1), см. `docs/architecture/2026-09-10-operator-analytics-contract.md` (раздел «Операторские настройки»). Вкладка Telegram у тенанта остаётся (подключение аккаунта, `GET /api/tg/status`).
|
||||||
|
⚠ **Изменение (2026-09-14):** настройки ИИ-провайдера (`aiProvider`, `aiConfigs`, `providers`) из настроек тенанта убраны — провайдера, модель, адрес и ключ задаёт оператор в консоли (раздел «ИИ», `GET/PUT /api/operator/settings/ai-config`); все ИИ-вызовы всех пользователей идут на эту конфигурацию.
|
||||||
|
|
||||||
### 4.7 Промпты
|
### 4.7 Промпты
|
||||||
- `aiPrompt`, `aiFilterPrompt`, `cardPrompt` — plain string, редактируются на вкладке ИИ; содержат плейсхолдеры `{domain}`/`{keywords}`.
|
- `aiPrompt`, `aiFilterPrompt`, `cardPrompt` — plain string, редактируются на вкладке ИИ; содержат плейсхолдеры `{domain}`/`{keywords}`.
|
||||||
@@ -383,7 +383,6 @@ links/files/history/tzText/reminder` — модули; `isNew/prevCol/isVacancy/
|
|||||||
`{passed, wouldCreateCard, targetContainer, matchHits, parsed, stages:[{stage, pass, skipped, reason, kw, label}]}`.
|
`{passed, wouldCreateCard, targetContainer, matchHits, parsed, stages:[{stage, pass, skipped, reason, kw, label}]}`.
|
||||||
Коды `stage`: `length|stop|resume|type|exclude|ml|ai|spam_ai|budget`; `skipped=true` — этап выключен
|
Коды `stage`: `length|stop|resume|type|exclude|ml|ai|spam_ai|budget`; `skipped=true` — этап выключен
|
||||||
настройкой. `parsed` — разбор текста (поля карточки) либо null. Запись в систему не производится.
|
настройкой. `parsed` — разбор текста (поля карточки) либо null. Запись в систему не производится.
|
||||||
- **`POST /api/ai/check`**: `{ok: bool, message: string, local?, keySet?}`.
|
|
||||||
- Комментарии карточки: `{id, by: string, text, time: string}` — `by` всегда «Вы», `time` «только что».
|
- Комментарии карточки: `{id, by: string, text, time: string}` — `by` всегда «Вы», `time` «только что».
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -402,7 +401,7 @@ links/files/history/tzText/reminder` — модули; `isNew/prevCol/isVacancy/
|
|||||||
|---|---:|
|
|---|---:|
|
||||||
| Auth `/api/auth` | 4 |
|
| Auth `/api/auth` | 4 |
|
||||||
| Telegram `/api/tg` | 14 |
|
| Telegram `/api/tg` | 14 |
|
||||||
| Settings/rates/meta (`/api/settings`, `/api/ai/check`, `/api/rates`) | 5 |
|
| Settings/rates/meta (`/api/settings`, `/api/rates`) | 4 |
|
||||||
| Processing `/api/pipeline` (+ `/api/admin/check-message`) | 7 |
|
| Processing `/api/pipeline` (+ `/api/admin/check-message`) | 7 |
|
||||||
| ML `/api/ml` | 5 |
|
| ML `/api/ml` | 5 |
|
||||||
| Discovery `/api/discovery` | 13 |
|
| Discovery `/api/discovery` | 13 |
|
||||||
|
|||||||
@@ -275,3 +275,73 @@
|
|||||||
> Примечание для вкладки Telegram у тенанта: `GET /api/tg/status` остаётся (подключение аккаунта),
|
> Примечание для вкладки Telegram у тенанта: `GET /api/tg/status` остаётся (подключение аккаунта),
|
||||||
> поле `keysSet` отражает глобальные ключи; команды `start-phone`/`start-qr` без ключей отвечают
|
> поле `keysSet` отражает глобальные ключи; команды `start-phone`/`start-qr` без ключей отвечают
|
||||||
> `400 { "detail": "Ключи Telegram не заданы оператором" }`.
|
> `400 { "detail": "Ключи Telegram не заданы оператором" }`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Операторские настройки: глобальная конфигурация ИИ
|
||||||
|
|
||||||
|
Провайдер, модель, адрес API и ключ ИИ задаёт оператор **глобально**, едины для всех тенантов
|
||||||
|
(в настройках тенанта ключей `aiProvider`/`aiConfigs` больше нет). Хранилище — системная таблица
|
||||||
|
`public.global_settings` (ключ `aiConfig`), `apiKey` хранится зашифрованным (`enc:`) и наружу
|
||||||
|
не отдаётся. На эту конфигурацию работают все ИИ-вызовы всех тенантов: классификация, фильтр,
|
||||||
|
ключи поиска, карточки (`AiProviderConfigBuilder`).
|
||||||
|
|
||||||
|
### GET /api/operator/settings/ai-config
|
||||||
|
|
||||||
|
Маскированный снимок конфигурации вместе с каталогом провайдеров для выбора.
|
||||||
|
|
||||||
|
**200**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"providerId": "deepseek",
|
||||||
|
"baseUrl": "https://api.deepseek.com",
|
||||||
|
"model": "deepseek-v4-flash",
|
||||||
|
"keySet": true,
|
||||||
|
"keyMasked": "sk-o…-123",
|
||||||
|
"providers": [
|
||||||
|
{ "id": "deepseek", "name": "DeepSeek", "base": "https://api.deepseek.com", "local": false, "models": ["…"] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `providerId` — пусто, если конфигурацию ещё не задавали.
|
||||||
|
- `baseUrl`/`model` — эффективные значения (адрес каталога, первая модель каталога), если не переопределены.
|
||||||
|
- `keyMasked` — **маска** (пусто / `x…` / `1234…5678`); открытый ключ не возвращается никогда.
|
||||||
|
- `providers` — каталог `AiProviders` (`id`/`name`/`base`/`local`/`models`); адрес каталогных облачных
|
||||||
|
провайдеров фиксирован (SSRF-гейт), свой `baseUrl` задаётся только локальным и `custom`.
|
||||||
|
|
||||||
|
**Коды**: `200`, `401`.
|
||||||
|
|
||||||
|
### PUT /api/operator/settings/ai-config
|
||||||
|
|
||||||
|
Сохранение/смена. Поля можно передавать **по отдельности** (частичное обновление): непереданное поле
|
||||||
|
(`null` или отсутствие в JSON) сохраняет текущее значение. Если конфигурации ещё нет, `providerId` обязателен.
|
||||||
|
При смене провайдера `baseUrl`/`model`/`apiKey` не переносятся от старого (дефолты каталога); ключ
|
||||||
|
меняется только при явной передаче (маска не принимается).
|
||||||
|
|
||||||
|
**Тело**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "providerId": "deepseek", "model": "deepseek-v4-pro", "apiKey": "sk-…" }
|
||||||
|
```
|
||||||
|
|
||||||
|
**200** — маскированный снимок (форма как у GET).
|
||||||
|
|
||||||
|
**Ошибки**
|
||||||
|
|
||||||
|
- `400 { "detail": "Укажите хотя бы одно поле (providerId, baseUrl, model, apiKey)" }`
|
||||||
|
- `400 { "detail": "Провайдер не из списка разрешённых" }`
|
||||||
|
- `400 { "detail": "Конфигурация ИИ ещё не задана — укажите providerId" }`
|
||||||
|
- `400 { "detail": "Укажите model — у выбранного провайдера нет моделей по умолчанию" }`
|
||||||
|
- `400 { "detail": "API-ключ должен быть не короче 8 символов, без маски" }`
|
||||||
|
- `401 { "detail": "Требуется вход оператора" }`
|
||||||
|
|
||||||
|
### POST /api/operator/settings/ai-config/check
|
||||||
|
|
||||||
|
Проверка связи с сохранённым провайдером (`200` — результат `AiCheckResultDto`). Локальный провайдер
|
||||||
|
отвечает `ok: true` без HTTP; облачный — запрос к списку моделей (приватные адреса запрещены, SSRF-гейт).
|
||||||
|
`400 { "detail": "Сначала сохраните конфигурацию ИИ" }` — конфигурации ещё нет.
|
||||||
|
|
||||||
|
**Аудит**: событие `ai_config_changed` (актор `operator`, `tenantId: null`, детали
|
||||||
|
`{providerId, baseUrl, model, keySet}` — без ключа).
|
||||||
|
|||||||
@@ -186,12 +186,13 @@
|
|||||||
**Настройки → Telegram:** подключение аккаунта, авто-мониторинг новых чатов.
|
**Настройки → Telegram:** подключение аккаунта, авто-мониторинг новых чатов.
|
||||||
|
|
||||||
**Настройки → ИИ:**
|
**Настройки → ИИ:**
|
||||||
- провайдер и модель (можно выбрать один, включая локальные OpenAI-совместимые);
|
|
||||||
- ключ API (хранится зашифрованно);
|
|
||||||
- **промпты**: базовый (не меняется) + свой промпт; библиотека готовых промптов по сферам
|
|
||||||
с поиском и категориями; сохранённые свои промпты («Мои промпты»);
|
|
||||||
- вкл/выкл ИИ и ИИ-фильтр. Если ML уже уверенно обрабатывает поток — система подскажет,
|
- вкл/выкл ИИ и ИИ-фильтр. Если ML уже уверенно обрабатывает поток — система подскажет,
|
||||||
что ИИ можно отключить.
|
что ИИ можно отключить;
|
||||||
|
- **промпты**: базовый (не меняется) + свой промпт; библиотека готовых промптов по сферам
|
||||||
|
с поиском и категориями; сохранённые свои промпты («Мои промпты»).
|
||||||
|
|
||||||
|
Провайдера, модель и API-ключ задаёт оператор сервиса — они едины для всех пользователей
|
||||||
|
и в кабинете не настраиваются.
|
||||||
|
|
||||||
**Настройки → ML:** включение, обучение на ваших действиях, проверка модели на сообщении/канале,
|
**Настройки → ML:** включение, обучение на ваших действиях, проверка модели на сообщении/канале,
|
||||||
сброс обучения, показатели самооценки.
|
сброс обучения, показатели самооценки.
|
||||||
@@ -261,6 +262,9 @@
|
|||||||
сырой JSON доступен под спойлером.
|
сырой JSON доступен под спойлером.
|
||||||
- **Аналитика** — обзор за период (число пространств, расход токенов, входы/выходы/неудачные входы),
|
- **Аналитика** — обзор за период (число пространств, расход токенов, входы/выходы/неудачные входы),
|
||||||
расход токенов с группировкой по дням/пространствам/провайдерам/моделям и лента действий.
|
расход токенов с группировкой по дням/пространствам/провайдерам/моделям и лента действий.
|
||||||
|
- **ИИ** — глобальный провайдер ИИ: выбор провайдера из каталога, модель, адрес API (для локальных
|
||||||
|
и «Другого») и API-ключ, а также проверка связи. Конфигурация единая для всех пользователей;
|
||||||
|
ключ хранится зашифрованным и показывается только маской.
|
||||||
- **Состояние системы** — доступность ядра, базы данных и сервисов (Telegram, ИИ, ML).
|
- **Состояние системы** — доступность ядра, базы данных и сервисов (Telegram, ИИ, ML).
|
||||||
|
|
||||||
> **Dev-окружение:** вход в консоль — `operator`/`operator`. В обычной (прод) сборке учётные
|
> **Dev-окружение:** вход в консоль — `operator`/`operator`. В обычной (прод) сборке учётные
|
||||||
|
|||||||
+5
-3
@@ -191,9 +191,9 @@ Telegram-аккаунт, выбирает каналы/группы для мо
|
|||||||
## 8. Настройки тенанта
|
## 8. Настройки тенанта
|
||||||
|
|
||||||
- Telegram: ключи приложения (оператор), подключение аккаунта, авто-мониторинг новых.
|
- Telegram: ключи приложения (оператор), подключение аккаунта, авто-мониторинг новых.
|
||||||
- ИИ: провайдер (один; включая локальные), модель, ключ (хранится зашифрованно),
|
- ИИ: провайдер (один; включая локальные), модель и ключ задаёт **оператор** глобально (едины для всех
|
||||||
промпты (базовый + свой), библиотека готовых промптов по сферам + «мои промпты»,
|
тенантов; в консоли оператора раздел «ИИ», ключ хранится зашифрованно); пользователю — промпты
|
||||||
вкл/выкл ИИ, вкл/выкл ИИ-фильтр.
|
(базовый + свой), библиотека готовых промптов по сферам + «мои промпты», вкл/выкл ИИ, вкл/выкл ИИ-фильтр.
|
||||||
- ML: вкл/выкл, обучение на действиях, проверка на сообщении/канале, сброс, самооценка
|
- ML: вкл/выкл, обучение на действиях, проверка на сообщении/канале, сброс, самооценка
|
||||||
(«ML справляется с последними N сообщениями — ИИ можно отключить»).
|
(«ML справляется с последними N сообщениями — ИИ можно отключить»).
|
||||||
- Обработка: стоп-фразы, длина, резюме, тип заявки, домен/ключи, маркеры найма/заказа.
|
- Обработка: стоп-фразы, длина, резюме, тип заявки, домен/ключи, маркеры найма/заказа.
|
||||||
@@ -221,6 +221,8 @@ Telegram-аккаунт, выбирает каналы/группы для мо
|
|||||||
## 10. Админка оператора
|
## 10. Админка оператора
|
||||||
|
|
||||||
- Тенанты: создание, инвайты, статус, лимиты/бюджеты, приостановка.
|
- Тенанты: создание, инвайты, статус, лимиты/бюджеты, приостановка.
|
||||||
|
- Глобальные настройки сервиса: ключи приложения Telegram и конфигурация ИИ-провайдера
|
||||||
|
(провайдер/модель/baseUrl/ключ; ключ зашифрован, наружу — маска) с проверкой связи.
|
||||||
- Health всех сервисов и очередей.
|
- Health всех сервисов и очередей.
|
||||||
- Аудит: входы/выходы, инвайты, impersonation, действия оператора и пользователей тенанта
|
- Аудит: входы/выходы, инвайты, impersonation, действия оператора и пользователей тенанта
|
||||||
(создание/перенос/удаление карточек, комментарии, контейнеры, настройки, каналы).
|
(создание/перенос/удаление карточек, комментарии, контейнеры, настройки, каналы).
|
||||||
|
|||||||
+23
-16
@@ -121,8 +121,10 @@ global_settings(key, value, updated_at)
|
|||||||
|
|
||||||
> `token_usage_events` — история расхода токенов (этап 10, T2; подробнее — §13.10).
|
> `token_usage_events` — история расхода токенов (этап 10, T2; подробнее — §13.10).
|
||||||
> `global_settings` — глобальные настройки уровня сервиса; сейчас хранит ключи приложения Telegram
|
> `global_settings` — глобальные настройки уровня сервиса; сейчас хранит ключи приложения Telegram
|
||||||
> (`telegramKeys`: `api_id`/`api_hash`, hash — в `enc:`), которые задаёт **оператор** глобально
|
> (`telegramKeys`: `api_id`/`api_hash`, hash — в `enc:`) и конфигурацию ИИ-провайдера (`aiConfig`:
|
||||||
> (ручки `GET/PUT /api/operator/settings/telegram-keys`); тенант ключи не видит/не задаёт.
|
> `providerId`/`baseUrl`/`model`/`apiKey` — в `enc:`), которые задаёт **оператор** глобально
|
||||||
|
> (ручки `GET/PUT /api/operator/settings/telegram-keys` и `/ai-config`, проверка связи —
|
||||||
|
> `POST /api/operator/settings/ai-config/check`); тенант эти настройки не видит и не задаёт.
|
||||||
> Операторские таблицы этапа 7 (`operators`, `operator_sessions`, `tenant_limits`, `audit_log`) и их
|
> Операторские таблицы этапа 7 (`operators`, `operator_sessions`, `tenant_limits`, `audit_log`) и их
|
||||||
> контур описаны в §13.8. С этапа 12 счётчики распределённого rate-limit и попыток входа —
|
> контур описаны в §13.8. С этапа 12 счётчики распределённого rate-limit и попыток входа —
|
||||||
> `public.rate_limit_counters` (см. §10).
|
> `public.rate_limit_counters` (см. §10).
|
||||||
@@ -468,7 +470,8 @@ DEAL_MTLS_ENABLED=0|1 DEAL_MTLS_CERT_PASSWORD=... DEAL_DEFAULT_AI_BU
|
|||||||
ответ — полный снимок; секреты наружу только масками `keyMasked`/`apiId`; внутренние ключи
|
ответ — полный снимок; секреты наружу только масками `keyMasked`/`apiId`; внутренние ключи
|
||||||
`ratesCache`/`mlDecisions`/`aiDecisions` не публикуются);
|
`ratesCache`/`mlDecisions`/`aiDecisions` не публикуются);
|
||||||
- шифрование секретов AI/Telegram: AES-256-GCM, в БД — `enc:` + Base64 (ключ — env/file, см. §13.4a);
|
- шифрование секретов AI/Telegram: AES-256-GCM, в БД — `enc:` + Base64 (ключ — env/file, см. §13.4a);
|
||||||
- проверка подключения ИИ: `POST /api/ai/check` (локальный провайдер / HTTP-проверка облачного);
|
- проверка подключения ИИ: `POST /api/operator/settings/ai-config/check` (операторская; локальный провайдер /
|
||||||
|
HTTP-проверка облачного);
|
||||||
- курсы валют: `GET /api/rates`, `POST /api/rates/refresh` (кэш `ratesCache` в settings; `mock`/ЦБ);
|
- курсы валют: `GET /api/rates`, `POST /api/rates/refresh` (кэш `ratesCache` в settings; `mock`/ЦБ);
|
||||||
- ML-панель на детерминированной заглушке: `GET /api/ml/status`, `POST /api/ml/reset|predict`
|
- ML-панель на детерминированной заглушке: `GET /api/ml/status`, `POST /api/ml/reset|predict`
|
||||||
(candidates → `{items:[]}`, apply → 404 — нет telegram-данных до этапа 6);
|
(candidates → `{items:[]}`, apply → 404 — нет telegram-данных до этапа 6);
|
||||||
@@ -746,28 +749,30 @@ health — `GET /api/health` → `{"ok":true,"service":"deal"}`.
|
|||||||
|
|
||||||
### 4a. Шифрование секретов настроек (ключи AI/Telegram)
|
### 4a. Шифрование секретов настроек (ключи AI/Telegram)
|
||||||
|
|
||||||
Секреты (`aiConfigs[].apiKey`) хранятся в `settings.ValueJson` шифротекстом:
|
Секреты хранятся шифротекстом `enc:` + Base64(nonce‖ct‖tag), AES-256-GCM (nonce 12 Б, tag 16 Б).
|
||||||
`enc:` + Base64(nonce‖ct‖tag), AES-256-GCM (nonce 12 Б, tag 16 Б). Ключ шифрования — env
|
Ключ шифрования — env `DEAL_ENCRYPTION_KEY` (32 байта в urlsafe-Base64); при отсутствии в dev
|
||||||
`DEAL_ENCRYPTION_KEY` (32 байта в urlsafe-Base64); при отсутствии в dev берётся/создаётся файл
|
берётся/создаётся файл `<ContentRoot>/data/encryption.key` (путь переопределяется env
|
||||||
`<ContentRoot>/data/encryption.key` (путь переопределяется env `DEAL_ENCRYPTION_KEY_FILE`) —
|
`DEAL_ENCRYPTION_KEY_FILE`) — при генерации лог-warning. Невалидный env-ключ — ошибка при старте.
|
||||||
при генерации лог-warning. Невалидный env-ключ — ошибка при старте. Наружу секреты не отдаются:
|
Наружу секреты не отдаются: только маски `keyMasked` (первые 4 + «…» + последние 4, len≤8 — как есть)
|
||||||
в GET/PATCH `/api/settings` только маски `keyMasked` (первые 4 + «…» + последние 4, len≤8 — как есть)
|
|
||||||
и `keySet`.
|
и `keySet`.
|
||||||
|
|
||||||
> Исторический раздел (этап 2). С этапа 12 ключей Telegram (`tgKeys`/`apiId`/`apiHash`) в настройках
|
> Исторический раздел (этап 2). С этапа 12 ключей Telegram (`tgKeys`/`apiId`/`apiHash`) в настройках
|
||||||
> тенанта нет — они задаются **оператором** глобально (таблица `public.global_settings`,
|
> тенанта нет — они задаются **оператором** глобально (таблица `public.global_settings`,
|
||||||
> `GET/PUT /api/operator/settings/telegram-keys`; hash шифруется тем же AES-256-GCM).
|
> `GET/PUT /api/operator/settings/telegram-keys`; hash шифруется тем же AES-256-GCM).
|
||||||
|
>
|
||||||
|
> С 2026-09-14 там же живёт и конфигурация ИИ-провайдера (ключ `aiConfig` в `public.global_settings`,
|
||||||
|
> `GET/PUT /api/operator/settings/ai-config`): провайдер, модель, baseUrl и API-ключ задаёт оператор,
|
||||||
|
> все ИИ-вызовы всех тенантов идут на эту конфигурацию; в настройках тенанта ключей `aiProvider`/
|
||||||
|
> `aiConfigs` больше нет.
|
||||||
|
|
||||||
### 4b. Эндпоинты этапа 2 (настройки тенанта; сессия `deal_session` обязательна, иначе 401)
|
### 4b. Эндпоинты этапа 2 (настройки тенанта; сессия `deal_session` обязательна, иначе 401)
|
||||||
|
|
||||||
- `GET /api/settings` — публичный снимок дерева настроек: дефолты модуля, перекрытые
|
- `GET /api/settings` — публичный снимок дерева настроек: дефолты модуля, перекрытые
|
||||||
переопределениями из `settings` тенанта; включает списки `providers`/`aiConfigs`/`tgKeys`/`myPrompts`.
|
переопределениями из `settings` тенанта; включает `myPrompts` и колонки, но не настройки
|
||||||
|
ИИ-провайдера (они операторские).
|
||||||
`PATCH /api/settings` — частичное обновление (невалидное поле мягко пропускается, ответ — полный
|
`PATCH /api/settings` — частичное обновление (невалидное поле мягко пропускается, ответ — полный
|
||||||
снимок). Побочные эффекты: при `rateSource` — фоновый refresh курсов. Внутренние ключи
|
снимок). Побочные эффекты: при `rateSource` — фоновый refresh курсов. Внутренние ключи
|
||||||
(`ratesCache`, `mlDecisions`, `aiDecisions`) в GET/PATCH не участвуют.
|
(`ratesCache`, `mlDecisions`, `aiDecisions`) в GET/PATCH не участвуют.
|
||||||
- `POST /api/ai/check` — проверка подключения активного провайдера (`aiProvider` + `aiConfigs`, ключ
|
|
||||||
расшифровывается): локальный провайдер → `ok:true` «Локальный сервер…»; облачный — HTTP `GET
|
|
||||||
{base}/models`; без ключа → «Не задан API-ключ».
|
|
||||||
- `GET /api/rates` / `POST /api/rates/refresh` — курсы к RUB (`base` = `RUB`); источник по `rateSource`
|
- `GET /api/rates` / `POST /api/rates/refresh` — курсы к RUB (`base` = `RUB`); источник по `rateSource`
|
||||||
(`mock` — константа, `cbr` — ЦБ РФ, ≤4 запроса/сутки, интервал 6 ч; `USDT`=`USD`); кэш — внутренняя
|
(`mock` — константа, `cbr` — ЦБ РФ, ≤4 запроса/сутки, интервал 6 ч; `USDT`=`USD`); кэш — внутренняя
|
||||||
настройка `ratesCache` `{rates, source, updatedAtMs}`.
|
настройка `ratesCache` `{rates, source, updatedAtMs}`.
|
||||||
@@ -1107,7 +1112,8 @@ docker compose -f deploy/compose.dev.yml down # погасить ст
|
|||||||
#### AI-фасад ai-service (Ruling 5)
|
#### AI-фасад ai-service (Ruling 5)
|
||||||
|
|
||||||
- Без БД: core передаёт в теле запроса заполненные промпты (подстановка `{domain}`/`{keywords}`), конфиг
|
- Без БД: core передаёт в теле запроса заполненные промпты (подстановка `{domain}`/`{keywords}`), конфиг
|
||||||
провайдера (id/base/model/apiKey/api_style — расшифрованный из `aiConfigs`) и текст. Методы: `Filter`
|
провайдера (id/base/model/apiKey/api_style — расшифрованный из глобальной конфигурации оператора,
|
||||||
|
ключ `aiConfig`) и текст. Методы: `Filter`
|
||||||
→ {pass,reason}; `Classify` → {ok,json} (json-строку маппит core в `AiParsedLeadDto`, строгий маппинг);
|
→ {pass,reason}; `Classify` → {ok,json} (json-строку маппит core в `AiParsedLeadDto`, строгий маппинг);
|
||||||
`GenerateKeywords` → {keywords}; `EvaluateFit` → {fit,reason} (Discovery).
|
`GenerateKeywords` → {keywords}; `EvaluateFit` → {fit,reason} (Discovery).
|
||||||
- Транспорт: OpenAI-совместимые `POST {base}/chat/completions` (Bearer) и Anthropic
|
- Транспорт: OpenAI-совместимые `POST {base}/chat/completions` (Bearer) и Anthropic
|
||||||
@@ -1123,8 +1129,9 @@ docker compose -f deploy/compose.dev.yml down # погасить ст
|
|||||||
(`PUT /api/operator/settings/telegram-keys`, hash шифруется) → `POST /api/tg/start-qr` → QR-скан →
|
(`PUT /api/operator/settings/telegram-keys`, hash шифруется) → `POST /api/tg/start-qr` → QR-скан →
|
||||||
фаза `ready` («Telegram подключён, сессия сохранена»), затем реальные диалоги/мониторинг/«Перечитать»/
|
фаза `ready` («Telegram подключён, сессия сохранена»), затем реальные диалоги/мониторинг/«Перечитать»/
|
||||||
discovery-поиск и вступления. В настройках тенанта ключей нет (решение владельца, вариант A).
|
discovery-поиск и вступления. В настройках тенанта ключей нет (решение владельца, вариант A).
|
||||||
- LLM: `PATCH /api/settings` `aiConfigs`/`aiProvider` (напр. DeepSeek или локальный OpenAI-совместимый) →
|
- LLM: оператор задаёт провайдера и модель в консоли (`PUT /api/operator/settings/ai-config`, напр. DeepSeek
|
||||||
`POST /api/ai/check`; реальная классификация/фильтр/генерация ключей при `Services__Ai__UseLocal=false`.
|
или локальный OpenAI-совместимый) → `POST /api/operator/settings/ai-config/check`; реальная
|
||||||
|
классификация/фильтр/генерация ключей при `Services__Ai__UseLocal=false`.
|
||||||
- Сквозной smoke стека — `scripts/dev-smoke.sh` (одна команда; Docker Desktop должен быть поднят).
|
- Сквозной smoke стека — `scripts/dev-smoke.sh` (одна команда; Docker Desktop должен быть поднят).
|
||||||
|
|
||||||
### 8. Этап 7 — SaaS-контур (Tasks 1–14; бэкапы — §13.9; финальные доки — Task 16): оператор/инвайты/лимиты/аудит/rate-limit/mTLS/логи/compose-prod
|
### 8. Этап 7 — SaaS-контур (Tasks 1–14; бэкапы — §13.9; финальные доки — Task 16): оператор/инвайты/лимиты/аудит/rate-limit/mTLS/логи/compose-prod
|
||||||
|
|||||||
Reference in New Issue
Block a user