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

Первый коммит: модульный монолит ядра (.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,74 @@
"""Мониторинг пайплайна — вкладка «Обработка» (очередь и отсев)."""
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from ..auth import current_login
from ..services import processing as processing_svc
router = APIRouter(prefix="/api/pipeline", tags=["processing"])
class ReturnBody(BaseModel):
reason: str = ""
@router.get("/stats")
def stats(_: str = Depends(current_login)) -> dict:
"""Сводка: размер очереди (new/ai) и число записей в отсеве."""
return processing_svc.stats()
@router.get("/queue")
def queue(limit: int = 100, _: str = Depends(current_login)) -> dict:
"""Сырые сообщения, ожидающие обработки (этап 1 или ИИ)."""
q = processing_svc.queue_counts()
return {
"items": processing_svc.list_queue(limit),
"counts": q,
"rejected": processing_svc.rejected_count(),
}
@router.get("/rejected")
def rejected(
q: str = "",
offset: int = 0,
limit: int = 100,
_: str = Depends(current_login),
) -> dict:
"""Отсев: что и почему не прошло пайплайн (полнотекстовый поиск по q)."""
return processing_svc.list_rejected(q, offset, limit)
@router.post("/rejected/clear")
def rejected_clear(_: str = Depends(current_login)) -> dict:
"""Ручная полная очистка отсева (безвозвратно)."""
cleared = processing_svc.clear_all()
return {"ok": True, "cleared": cleared}
@router.delete("/rejected/{rej_id}")
def rejected_delete(rej_id: str, _: str = Depends(current_login)) -> dict:
processing_svc.delete_one(rej_id)
return {"ok": True}
@router.post("/rejected/{rej_id}/return")
def rejected_return(rej_id: str, body: ReturnBody, _: str = Depends(current_login)) -> dict:
"""Вернуть отсеянное сообщение в обработку.
Для возвращённого сообщения причины отсева (стоп-лист, резюме, тип,
без суммы, устарело, ML/ИИ-спам) игнорируются: оно уходит на ML/ИИ
и создаёт карточку. ML обучается на действии (снятие «спама»), а решение
ИИ по возвращённому сообщению снова учит ML. Запись в отсеве помечается
«возвращено» с указанной пользователем причиной.
"""
try:
out = processing_svc.return_to_queue(rej_id, body.reason)
except KeyError as exc:
raise HTTPException(404, "Запись не найдена") from exc
except ValueError as exc:
raise HTTPException(400, str(exc)) from exc
return out