chandra2 / main.py
Hyungseoky's picture
Upload 8 files
9773110 verified
Raw
History Blame Contribute Delete
5.34 kB
# main.py - SiLIX Document Intelligence API (Chandra OCR 2 ๋ฒ„์ „)
# ์‹คํ–‰: docker compose up -d
from fastapi import FastAPI, UploadFile, File, HTTPException, BackgroundTasks
from fastapi.responses import JSONResponse
from pathlib import Path
import shutil
import uuid
import logging
from tool_chandra import async_process_document
from config_chandra import *
from typing import Dict
import time
import aiofiles
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s | %(levelname)s | %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)
app = FastAPI(
title="SiLIX Document Intelligence API",
description="๋ฌธ์„œ ๋ณตํ˜ธํ™” โ†’ ๋ ˆ์ด์•„์›ƒ ๋ถ„์„ โ†’ ๋งˆํฌ๋‹ค์šด/JSON ์ถ”์ถœ ์—”์ง„ (Chandra OCR 2)",
version="2.0.0"
)
BASE_DIR = Path(__file__).parent
UPLOAD_DIR = BASE_DIR / "uploads"
UPLOAD_DIR.mkdir(exist_ok=True)
def cleanup_path(path: str):
p = Path(path)
try:
if p.is_file():
p.unlink()
elif p.is_dir():
shutil.rmtree(p, ignore_errors=True)
except Exception as e:
logger.warning(f"์ •๋ฆฌ ์‹คํŒจ: {path}, ์˜ค๋ฅ˜: {e}")
@app.post("/process-file/")
async def process_file(
file: UploadFile = File(...),
background_tasks: BackgroundTasks = None,
highqual: bool = False,
use_large_model: bool = False # ๊ธฐ์กด ์ธํ„ฐํŽ˜์ด์Šค ํ˜ธํ™˜ (Chandra์—์„œ๋Š” ๋ฌด์‹œ)
):
"""
์—…๋กœ๋“œ๋œ ํŒŒ์ผ์„ ๋ณตํ˜ธํ™”ํ•˜๊ณ , Chandra OCR 2๋กœ ๋ ˆ์ด์•„์›ƒ ๋ถ„์„์„ ์ˆ˜ํ–‰ํ•˜์—ฌ
๋งˆํฌ๋‹ค์šด, JSON, ์‹œ๊ฐํ™” ์ด๋ฏธ์ง€ ๋“ฑ์„ ํฌํ•จํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
Args:
file: ์—…๋กœ๋“œ๋œ ํŒŒ์ผ (PDF, Office, ์ด๋ฏธ์ง€ ๋“ฑ)
background_tasks: ๋น„๋™๊ธฐ ์ •๋ฆฌ ์ž‘์—…
highqual: ๊ณ ํ•ด์ƒ๋„ ์ฒ˜๋ฆฌ ์—ฌ๋ถ€
use_large_model: (ํ˜ธํ™˜์šฉ, Chandra์—์„œ๋Š” ๋ฌด์‹œ๋จ)
"""
content_type = file.content_type
filename = file.filename
job_id = str(uuid.uuid4())
# --- 1. ํŒŒ์ผ ํ˜•์‹ ๋ฐ ํฌ๊ธฐ ๊ฒ€์ฆ ---
if content_type not in ALLOWED_TYPES:
raise HTTPException(status_code=400, detail=f"์ง€์›ํ•˜์ง€ ์•Š๋Š” ํ˜•์‹: {content_type}")
content = await file.read()
if len(content) > MAX_FILE_SIZE:
raise HTTPException(status_code=413, detail="ํŒŒ์ผ ํฌ๊ธฐ ์ดˆ๊ณผ (10MB ์ œํ•œ)")
upload_path = UPLOAD_DIR / f"{job_id}_{filename}"
try:
# --- 2. ํŒŒ์ผ ์ €์žฅ (๋น„๋™๊ธฐ) ---
async with aiofiles.open(upload_path, "wb") as f:
await f.write(content)
logger.info(f"[{job_id}] ์—…๋กœ๋“œ ์™„๋ฃŒ: {filename}")
# --- 3. Chandra OCR 2๋กœ ๋ฌธ์„œ ์ฒ˜๋ฆฌ ---
result = await async_process_document(
file_path=str(upload_path),
job_id=job_id,
highqual=highqual,
use_large_model=use_large_model
)
if result["status"] == "error":
logger.error(f"[{job_id}] ๋ฌธ์„œ ์ฒ˜๋ฆฌ ์‹คํŒจ: {result['message']}")
raise HTTPException(status_code=500, detail=result["message"])
# temp_dir ์ •๋ฆฌ ๋“ฑ๋ก
temp_dir = result.get("temp_dir")
if temp_dir and background_tasks:
background_tasks.add_task(cleanup_path, temp_dir)
# ์‘๋‹ต ์ƒ์„ฑ (๊ธฐ์กด ์ธํ„ฐํŽ˜์ด์Šค ์™„์ „ ํ˜ธํ™˜)
response = {
"job_id": job_id,
"filename": filename,
"status": "processed",
"message": result["message"],
"original_path": str(upload_path),
"decrypted_path": result["decrypted_path"],
"decrypted_filename": result["decrypted_filename"],
"temp_dir": None,
"full_markdown": result["full_markdown"],
"filtered_markdown": result["filtered_markdown"],
"page_markdowns": result["page_markdowns"],
"page_processed_images": result["page_processed_images"],
"json": result["json"],
"total_pages": result["total_pages"]
}
logger.info(f"[{job_id}] ์ฒ˜๋ฆฌ ์™„๋ฃŒ (Chandra OCR 2)")
return JSONResponse(response)
except HTTPException:
raise
except Exception as e:
logger.error(f"[{job_id}] ๋‚ด๋ถ€ ์˜ค๋ฅ˜: {e}", exc_info=True)
raise HTTPException(status_code=500, detail="๋‚ด๋ถ€ ์„œ๋ฒ„ ์˜ค๋ฅ˜")
finally:
if background_tasks:
background_tasks.add_task(cleanup_path, str(upload_path))
@app.get("/")
async def root():
return {
"message": "SiLIX Document Intelligence API ์šด์˜ ์ค‘",
"version": "2.0.0",
"engine": "Chandra OCR 2",
"endpoints": {
"POST /process-file/": "๋ฌธ์„œ ๋ณตํ˜ธํ™” ๋ฐ ๋ ˆ์ด์•„์›ƒ ๋ถ„์„ (์ด๋ฏธ์ง€ ํฌํ•จ)",
"Query Parameters": {
"highqual": "bool, ๊ณ ํ•ด์ƒ๋„ ์ฒ˜๋ฆฌ ์—ฌ๋ถ€ (๊ธฐ๋ณธ: False)",
"use_large_model": "bool, (ํ˜ธํ™˜์šฉ, Chandra์—์„œ๋Š” ๋ฌด์‹œ๋จ)"
}
},
"response_fields": {
"full_markdown": "Base64 ์ธ์ฝ”๋”ฉ๋œ ์ด๋ฏธ์ง€ ํฌํ•จ ๋งˆํฌ๋‹ค์šด",
"filtered_markdown": "์ด๋ฏธ์ง€ ์ž๋ฆฌ์— '[์ด๋ฏธ์ง€]'๋งŒ ํ‘œ์‹œ๋œ ๋งˆํฌ๋‹ค์šด",
"page_markdowns": "๊ฐ ํŽ˜์ด์ง€์˜ ์ˆœ์ˆ˜ ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ๋งˆํฌ๋‹ค์šด ๋ฆฌ์ŠคํŠธ",
"page_processed_images": "๊ฐ ํŽ˜์ด์ง€์˜ ๋ ˆ์ด์•„์›ƒ ๋ฐ•์Šค๊ฐ€ ์˜ค๋ฒ„๋ ˆ์ด๋œ ์ด๋ฏธ์ง€ (Base64, PNG)"
}
}