Files
aiform_prod/backend/app/api/upload.py
AI Assistant 134eb42493 fix: Исправлен OCR endpoint - /process → /analyze-file
Проблема:
 HTTP 404 Not Found при вызове /process
 OCR не работал вообще
 Gemini Vision не получал данные

Решение:
 Изменен endpoint на /analyze-file (правильный)
 Исправлено в 3 местах:
   - ocr_service.py (line 48)
   - upload.py - /policy endpoint (line 53)
   - upload.py - /passport endpoint (line 122)

Теперь:
 OCR будет работать
 Gemini Vision получит текст
 Debug панель покажет результаты

Тестирование:
1. Перезапусти backend
2. Загрузи файл полиса
3. Смотри логи:
   🔍 Starting OCR for: filename
   📄 OCR completed: XXX chars
   🤖 Starting AI analysis
    AI Analysis complete
2025-10-25 10:39:57 +03:00

333 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Upload API Routes - Загрузка файлов с OCR и S3
"""
from fastapi import APIRouter, UploadFile, File, HTTPException
from typing import List
import httpx
import uuid
import os
from ..config import settings
from ..services.s3_service import s3_service
from ..services.ocr_service import ocr_service
from ..services.redis_service import redis_service
from ..services.rabbitmq_service import rabbitmq_service
import logging
import json
router = APIRouter(prefix="/api/v1/upload", tags=["Upload"])
logger = logging.getLogger(__name__)
UPLOAD_DIR = "/tmp/erv_uploads"
os.makedirs(UPLOAD_DIR, exist_ok=True)
@router.post("/policy")
async def upload_policy(file: UploadFile = File(...)):
"""
Загрузить скан полиса + OCR обработка
Returns:
- file_id: ID загруженного файла
- ocr_text: распознанный текст
- extracted_data: извлеченные данные (номер полиса, серия, даты)
"""
try:
# Генерируем уникальный ID
file_id = str(uuid.uuid4())
file_ext = file.filename.split('.')[-1] if '.' in file.filename else 'jpg'
file_path = f"{UPLOAD_DIR}/{file_id}.{file_ext}"
# Сохраняем файл
with open(file_path, "wb") as f:
content = await file.read()
f.write(content)
logger.info(f"📄 File saved: {file_path}")
# Отправляем на OCR
try:
async with httpx.AsyncClient(timeout=60.0) as client:
with open(file_path, "rb") as f:
files = {"file": (file.filename, f, file.content_type)}
response = await client.post(
f"{settings.ocr_api_url}/analyze-file",
files=files
)
if response.status_code == 200:
ocr_result = response.json()
logger.info(f"✅ OCR completed for policy")
# TODO: Извлечь номер полиса, серию, даты из OCR текста
# Используем regex или AI для парсинга
return {
"success": True,
"file_id": file_id,
"ocr_text": ocr_result.get("text", ""),
"extracted_data": {
"policy_number": None, # TODO: парсинг
"policy_series": None,
"start_date": None,
"end_date": None
}
}
else:
logger.error(f"OCR error: {response.status_code}")
raise HTTPException(status_code=500, detail="OCR service error")
except Exception as ocr_error:
logger.error(f"OCR processing error: {ocr_error}")
# Возвращаем без OCR
return {
"success": True,
"file_id": file_id,
"ocr_text": "",
"extracted_data": {},
"message": "Файл загружен, но OCR не удалось выполнить"
}
except Exception as e:
logger.error(f"File upload error: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/passport")
async def upload_passport(file: UploadFile = File(...)):
"""
Загрузить скан паспорта + OCR для ФИО
Returns:
- file_id: ID загруженного файла
- ocr_text: распознанный текст
- extracted_data: ФИО, дата рождения, серия/номер
"""
try:
file_id = str(uuid.uuid4())
file_ext = file.filename.split('.')[-1] if '.' in file.filename else 'jpg'
file_path = f"{UPLOAD_DIR}/{file_id}.{file_ext}"
with open(file_path, "wb") as f:
content = await file.read()
f.write(content)
logger.info(f"📄 Passport saved: {file_path}")
# OCR обработка
try:
async with httpx.AsyncClient(timeout=60.0) as client:
with open(file_path, "rb") as f:
files = {"file": (file.filename, f, file.content_type)}
response = await client.post(
f"{settings.ocr_api_url}/analyze-file",
files=files
)
if response.status_code == 200:
ocr_result = response.json()
logger.info(f"✅ OCR completed for passport")
# TODO: Извлечь ФИО через regex или AI
return {
"success": True,
"file_id": file_id,
"ocr_text": ocr_result.get("text", ""),
"extracted_data": {
"full_name": None, # TODO: парсинг
"birth_date": None,
"passport_series": None,
"passport_number": None
}
}
else:
raise HTTPException(status_code=500, detail="OCR service error")
except Exception as ocr_error:
logger.error(f"OCR error: {ocr_error}")
return {
"success": True,
"file_id": file_id,
"ocr_text": "",
"extracted_data": {},
"message": "Файл загружен, но OCR не удалось"
}
except Exception as e:
logger.error(f"Passport upload error: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/files")
async def upload_files(files: List[UploadFile] = File(...), folder: str = "claims"):
"""
Универсальная загрузка файлов в S3
Поддерживает множественную загрузку
Args:
files: Список файлов для загрузки (макс 10 файлов по 15MB)
folder: Папка в S3 (claims, policies, documents и т.д.)
Returns:
List[dict]: Список загруженных файлов с URLs
"""
# Защита: лимит файлов
if len(files) > 10:
raise HTTPException(status_code=400, detail="Максимум 10 файлов за раз")
# Защита: санитизация folder
allowed_folders = ['claims', 'policies', 'documents', 'passports', 'tickets']
if folder not in allowed_folders:
folder = 'claims'
try:
uploaded_files = []
MAX_FILE_SIZE = 15 * 1024 * 1024 # 15MB
for file in files:
try:
# Читаем содержимое файла
content = await file.read()
# Защита: проверка размера файла
if len(content) > MAX_FILE_SIZE:
uploaded_files.append({
"success": False,
"filename": file.filename,
"error": f"Файл больше 15MB ({len(content) / 1024 / 1024:.1f}MB)"
})
continue
# Защита: валидация типа файла
allowed_types = ['image/', 'application/pdf']
if file.content_type and not any(file.content_type.startswith(t) for t in allowed_types):
uploaded_files.append({
"success": False,
"filename": file.filename,
"error": f"Недопустимый тип файла: {file.content_type}"
})
continue
# Загружаем в S3
file_url = await s3_service.upload_file(
file_content=content,
filename=file.filename,
content_type=file.content_type or 'application/octet-stream',
folder=folder
)
if file_url:
file_id = str(uuid.uuid4())
ocr_result = None # Инициализация
# Запускаем OCR в фоне через RabbitMQ
ocr_task = {
"file_id": file_id,
"file_url": file_url,
"filename": file.filename,
"folder": folder,
"content_type": file.content_type
}
# Запускаем OCR напрямую (без очереди пока)
try:
logger.info(f"🔍 Starting OCR for: {file.filename}")
# Запускаем OCR
ocr_result = await ocr_service.process_document(content, file.filename)
logger.info(f"📊 OCR returned: {type(ocr_result)}")
if isinstance(ocr_result, dict):
# Сохраняем результат в Redis на 1 час
await redis_service.set(
f"ocr_result:{file_id}",
json.dumps(ocr_result, ensure_ascii=False),
expire=3600
)
logger.info(f"💾 OCR result cached in Redis: {file_id}")
logger.info(f"📊 Document type: {ocr_result.get('document_type', 'unknown')}")
logger.info(f"✅ Valid: {ocr_result.get('is_valid', False)}, Confidence: {ocr_result.get('confidence', 0)}")
if ocr_result.get('document_type') == 'garbage':
logger.warning(f"🗑️ GARBAGE uploaded: {file.filename} (but user doesn't know)")
else:
logger.error(f"❌ OCR returned non-dict: {type(ocr_result)}")
except Exception as ocr_error:
logger.error(f"⚠️ OCR error: {ocr_error}")
import traceback
logger.error(f"Traceback: {traceback.format_exc()}")
uploaded_files.append({
"success": True,
"filename": file.filename,
"url": file_url,
"file_id": file_id,
"size": len(content),
"content_type": file.content_type,
"ocr_result": ocr_result
})
else:
uploaded_files.append({
"success": False,
"filename": file.filename,
"error": "S3 upload failed"
})
except Exception as file_error:
logger.error(f"Error uploading {file.filename}: {file_error}")
uploaded_files.append({
"success": False,
"filename": file.filename,
"error": str(file_error)
})
return {
"success": True,
"uploaded_count": len([f for f in uploaded_files if f.get("success")]),
"total_count": len(files),
"files": uploaded_files
}
except Exception as e:
logger.error(f"Batch upload error: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/ocr-result/{file_id}")
async def get_ocr_result(file_id: str):
"""
Получить результат OCR по file_id из Redis
Args:
file_id: UUID файла
Returns:
OCR результат или None если еще не обработан
"""
try:
# Достаем из Redis
result_json = await redis_service.get(f"ocr_result:{file_id}")
if result_json:
result = json.loads(result_json)
return {
"success": True,
"found": True,
"file_id": file_id,
"ocr_result": result
}
else:
return {
"success": True,
"found": False,
"message": "OCR результат еще не готов или не найден"
}
except Exception as e:
logger.error(f"Error getting OCR result: {e}")
raise HTTPException(status_code=500, detail=str(e))