diff --git a/storage/2025/November/week5/399466_1_заявление_потребителя_Селдушев____стр.pdf b/storage/2025/November/week5/399466_1_заявление_потребителя_Селдушев____стр.pdf new file mode 100644 index 00000000..8f62f23c Binary files /dev/null and b/storage/2025/November/week5/399466_1_заявление_потребителя_Селдушев____стр.pdf differ diff --git a/ticket_form/backend/app/api/documents.py b/ticket_form/backend/app/api/documents.py index 4adf5d1b..ea2a44ee 100644 --- a/ticket_form/backend/app/api/documents.py +++ b/ticket_form/backend/app/api/documents.py @@ -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}", } diff --git a/ticket_form/docs/SESSION_LOG_2025-11-29_RAG_WORKFLOW.md b/ticket_form/docs/SESSION_LOG_2025-11-29_RAG_WORKFLOW.md index 056ea3f4..0f7bb15c 100644 --- a/ticket_form/docs/SESSION_LOG_2025-11-29_RAG_WORKFLOW.md +++ b/ticket_form/docs/SESSION_LOG_2025-11-29_RAG_WORKFLOW.md @@ -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` — шаблон формы заявления + diff --git a/ticket_form/docs/n8n_nodes/README_SETUP.md b/ticket_form/docs/n8n_nodes/README_SETUP.md index 4a109c09..3c28841a 100644 --- a/ticket_form/docs/n8n_nodes/README_SETUP.md +++ b/ticket_form/docs/n8n_nodes/README_SETUP.md @@ -97,3 +97,4 @@ check_all_ready (FALSE) → (конец) + diff --git a/ticket_form/docs/n8n_nodes/check_all_ready.json b/ticket_form/docs/n8n_nodes/check_all_ready.json index 15104925..c7e1dd5c 100644 --- a/ticket_form/docs/n8n_nodes/check_all_ready.json +++ b/ticket_form/docs/n8n_nodes/check_all_ready.json @@ -30,3 +30,4 @@ + diff --git a/ticket_form/docs/n8n_nodes/publish_docs_ready.json b/ticket_form/docs/n8n_nodes/publish_docs_ready.json index a0949262..914dc44e 100644 --- a/ticket_form/docs/n8n_nodes/publish_docs_ready.json +++ b/ticket_form/docs/n8n_nodes/publish_docs_ready.json @@ -18,3 +18,4 @@ + diff --git a/ticket_form/docs/n8n_nodes/redis_incr_ready.json b/ticket_form/docs/n8n_nodes/redis_incr_ready.json index 39747e7b..e473a53f 100644 --- a/ticket_form/docs/n8n_nodes/redis_incr_ready.json +++ b/ticket_form/docs/n8n_nodes/redis_incr_ready.json @@ -17,3 +17,4 @@ + diff --git a/ticket_form/docs/n8n_nodes/update_ocr_error.json b/ticket_form/docs/n8n_nodes/update_ocr_error.json index c862f8a7..55927f69 100644 --- a/ticket_form/docs/n8n_nodes/update_ocr_error.json +++ b/ticket_form/docs/n8n_nodes/update_ocr_error.json @@ -19,3 +19,4 @@ + diff --git a/ticket_form/docs/n8n_nodes/update_ocr_status.json b/ticket_form/docs/n8n_nodes/update_ocr_status.json index 1d8fdead..5a5f7154 100644 --- a/ticket_form/docs/n8n_nodes/update_ocr_status.json +++ b/ticket_form/docs/n8n_nodes/update_ocr_status.json @@ -18,3 +18,4 @@ +