Spaces:
Sleeping
Sleeping
| """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") | |
| 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 ---------- | |
| async def root(): | |
| return FileResponse("static/index.html") | |
| 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 ---------- | |
| async def ws_endpoint(client_ws: WebSocket): | |
| await realtime_relay(client_ws) | |
| # ---------- Public proxy: warehouse XML (digerservisler kullaniyor) ---------- | |
| async def warehouse_xml(): | |
| xml = await asyncio.to_thread(get_cached_warehouse_xml) | |
| if not xml: | |
| return Response( | |
| content='<?xml version="1.0" encoding="UTF-8"?>\n<Products></Products>', | |
| 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) ---------- | |
| 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", | |
| }, | |
| ) | |
| 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", | |
| }, | |
| ) | |
| 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 ---------- | |
| 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} | |
| 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} | |