Files
aiform_prod/backend/app/services/ocr_service.py
AI Assistant ddca18716d docs: Обновлен SESSION_LOG + инструкции по перезапуску
Добавлено:
 SESSION_LOG_2025-10-24.md обновлен с работой 25 октября
 git_push_all.sh - скрипт для автоматического push
 restart_backend.sh - скрипт перезапуска backend
 RESTART_INSTRUCTIONS.md - подробные инструкции
 QUICK_START.md - быстрый старт

Документация сессии 25 октября:
- 9 коммитов
- Split-screen UI + Debug панель
- OCR + AI анализ (Gemini Vision)
- S3 Upload + Draft автосохранение
- Step2 переделан (7 типов событий из erv_ticket)
- MySQL валидация полисов (33,963 шт)
- Безопасность и UX улучшения
- ~1500 строк кода, 20 файлов, 12 проблем решено

Готово к тестированию после перезапуска backend!
2025-10-25 09:56:01 +03:00

179 lines
6.9 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.

"""
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()