PR-AGENT / server /main.py
Seth
Update
865237e
"""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.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
@asynccontextmanager
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
@app.get("/api/health")
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
@app.get("/api/form-schema/{commodity_code}")
def form_schema(commodity_code: int):
conn = connect()
try:
return get_or_create_schema(conn, commodity_code)
finally:
conn.close()
@app.post("/api/build-pr")
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()
@app.post("/api/chat")
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()