ahmadsayadi commited on
Commit
a8989ed
·
1 Parent(s): 4016462

deploy api prediksi status gizi

Browse files
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