# app/sim_api.py from __future__ import annotations from typing import Dict, Any, List, Tuple from app.catalog import load_catalog, find_item_by_name, find_item_by_sku def _pick_item(order_it: Dict[str, Any]) -> Dict[str, Any] | None: it = None if "sku" in order_it: it = find_item_by_sku(order_it["sku"]) if not it and "name" in order_it: it = find_item_by_name(order_it["name"]) return it def check_item_availability(order_it: Dict[str, Any]) -> Tuple[bool, Dict[str, Any]]: """ Returns (is_available, info) info contains { "reason": "...", "alternatives": [...] } when not available For size-based items, verify stock for requested size. """ it = _pick_item(order_it) if not it: return False, {"reason": "unknown_item", "alternatives": []} qty = int(order_it.get("qty", 0) or 0) if qty < 1: return False, {"reason": "qty_missing", "alternatives": []} # size key heuristics size = order_it.get("size") stock_map = it.get("stock") or {} if "one_size" in stock_map: avail = stock_map["one_size"] if avail >= qty: return True, {"price_each": (it.get("price") or {}).get("one_size", 0.0)} else: return False, {"reason": "insufficient_stock", "have": avail, "alternatives": []} if size: have = int(stock_map.get(size, 0)) if have >= qty: return True, {"price_each": (it.get("price") or {}).get(size, 0.0)} else: # propose other sizes with stock alts = [] for s, have_s in stock_map.items(): if have_s >= qty: alts.append({"size": s, "have": have_s, "price_each": (it.get("price") or {}).get(s, 0.0)}) return False, {"reason": "size_out_of_stock", "have": have, "alternatives": alts} else: # missing required option — let schema enforcement ask; but if user skipped, treat as not available return False, {"reason": "size_missing", "alternatives": [{"hint": "provide size"}]} def place_order(order_items: List[Dict[str, Any]]) -> Dict[str, Any]: """ Verifies each item and (if all available) returns summary. We do not mutate stock here (sim). """ ok = True lines = [] total = 0.0 for it in order_items: item_def = _pick_item(it) if not item_def: return {"ok": False, "reason": "unknown_item", "item": it} avail, info = check_item_availability(it) if not avail: return {"ok": False, "reason": info.get("reason"), "item": it, "alternatives": info.get("alternatives", [])} qty = int(it["qty"]) unit = info.get("price_each", 0.0) line_total = unit * qty total += line_total lines.append({ "sku": item_def["sku"], "name": item_def["name"], "qty": qty, "options": {k: v for k, v in it.items() if k not in ("name","sku","qty")}, "unit": unit, "line_total": line_total }) return {"ok": True, "total": round(total, 2), "lines": lines}