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
This commit is contained in:
Ondřej Glaser
2026-05-13 15:25:04 +02:00
commit 48cef99257
139 changed files with 20171 additions and 0 deletions

79
vv-check/main.py Normal file
View File

@@ -0,0 +1,79 @@
"""FastAPI: Excel workbook → detect VV sheets → find price inconsistencies → Excel report."""
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_logic import analyse, write_report
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = FastAPI(title="VV Price Check")
app.add_middleware(CORSMiddleware, allow_origins=["*"],
allow_methods=["*"], allow_headers=["*"])
WORK_DIR = Path(os.getenv("WORK_DIR", "/tmp/vv-check"))
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/check")
async def check(file: UploadFile = File(...)):
suffix = Path(file.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()
input_path = job_dir / f"input{suffix}"
input_path.write_bytes(await file.read())
logger.info("Job %s: %s (%d bytes)", job_id, file.filename, input_path.stat().st_size)
try:
result = analyse(input_path)
except Exception as exc:
logger.exception("Analysis failed")
raise HTTPException(500, f"Analýza selhala: {exc}")
jobs[job_id] = {
"filename": file.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_report(job["result"], job.get("filename") or "kalkulace.xlsx", out_path)
stem = Path(job["filename"]).stem if job.get("filename") else "kalkulace"
return FileResponse(
str(out_path),
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
filename=f"kontrola_cen_{stem}.xlsx",
)
@app.get("/health")
async def health():
return {"status": "ok"}
app.mount("/static", StaticFiles(directory="static"), name="static")