File size: 3,462 Bytes
84983c9
 
 
 
 
 
865237e
84983c9
 
 
 
 
 
 
 
865237e
 
84983c9
 
 
 
865237e
84983c9
865237e
 
 
 
 
 
 
 
 
84983c9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a44477f
84983c9
 
 
865237e
 
 
 
 
 
 
 
 
84983c9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
865237e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84983c9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
"""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()