Spaces:
Sleeping
Sleeping
| import os | |
| import json | |
| from typing import List, Optional | |
| from fastapi import FastAPI, HTTPException | |
| from pydantic import BaseModel, Field, ConfigDict | |
| from typing import List | |
| import oracledb | |
| from dotenv import load_dotenv | |
| # ๋ก์ปฌ ๊ฐ๋ฐ: .env ํ์ผ ๋ก๋ (์์ผ๋ฉด) | |
| load_dotenv() | |
| # ----- ํ๊ฒฝ ๋ณ์ ----- | |
| DB_USER = os.environ["DB_USER"] | |
| DB_PASSWORD = os.environ["DB_PASSWORD"] | |
| WALLET_DIR = os.environ["WALLET_DIR"] | |
| WALLET_PASSWORD = os.environ["WALLET_PASSWORD"] | |
| # tnsnames.ora ์์ alias ํ์ธ (๋ณดํต *_high) | |
| TNS_ALIAS = os.environ.get("DB_TNS_ALIAS", "musclecare_high") | |
| # ----- ์ฐ๊ฒฐ ํ: Thin + mTLS (์ง๊ฐ) ----- | |
| # ์ ๋ ํธ์ถํ์ง ๋ง์ธ์: oracledb.init_oracle_client() # (Thick๋ก ๋น ์ ธ์ ์คํจ ๊ฐ๋ฅ) | |
| pool: oracledb.ConnectionPool = oracledb.create_pool( | |
| user=DB_USER, | |
| password=DB_PASSWORD, | |
| dsn=TNS_ALIAS, # Wallet tnsnames.ora์ alias | |
| config_dir=WALLET_DIR, | |
| wallet_location=WALLET_DIR, | |
| wallet_password=WALLET_PASSWORD, | |
| min=1, max=4, increment=1, | |
| homogeneous=True, | |
| timeout=60, | |
| retry_count=6, retry_delay=2 | |
| ) | |
| app = FastAPI(title="MuscleCare Hybrid Server (mTLS)") | |
| # ----- ๋ชจ๋ธ ----- | |
| class StatePayload(BaseModel): | |
| model_config = ConfigDict(protected_namespaces=()) | |
| user_id: str | |
| rms_base: Optional[float] = None | |
| freq_base: Optional[float] = None | |
| user_emb: Optional[List[float]] = Field(default=None, description="length=12") | |
| model_version: Optional[str] = None | |
| class LogItem(BaseModel): | |
| user_id: str | |
| session_id: Optional[str] = None | |
| measure_date: str # 'YYYY-MM-DD' | |
| rms: Optional[float] = None | |
| freq: Optional[float] = None | |
| fatigue: Optional[float] = None | |
| mode: Optional[str] = Field(default="EMA", description="EMA/Hybrid/E2E") | |
| window_count: Optional[int] = None | |
| class LogsPayload(BaseModel): | |
| items: List[LogItem] = Field(..., min_length=1) | |
| # ----- ์ ํธ ----- | |
| def clob_json(obj) -> str: | |
| return json.dumps(obj, separators=(",", ":"), ensure_ascii=False) | |
| # ----- ์๋ํฌ์ธํธ ----- | |
| def root(): | |
| """๋ฃจํธ ์๋ํฌ์ธํธ - ์๋ฒ ์ํ ํ์ธ""" | |
| return { | |
| "status": "running", | |
| "message": "MuscleCare API Server", | |
| "version": "1.0.0", | |
| "endpoints": { | |
| "health": "/health", | |
| "docs": "/docs", | |
| "upload_state": "/upload_state", | |
| "upload_logs": "/upload_logs" | |
| } | |
| } | |
| def health(): | |
| try: | |
| with pool.acquire() as conn: | |
| with conn.cursor() as cur: | |
| cur.execute("SELECT 1 FROM DUAL") | |
| v = cur.fetchone()[0] | |
| return {"ok": True, "db": v} | |
| except Exception as e: | |
| raise HTTPException(500, f"DB health check failed: {e}") | |
| def upload_state(p: StatePayload): | |
| # MERGE INTO MuscleCare.user_state | |
| try: | |
| emb_json = None | |
| if p.user_emb is not None: | |
| if len(p.user_emb) != 12: | |
| raise HTTPException(400, "user_emb must have length=12") | |
| emb_json = clob_json(p.user_emb) | |
| with pool.acquire() as conn: | |
| with conn.cursor() as cur: | |
| cur.execute(""" | |
| MERGE INTO MuscleCare.user_state t | |
| USING ( | |
| SELECT :user_id AS user_id FROM dual | |
| ) s | |
| ON (t.user_id = s.user_id) | |
| WHEN MATCHED THEN UPDATE SET | |
| rms_base = :rms_base, | |
| freq_base = :freq_base, | |
| user_emb = :user_emb, | |
| model_version = :model_version, | |
| last_sync = CURRENT_TIMESTAMP | |
| WHEN NOT MATCHED THEN INSERT | |
| (user_id, rms_base, freq_base, user_emb, model_version, last_sync) | |
| VALUES | |
| (:user_id, :rms_base, :freq_base, :user_emb, :model_version, CURRENT_TIMESTAMP) | |
| """, dict( | |
| user_id=p.user_id, | |
| rms_base=p.rms_base, | |
| freq_base=p.freq_base, | |
| user_emb=emb_json, | |
| model_version=p.model_version | |
| )) | |
| conn.commit() | |
| return {"ok": True} | |
| except HTTPException: | |
| raise | |
| except Exception as e: | |
| raise HTTPException(500, f"upload_state failed: {e}") | |
| def upload_logs(body: LogsPayload): | |
| try: | |
| rows = [( | |
| it.user_id, it.session_id, it.measure_date, | |
| it.rms, it.freq, it.fatigue, it.mode, it.window_count | |
| ) for it in body.items] | |
| with pool.acquire() as conn: | |
| with conn.cursor() as cur: | |
| cur.executemany(""" | |
| INSERT INTO MuscleCare.fatigue_logs | |
| (user_id, session_id, measure_date, rms, freq, fatigue, mode, window_count, created_at) | |
| VALUES | |
| (:1, :2, TO_DATE(:3,'YYYY-MM-DD'), :4, :5, :6, :7, :8, CURRENT_TIMESTAMP) | |
| """, rows) | |
| conn.commit() | |
| return {"ok": True, "inserted": len(rows)} | |
| except Exception as e: | |
| raise HTTPException(500, f"upload_logs failed: {e}") | |