181 lines
5.3 KiB
Python
181 lines
5.3 KiB
Python
import os
|
|
import traceback
|
|
from flask import Flask, request, jsonify
|
|
from flask_cors import CORS
|
|
import joblib
|
|
import pandas as pd
|
|
|
|
app = Flask(__name__)
|
|
CORS(app)
|
|
|
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
MODEL_PATH = os.path.join(BASE_DIR, 'model_rekomendasi_15mapel.pkl')
|
|
ENCODER_PATH = os.path.join(BASE_DIR, 'encoders_15mapel.pkl')
|
|
FEATURE_COLUMNS_PATH = os.path.join(BASE_DIR, 'feature_columns_15mapel.pkl')
|
|
|
|
model = None
|
|
encoders = None
|
|
feature_columns = None
|
|
|
|
|
|
def load_artifacts():
|
|
global model, encoders, feature_columns
|
|
|
|
model = joblib.load(MODEL_PATH)
|
|
encoders = joblib.load(ENCODER_PATH)
|
|
feature_columns = joblib.load(FEATURE_COLUMNS_PATH)
|
|
|
|
if not isinstance(feature_columns, list):
|
|
raise ValueError("feature_columns_15mapel.pkl harus berisi list nama kolom.")
|
|
|
|
required_encoder_keys = ['ekonomi_orang_tua', 'rekomendasi_jurusan']
|
|
for key in required_encoder_keys:
|
|
if key not in encoders:
|
|
raise KeyError(f"Encoder '{key}' tidak ditemukan pada encoders_15mapel.pkl")
|
|
|
|
|
|
def safe_float(value, default=0.0):
|
|
try:
|
|
if value is None or value == '':
|
|
return float(default)
|
|
return float(value)
|
|
except (ValueError, TypeError):
|
|
return float(default)
|
|
|
|
|
|
def validate_required_features(data):
|
|
if not isinstance(data, dict):
|
|
return False, "Payload JSON tidak valid."
|
|
|
|
missing = []
|
|
for col in feature_columns:
|
|
if col == 'ekonomi_orang_tua':
|
|
continue
|
|
if col not in data:
|
|
missing.append(col)
|
|
|
|
if 'ekonomi_orang_tua' not in data:
|
|
missing.append('ekonomi_orang_tua')
|
|
|
|
if missing:
|
|
return False, f"Fitur berikut belum dikirim: {', '.join(missing)}"
|
|
|
|
return True, None
|
|
|
|
|
|
try:
|
|
load_artifacts()
|
|
print("✅ API Flask siap. Model, encoder, dan feature columns berhasil dimuat.")
|
|
except Exception as e:
|
|
print("❌ Gagal memuat aset machine learning.")
|
|
print(str(e))
|
|
|
|
|
|
@app.route('/', methods=['GET'])
|
|
def home():
|
|
return jsonify({
|
|
'success': True,
|
|
'message': 'Flask API Sistem Rekomendasi Jurusan aktif.'
|
|
})
|
|
|
|
|
|
@app.route('/health', methods=['GET'])
|
|
def health():
|
|
artifacts_ready = all([
|
|
model is not None,
|
|
encoders is not None,
|
|
feature_columns is not None
|
|
])
|
|
|
|
return jsonify({
|
|
'success': artifacts_ready,
|
|
'model_loaded': model is not None,
|
|
'encoders_loaded': encoders is not None,
|
|
'feature_columns_loaded': feature_columns is not None
|
|
}), 200 if artifacts_ready else 500
|
|
|
|
|
|
@app.route('/predict', methods=['POST'])
|
|
def predict():
|
|
global model, encoders, feature_columns
|
|
|
|
try:
|
|
if model is None or encoders is None or feature_columns is None:
|
|
return jsonify({
|
|
'success': False,
|
|
'message': 'Aset model belum berhasil dimuat. Periksa file .pkl dan path-nya.'
|
|
}), 500
|
|
|
|
data = request.get_json(silent=True)
|
|
if data is None:
|
|
return jsonify({
|
|
'success': False,
|
|
'message': 'Request harus berformat JSON.'
|
|
}), 400
|
|
|
|
is_valid, error_message = validate_required_features(data)
|
|
if not is_valid:
|
|
return jsonify({
|
|
'success': False,
|
|
'message': error_message
|
|
}), 400
|
|
|
|
eko_text = str(data.get('ekonomi_orang_tua', 'Mampu')).strip()
|
|
|
|
ekonomi_classes = list(encoders['ekonomi_orang_tua'].classes_)
|
|
if eko_text not in ekonomi_classes:
|
|
return jsonify({
|
|
'success': False,
|
|
'message': f"Nilai ekonomi_orang_tua tidak valid. Gunakan salah satu: {', '.join(ekonomi_classes)}"
|
|
}), 400
|
|
|
|
encoded_eko = encoders['ekonomi_orang_tua'].transform([eko_text])[0]
|
|
|
|
input_dict = {}
|
|
for col in feature_columns:
|
|
if col == 'ekonomi_orang_tua':
|
|
input_dict[col] = encoded_eko
|
|
else:
|
|
input_dict[col] = safe_float(data.get(col), 0)
|
|
|
|
df_input = pd.DataFrame([input_dict], columns=feature_columns)
|
|
|
|
pred_idx = model.predict(df_input)[0]
|
|
nama_jurusan = encoders['rekomendasi_jurusan'].inverse_transform([pred_idx])[0]
|
|
|
|
probabilities = None
|
|
if hasattr(model, 'predict_proba'):
|
|
try:
|
|
proba = model.predict_proba(df_input)[0]
|
|
class_labels = encoders['rekomendasi_jurusan'].inverse_transform(model.classes_)
|
|
probabilities = [
|
|
{
|
|
'jurusan': str(label),
|
|
'probabilitas': round(float(score), 4)
|
|
}
|
|
for label, score in sorted(
|
|
zip(class_labels, proba),
|
|
key=lambda x: x[1],
|
|
reverse=True
|
|
)
|
|
]
|
|
except Exception:
|
|
probabilities = None
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'prediksi_jurusan': str(nama_jurusan),
|
|
'top_predictions': probabilities
|
|
}), 200
|
|
|
|
except Exception as e:
|
|
traceback.print_exc()
|
|
return jsonify({
|
|
'success': False,
|
|
'message': f'Terjadi kesalahan saat melakukan prediksi: {str(e)}'
|
|
}), 500
|
|
|
|
|
|
if __name__ == '__main__':
|
|
app.run(host='127.0.0.1', port=5000, debug=True) |