Files
aiform_dev/backend/app/services/ocr_service.py

179 lines
6.9 KiB
Python
Raw Normal View History

"""
OCR Service - Распознавание документов + AI проверка
"""
import httpx
import logging
from typing import Optional, Dict, Any
from ..config import settings
import json
logger = logging.getLogger(__name__)
class OCRService:
"""Сервис для OCR и AI анализа документов"""
def __init__(self):
self.ocr_url = settings.ocr_api_url
self.ai_api_key = settings.openrouter_api_key
self.ai_model = settings.openrouter_model
async def process_document(self, file_content: bytes, filename: str) -> Dict[str, Any]:
"""
Обработка документа: OCR + AI анализ
Args:
file_content: Содержимое файла
filename: Имя файла
Returns:
Dict с результатами OCR и AI анализа
"""
result = {
"ocr_text": "",
"ai_analysis": None,
"document_type": "unknown", # policy, passport, ticket, other, garbage
"is_valid": False,
"confidence": 0.0,
"extracted_data": {}
}
try:
# Шаг 1: OCR распознавание текста
logger.info(f"🔍 Starting OCR for: {filename}")
async with httpx.AsyncClient(timeout=60.0) as client:
files = {"file": (filename, file_content, "image/jpeg")}
response = await client.post(
f"{self.ocr_url}/process",
files=files
)
if response.status_code == 200:
ocr_result = response.json()
ocr_text = ocr_result.get("text", "")
result["ocr_text"] = ocr_text
logger.info(f"📄 OCR completed: {len(ocr_text)} chars")
logger.debug(f"OCR Text preview: {ocr_text[:200]}...")
else:
logger.error(f"❌ OCR failed: {response.status_code}")
return result
# Шаг 2: AI анализ - что это за документ?
logger.info(f"🤖 Starting AI analysis with {self.ai_model}")
ai_analysis = await self._analyze_with_vision(ocr_text)
result["ai_analysis"] = ai_analysis
if ai_analysis:
result["document_type"] = ai_analysis.get("document_type", "unknown")
result["is_valid"] = ai_analysis.get("is_valid_policy", False)
result["confidence"] = ai_analysis.get("confidence", 0.0)
result["extracted_data"] = ai_analysis.get("extracted_data", {})
# Логируем результат
logger.info(f"✅ AI Analysis complete:")
logger.info(f" Document type: {result['document_type']}")
logger.info(f" Valid policy: {result['is_valid']}")
logger.info(f" Confidence: {result['confidence']}")
if result['document_type'] == 'garbage':
logger.warning(f"⚠️ GARBAGE DETECTED: {filename} - not a policy document!")
elif result['document_type'] == 'policy':
logger.info(f"✅ VALID POLICY: {filename}")
if result['extracted_data']:
logger.info(f" Extracted: {json.dumps(result['extracted_data'], ensure_ascii=False)}")
except Exception as e:
logger.error(f"❌ OCR/AI processing error: {e}")
return result
async def _analyze_with_vision(self, ocr_text: str) -> Optional[Dict[str, Any]]:
"""
Анализ через Gemini Vision
Проверяет:
- Это полис или нет?
- Извлекает данные полиса
"""
try:
prompt = f"""Проанализируй этот текст из OCR документа.
Текст: {ocr_text}
Задачи:
1. Определи тип документа: policy (страховой полис), passport, ticket, other, garbage (не документ)
2. Если это полис - извлеки данные:
- voucher (номер полиса вида E1000-302538524)
- holder_name (ФИО держателя)
- insured_from (дата начала)
- insured_to (дата окончания)
- destination (страна/регион)
3. Оцени confidence (0.0-1.0) насколько уверен
4. is_valid_policy: true если это реальный страховой полис
Ответь ТОЛЬКО в формате JSON:
{{
"document_type": "policy|passport|ticket|other|garbage",
"is_valid_policy": true/false,
"confidence": 0.95,
"extracted_data": {{
"voucher": "E1000-302538524",
"holder_name": "...",
"insured_from": "DD.MM.YYYY",
"insured_to": "DD.MM.YYYY",
"destination": "..."
}}
}}"""
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
"https://openrouter.ai/api/v1/chat/completions",
headers={
"Authorization": f"Bearer {self.ai_api_key}",
"HTTP-Referer": "http://147.45.146.17:8100",
"Content-Type": "application/json"
},
json={
"model": self.ai_model,
"messages": [
{
"role": "user",
"content": prompt
}
],
"temperature": 0.1,
"max_tokens": 500
}
)
if response.status_code == 200:
ai_response = response.json()
content = ai_response["choices"][0]["message"]["content"]
# Парсим JSON из ответа
# Убираем markdown если есть
if "```json" in content:
content = content.split("```json")[1].split("```")[0]
elif "```" in content:
content = content.split("```")[1].split("```")[0]
analysis = json.loads(content.strip())
return analysis
else:
logger.error(f"❌ AI API error: {response.status_code}")
return None
except Exception as e:
logger.error(f"❌ AI analysis error: {e}")
return None
# Глобальный экземпляр
ocr_service = OCRService()
feat: Step2 переделан + улучшен Debug Panel с полными S3 URL Step2Details (по скриншоту): ✅ Индикатор '✅ Полис найден' вверху ✅ Select с типами событий из erv_ticket: - Задержка авиарейса (более 3 часов) - Отмена авиарейса - Пропуск стыковочного рейса - Посадка на запасной аэродром - Задержка отправки поезда - Отмена поезда - Задержка/отмена парома/круизного судна ✅ Дата наступления страхового случая (DatePicker) ✅ Номер рейса/поезда/парома ✅ Загрузка подтверждающих документов: - Посадочный талон, билет, справка и т.д. - До 10 файлов по 15MB - HEIC, PDF, фото Debug Panel улучшения: ✅ Полные S3 URL (не обрезанные) ✅ Кнопка '🔗 Открыть в новой вкладке' ✅ word-break: break-all для длинных URL ✅ Показывает все файлы из массива ✅ Для каждого файла: - Filename - File ID (UUID) - Size (KB) - Полный S3 URL (кликабельный) Теперь в Debug видно КУДА загрузилось: https://s3.twcstorage.ru/f9825c87-.../policies/20251024_213045_abc123_file.jpg Можно кликнуть и посмотреть глазами! 👀
2025-10-25 09:27:56 +03:00