Spaces:
Sleeping
Sleeping
| from fastapi import FastAPI, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel | |
| from typing import List, Optional | |
| import sys | |
| import os | |
| import pandas as pd | |
| import numpy as np | |
| import threading | |
| # ๊ฐ์ ํด๋์ logic.py ์ํฌํธ๋ฅผ ์ํด ๊ฒฝ๋ก ์ถ๊ฐ | |
| sys.path.append(os.path.dirname(os.path.abspath(__file__))) | |
| import logic | |
| app = FastAPI(title="K-Recipe2Vec API") | |
| # CORS ์ค์ | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # --- Request Models --- | |
| class IngredientRequest(BaseModel): | |
| recipe_id: int | |
| target: List[str] | |
| stopwords: List[str] = [] | |
| w_w2v: float = 0.5 | |
| w_d2v: float = 0.5 | |
| w_method: float = 0.0 | |
| w_cat: float = 0.0 | |
| class CustomContextRequest(BaseModel): | |
| context_ings: List[str] | |
| target: List[str] | |
| stopwords: List[str] = [] | |
| w_w2v: float = 0.5 | |
| w_d2v: float = 0.5 | |
| excluded: List[str] = [] | |
| # --- Startup --- | |
| def startup_event(): | |
| # ๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋ ๋์์ ๋ชจ๋ธ ๋ก๋ฉ ์์ (์ฑ ์์์ ๋ง์ง ์์) | |
| threading.Thread(target=logic.load_resources).start() | |
| # --- ๋ถ์ฉ์ด ์บ์ ๋ฐ ํํฐ๋ง --- | |
| _stopwords_cache = None | |
| def get_stopwords(): | |
| """๋ถ์ฉ์ด ๋ชฉ๋ก์ ์บ์ํ์ฌ ๋ฐํ""" | |
| global _stopwords_cache | |
| if _stopwords_cache is None: | |
| try: | |
| _stopwords_cache = set(logic.load_global_stopwords()) | |
| except: | |
| _stopwords_cache = set() | |
| return _stopwords_cache | |
| def filter_ingredients(ingredients_list): | |
| """์ฌ๋ฃ ๋ชฉ๋ก์์ ๋ถ์ฉ์ด ์ ๊ฑฐ""" | |
| stopwords = get_stopwords() | |
| if not stopwords: | |
| return ingredients_list | |
| return [ing for ing in ingredients_list if ing not in stopwords] | |
| # --- Endpoints --- | |
| def health_check(): | |
| # ๋ชจ๋ธ ๋ก๋ฉ ์ํ ํ์ธ | |
| status = "loading" if logic.df is None else "ok" | |
| return {"status": status, "service": "K-Recipe2Vec API"} | |
| def list_recipes(limit: int = 50, offset: int = 0): | |
| """์ ์ฒด ๋ ์ํผ ๋ชฉ๋ก ์กฐํ (ํ์ด์ง๋ค์ด์ )""" | |
| logic.ensure_initialized() | |
| try: | |
| total = len(logic.df) | |
| subset = logic.df.iloc[offset:offset+limit][['๋ ์ํผ์ผ๋ จ๋ฒํธ', '์๋ฆฌ๋ช ', '์ฌ๋ฃํ ํฐ', '์๋ฆฌ๋ฐฉ๋ฒ๋ณ๋ช ', '์๋ฆฌ์ข ๋ฅ๋ณ๋ช _์ธ๋ถํ']] | |
| output = [] | |
| for _, row in subset.iterrows(): | |
| output.append({ | |
| "id": int(row['๋ ์ํผ์ผ๋ จ๋ฒํธ']), | |
| "name": row['์๋ฆฌ๋ช '], | |
| "ingredients": filter_ingredients(row['์ฌ๋ฃํ ํฐ']), | |
| "method": row['์๋ฆฌ๋ฐฉ๋ฒ๋ณ๋ช '], | |
| "category": row['์๋ฆฌ์ข ๋ฅ๋ณ๋ช _์ธ๋ถํ'] | |
| }) | |
| return {"total": total, "recipes": output} | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| def search_recipes(q: str): | |
| """์๋ฆฌ๋ช ์ผ๋ก ๋ ์ํผ ๊ฒ์""" | |
| logic.ensure_initialized() # ๋ก๋ฉ ๋๊ธฐ | |
| if not q: return [] | |
| try: | |
| mask = logic.df['์๋ฆฌ๋ช '].str.contains(q, case=False, na=False) | |
| results = logic.df.loc[mask, ['๋ ์ํผ์ผ๋ จ๋ฒํธ', '์๋ฆฌ๋ช ', '์ฌ๋ฃํ ํฐ']].head(20) | |
| output = [] | |
| for _, row in results.iterrows(): | |
| output.append({ | |
| "id": int(row['๋ ์ํผ์ผ๋ จ๋ฒํธ']), | |
| "name": row['์๋ฆฌ๋ช '], | |
| "ingredients": filter_ingredients(row['์ฌ๋ฃํ ํฐ']) | |
| }) | |
| return output | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| def get_recipe_detail(recipe_id: int): | |
| """๋ ์ํผ ์์ธ ์ ๋ณด ์กฐํ""" | |
| logic.ensure_initialized() # ๋ก๋ฉ ๋๊ธฐ | |
| try: | |
| row = logic.df[logic.df['๋ ์ํผ์ผ๋ จ๋ฒํธ'] == recipe_id] | |
| if row.empty: | |
| raise HTTPException(status_code=404, detail="Recipe not found") | |
| row = row.iloc[0] | |
| return { | |
| "id": int(row['๋ ์ํผ์ผ๋ จ๋ฒํธ']), | |
| "name": row['์๋ฆฌ๋ช '], | |
| "method": row['์๋ฆฌ๋ฐฉ๋ฒ๋ณ๋ช '], | |
| "category": row['์๋ฆฌ์ข ๋ฅ๋ณ๋ช _์ธ๋ถํ'], | |
| "ingredients": filter_ingredients(row['์ฌ๋ฃํ ํฐ']) | |
| } | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| def recommend_db_single(req: IngredientRequest): | |
| """DB ๋ ์ํผ ๊ธฐ๋ฐ ๋จ์ผ ์ฌ๋ฃ ๋์ฒด""" | |
| logic.ensure_initialized() # ๋ก๋ฉ ๋๊ธฐ | |
| if not req.target: | |
| raise HTTPException(status_code=400, detail="Target ingredient required") | |
| try: | |
| df = logic.substitute_single( | |
| req.recipe_id, req.target[0], req.stopwords, | |
| req.w_w2v, req.w_d2v, req.w_method, req.w_cat | |
| ) | |
| if df.empty: return [] | |
| df = df.replace([np.inf, -np.inf], 0).fillna(0) | |
| return df.to_dict(orient="records") | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| def recommend_db_multi(req: IngredientRequest): | |
| """DB ๋ ์ํผ ๊ธฐ๋ฐ ๋ค์ค ์ฌ๋ฃ ๋์ฒด""" | |
| logic.ensure_initialized() # ๋ก๋ฉ ๋๊ธฐ | |
| try: | |
| results = logic.substitute_multi( | |
| req.recipe_id, req.target, req.stopwords, | |
| req.w_w2v, req.w_d2v, req.w_method, req.w_cat | |
| ) | |
| formatted = [] | |
| for subs, score, saving in results: | |
| formatted.append({ | |
| "substitutes": subs, | |
| "score": float(score), | |
| "saving_score": int(saving) | |
| }) | |
| return formatted | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| def recommend_custom_single(req: CustomContextRequest): | |
| """์ฌ์ฉ์ ์ ์ ์ฌ๋ฃ ๊ธฐ๋ฐ ๋จ์ผ ๋์ฒด""" | |
| logic.ensure_initialized() # ๋ก๋ฉ ๋๊ธฐ | |
| if not req.target: | |
| raise HTTPException(status_code=400, detail="Target ingredient required") | |
| try: | |
| df = logic.substitute_single_custom( | |
| req.target[0], req.context_ings, req.stopwords, | |
| req.w_w2v, req.w_d2v, excluded_ings=req.excluded | |
| ) | |
| if df.empty: return [] | |
| df = df.replace([np.inf, -np.inf], 0).fillna(0) | |
| return df.to_dict(orient="records") | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |