# flask-api/app.py from flask import Flask, request, jsonify from flask_cors import CORS import joblib import numpy as np import os from datetime import datetime app = Flask(__name__) CORS(app) # Mengizinkan akses dari Laravel # ============================================ # KONFIGURASI PATH MODEL # ============================================ BASE_DIR = os.path.dirname(os.path.abspath(__file__)) MODELS_DIR = os.path.join(BASE_DIR, 'models') # ============================================ # LOAD MODEL DAN ENCODERS # ============================================ print("=" * 50) print("šŸš€ Memulai Flask API Server...") print("=" * 50) try: # Load model Decision Tree model = joblib.load(os.path.join(MODELS_DIR, 'model_harian.pkl')) print("āœ… Model Decision Tree berhasil diload") # Load encoder untuk mood (input) encoder_mood = joblib.load(os.path.join(MODELS_DIR, 'encoder_mood.pkl')) print(f"āœ… Encoder mood berhasil diload (classes: {list(encoder_mood.classes_)})") # Load encoder untuk label (output) encoder_label = joblib.load(os.path.join(MODELS_DIR, 'encoder_label.pkl')) print(f"āœ… Encoder label berhasil diload (classes: {list(encoder_label.classes_)})") model_loaded = True except FileNotFoundError as e: print(f"āŒ ERROR: File model tidak ditemukan - {e}") print("Pastikan folder 'models' berisi:") print(" - model_harian.pkl") print(" - encoder_mood.pkl") print(" - encoder_label.pkl") model_loaded = False model = None encoder_mood = None encoder_label = None print("=" * 50) # ============================================ # ENDPOINT HEALTH CHECK # ============================================ @app.route('/health', methods=['GET']) def health(): """Cek status server dan model""" return jsonify({ 'status': 'healthy', 'model_loaded': model_loaded, 'timestamp': datetime.now().isoformat(), 'mood_classes': list(encoder_mood.classes_) if encoder_mood else [], 'label_classes': list(encoder_label.classes_) if encoder_label else [] }) # ============================================ # ENDPOINT INFO MODEL # ============================================ @app.route('/info', methods=['GET']) def info(): """Informasi detail tentang model""" if not model_loaded: return jsonify({'error': 'Model tidak tersedia'}), 503 return jsonify({ 'model_type': type(model).__name__, 'model_parameters': model.get_params() if hasattr(model, 'get_params') else {}, 'features': ['mood (encoded)', 'durasi_belajar (menit)', 'durasi_tidur (jam)'], 'mood_classes': list(encoder_mood.classes_), 'label_classes': list(encoder_label.classes_), 'description': 'Decision Tree Classifier untuk rekomendasi durasi belajar' }) # ============================================ # ENDPOINT PREDIKSI HARIAN (HARI 1-7) # ============================================ @app.route('/predict', methods=['POST']) def predict_daily(): """ Prediksi berdasarkan input HARIAN Digunakan untuk 7 hari pertama """ if not model_loaded: return jsonify({ 'success': False, 'error': 'Model tidak tersedia' }), 503 try: data = request.json # ======================================== # VALIDASI INPUT # ======================================== required_fields = ['mood', 'durasi_belajar', 'durasi_tidur'] for field in required_fields: if field not in data: return jsonify({ 'success': False, 'error': f'Field "{field}" diperlukan' }), 400 mood = data['mood'] durasi_belajar = int(data['durasi_belajar']) durasi_tidur = int(data['durasi_tidur']) # ======================================== # ENCODE MOOD # ======================================== try: mood_encoded = encoder_mood.transform([mood])[0] except ValueError as e: return jsonify({ 'success': False, 'error': f'Mood "{mood}" tidak valid. Mood yang tersedia: {list(encoder_mood.classes_)}' }), 400 # ======================================== # PREDIKSI DENGAN DECISION TREE # ======================================== features = np.array([[ mood_encoded, durasi_belajar, durasi_tidur ]]) prediction = model.predict(features) label = encoder_label.inverse_transform(prediction)[0] # Hitung confidence (probabilitas) proba = model.predict_proba(features)[0] confidence = float(max(proba)) # Probabilitas per kelas proba_dict = {} for i, prob in enumerate(proba): class_name = encoder_label.inverse_transform([i])[0] proba_dict[class_name] = float(prob) # ======================================== # RESPONSE # ======================================== return jsonify({ 'success': True, 'prediction': label, 'confidence': confidence, 'probabilities': proba_dict, 'input_received': { 'mood': mood, 'durasi_belajar': durasi_belajar, 'durasi_tidur': durasi_tidur }, 'model_used': 'Decision Tree (daily)' }) except Exception as e: return jsonify({ 'success': False, 'error': str(e) }), 500 # ============================================ # ENDPOINT PREDIKSI POLA (HARI KE-8 dst) # ============================================ @app.route('/predict/pattern', methods=['POST']) def predict_pattern(): """ Prediksi berdasarkan POLA 7 HARI TERAKHIR Tetap menggunakan Decision Tree! Digunakan untuk hari ke-8 dan seterusnya """ if not model_loaded: return jsonify({ 'success': False, 'error': 'Model tidak tersedia' }), 503 try: data = request.json # ======================================== # VALIDASI INPUT # ======================================== required_fields = ['avg_duration', 'most_frequent_mood', 'avg_sleep'] for field in required_fields: if field not in data: return jsonify({ 'success': False, 'error': f'Field "{field}" diperlukan' }), 400 avg_duration = float(data['avg_duration']) most_frequent_mood = data['most_frequent_mood'] avg_sleep = float(data['avg_sleep']) trend = data.get('trend', 'stabil') consistency = data.get('consistency', 0) # ======================================== # ENCODE MOOD # ======================================== try: mood_encoded = encoder_mood.transform([most_frequent_mood])[0] except ValueError as e: # Fallback: coba cari mood terdekat available_moods = list(encoder_mood.classes_) print(f"āš ļø Mood '{most_frequent_mood}' tidak dikenal. Menggunakan 'Biasa Saja' sebagai fallback.") mood_encoded = encoder_mood.transform(['Biasa Saja'])[0] # ======================================== # PREDIKSI DENGAN DECISION TREE # Fitur yang digunakan: [mood_encoded, avg_duration, avg_sleep] # ======================================== features = np.array([[ mood_encoded, int(avg_duration), int(avg_sleep) ]]) prediction = model.predict(features) label = encoder_label.inverse_transform(prediction)[0] # Hitung confidence (probabilitas) proba = model.predict_proba(features)[0] confidence = float(max(proba)) # ======================================== # ANALISIS TAMBAHAN (untuk frontend) # ======================================== # Rekomendasi berdasarkan trend trend_recommendation = "" if trend == 'meningkat': trend_recommendation = "Bagus! Durasi belajarmu terus meningkat. Pertahankan momentum ini!" elif trend == 'menurun': trend_recommendation = "Durasi belajarmu menurun. Coba buat jadwal belajar yang lebih konsisten." else: trend_recommendation = "Konsistensimu stabil. Tingkatkan sedikit demi sedikit untuk hasil lebih baik." # Rekomendasi berdasarkan konsistensi consistency_recommendation = "" if consistency >= 6: consistency_recommendation = "Luar biasa! Kamu sangat konsisten. Terus pertahankan!" elif consistency >= 4: consistency_recommendation = "Cukup baik, tapi masih ada hari yang terlewat. Ayo lebih konsisten lagi!" else: consistency_recommendation = "Masih banyak hari yang terlewat. Yuk, mulai rutin mencatat aktivitas!" # Rekomendasi berdasarkan mood mood_recommendation = "" if most_frequent_mood in ['Bagus', 'Lumayan']: mood_recommendation = "Mood positifmu mendukung belajar efektif!" else: mood_recommendation = "Coba cari aktivitas yang menyenangkan sebelum belajar agar mood lebih baik." # Rekomendasi berdasarkan tidur sleep_recommendation = "" if avg_sleep >= 7: sleep_recommendation = "Tidur cukup! Ini bagus untuk konsentrasi." elif avg_sleep >= 5: sleep_recommendation = "Tidur kurang ideal. Coba tidur lebih awal." else: sleep_recommendation = "Kurang tidur akan mempengaruhi konsentrasi belajar. Prioritaskan istirahat!" # ======================================== # RESPONSE # ======================================== return jsonify({ 'success': True, 'prediction': label, 'confidence': confidence, 'pattern_analysis': { 'avg_duration': round(avg_duration, 1), 'most_frequent_mood': most_frequent_mood, 'avg_sleep': round(avg_sleep, 1), 'trend': trend, 'consistency': f"{consistency}/7 hari", 'trend_recommendation': trend_recommendation, 'consistency_recommendation': consistency_recommendation, 'mood_recommendation': mood_recommendation, 'sleep_recommendation': sleep_recommendation }, 'input_received': { 'avg_duration': avg_duration, 'most_frequent_mood': most_frequent_mood, 'avg_sleep': avg_sleep, 'trend': trend, 'consistency': consistency }, 'model_used': 'Decision Tree (pattern-based)', 'note': 'Prediksi berdasarkan pola 7 hari terakhir menggunakan Decision Tree' }) except Exception as e: return jsonify({ 'success': False, 'error': str(e) }), 500 # ============================================ # ENDPOINT BATCH PREDICTION (Opsional) # ============================================ @app.route('/predict/batch', methods=['POST']) def predict_batch(): """ Prediksi untuk multiple data (batch) """ if not model_loaded: return jsonify({ 'success': False, 'error': 'Model tidak tersedia' }), 503 try: data = request.json samples = data.get('samples', []) if not samples: return jsonify({ 'success': False, 'error': 'Field "samples" diperlukan' }), 400 results = [] for sample in samples: try: mood_encoded = encoder_mood.transform([sample['mood']])[0] features = np.array([[ mood_encoded, int(sample['durasi_belajar']), int(sample['durasi_tidur']) ]]) prediction = model.predict(features) label = encoder_label.inverse_transform(prediction)[0] results.append({ 'input': sample, 'prediction': label }) except Exception as e: results.append({ 'input': sample, 'error': str(e) }) return jsonify({ 'success': True, 'results': results }) except Exception as e: return jsonify({ 'success': False, 'error': str(e) }), 500 # ============================================ # RUN SERVER # ============================================ if __name__ == '__main__': print("\n" + "=" * 50) print("šŸš€ Menjalankan Flask API Server...") print("=" * 50) print(f"šŸ“ Endpoint yang tersedia:") print(f" GET /health - Cek status server") print(f" GET /info - Informasi model") print(f" POST /predict - Prediksi harian (hari 1-7)") print(f" POST /predict/pattern - Prediksi pola (hari 8+)") print(f" POST /predict/batch - Prediksi batch") print("=" * 50) print("\nšŸ”„ Server berjalan di http://127.0.0.1:5000") print(" Tekan Ctrl+C untuk menghentikan server") print("=" * 50) app.run(debug=True, host='0.0.0.0', port=5000)