import uvicorn from fastapi import FastAPI, HTTPException, Request from fastapi.responses import FileResponse, JSONResponse from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from pydantic import BaseModel from typing import List, Optional, Dict, Any import os import uuid from matplotlib import font_manager from . import core, ppt app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # --- Pydantic 모델 --- class OptionsRequest(BaseModel): job: Optional[str] = None bm: Optional[str] = None sales: Optional[str] = None class SearchParamsModel(BaseModel): job: str bm: str sales: str emp: str userBase: str userValue: int class DiagnosisRequest(BaseModel): params: SearchParamsModel answers: List[int] # Get BASE_DIR for the project root (one level up from this 'core' folder) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # --- 폰트 설치 확인 (디버깅용) --- font_path = os.path.join(BASE_DIR, "core", "data", "Paperlogy-4Regular.ttf") if os.path.exists(font_path): font_name = font_manager.FontProperties(fname=font_path).get_name() print(f"Loaded font: {font_name}") else: print(f"Warning: Font file not found at {font_path}") # --- 엔드포인트 --- @app.post("/api/options") def get_options_endpoint(req: OptionsRequest): return core.get_step_options(core.conds_df, job=req.job, bm=req.bm, sales=req.sales) @app.post("/api/survey") def get_survey_items_endpoint(req: SearchParamsModel): levels = core.get_levels_by_wage(core.avg_df, req.job, req.bm, req.sales, req.emp, req.userBase, req.userValue) if not levels: levels = ["L3", "L4"] bars_indicator, _ = core.make_bars_table(core.bars_df, req.job, levels) return bars_indicator.to_dict(orient='records') @app.post("/api/diagnosis") def post_diagnosis_endpoint(req: DiagnosisRequest): p = req.params levels = core.get_levels_by_wage(core.avg_df, p.job, p.bm, p.sales, p.emp, p.userBase, p.userValue) if not levels: levels = ["L3", "L4"] _, ppt_tables = core.make_bars_table(core.bars_df, p.job, levels) levels_def = core.get_levels_definition(p.job, levels, core.job_def) final_lv, level_out = core.judge_level(req.answers, core.factors, levels, levels_def) head_msg, text1, text2, pct, c3, table3 = core.judge_wage(core.raw_df, p.job, p.bm, p.sales, p.emp, final_lv, p.userBase, p.userValue) table1, chart1 = core.format_table(ppt_tables[0], core.factors, req.answers, cut=2) table2, chart2 = core.format_table(ppt_tables[1], core.factors, req.answers, cut=4) level_out.columns = ['left', 'right'] level_info = { "left": level_out['left'].to_dict(), "right": level_out['right'].to_dict(), } for chart in [chart1, chart2]: chart.columns = ['subject', 'target', 'user'] subjects = [ '기술 전문성', '도메인 이해', '데이터 리터러시', '고객중심 사고', '문제해결능력', '애자일 실행', '협업', '책임감', '영향범위', '리더십' ] chart['subject'] = subjects wage_records = table3.to_dict(orient='records') for rec in wage_records: rec['description'] = text1 level_chart = { "left": chart1.to_dict(orient='records'), "right": chart2.to_dict(orient='records') } info_pool = [p.job, final_lv, p.userBase, p.bm, p.sales, p.emp] result_data = { "headMessage": head_msg.split('\n'), "levelInfo": level_info, "levelChart": level_chart, "percentile": pct, "wageOutput": wage_records, "info": [x for x in info_pool if x != '전체'] } return result_data @app.post("/api/report") def download_report(req: DiagnosisRequest): p = req.params levels = core.get_levels_by_wage(core.avg_df, p.job, p.bm, p.sales, p.emp, p.userBase, p.userValue) if not levels: levels = ["L3", "L4"] _, ppt_tables = core.make_bars_table(core.bars_df, p.job, levels) levels_def = core.get_levels_definition(p.job, levels, core.job_def) final_lv, lv_out = core.judge_level(req.answers, core.factors, levels, levels_def) head_msg, blk1, blk2, pct, c3, t3 = core.judge_wage(core.raw_df, p.job, p.bm, p.sales, p.emp, final_lv, p.userBase, p.userValue) table1, chart1 = core.format_table(ppt_tables[0], core.factors, req.answers, cut=2) table2, chart2 = core.format_table(ppt_tables[1], core.factors, req.answers, cut=4) ppt_dataset = { 0 : {"head_message": head_msg, "texts": [blk1, blk2, f"{pct:.1f}%"], "tables": [lv_out.iloc[:2], lv_out.iloc[2:], t3]}, 1 : {"title": lv_out.columns[0], "table": [table1]}, 2 : {"title": lv_out.columns[1], "table": [table2]} } out_path = os.path.join(core.BASE_DIR, "tmp", f"report_{uuid.uuid4().hex[:8]}.pptx") os.makedirs(os.path.dirname(out_path), exist_ok=True) ppt.update_ppt_with_data(ppt_dataset, out_path) return FileResponse(out_path, filename="진단리포트.pptx", media_type="application/vnd.openxmlformats-officedocument.presentationml.presentation") # --- 정적 파일 서빙 --- # api.py가 core/ 폴더 안에 있으므로 BASE_DIR는 상위 디렉토리(루트)입니다. BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) OUT_DIR = os.path.join(BASE_DIR, "out") if os.path.exists(OUT_DIR): app.mount("/_next", StaticFiles(directory=os.path.join(OUT_DIR, "_next")), name="next_static") @app.api_route("/{full_path:path}", methods=["GET", "HEAD"]) async def serve_spa(full_path: str): if full_path.startswith("api"): raise HTTPException(status_code=404) file_path = os.path.join(OUT_DIR, full_path) if os.path.isfile(file_path): return FileResponse(file_path) html_file = os.path.join(OUT_DIR, f"{full_path}.html") if os.path.isfile(html_file): return FileResponse(html_file) index_file = os.path.join(file_path, "index.html") if os.path.isdir(file_path) and os.path.isfile(index_file): return FileResponse(index_file) return FileResponse(os.path.join(OUT_DIR, "index.html")) if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=7860)