"""Shared utilities — upload validation, embed status tracking, global process registries.""" import json import asyncio from pathlib import Path from fastapi import HTTPException, UploadFile from config import _EMBED_STATUS_DIR, _MAX_UPLOAD_BYTES # ── Global process registries (used by routes and shutdown handler) ─────────── running_reports: dict[str, asyncio.subprocess.Process] = {} running_terraform: dict[str, asyncio.subprocess.Process] = {} # ── Embed status (file-based) ──────────────────────────────────────────────── def set_embed_status(task_id: str, data: dict): (_EMBED_STATUS_DIR / f"{task_id}.json").write_text(json.dumps(data), encoding="utf-8") def get_embed_status(task_id: str) -> dict | None: p = _EMBED_STATUS_DIR / f"{task_id}.json" if p.exists(): try: return json.loads(p.read_text(encoding="utf-8")) except Exception: return None return None def update_embed_status(task_id: str, updates: dict): current = get_embed_status(task_id) or {} current.update(updates) set_embed_status(task_id, current) # ── File upload validation ──────────────────────────────────────────────────── async def validate_upload(file: UploadFile, allowed_exts: set[str] | None = None, max_bytes: int = _MAX_UPLOAD_BYTES): """Validate uploaded file size and extension. Returns file data bytes.""" data = await file.read() if len(data) > max_bytes: raise HTTPException(400, f"Arquivo excede o limite de {max_bytes // (1024*1024)}MB") if allowed_exts and file.filename: ext = Path(file.filename).suffix.lower() if ext not in allowed_exts: raise HTTPException(400, f"Extensão '{ext}' não permitida. Aceitas: {', '.join(sorted(allowed_exts))}") return data # ── Report file classification ──────────────────────────────────────────────── def classify_report_file(fname: str) -> str: """Classify a report file into a category based on its filename.""" fl = fname.lower() if "summary_report" in fl: return "summary" if "error_report" in fl or "error" in fl and fl.endswith(".csv"): return "error" if fl.startswith("obp_") and "findings" in fl: return "obp_finding" if fl.startswith("obp_") and "best_practices" in fl: return "obp_best_practice" if fl.startswith("obp_"): return "obp_finding" if fl.startswith("raw_data_"): return "raw_data" if fl.startswith("cis_"): return "cis_finding" if "consolidated_report" in fl: return "consolidated" if fl.endswith(".png"): return "diagram" return "other"