Files
AI_portal/vv-compare/main.py
Ondřej Glaser 48cef99257 Initial portal commit: landing + 9 AI-powered apps
Apps:
- dwg-rooms: extract room numbers from DWG/DXF
- dwg-counting: count symbols in PDF drawings (OpenCV template matching)
- contract-check: review PDF contracts against a checklist (Claude vision + Tesseract OCR fallback)
- email-drafter: bullet notes → polished Czech/English business emails
- invoice-extractor: PDF/image invoice → structured data → Excel
- translator: Czech-first translator across 19 languages with tone control
- vv-check: find inconsistent unit prices across VV sheets in one workbook
- vv-compare: diff original vs new VV files (changes / added / removed)
- feature-request: portal users submit ideas + sample files

Infrastructure:
- LiteLLM gateway with per-app virtual keys + budgets
- Langfuse observability
- Geist font, shared theme, cross-subdomain back link + theme sync via cookie/URL
- Caddy reverse proxy on *.klas.chat
2026-05-13 15:25:04 +02:00

91 lines
2.6 KiB
Python

"""FastAPI: two Excel VVs (original + new) → comparison report Excel."""
import logging
import os
import uuid
from pathlib import Path
from fastapi import FastAPI, File, HTTPException, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from vv_compare import compare, write_compare_report
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI(title="VV Compare")
app.add_middleware(CORSMiddleware, allow_origins=["*"],
allow_methods=["*"], allow_headers=["*"])
WORK_DIR = Path(os.getenv("WORK_DIR", "/tmp/vv-compare"))
WORK_DIR.mkdir(parents=True, exist_ok=True)
jobs: dict[str, dict] = {}
@app.get("/")
async def root():
return FileResponse("static/index.html")
@app.post("/api/compare")
async def do_compare(
original: UploadFile = File(...),
new: UploadFile = File(...),
):
for f in (original, new):
suffix = Path(f.filename or "").suffix.lower()
if suffix not in (".xlsx", ".xlsm"):
raise HTTPException(400, "Podporované formáty: .xlsx, .xlsm")
job_id = str(uuid.uuid4())
job_dir = WORK_DIR / job_id
job_dir.mkdir()
orig_path = job_dir / f"original{Path(original.filename).suffix}"
new_path = job_dir / f"new{Path(new.filename).suffix}"
orig_path.write_bytes(await original.read())
new_path.write_bytes(await new.read())
logger.info("Job %s: original=%s, new=%s", job_id, original.filename, new.filename)
try:
result = compare(orig_path, new_path)
except Exception as exc:
logger.exception("Compare failed")
raise HTTPException(500, f"Porovnání selhalo: {exc}")
jobs[job_id] = {
"original_filename": original.filename,
"new_filename": new.filename,
"job_dir": str(job_dir),
"result": result,
}
return {"job_id": job_id, "result": result}
@app.get("/api/report/{job_id}")
async def report(job_id: str):
if job_id not in jobs:
raise HTTPException(404, "Úloha nenalezena")
job = jobs[job_id]
out_path = Path(job["job_dir"]) / "report.xlsx"
write_compare_report(
job["result"],
job.get("original_filename") or "puvodni.xlsx",
job.get("new_filename") or "novy.xlsx",
out_path,
)
return FileResponse(
str(out_path),
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
filename="Porovnani_VV_puvodni_vs_novy.xlsx",
)
@app.get("/health")
async def health():
return {"status": "ok"}
app.mount("/static", StaticFiles(directory="static"), name="static")