"""BF-Realtime V2 — FastAPI entry point. Sadece routing ve startup logic. Tum is logic'i ayri modullerde: - product_index: Trek katalog XML parse + hash index - product_matcher: fuzzy ana urun + renk varyanti eslestirme - stock_service: BizimHesap + Trek PHP stok cache - tools: get_warehouse_stock implementasyonu - realtime_relay: OpenAI Realtime WS proxy """ from __future__ import annotations import asyncio import logging from fastapi import FastAPI, WebSocket from fastapi.responses import FileResponse, JSONResponse, Response from fastapi.staticfiles import StaticFiles from config import ( OPENAI_API_KEY, REALTIME_MODEL, REFRESH_INTERVAL, ) from product_index import background_refresh_loop, get_index from product_matcher import find_main_product_in_text from realtime_relay import realtime_relay from stock_service import ( cached_bh, cached_bh_inventory, get_cached_warehouse_xml, ) logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) app = FastAPI(title="BF-Realtime V2") app.mount("/static", StaticFiles(directory="static"), name="static") @app.on_event("startup") async def _startup(): """Background refresh tasks — index + warehouse XML her saatte tazelenir.""" async def warehouse_loop(): while True: try: await asyncio.to_thread(get_cached_warehouse_xml) except Exception: logger.exception("warehouse refresh hatasi") await asyncio.sleep(REFRESH_INTERVAL) asyncio.create_task(background_refresh_loop(REFRESH_INTERVAL)) asyncio.create_task(warehouse_loop()) # ---------- Frontend ---------- @app.get("/") async def root(): return FileResponse("static/index.html") @app.get("/health") async def health(): idx = get_index() return { "status": "ok", "model": REALTIME_MODEL, "has_api_key": bool(OPENAI_API_KEY), "index_main_count": idx.main_count, } # ---------- Realtime WebSocket ---------- @app.websocket("/ws") async def ws_endpoint(client_ws: WebSocket): await realtime_relay(client_ws) # ---------- Public proxy: warehouse XML (digerservisler kullaniyor) ---------- @app.get("/warehouse-xml") async def warehouse_xml(): xml = await asyncio.to_thread(get_cached_warehouse_xml) if not xml: return Response( content='\n', media_type="application/xml", headers={"Cache-Control": "no-cache"}, ) return Response( content=xml, media_type="application/xml", headers={ "Access-Control-Allow-Origin": "*", "Cache-Control": "public, max-age=300", }, ) # ---------- Public proxy: BizimHesap raw (tavsiye/sold/diger client'lar) ---------- @app.get("/bh/products") async def bh_products(): from stock_service import bh_get from config import BIZIMHESAP_BASE, CACHE_TTL_BH_PRODUCTS data, status = await asyncio.to_thread( cached_bh, "products", lambda: bh_get(f"{BIZIMHESAP_BASE}/products"), CACHE_TTL_BH_PRODUCTS, ) if data is None: return JSONResponse( {"resultCode": 0, "errorText": "BizimHesap fetch failed", "data": None}, status_code=502, ) return JSONResponse( data, headers={ "Access-Control-Allow-Origin": "*", "X-Cache": status, "Cache-Control": "public, max-age=300", }, ) @app.get("/bh/warehouses") async def bh_warehouses(): from stock_service import bh_get from config import BIZIMHESAP_BASE, CACHE_TTL_BH_WAREHOUSES data, status = await asyncio.to_thread( cached_bh, "warehouses", lambda: bh_get(f"{BIZIMHESAP_BASE}/warehouses"), CACHE_TTL_BH_WAREHOUSES, ) if data is None: return JSONResponse( {"resultCode": 0, "errorText": "BizimHesap fetch failed", "data": None}, status_code=502, ) return JSONResponse( data, headers={ "Access-Control-Allow-Origin": "*", "X-Cache": status, "Cache-Control": "public, max-age=300", }, ) @app.get("/bh/inventory/{wid}") async def bh_inventory(wid: str): data, status = await asyncio.to_thread(cached_bh_inventory, wid) if data is None: return JSONResponse( {"resultCode": 0, "errorText": "BizimHesap fetch failed", "data": None}, status_code=502, ) return JSONResponse( data, headers={ "Access-Control-Allow-Origin": "*", "X-Cache": status, "Cache-Control": "public, max-age=300", }, ) # ---------- Debug ---------- @app.get("/debug-find") async def debug_find(q: str): p = find_main_product_in_text(q) return {"query": q, "matched": (p.get("name") if p else None), "result": p} @app.get("/debug-search") async def debug_search(q: str): from smart_warehouse_with_price import get_warehouse_stock_smart_with_price result = await asyncio.to_thread(get_warehouse_stock_smart_with_price, q) return {"query": q, "result": result}