Spaces:
Running
Running
| """FastAPI app — JSON API for ProcureMind (Hugging Face Space / local dev).""" | |
| from __future__ import annotations | |
| import os | |
| from contextlib import asynccontextmanager | |
| from typing import Any | |
| from fastapi import FastAPI | |
| from fastapi.responses import JSONResponse | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel, Field | |
| from server.openai_client import load_dotenv_env | |
| load_dotenv_env() | |
| from server.agent import coerce_payload, run_agent | |
| from server.catalog import connect, get_commodity, summarize_row | |
| from server.form_schema import ensure_form_schema_table, get_or_create_schema | |
| from server.pr_lines import build_pr_rows | |
| async def lifespan(_: FastAPI): | |
| from pathlib import Path | |
| p = os.environ.get("UNSPSC_DB_PATH", "") | |
| if p and not Path(p).exists(): | |
| print(f"WARNING: catalogue database path does not exist: {p}") | |
| try: | |
| conn = connect() | |
| ensure_form_schema_table(conn) | |
| conn.close() | |
| except Exception as ex: | |
| print(f"WARNING: could not init auxiliary tables: {ex}") | |
| yield | |
| app = FastAPI(title="ProcureMind API", lifespan=lifespan) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| class ChatRequest(BaseModel): | |
| message: str = "" | |
| selected_commodity_code: int | None = Field( | |
| default=None, | |
| description="Skip LLM and lock this catalogue commodity (disambiguation choice)", | |
| ) | |
| class BuildPrRequest(BaseModel): | |
| commodity_code: int | |
| dynamic_values: dict[str, Any] = Field(default_factory=dict) | |
| deliveries: int = 4 | |
| interval: str = "Quarterly" | |
| other_spec: str = "" | |
| year: int = 2026 | |
| def health(): | |
| return {"status": "ok"} | |
| def _enrich_found(conn, payload: dict) -> dict: | |
| if payload.get("status") != "found": | |
| return payload | |
| code = payload.get("commodity_code") | |
| if code is None or payload.get("selected_details"): | |
| return payload | |
| row = get_commodity(conn, int(code)) | |
| if row: | |
| payload["selected_details"] = summarize_row(row) | |
| return payload | |
| def form_schema(commodity_code: int): | |
| conn = connect() | |
| try: | |
| return get_or_create_schema(conn, commodity_code) | |
| finally: | |
| conn.close() | |
| def build_pr(req: BuildPrRequest): | |
| conn = connect() | |
| try: | |
| schema = get_or_create_schema(conn, req.commodity_code) | |
| fields = schema.get("fields") or [] | |
| rows = build_pr_rows( | |
| mat_grp=req.commodity_code, | |
| dynamic_fields_ordered=fields, | |
| dynamic_values=req.dynamic_values, | |
| deliveries=req.deliveries, | |
| interval=req.interval, | |
| other_spec=req.other_spec, | |
| year=req.year, | |
| ) | |
| return {"rows": rows} | |
| finally: | |
| conn.close() | |
| def chat(req: ChatRequest): | |
| msg = (req.message or "").strip() | |
| if not msg and req.selected_commodity_code is None: | |
| return JSONResponse( | |
| {"detail": "Provide message or selected_commodity_code"}, | |
| status_code=400, | |
| ) | |
| conn = connect() | |
| try: | |
| payload = run_agent( | |
| msg, | |
| conn=conn, | |
| selected_code=req.selected_commodity_code, | |
| ) | |
| payload = _enrich_found(conn, payload) | |
| return coerce_payload(payload) | |
| finally: | |
| conn.close() | |