Spaces:
Sleeping
Sleeping
| """FastAPI backend for Railway — word complexity inference.""" | |
| from __future__ import annotations | |
| import os | |
| from contextlib import asynccontextmanager | |
| from fastapi import FastAPI, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from app.config import CORS_ORIGINS, MODEL_PATH | |
| from app.schemas import ( | |
| AlterComplexityRequest, | |
| AlterComplexityResponse, | |
| EfficiencyRequest, | |
| EfficiencyResponse, | |
| HealthResponse, | |
| InterveneAllRequest, | |
| InterveneAllResponse, | |
| InterveneRequest, | |
| InterveneResponse, | |
| PredictRequest, | |
| PredictResponse, | |
| ) | |
| from app.services.efficiency import compare_efficiency | |
| from app.services.interventions import run_intervention | |
| from app.services.model import get_predictor, model_status, predict | |
| async def lifespan(_app: FastAPI): | |
| if os.environ.get("EAGER_LOAD_MODEL", "1") == "1": | |
| try: | |
| get_predictor() | |
| except RuntimeError as exc: | |
| print(f"WARN: model not loaded at startup: {exc}") | |
| yield | |
| app = FastAPI(title="Word Complexity API", version="1.0.0", lifespan=lifespan) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"] if os.environ.get("CORS_ALLOW_ALL") == "1" else CORS_ORIGINS, | |
| allow_credentials=os.environ.get("CORS_ALLOW_ALL") != "1", | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| def root(): | |
| ready, err = model_status() | |
| return { | |
| "service": "Word Complexity API", | |
| "health": "/health", | |
| "docs": "/docs", | |
| "model_ready": ready, | |
| "model_error": err, | |
| } | |
| def health(): | |
| ready, err = model_status() | |
| return HealthResponse( | |
| status="ok" if ready else "degraded", | |
| model_path=str(MODEL_PATH), | |
| model_ready=ready, | |
| model_error=err, | |
| ) | |
| def api_predict(body: PredictRequest): | |
| try: | |
| data, latency_ms, target_in = predict(body.sentence.strip(), body.target_word.strip()) | |
| except RuntimeError as exc: | |
| raise HTTPException(status_code=503, detail=str(exc)) from exc | |
| return PredictResponse(**data, latency_ms=latency_ms, target_in_sentence=target_in) | |
| def api_intervene(body: InterveneRequest): | |
| try: | |
| predictor = get_predictor() | |
| detail = run_intervention( | |
| predictor, | |
| body.sentence.strip(), | |
| body.target_word.strip(), | |
| body.reason, | |
| body.use_llm, | |
| ) | |
| except RuntimeError as exc: | |
| raise HTTPException(status_code=503, detail=str(exc)) from exc | |
| except ValueError as exc: | |
| raise HTTPException(status_code=400, detail=str(exc)) from exc | |
| return InterveneResponse(intervention=detail) | |
| def api_intervene_all(body: InterveneAllRequest): | |
| try: | |
| predictor = get_predictor() | |
| base = predictor.predict(body.sentence.strip(), body.target_word.strip()) | |
| from utils import REASON_ORDER | |
| results = [ | |
| run_intervention( | |
| predictor, | |
| body.sentence.strip(), | |
| body.target_word.strip(), | |
| reason, | |
| body.use_llm, | |
| predicted_reason=body.predicted_reason, | |
| ) | |
| for reason in REASON_ORDER | |
| ] | |
| except RuntimeError as exc: | |
| raise HTTPException(status_code=503, detail=str(exc)) from exc | |
| best = max(results, key=lambda r: r.hardness_drop) | |
| faithful = None | |
| if body.predicted_reason: | |
| faithful = best.reason == body.predicted_reason | |
| return InterveneAllResponse( | |
| base_level=base.complexity_level, | |
| results=results, | |
| best_reason=best.reason, | |
| faithful=faithful, | |
| ) | |
| def api_efficiency(body: EfficiencyRequest): | |
| return compare_efficiency(body.sentence.strip(), body.target_word.strip(), body.local_latency_ms) | |
| def api_alter(body: AlterComplexityRequest): | |
| from reason_interventions import alter_complexity | |
| try: | |
| result = alter_complexity( | |
| body.reason, | |
| body.sentence.strip(), | |
| body.target_word.strip(), | |
| body.direction, | |
| ) | |
| except RuntimeError as exc: | |
| raise HTTPException(status_code=503, detail=str(exc)) from exc | |
| except ValueError as exc: | |
| raise HTTPException(status_code=400, detail=str(exc)) from exc | |
| if result.reason == "Lexical Rarity": | |
| updates = "Updated sentence and target word above — click Predict again." | |
| else: | |
| updates = "Updated sentence above (target word unchanged) — click Predict again." | |
| return AlterComplexityResponse( | |
| reason=result.reason, | |
| direction=result.direction, | |
| original_sentence=result.original_sentence, | |
| original_target_word=result.original_target, | |
| new_sentence=result.new_sentence, | |
| new_target_word=result.new_target, | |
| edit_method=result.edit_method, | |
| explanation=result.explanation, | |
| updates=updates, | |
| ) | |