Spaces:
Runtime error
Runtime error
Commit ·
a8989ed
1
Parent(s): 4016462
deploy api prediksi status gizi
Browse files- Dockerfile +13 -0
- app.py +111 -0
- model/le_jenis_kelamin.pkl +3 -0
- model/le_status_gizi.pkl +3 -0
- model/svm.pkl +3 -0
- requirements-hf.txt +7 -0
Dockerfile
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
COPY requirements-hf.txt .
|
| 6 |
+
RUN pip install --no-cache-dir -r requirements-hf.txt
|
| 7 |
+
|
| 8 |
+
COPY app.py .
|
| 9 |
+
COPY model/ ./model/
|
| 10 |
+
|
| 11 |
+
EXPOSE 7860
|
| 12 |
+
|
| 13 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
app.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, HTTPException
|
| 2 |
+
from pydantic import BaseModel, field_validator
|
| 3 |
+
from typing import List, Optional
|
| 4 |
+
import pandas as pd
|
| 5 |
+
import pickle
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
# === PATH MODEL ===
|
| 9 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 10 |
+
MODEL_DIR = os.path.join(HERE, "model")
|
| 11 |
+
MODEL_PATH = os.path.join(MODEL_DIR, "svm.pkl")
|
| 12 |
+
LE_JK_PATH = os.path.join(MODEL_DIR, "le_jenis_kelamin.pkl")
|
| 13 |
+
LE_STATUS_PATH = os.path.join(MODEL_DIR, "le_status_gizi.pkl")
|
| 14 |
+
|
| 15 |
+
FEATURE_ORDER = ["tinggi", "berat", "umur_bulan", "jenis_kelamin_encoded"]
|
| 16 |
+
|
| 17 |
+
GENDER_ALIASES = {"L": "L", "P": "P", "l": "L", "p": "P", "Male": "L", "Female": "P", "M": "L", "F": "P"}
|
| 18 |
+
|
| 19 |
+
# === Input pakai camelCase ===
|
| 20 |
+
class Item(BaseModel):
|
| 21 |
+
tinggiCm: float
|
| 22 |
+
beratKg: float
|
| 23 |
+
usiaBulan: int
|
| 24 |
+
jenisKelamin: str
|
| 25 |
+
jenisKelaminEncoded: Optional[int] = None
|
| 26 |
+
|
| 27 |
+
@field_validator("jenisKelamin")
|
| 28 |
+
@classmethod
|
| 29 |
+
def normalisasi_jk(cls, v):
|
| 30 |
+
if v is None:
|
| 31 |
+
return v
|
| 32 |
+
v = str(v).strip()
|
| 33 |
+
return GENDER_ALIASES.get(v, GENDER_ALIASES.get(v.upper(), v.upper()[0]))
|
| 34 |
+
|
| 35 |
+
def to_features(self, le_jk):
|
| 36 |
+
try:
|
| 37 |
+
jk_encoded = (
|
| 38 |
+
int(self.jenisKelaminEncoded)
|
| 39 |
+
if self.jenisKelaminEncoded is not None
|
| 40 |
+
else int(le_jk.transform([self.jenisKelamin])[0])
|
| 41 |
+
)
|
| 42 |
+
except Exception:
|
| 43 |
+
raise ValueError(f"Jenis kelamin '{self.jenisKelamin}' tidak dikenali encoder")
|
| 44 |
+
|
| 45 |
+
return {
|
| 46 |
+
"tinggi": float(self.tinggiCm),
|
| 47 |
+
"berat": float(self.beratKg),
|
| 48 |
+
"umur_bulan": int(self.usiaBulan),
|
| 49 |
+
"jenis_kelamin_encoded": jk_encoded,
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
class BatchRequest(BaseModel):
|
| 53 |
+
data: List[Item]
|
| 54 |
+
|
| 55 |
+
# === Fungsi bantu ===
|
| 56 |
+
def load_pickle(path):
|
| 57 |
+
if not os.path.exists(path):
|
| 58 |
+
raise FileNotFoundError(f"Tidak ditemukan: {path}")
|
| 59 |
+
with open(path, "rb") as f:
|
| 60 |
+
return pickle.load(f)
|
| 61 |
+
|
| 62 |
+
# === Muat model & encoder ===
|
| 63 |
+
try:
|
| 64 |
+
model = load_pickle(MODEL_PATH)
|
| 65 |
+
le_jk = load_pickle(LE_JK_PATH)
|
| 66 |
+
le_status = load_pickle(LE_STATUS_PATH)
|
| 67 |
+
_load_error = None
|
| 68 |
+
except Exception as e:
|
| 69 |
+
model = le_jk = le_status = None
|
| 70 |
+
_load_error = e
|
| 71 |
+
|
| 72 |
+
app = FastAPI(
|
| 73 |
+
title="API Prediksi Status Gizi (camelCase)",
|
| 74 |
+
description="API untuk prediksi status gizi anak",
|
| 75 |
+
version="1.0.0"
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
@app.get("/")
|
| 79 |
+
def root():
|
| 80 |
+
return {"message": "API Prediksi Status Gizi", "status": "running"}
|
| 81 |
+
|
| 82 |
+
@app.get("/health")
|
| 83 |
+
def health():
|
| 84 |
+
return {"status": "ok"} if _load_error is None else {"status": "error", "detail": str(_load_error)}
|
| 85 |
+
|
| 86 |
+
@app.post("/predict")
|
| 87 |
+
def predict(item: Item):
|
| 88 |
+
if any(x is None for x in (model, le_jk, le_status)):
|
| 89 |
+
raise HTTPException(status_code=500, detail=f"Gagal memuat model: {_load_error}")
|
| 90 |
+
try:
|
| 91 |
+
row = item.to_features(le_jk)
|
| 92 |
+
X = pd.DataFrame([row])[FEATURE_ORDER]
|
| 93 |
+
y_enc = int(model.predict(X)[0])
|
| 94 |
+
y_label = str(le_status.inverse_transform([y_enc])[0])
|
| 95 |
+
return {"label": y_label, "labelEncoded": y_enc}
|
| 96 |
+
except Exception as e:
|
| 97 |
+
raise HTTPException(status_code=400, detail=str(e))
|
| 98 |
+
|
| 99 |
+
@app.post("/predictBatch")
|
| 100 |
+
def predict_batch(req: BatchRequest):
|
| 101 |
+
if any(x is None for x in (model, le_jk, le_status)):
|
| 102 |
+
raise HTTPException(status_code=500, detail=f"Gagal memuat model: {_load_error}")
|
| 103 |
+
try:
|
| 104 |
+
rows = [item.to_features(le_jk) for item in req.data]
|
| 105 |
+
X = pd.DataFrame(rows)[FEATURE_ORDER]
|
| 106 |
+
y_enc = model.predict(X)
|
| 107 |
+
y_label = le_status.inverse_transform(y_enc)
|
| 108 |
+
results = [{"label": str(lbl), "labelEncoded": int(enc)} for lbl, enc in zip(y_label, y_enc)]
|
| 109 |
+
return {"results": results}
|
| 110 |
+
except Exception as e:
|
| 111 |
+
raise HTTPException(status_code=400, detail=str(e))
|
model/le_jenis_kelamin.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:457bfe436860b313b799a51756043cd0c6f5af16fc7f85a85e6a43dc3886758a
|
| 3 |
+
size 251
|
model/le_status_gizi.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:751540ac5db9f5dd674901e217129e1cb711712e2daac9338723461ab7a21334
|
| 3 |
+
size 286
|
model/svm.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:18924a4ffb3ea6a4f9123ea2de35b582c067bababf1463cd9389a16439f3ba55
|
| 3 |
+
size 12395
|
requirements-hf.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.104.1
|
| 2 |
+
uvicorn==0.24.0
|
| 3 |
+
pandas==2.1.4
|
| 4 |
+
numpy==1.26.2
|
| 5 |
+
scikit-learn==1.3.2
|
| 6 |
+
pydantic==2.5.2
|
| 7 |
+
python-multipart==0.0.6
|