File size: 5,291 Bytes
fc7db98
 
 
 
 
 
 
 
 
 
 
 
29ad923
 
fc7db98
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29ad923
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
"""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


@asynccontextmanager
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=["*"],
)


@app.get("/")
def root():
    ready, err = model_status()
    return {
        "service": "Word Complexity API",
        "health": "/health",
        "docs": "/docs",
        "model_ready": ready,
        "model_error": err,
    }


@app.get("/health", response_model=HealthResponse)
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,
    )


@app.post("/api/predict", response_model=PredictResponse)
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)


@app.post("/api/intervene", response_model=InterveneResponse)
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)


@app.post("/api/intervene/all", response_model=InterveneAllResponse)
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,
    )


@app.post("/api/efficiency", response_model=EfficiencyResponse)
def api_efficiency(body: EfficiencyRequest):
    return compare_efficiency(body.sentence.strip(), body.target_word.strip(), body.local_latency_ms)


@app.post("/api/alter", response_model=AlterComplexityResponse)
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,
    )