FastAPI app that parses CMM inspection reports (PDF/Excel/CSV), computes SPC metrics (Cp/Cpk/Pp/Ppk, control limits, Shapiro-Wilk), generates interactive Plotly charts, and provides AI-powered quality summaries via Azure OpenAI with graceful fallback. Includes 21 passing tests covering parsers, SPC calculations, and API endpoints.
47 lines
1.4 KiB
Python
47 lines
1.4 KiB
Python
from __future__ import annotations
|
|
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
from fastapi import APIRouter, HTTPException, UploadFile
|
|
|
|
from app.config import settings
|
|
from app.services.batch import process_batch
|
|
|
|
router = APIRouter()
|
|
|
|
ALLOWED_EXTENSIONS = {".pdf", ".xlsx", ".xls", ".csv"}
|
|
|
|
|
|
@router.post("/upload")
|
|
async def upload_files(files: list[UploadFile]):
|
|
if not files:
|
|
raise HTTPException(400, "No files provided")
|
|
|
|
saved: list[Path] = []
|
|
tmp_dir = Path(tempfile.mkdtemp(prefix="cmm_"))
|
|
|
|
for f in files:
|
|
if not f.filename:
|
|
continue
|
|
ext = Path(f.filename).suffix.lower()
|
|
if ext not in ALLOWED_EXTENSIONS:
|
|
raise HTTPException(
|
|
400, f"Unsupported file type: {ext}. Allowed: {ALLOWED_EXTENSIONS}"
|
|
)
|
|
size = 0
|
|
dest = tmp_dir / f.filename
|
|
with open(dest, "wb") as out:
|
|
while chunk := await f.read(1024 * 64):
|
|
size += len(chunk)
|
|
if size > settings.max_upload_mb * 1024 * 1024:
|
|
raise HTTPException(400, f"File too large (max {settings.max_upload_mb} MB)")
|
|
out.write(chunk)
|
|
saved.append(dest)
|
|
|
|
if not saved:
|
|
raise HTTPException(400, "No valid files uploaded")
|
|
|
|
batch_id = await process_batch(saved)
|
|
return {"batch_id": batch_id, "file_count": len(saved)}
|