Spaces:
Running
Running
| 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}") | |
| # --- 엔드포인트 --- | |
| def get_options_endpoint(req: OptionsRequest): | |
| return core.get_step_options(core.conds_df, job=req.job, bm=req.bm, sales=req.sales) | |
| 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') | |
| 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 | |
| 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") | |
| 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) | |