Initial commit: Robot ökoszisztéma v2.0 - Stabilizált jármű és szerviz robotok
This commit is contained in:
135
backend/app/services/document_service.py
Executable file
135
backend/app/services/document_service.py
Executable file
@@ -0,0 +1,135 @@
|
||||
# /opt/docker/dev/service_finder/backend/app/services/document_service.py
|
||||
import os
|
||||
import logging
|
||||
import asyncio
|
||||
from PIL import Image
|
||||
from uuid import uuid4
|
||||
from datetime import datetime, timezone
|
||||
from fastapi import UploadFile, BackgroundTasks, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func, and_
|
||||
|
||||
from app.models.document import Document
|
||||
from app.models.identity import User
|
||||
from app.services.config_service import config # 2.0 Dinamikus beállítások
|
||||
from app.workers.ocr.robot_1_ocr_processor import OCRRobot # Robot 1 hívása
|
||||
|
||||
logger = logging.getLogger("Document-Service-2.0")
|
||||
|
||||
class DocumentService:
|
||||
"""
|
||||
Document Service 2.0 - Admin-vezérelt Pipeline.
|
||||
Feladata: Tárolás, Optimalizálás, Kvótamanagement és Robot Trigger.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
async def process_upload(
|
||||
db: AsyncSession,
|
||||
user_id: int,
|
||||
file: UploadFile,
|
||||
parent_type: str, # pl. "asset", "organization", "transfer"
|
||||
parent_id: str,
|
||||
doc_type: str, # pl. "invoice", "registration_card", "sale_contract"
|
||||
background_tasks: BackgroundTasks
|
||||
):
|
||||
try:
|
||||
# --- 1. ADMIN KVÓTA ELLENŐRZÉS ---
|
||||
user_stmt = select(User).where(User.id == user_id)
|
||||
user = (await db.execute(user_stmt)).scalar_one()
|
||||
|
||||
# Lekérjük a csomagnak megfelelő havi limitet (pl. Free: 1, Premium: 10)
|
||||
limits = await config.get_setting(db, "ocr_monthly_limit", default={"free": 1, "premium": 10, "vip": 100})
|
||||
user_role = user.role.value if hasattr(user.role, 'value') else str(user.role)
|
||||
allowed_ocr = limits.get(user_role, 1)
|
||||
|
||||
# Megnézzük a havi felhasználást
|
||||
now = datetime.now(timezone.utc)
|
||||
start_of_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||||
|
||||
count_stmt = select(func.count(Document.id)).where(
|
||||
and_(
|
||||
Document.user_id == user_id,
|
||||
Document.created_at >= start_of_month
|
||||
)
|
||||
)
|
||||
used_count = (await db.execute(count_stmt)).scalar()
|
||||
|
||||
if used_count >= allowed_ocr:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail=f"Havi dokumentum limit túllépve ({allowed_ocr}). Válts csomagot a folytatáshoz!"
|
||||
)
|
||||
|
||||
# --- 2. DINAMIKUS TÁROLÁS ÉS OPTIMALIZÁLÁS ---
|
||||
file_uuid = str(uuid4())
|
||||
nas_base = await config.get_setting(db, "storage_nas_path", default="/mnt/nas/app_data")
|
||||
|
||||
# Útvonal sablonok az adminból (Pl. "vault/{parent_type}/{parent_id}")
|
||||
vault_dir = os.path.join(nas_base, parent_type, parent_id, "vault")
|
||||
thumb_dir = os.path.join(getattr(config, "STATIC_DIR", "static"), "previews", parent_type, parent_id)
|
||||
|
||||
os.makedirs(vault_dir, exist_ok=True)
|
||||
os.makedirs(thumb_dir, exist_ok=True)
|
||||
|
||||
content = await file.read()
|
||||
temp_path = f"/tmp/{file_uuid}_{file.filename}"
|
||||
with open(temp_path, "wb") as f: f.write(content)
|
||||
|
||||
# Kép feldolgozása PIL-lel
|
||||
img = Image.open(temp_path)
|
||||
|
||||
# Thumbnail generálás (SSD/Static területre)
|
||||
thumb_filename = f"{file_uuid}_thumb.webp"
|
||||
thumb_path = os.path.join(thumb_dir, thumb_filename)
|
||||
thumb_img = img.copy()
|
||||
thumb_img.thumbnail((300, 300))
|
||||
thumb_img.save(thumb_path, "WEBP", quality=80)
|
||||
|
||||
# Optimalizált eredeti (NAS / Vault területre)
|
||||
max_width = await config.get_setting(db, "img_max_width", default=1600)
|
||||
vault_filename = f"{file_uuid}.webp"
|
||||
vault_path = os.path.join(vault_dir, vault_filename)
|
||||
|
||||
if img.width > max_width:
|
||||
ratio = max_width / img.width
|
||||
img = img.resize((max_width, int(img.height * ratio)), Image.Resampling.LANCZOS)
|
||||
|
||||
img.save(vault_path, "WEBP", quality=85)
|
||||
|
||||
# --- 3. MENTÉS ---
|
||||
new_doc = Document(
|
||||
id=uuid4(),
|
||||
user_id=user_id,
|
||||
parent_type=parent_type,
|
||||
parent_id=parent_id,
|
||||
doc_type=doc_type,
|
||||
original_name=file.filename,
|
||||
file_hash=file_uuid,
|
||||
file_ext="webp",
|
||||
mime_type="image/webp",
|
||||
file_size=os.path.getsize(vault_path),
|
||||
has_thumbnail=True,
|
||||
thumbnail_path=f"/static/previews/{parent_type}/{parent_id}/{thumb_filename}",
|
||||
status="uploaded"
|
||||
)
|
||||
db.add(new_doc)
|
||||
await db.flush()
|
||||
|
||||
# --- 4. ROBOT TRIGGER (OCR AUTOMATIZMUS) ---
|
||||
# Megnézzük, hogy ez a típus (pl. invoice) igényel-e automatikus OCR-t
|
||||
auto_ocr_types = await config.get_setting(db, "ocr_auto_trigger_types", default=["invoice", "registration_card", "sale_contract"])
|
||||
|
||||
if doc_type in auto_ocr_types:
|
||||
# Robot 1 (OCR) sorba állítása háttérfolyamatként
|
||||
background_tasks.add_task(OCRRobot.process_document, db, new_doc.id)
|
||||
new_doc.status = "processing"
|
||||
logger.info(f"🤖 Robot 1 (OCR) riasztva: {new_doc.id}")
|
||||
|
||||
await db.commit()
|
||||
os.remove(temp_path)
|
||||
return new_doc
|
||||
|
||||
except Exception as e:
|
||||
if 'temp_path' in locals() and os.path.exists(temp_path): os.remove(temp_path)
|
||||
logger.error(f"Document Upload Error: {e}")
|
||||
raise e
|
||||
Reference in New Issue
Block a user