feat(backend): кэширование form_draft в check-ocr-status

- Добавлена проверка наличия черновика в БД перед запуском RAG
- Если documents_hash совпадает — возвращаем черновик из кэша
- Если черновика нет или он устарел — запускаем RAG workflow
- Добавлен параметр force_refresh для принудительного обновления
- Импортирован db сервис для работы с PostgreSQL
This commit is contained in:
Fedor
2025-11-30 11:30:36 +03:00
parent 985ee23810
commit 3801bc4949
9 changed files with 119 additions and 5 deletions

View File

@@ -8,9 +8,11 @@ from typing import Optional, List
import httpx
import json
import uuid
import hashlib
from datetime import datetime
import logging
from ..services.redis_service import redis_service
from ..services.database import db
from ..config import settings
router = APIRouter(prefix="/api/v1/documents", tags=["Documents"])
@@ -611,20 +613,35 @@ async def generate_documents_list(request: Request):
def compute_documents_hash(doc_ids: List[str]) -> str:
"""Вычисляет hash от списка document_id для проверки актуальности черновика"""
sorted_ids = sorted([d for d in doc_ids if d])
hash_input = ','.join(sorted_ids)
# Используем простой hash как в n8n (djb2)
hash_val = 5381
for char in hash_input:
hash_val = ((hash_val << 5) + hash_val) + ord(char)
return format(abs(hash_val) & 0xFFFFFFFF, 'x').zfill(8)
@router.post("/check-ocr-status")
async def check_ocr_status(request: Request):
"""
Проверка статуса OCR обработки документов.
Вызывается при нажатии "Продолжить" после загрузки документов.
Пушит запрос в Redis канал clpr:check:ocr_status, который слушает n8n workflow.
n8n проверяет статус всех документов и публикует результат в ocr_events:{session_id}.
Логика:
1. Проверяем наличие form_draft в payload
2. Если черновик есть и documents_hash совпадает — возвращаем его
3. Если черновика нет или он устарел — запускаем RAG workflow
"""
try:
body = await request.json()
claim_id = body.get("claim_id")
session_id = body.get("session_id")
force_refresh = body.get("force_refresh", False) # Принудительное обновление
if not claim_id or not session_id:
raise HTTPException(
@@ -637,10 +654,98 @@ async def check_ocr_status(request: Request):
extra={
"claim_id": claim_id,
"session_id": session_id,
"force_refresh": force_refresh,
},
)
# Публикуем запрос в Redis для n8n workflow
# =====================================================
# ШАГ 1: Проверяем наличие черновика в БД
# =====================================================
if not force_refresh:
try:
# Получаем form_draft и список документов
claim_data = await db.fetch_one("""
SELECT
c.payload->'form_draft' AS form_draft,
(
SELECT array_agg(cd.id::text ORDER BY cd.id)
FROM clpr_claim_documents cd
WHERE cd.claim_id::uuid = c.id
) AS document_ids
FROM clpr_claims c
WHERE c.id = $1::uuid
""", claim_id)
if claim_data and claim_data.get('form_draft'):
form_draft = claim_data['form_draft']
# Если form_draft — строка, парсим JSON
if isinstance(form_draft, str):
form_draft = json.loads(form_draft)
saved_hash = form_draft.get('documents_hash', '')
document_ids = claim_data.get('document_ids') or []
current_hash = compute_documents_hash(document_ids)
logger.info(
"📋 Draft check",
extra={
"saved_hash": saved_hash,
"current_hash": current_hash,
"docs_count": len(document_ids),
},
)
# ✅ Черновик актуален — возвращаем его!
if saved_hash == current_hash:
logger.info(
"✅ Using cached form_draft",
extra={
"claim_id": claim_id,
"hash": saved_hash,
},
)
# Публикуем событие что данные готовы
event_data = {
"event_type": "form_draft_ready",
"status": "ready",
"message": "Черновик формы готов",
"claim_id": claim_id,
"session_id": session_id,
"form_draft": form_draft,
"from_cache": True,
"timestamp": datetime.utcnow().isoformat(),
}
await redis_service.publish(
f"ocr_events:{session_id}",
json.dumps(event_data, ensure_ascii=False)
)
return {
"success": True,
"status": "ready",
"message": "Черновик формы готов (из кэша)",
"from_cache": True,
"form_draft": form_draft,
"listen_channel": f"ocr_events:{session_id}",
}
else:
logger.info(
"🔄 Draft outdated, running RAG",
extra={
"reason": "documents_hash mismatch",
"saved_hash": saved_hash,
"current_hash": current_hash,
},
)
except Exception as e:
logger.warning(f"⚠️ Draft check failed: {e}, proceeding with RAG")
# =====================================================
# ШАГ 2: Черновика нет или устарел — запускаем RAG
# =====================================================
event_data = {
"claim_id": claim_id,
"session_token": session_id,
@@ -655,7 +760,7 @@ async def check_ocr_status(request: Request):
)
logger.info(
"✅ OCR status check published",
"✅ OCR status check published (running RAG)",
extra={
"channel": channel,
"subscribers": subscribers,
@@ -665,7 +770,9 @@ async def check_ocr_status(request: Request):
return {
"success": True,
"message": "Запрос на проверку OCR статуса отправлен",
"status": "processing",
"message": "Запрос на обработку документов отправлен",
"from_cache": False,
"channel": channel,
"listen_channel": f"ocr_events:{session_id}",
}

View File

@@ -431,3 +431,4 @@ return [
- `ticket_form/docs/SQL_CLAIMSAVE_FINAL_FIXED_NEW_FLOW_WITH_UPLOADED_FIXED.sql` — SQL для сохранения документов
- `ticket_form/frontend/src/components/form/generateConfirmationFormHTML.ts` — шаблон формы заявления

View File

@@ -97,3 +97,4 @@ check_all_ready (FALSE) → (конец)

View File

@@ -30,3 +30,4 @@

View File

@@ -17,3 +17,4 @@

View File

@@ -19,3 +19,4 @@

View File

@@ -18,3 +18,4 @@