Download app.py from BEASTZZZ/OIML: direct link, hf CLI and curl.
- Browser
- Download file 9.77 kB
-
https://huggingface.co/spaces/BEASTZZZ/OIML/resolve/main/app.py
- Command line
-
hf download hf://spaces/BEASTZZZ/OIML/app.py
-
curl -L -o app.py https://huggingface.co/spaces/BEASTZZZ/OIML/resolve/main/app.py
9.77 kB
| """ | |
| NAWI reporter -- FastAPI service. | |
| Runs as a Docker Space on Hugging Face, or anywhere else a container runs. | |
| Two jobs the browser genuinely cannot do: | |
| 1. POST /render DOCX needs docxtpl; PDF needs LibreOffice. | |
| 2. POST /verify Independently re-derive every verdict from the raw | |
| readings, so a submission is never accepted on the | |
| client's word alone. | |
| Everything else -- mpe lookup, error computation, load-point generation -- | |
| runs in the browser, because it must keep working inside a shielded EMC | |
| chamber with no network. | |
| Calling it | |
| ---------- | |
| Plain REST. No queue protocol, no SDK: | |
| const r = await fetch(`${SPACE_URL}/render`, { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json", | |
| Authorization: `Bearer ${jwt}` }, | |
| body: JSON.stringify({ ref: "RRSL-FBD/NAWI/2026/0042" }), | |
| }); | |
| Secrets (Space Settings -> Repository secrets, never Variables): | |
| SUPABASE_JWT_SECRET validates access tokens | |
| SUPABASE_URL | |
| SUPABASE_SERVICE_KEY bypasses RLS; never ship this to a browser | |
| """ | |
| import json | |
| import os | |
| import tempfile | |
| import traceback | |
| from typing import Any, Dict, Optional | |
| from fastapi import FastAPI, Header, HTTPException, Request | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import FileResponse, JSONResponse | |
| from pydantic import BaseModel | |
| import auth | |
| import recompute as rc | |
| import render as rnd | |
| import supabase_io as db | |
| VERSION = "1.1.0" | |
| OUT_DIR = os.environ.get("REPORT_OUT_DIR", tempfile.gettempdir()) | |
| # Lock CORS to the frontend. A Space URL is public, so an open policy | |
| # would let any page on the internet spend this container's CPU. | |
| ALLOWED = [o.strip() for o in | |
| os.environ.get("ALLOWED_ORIGINS", "*").split(",") if o.strip()] | |
| app = FastAPI( | |
| title="NAWI reporter", | |
| version=VERSION, | |
| description="OIML R 76 type-evaluation report rendering and verification", | |
| ) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=ALLOWED, | |
| allow_credentials=False, | |
| allow_methods=["GET", "POST", "OPTIONS"], | |
| allow_headers=["Authorization", "Content-Type"], | |
| ) | |
| # --------------------------------------------------------------------------- | |
| # request models | |
| # --------------------------------------------------------------------------- | |
| class RefRequest(BaseModel): | |
| ref: str | |
| class PayloadRequest(BaseModel): | |
| evaluation: Dict[str, Any] | |
| def _caller(authorization: Optional[str], *roles: str) -> auth.Caller: | |
| """Validate the bearer token, or refuse before doing any work.""" | |
| if not authorization: | |
| raise HTTPException(401, "missing Authorization header") | |
| try: | |
| return auth.require(authorization, *roles) | |
| except auth.AuthError as exc: | |
| raise HTTPException(401, str(exc)) | |
| # --------------------------------------------------------------------------- | |
| # endpoints | |
| # --------------------------------------------------------------------------- | |
| def root(): | |
| """Landing page, so opening the Space in a browser explains itself.""" | |
| return { | |
| "service": "nawi-reporter", | |
| "version": VERSION, | |
| "docs": "/docs", | |
| "endpoints": { | |
| "GET /health": "public — service status", | |
| "POST /verify": "re-derive every verdict from the raw readings", | |
| "POST /render": "render DOCX + PDF, store them, return signed URLs", | |
| "POST /render-payload": "render straight from a posted evaluation", | |
| }, | |
| } | |
| def health(): | |
| """Public. Also what the keep-warm cron hits. | |
| A free CPU Space sleeps after about 48 hours idle and takes 30 to 60 | |
| seconds to wake. Never let a user trigger the first request. | |
| """ | |
| return { | |
| "ok": True, | |
| "service": "nawi-reporter", | |
| "version": VERSION, | |
| "libreoffice": bool(rnd._soffice()), | |
| "template": os.path.exists(rnd.TEMPLATE), | |
| "supabase_configured": db.configured(), | |
| "jwt_configured": auth.configured() != "none", | |
| "jwt_modes": auth.configured(), | |
| } | |
| def verify(body: RefRequest, authorization: str = Header(None)): | |
| """Re-derive every verdict from the raw observations. | |
| The browser already computed these while the technician typed. This is | |
| the authoritative pass: same engine, same standards JSON, but starting | |
| from I, dL, L and E0 alone. A divergence means a stale client, a | |
| tampered payload, or a bug -- any of which should stop a submission. | |
| """ | |
| caller = _caller(authorization, "technician", "reviewer", "approver", | |
| "director", "admin", "auditor") | |
| try: | |
| ev = db.fetch_evaluation(body.ref, caller) | |
| computed = rc.recompute(ev) | |
| payload = rc.canonical_payload(ev, computed) | |
| return { | |
| "ok": True, | |
| "ref": ev["ref"], | |
| "standard_id": ev.get("standard_id"), | |
| "verdict": computed["verdict"], | |
| "agrees_with_client": computed["agrees_with_client"], | |
| "divergences": computed["divergences"], | |
| "specification": computed["specification"], | |
| "derived": computed["derived"], | |
| "tests": {k: {"verdict": v["verdict"], | |
| "rows": len(v.get("rows") or v.get("series") or [])} | |
| for k, v in computed["tests"].items()}, | |
| "payload_sha256": rc.payload_sha256(payload), | |
| "checked_by": {"user_id": caller.user_id, "role": caller.role}, | |
| } | |
| except db.SupabaseError as exc: | |
| raise HTTPException(404, str(exc)) | |
| except Exception as exc: # noqa: BLE001 | |
| traceback.print_exc() | |
| raise HTTPException(500, f"{type(exc).__name__}: {exc}") | |
| def render(body: RefRequest, authorization: str = Header(None)): | |
| """Render DOCX and PDF, store them, return signed links.""" | |
| caller = _caller(authorization, "reviewer", "approver", "director", "admin") | |
| try: | |
| ev = db.fetch_evaluation(body.ref, caller) | |
| computed = rc.recompute(ev) | |
| payload = rc.canonical_payload(ev, computed) | |
| sha = rc.payload_sha256(payload) | |
| # Refuse to render a report whose numbers the client and server do | |
| # not agree on. Producing a signed document over a divergence would | |
| # be the single worst failure mode this system has. | |
| if computed["divergences"]: | |
| return JSONResponse( | |
| status_code=409, | |
| content={"ok": False, "kind": "divergence", | |
| "error": "client and server verdicts diverge; not rendering", | |
| "divergences": computed["divergences"]}, | |
| ) | |
| docx_path, pdf_path = rnd.render(ev, computed, sha, OUT_DIR) | |
| version = db.next_version(ev["id"]) | |
| docx_store = db.upload_report( | |
| docx_path, ev["ref"], version, | |
| "application/vnd.openxmlformats-officedocument.wordprocessingml.document") | |
| pdf_store = db.upload_report(pdf_path, ev["ref"], version, | |
| "application/pdf") if pdf_path else None | |
| db.record_report_version(ev["id"], version, ev.get("standard_id"), | |
| payload, sha, computed["verdict"], | |
| docx_store, pdf_store, caller) | |
| return { | |
| "ok": True, | |
| "ref": ev["ref"], | |
| "version": version, | |
| "verdict": computed["verdict"], | |
| "payload_sha256": sha, | |
| "is_synthetic": bool(ev.get("is_synthetic")), | |
| "docx_url": db.signed_url(docx_store), | |
| "pdf_url": db.signed_url(pdf_store) if pdf_store else None, | |
| "pdf_available": bool(pdf_store), | |
| "note": None if pdf_store else | |
| "LibreOffice unavailable; DOCX only. Use the browser " | |
| "print fallback for a PDF.", | |
| } | |
| except db.SupabaseError as exc: | |
| raise HTTPException(404, str(exc)) | |
| except Exception as exc: # noqa: BLE001 | |
| traceback.print_exc() | |
| raise HTTPException(500, f"{type(exc).__name__}: {exc}") | |
| def render_payload(body: PayloadRequest, authorization: str = Header(None)): | |
| """Render straight from a posted evaluation, without touching Supabase. | |
| Used for offline-captured work that has not synced yet, and for local | |
| development before the database exists. Returns the PDF as a file. | |
| """ | |
| caller = _caller(authorization, "technician", "reviewer", "approver", | |
| "director", "admin") | |
| ev = body.evaluation | |
| if not ev.get("ref") or not ev.get("instrument"): | |
| raise HTTPException(422, "evaluation needs at least 'ref' and 'instrument'") | |
| try: | |
| computed = rc.recompute(ev) | |
| sha = rc.payload_sha256(rc.canonical_payload(ev, computed)) | |
| docx_path, pdf_path = rnd.render(ev, computed, sha, OUT_DIR) | |
| out = pdf_path or docx_path | |
| return FileResponse( | |
| out, filename=os.path.basename(out), | |
| media_type="application/pdf" if pdf_path else | |
| "application/vnd.openxmlformats-officedocument." | |
| "wordprocessingml.document", | |
| headers={"X-Verdict": computed["verdict"], | |
| "X-Payload-SHA256": sha, | |
| "X-Rendered-By": caller.role}, | |
| ) | |
| except Exception as exc: # noqa: BLE001 | |
| traceback.print_exc() | |
| raise HTTPException(500, f"{type(exc).__name__}: {exc}") | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", 7860))) |