|
|
"""
|
|
|
๋ชจ๋ฐ์ผ ๋ฆฌ์กํธ ํ๋ฐํธ์์ ํธ์ถํ REST API ์๋ฒ (FastAPI)
|
|
|
|
|
|
์๋ํฌ์ธํธ:
|
|
|
- POST /api/advise : ์ข
ํฉ ์๋ด (๊ธ์ต+์ ๋ณด+๋ณต์ง)
|
|
|
- POST /api/qa : ์์ ์ง๋ฌธ/์ฑ๋ด ์๋ต
|
|
|
- GET /api/welfare/search : ๋ณต์ง์ ์ฑ
๊ฒ์
|
|
|
- GET /api/products/search : ๊ธ์ต์ํ RAG ๊ฒ์
|
|
|
- GET /health : ์ํ ์ ๊ฒ
|
|
|
"""
|
|
|
|
|
|
from fastapi import FastAPI, HTTPException, Query
|
|
|
from fastapi.staticfiles import StaticFiles
|
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
from pydantic import BaseModel, Field
|
|
|
from typing import List, Optional, Dict, Any
|
|
|
|
|
|
from agents.multi_agent_system import get_meeting_account_advisor
|
|
|
from agents.welfare_advisor import WelfareAdvisor
|
|
|
from agents.universal_info_advisor import get_universal_info_advisor
|
|
|
from models.rag_system import get_kb_rag
|
|
|
|
|
|
import pandas as pd
|
|
|
import uvicorn
|
|
|
|
|
|
|
|
|
app = FastAPI(title="KB ๊ธ์ต AI Advisor API", version="1.0.0")
|
|
|
|
|
|
|
|
|
app.add_middleware(
|
|
|
CORSMiddleware,
|
|
|
allow_origins=["*"],
|
|
|
allow_credentials=True,
|
|
|
allow_methods=["*"],
|
|
|
allow_headers=["*"],
|
|
|
)
|
|
|
|
|
|
|
|
|
try:
|
|
|
import os
|
|
|
FRONT_DIST = os.path.join(os.path.dirname(__file__), "frontend", "dist")
|
|
|
if os.path.isdir(FRONT_DIST):
|
|
|
app.mount("/", StaticFiles(directory=FRONT_DIST, html=True), name="frontend")
|
|
|
except Exception:
|
|
|
pass
|
|
|
|
|
|
|
|
|
class UserInfo(BaseModel):
|
|
|
age_group: Optional[str] = None
|
|
|
family_type: Optional[str] = None
|
|
|
income_level: Optional[str] = None
|
|
|
region: Optional[str] = None
|
|
|
|
|
|
|
|
|
class AdviseRequest(BaseModel):
|
|
|
purpose: str = Field(..., description="์: ์คํ์ธ ์ดํ์ฐ์, ๊ฒฐํผ์ค๋น ๋ฑ")
|
|
|
duration_months: int = Field(..., ge=1, le=120)
|
|
|
target_amount: int = Field(..., ge=1)
|
|
|
detailed_purpose: Optional[str] = None
|
|
|
current_amount: Optional[int] = 0
|
|
|
user_info: Optional[UserInfo] = None
|
|
|
|
|
|
|
|
|
class AdviseResponse(BaseModel):
|
|
|
purpose_category: Optional[str]
|
|
|
financial_recommendation: Optional[str]
|
|
|
detailed_info: Optional[str]
|
|
|
welfare_recommendation: Optional[str]
|
|
|
comprehensive_advice: Optional[str]
|
|
|
progress_info: Optional[str] = None
|
|
|
|
|
|
|
|
|
class QARequest(BaseModel):
|
|
|
question: str
|
|
|
detailed_context: Optional[str] = None
|
|
|
|
|
|
|
|
|
class QAResponse(BaseModel):
|
|
|
answer: str
|
|
|
category: Optional[str] = None
|
|
|
|
|
|
|
|
|
@app.get("/health")
|
|
|
def health() -> Dict[str, Any]:
|
|
|
return {"status": "ok"}
|
|
|
|
|
|
|
|
|
@app.post("/api/advise", response_model=AdviseResponse)
|
|
|
def api_advise(req: AdviseRequest):
|
|
|
try:
|
|
|
advisor = get_meeting_account_advisor()
|
|
|
results = advisor.get_comprehensive_advice(
|
|
|
purpose=req.purpose,
|
|
|
duration_months=req.duration_months,
|
|
|
target_amount=req.target_amount,
|
|
|
detailed_purpose=req.detailed_purpose,
|
|
|
)
|
|
|
|
|
|
|
|
|
progress_info = None
|
|
|
if req.current_amount and req.current_amount > 0:
|
|
|
progress_info = advisor.get_savings_progress_tracking(
|
|
|
req.purpose,
|
|
|
req.duration_months,
|
|
|
req.target_amount,
|
|
|
req.current_amount,
|
|
|
)
|
|
|
|
|
|
|
|
|
welfare_text = results.get("welfare_recommendation")
|
|
|
if (not welfare_text or "์์ต๋๋ค" in welfare_text) and req.user_info:
|
|
|
welfare = WelfareAdvisor().get_welfare_recommendations(
|
|
|
req.purpose, req.user_info.model_dump()
|
|
|
)
|
|
|
if welfare:
|
|
|
results["welfare_recommendation"] = welfare
|
|
|
|
|
|
return AdviseResponse(
|
|
|
purpose_category=results.get("purpose_category"),
|
|
|
financial_recommendation=results.get("financial_recommendation"),
|
|
|
detailed_info=results.get("detailed_info"),
|
|
|
welfare_recommendation=results.get("welfare_recommendation"),
|
|
|
comprehensive_advice=results.get("comprehensive_advice"),
|
|
|
progress_info=progress_info,
|
|
|
)
|
|
|
except Exception as e:
|
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
|
|
|
@app.post("/api/qa", response_model=QAResponse)
|
|
|
def api_qa(req: QARequest):
|
|
|
try:
|
|
|
advisor = get_universal_info_advisor()
|
|
|
info = advisor.get_comprehensive_info(
|
|
|
purpose=req.question,
|
|
|
detailed_purpose=req.detailed_context,
|
|
|
)
|
|
|
return QAResponse(
|
|
|
answer=info.get("comprehensive_info", "๋ต๋ณ์ ์์ฑํ์ง ๋ชปํ์ต๋๋ค."),
|
|
|
category=info.get("category"),
|
|
|
)
|
|
|
except Exception as e:
|
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
|
|
|
@app.get("/api/welfare/search")
|
|
|
def api_welfare_search(
|
|
|
keyword: Optional[str] = Query(None, description="์ ์ฑ
๋ช
/๋ด์ฉ/๋์ ๊ฒ์์ด"),
|
|
|
region: Optional[str] = Query(None, description="์ง์ญ๋ช
(์: ์์ธ, ๋ถ์ฐ)"),
|
|
|
page: int = Query(1, ge=1),
|
|
|
size: int = Query(10, ge=1, le=100),
|
|
|
):
|
|
|
try:
|
|
|
wa = WelfareAdvisor()
|
|
|
df = wa.welfare_data
|
|
|
if df.empty:
|
|
|
return {"total": 0, "items": []}
|
|
|
|
|
|
filtered = df.copy()
|
|
|
if keyword:
|
|
|
kw = keyword.strip()
|
|
|
mask = (
|
|
|
filtered["policy_name"].fillna("").str.contains(kw, case=False)
|
|
|
| filtered["service_content_detail"].fillna("").str.contains(kw, case=False)
|
|
|
| filtered["target_audience_description"].fillna("").str.contains(kw, case=False)
|
|
|
)
|
|
|
filtered = filtered[mask]
|
|
|
|
|
|
if region and region != "์ ์ฒด":
|
|
|
filtered = filtered[
|
|
|
(filtered["region_name"].fillna("").str.contains(region, case=False))
|
|
|
| (filtered["region_name"] == "์ ๊ตญ")
|
|
|
]
|
|
|
|
|
|
total = len(filtered)
|
|
|
start = (page - 1) * size
|
|
|
end = start + size
|
|
|
page_df = filtered.iloc[start:end]
|
|
|
|
|
|
items = []
|
|
|
for _, row in page_df.iterrows():
|
|
|
items.append(
|
|
|
{
|
|
|
"policy_name": row.get("policy_name", ""),
|
|
|
"governing_body": row.get("governing_body_name", ""),
|
|
|
"target_audience": row.get("target_audience_description", ""),
|
|
|
"service_content": row.get("service_content_detail", ""),
|
|
|
"region": row.get("region_name", "์ ๊ตญ"),
|
|
|
"application_link": row.get("application_link", ""),
|
|
|
"last_updated": row.get("last_updated", ""),
|
|
|
"tags": row.get("target_audience_tags", ""),
|
|
|
}
|
|
|
)
|
|
|
|
|
|
return {"total": total, "items": items}
|
|
|
except Exception as e:
|
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
|
|
|
@app.get("/api/products/search")
|
|
|
def api_products_search(q: str = Query(..., description="๊ฒ์ ์ง์"), k: int = Query(5, ge=1, le=20)):
|
|
|
try:
|
|
|
rag = get_kb_rag()
|
|
|
results = rag.search_products(q, k=k) or []
|
|
|
return {"items": results}
|
|
|
except Exception as e:
|
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
uvicorn.run("api_server:app", host="0.0.0.0", port=8000, reload=True)
|
|
|
|
|
|
|
|
|
|