CLIO v1.0 — Scanner Inteligente com IA (MVP)

This commit is contained in:
Jarvis Deploy
2026-02-10 23:05:41 +00:00
commit 8e903d9222
41 changed files with 3190 additions and 0 deletions

View File

@@ -0,0 +1,143 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, desc
from typing import Optional
import base64
import os
from app.database import get_db
from app.models.user import User
from app.models.document import Document
from app.schemas.document import ScanRequest, DocumentResponse, DocumentListResponse
from app.services.ai_service import analyze_document
from app.utils.security import get_current_user
from app.config import settings
router = APIRouter(prefix="/api/documents", tags=["documents"])
@router.post("/scan", response_model=DocumentResponse)
async def scan_document(
req: ScanRequest,
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
# Check scan limit for free users
if user.plan == "free" and user.scan_count_today >= settings.FREE_SCAN_LIMIT:
raise HTTPException(status_code=429, detail="Limite de scans diários atingido. Faça upgrade para Premium.")
# Calculate file size
image_data = req.image
if "," in image_data:
image_data_clean = image_data.split(",", 1)[1]
else:
image_data_clean = image_data
file_size = len(base64.b64decode(image_data_clean))
# AI analysis
try:
result = await analyze_document(req.image)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Erro na análise IA: {str(e)}")
# Save document
doc = Document(
user_id=user.id,
title=result.get("title", "Documento sem título"),
category=result.get("category", "outro"),
original_image=req.image,
extracted_text=result.get("extracted_text", ""),
summary=result.get("summary", ""),
extracted_data=result.get("extracted_data", {}),
risk_alerts=result.get("risk_alerts", []),
tags=result.get("tags", []),
file_size=file_size
)
db.add(doc)
# Update scan count
user.scan_count_today += 1
await db.commit()
await db.refresh(doc)
return DocumentResponse(
id=doc.id, title=doc.title, category=doc.category,
extracted_text=doc.extracted_text, summary=doc.summary,
extracted_data=doc.extracted_data, risk_alerts=doc.risk_alerts,
tags=doc.tags, file_size=doc.file_size, created_at=doc.created_at
)
@router.get("/", response_model=DocumentListResponse)
async def list_documents(
search: Optional[str] = None,
category: Optional[str] = None,
page: int = Query(1, ge=1),
limit: int = Query(20, ge=1, le=100),
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
query = select(Document).where(Document.user_id == user.id)
count_query = select(func.count(Document.id)).where(Document.user_id == user.id)
if category:
query = query.where(Document.category == category)
count_query = count_query.where(Document.category == category)
if search:
search_filter = Document.extracted_text.ilike(f"%{search}%")
query = query.where(search_filter)
count_query = count_query.where(search_filter)
total = (await db.execute(count_query)).scalar()
result = await db.execute(query.order_by(desc(Document.created_at)).offset((page-1)*limit).limit(limit))
docs = result.scalars().all()
return DocumentListResponse(
documents=[DocumentResponse(
id=d.id, title=d.title, category=d.category,
extracted_text=d.extracted_text, summary=d.summary,
extracted_data=d.extracted_data, risk_alerts=d.risk_alerts,
tags=d.tags, file_size=d.file_size, created_at=d.created_at
) for d in docs],
total=total
)
@router.get("/{doc_id}", response_model=DocumentResponse)
async def get_document(
doc_id: int,
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
result = await db.execute(select(Document).where(Document.id == doc_id, Document.user_id == user.id))
doc = result.scalar_one_or_none()
if not doc:
raise HTTPException(status_code=404, detail="Documento não encontrado")
return DocumentResponse(
id=doc.id, title=doc.title, category=doc.category,
extracted_text=doc.extracted_text, summary=doc.summary,
extracted_data=doc.extracted_data, risk_alerts=doc.risk_alerts,
tags=doc.tags, file_size=doc.file_size, created_at=doc.created_at
)
@router.get("/{doc_id}/image")
async def get_document_image(
doc_id: int,
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
result = await db.execute(select(Document).where(Document.id == doc_id, Document.user_id == user.id))
doc = result.scalar_one_or_none()
if not doc:
raise HTTPException(status_code=404, detail="Documento não encontrado")
return {"image": doc.original_image}
@router.delete("/{doc_id}")
async def delete_document(
doc_id: int,
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
result = await db.execute(select(Document).where(Document.id == doc_id, Document.user_id == user.id))
doc = result.scalar_one_or_none()
if not doc:
raise HTTPException(status_code=404, detail="Documento não encontrado")
await db.delete(doc)
await db.commit()
return {"message": "Documento excluído"}