97 lines
3.3 KiB
Python
97 lines
3.3 KiB
Python
import os
|
|
from typing import Any, Dict, List, Optional
|
|
|
|
from fastapi import Depends, FastAPI, Header, HTTPException
|
|
|
|
API_KEY = os.getenv("ONEOK_ADAPTER_API_KEY", "").strip()
|
|
BASE_RATE_PER_M2 = float(os.getenv("ONEOK_BASE_RATE_PER_M2", "3200"))
|
|
INSTALL_RATE_PER_M2 = float(os.getenv("ONEOK_INSTALL_RATE_PER_M2", "900"))
|
|
CURRENCY = os.getenv("ONEOK_CURRENCY", "UAH")
|
|
|
|
app = FastAPI(title="1OK Calc Adapter", version="1.0.0")
|
|
|
|
|
|
def _auth(authorization: Optional[str] = Header(default=None)) -> None:
|
|
if not API_KEY:
|
|
return
|
|
expected = f"Bearer {API_KEY}"
|
|
if authorization != expected:
|
|
raise HTTPException(status_code=401, detail="Unauthorized")
|
|
|
|
|
|
def _normalize_units(payload: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
units = payload.get("window_units")
|
|
if isinstance(units, list) and units:
|
|
return [u for u in units if isinstance(u, dict)]
|
|
# Compatibility alias used by some LLM tool calls.
|
|
units = payload.get("windows")
|
|
if isinstance(units, list) and units:
|
|
return [u for u in units if isinstance(u, dict)]
|
|
# fallback to one unit in root payload
|
|
if "width_mm" in payload and "height_mm" in payload:
|
|
return [payload]
|
|
return []
|
|
|
|
|
|
@app.get("/health")
|
|
def health() -> Dict[str, Any]:
|
|
return {"status": "ok", "service": "oneok-calc-adapter"}
|
|
|
|
|
|
@app.post("/calc/window_quote")
|
|
def calc_window_quote(input_payload: Dict[str, Any], _: None = Depends(_auth)) -> Dict[str, Any]:
|
|
units = _normalize_units(input_payload)
|
|
if not units:
|
|
raise HTTPException(status_code=400, detail="window_units/windows or width_mm/height_mm required")
|
|
|
|
line_items: List[Dict[str, Any]] = []
|
|
subtotal = 0.0
|
|
|
|
for idx, unit in enumerate(units, 1):
|
|
width_mm = float(unit.get("width_mm") or 0)
|
|
height_mm = float(unit.get("height_mm") or 0)
|
|
if width_mm <= 0 or height_mm <= 0:
|
|
continue
|
|
area_m2 = (width_mm * height_mm) / 1_000_000.0
|
|
base = round(area_m2 * BASE_RATE_PER_M2, 2)
|
|
install = round(area_m2 * INSTALL_RATE_PER_M2, 2)
|
|
total = round(base + install, 2)
|
|
subtotal += total
|
|
|
|
line_items.append(
|
|
{
|
|
"item": unit.get("label") or f"window_{idx}",
|
|
"width_mm": width_mm,
|
|
"height_mm": height_mm,
|
|
"area_m2": round(area_m2, 3),
|
|
"base_price": base,
|
|
"install_price": install,
|
|
"total": total,
|
|
}
|
|
)
|
|
|
|
if not line_items:
|
|
raise HTTPException(status_code=400, detail="No valid window units for calculation")
|
|
|
|
assumptions = [
|
|
f"Базова ставка: {BASE_RATE_PER_M2} {CURRENCY}/м2",
|
|
f"Монтаж: {INSTALL_RATE_PER_M2} {CURRENCY}/м2",
|
|
"ОЦІНКА: без виїзного підтвердженого заміру ціна попередня.",
|
|
]
|
|
|
|
lead_days = 10
|
|
if str(input_payload.get("urgency", "")).lower() in {"urgent", "терміново"}:
|
|
lead_days = 7
|
|
|
|
return {
|
|
"currency": CURRENCY,
|
|
"line_items": line_items,
|
|
"totals": {
|
|
"subtotal": round(subtotal, 2),
|
|
"discount": 0.0,
|
|
"grand_total": round(subtotal, 2),
|
|
},
|
|
"assumptions": assumptions,
|
|
"lead_time_if_known": f"{lead_days} днів",
|
|
}
|