""" Root-level FastAPI entrypoint (Hugging Face Spaces convention expects an app.py at the repo root defining `app`). This is a thin wrapper: the actual extraction logic lives in src/ (rules extractor, trained ML model, ensemble predictor) so the core pipeline stays testable and importable on its own; this file just wires it up as the Space's entrypoint and defines the HTTP endpoints. Run locally from the repo root: uvicorn app:app --reload --port 8000 On Hugging Face Docker Spaces, the Dockerfile's CMD runs this same module on port 7860. """ import os import sys BASE_DIR = os.path.dirname(os.path.abspath(__file__)) SRC_DIR = os.path.join(BASE_DIR, "src") sys.path.insert(0, SRC_DIR) MODEL_PATH = os.path.join(SRC_DIR, "model.joblib") from fastapi import FastAPI, HTTPException from pydantic import BaseModel, Field from rules_extractor import extract_attributes_rules from predict import predict_ensemble, predict_ml app = FastAPI( title="Product Attribute Extraction API", description="Converts unstructured product/fashion descriptions into structured attributes.", version="1.0.0", ) class ExtractRequest(BaseModel): text: str = Field(..., min_length=1, description="Raw product description text") mode: str = Field( "ensemble", description="Extraction mode: 'ensemble' (default, rules+ml), 'ml', or 'rules'", ) class ExtractResponse(BaseModel): text: str attributes: dict mode: str from fastapi.responses import RedirectResponse @app.get("/") def root(): return RedirectResponse(url="/docs") @app.get("/health") def health(): return {"status": "ok"} @app.post("/extract", response_model=ExtractResponse) def extract(req: ExtractRequest): text = req.text.strip() if not text: raise HTTPException(status_code=400, detail="text must not be empty") if req.mode == "rules": attrs = extract_attributes_rules(text) elif req.mode == "ml": attrs = predict_ml(text, model_path=MODEL_PATH) elif req.mode == "ensemble": attrs = predict_ensemble(text, model_path=MODEL_PATH) else: raise HTTPException(status_code=400, detail="mode must be one of: ensemble, ml, rules") return ExtractResponse(text=text, attributes=attrs, mode=req.mode)