|
|
import joblib |
|
|
import pandas as pd |
|
|
from flask import Flask, request, jsonify, render_template |
|
|
from datetime import timedelta |
|
|
import os |
|
|
|
|
|
from tensorflow.keras.models import load_model |
|
|
from tensorflow.keras.preprocessing.image import load_img, img_to_array |
|
|
import numpy as np |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CROP_PRED_MODEL_PATH = 'random_forest_model.pkl' |
|
|
CROP_PRED_SCALER_PATH = 'scaler.pkl' |
|
|
|
|
|
|
|
|
MODEL_FILES_CLUSTERING = { |
|
|
"bangkalan": "kmeans_model_bangkalan.joblib", |
|
|
"sampang" : "kmeans_model_sampang.joblib", |
|
|
"pamekasan": "kmeans_model_pamekasan.joblib", |
|
|
"sumenep": "kmeans_model_sumenep.joblib", |
|
|
|
|
|
} |
|
|
SCALER_FILES = { |
|
|
"bangkalan": "scaler_bangkalan.joblib", |
|
|
"sampang": "scaler_sampang.joblib", |
|
|
"pamekasan": "scaler_pamekasan.joblib", |
|
|
"sumenep": "scaler_sumenep.joblib", |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
VAR_MODEL_PATH = "static/var_model_multivariate.joblib" |
|
|
|
|
|
|
|
|
|
|
|
CSV_MAP = { |
|
|
"Bangkalan": "static/bangkalan.csv", |
|
|
"Sampang": "static/sampang.csv", |
|
|
"Pamekasan": "static/pamekasan.csv", |
|
|
"Sumenep": "static/sumenep.csv", |
|
|
} |
|
|
|
|
|
|
|
|
LOADED_MODELS_CLUSTERING = {} |
|
|
LOADED_SCALERS = {} |
|
|
VAR_MODEL = None |
|
|
CROP_MODEL = None |
|
|
CROP_SCALER = None |
|
|
|
|
|
SUPPORTED_KABUPATEN_CLUSTERING = list(MODEL_FILES_CLUSTERING.keys()) |
|
|
FEATURE_NAMES_CROP = ['N', 'P', 'K', 'temperature', 'humidity', 'ph', 'rainfall'] |
|
|
|
|
|
|
|
|
WEATHER_MODEL = None |
|
|
WEATHER_CLASSES = [ |
|
|
"dew", "fogsmog", "frost", "glaze", "hail", |
|
|
"lightning", "rain", "rainbow", "rime", |
|
|
"sandstorm", "snow" |
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_all_assets(): |
|
|
"""Memuat semua model (Clustering, Forecasting, Crop Prediction) ke memori.""" |
|
|
global LOADED_MODELS_CLUSTERING, LOADED_SCALERS, VAR_MODEL, CROP_MODEL, CROP_SCALER |
|
|
print("--- MEMUAT SEMUA ASET ---") |
|
|
|
|
|
|
|
|
try: |
|
|
CROP_MODEL = joblib.load(CROP_PRED_MODEL_PATH) |
|
|
CROP_SCALER = joblib.load(CROP_PRED_SCALER_PATH) |
|
|
print(f"Model CROP ({CROP_PRED_MODEL_PATH}) dan Scaler berhasil dimuat.") |
|
|
except Exception as e: |
|
|
print(f"GAGAL memuat model CROP: {e}") |
|
|
|
|
|
|
|
|
for kab, filename in MODEL_FILES_CLUSTERING.items(): |
|
|
try: |
|
|
model_loaded = joblib.load(filename) |
|
|
LOADED_MODELS_CLUSTERING[kab] = model_loaded |
|
|
print(f"Model CLUSTERING {kab.title()} ({filename}) berhasil dimuat.") |
|
|
except Exception as e: |
|
|
print(f"GAGAL memuat model CLUSTERING {kab.title()} ({filename}): {e}") |
|
|
LOADED_MODELS_CLUSTERING[kab] = None |
|
|
|
|
|
|
|
|
for kab, filename in SCALER_FILES.items(): |
|
|
try: |
|
|
scaler_loaded = joblib.load(filename) |
|
|
LOADED_SCALERS[kab] = scaler_loaded |
|
|
print(f"Scaler CLUSTERING {kab.title()} ({filename}) berhasil dimuat.") |
|
|
except Exception as e: |
|
|
print(f"GAGAL memuat scaler CLUSTERING {kab.title()} ({filename}): {e}") |
|
|
LOADED_SCALERS[kab] = None |
|
|
|
|
|
|
|
|
try: |
|
|
VAR_MODEL = joblib.load(VAR_MODEL_PATH) |
|
|
print(f"Model VAR ({VAR_MODEL_PATH}) berhasil dimuat.") |
|
|
except Exception as e: |
|
|
print(f"GAGAL memuat model VAR: {e}. PASTIKAN PATH BENAR.") |
|
|
VAR_MODEL = None |
|
|
|
|
|
|
|
|
global WEATHER_MODEL |
|
|
try: |
|
|
WEATHER_MODEL = load_model("model_cuaca.h5") |
|
|
print("Model CUACA berhasil dimuat.") |
|
|
except Exception as e: |
|
|
WEATHER_MODEL = None |
|
|
print(f"GAGAL memuat model CUACA: {e}") |
|
|
|
|
|
|
|
|
|
|
|
print("--- SELESAI MEMUAT SEMUA ASET ---") |
|
|
|
|
|
|
|
|
load_all_assets() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app = Flask(__name__) |
|
|
|
|
|
@app.route('/') |
|
|
def home(): |
|
|
|
|
|
return render_template('index.html') |
|
|
|
|
|
@app.route('/crop') |
|
|
def crop(): |
|
|
|
|
|
return render_template('Crop.html', feature_names=FEATURE_NAMES_CROP, form_values={}) |
|
|
|
|
|
@app.route('/predict', methods=['POST']) |
|
|
def predict(): |
|
|
|
|
|
if CROP_MODEL is None or CROP_SCALER is None: |
|
|
return "Error: Model atau Scaler Crop Prediction tidak dimuat. Cek file .pkl Anda.", 500 |
|
|
|
|
|
try: |
|
|
data = request.form.to_dict() |
|
|
input_features = [] |
|
|
form_values = {} |
|
|
|
|
|
for name in FEATURE_NAMES_CROP: |
|
|
value = float(data[name]) |
|
|
input_features.append(value) |
|
|
form_values[name] = value |
|
|
|
|
|
input_df = pd.DataFrame([input_features], columns=FEATURE_NAMES_CROP) |
|
|
|
|
|
|
|
|
features_scaled = CROP_SCALER.transform(input_df) |
|
|
features_scaled = pd.DataFrame(features_scaled, columns=input_df.columns) |
|
|
|
|
|
prediction = CROP_MODEL.predict(features_scaled) |
|
|
|
|
|
translate = { |
|
|
'rice': 'padi', |
|
|
'maize': 'jagung', |
|
|
'jute': 'rami', |
|
|
'cotton': 'kapas', |
|
|
'coconut': 'kelapa', |
|
|
'papaya': 'pepaya', |
|
|
'orange': 'jeruk', |
|
|
'apple': 'apel', |
|
|
'muskmelon': 'blewah', |
|
|
'watermelon': 'semangka', |
|
|
'grapes': 'anggur', |
|
|
'mango': 'mangga', |
|
|
'banana': 'pisang', |
|
|
'pomegranate': 'delima', |
|
|
'lentil': 'lentil', |
|
|
'blackgram': 'kacang tunggak', |
|
|
'mungbean': 'kacang hijau', |
|
|
'mothbeans': 'kacang ngengat', |
|
|
'pigeonpeas': 'kacang gude', |
|
|
'kidneybeans': 'kacang merah', |
|
|
'chickpea': 'kacang arab', |
|
|
'coffee': 'kopi' |
|
|
} |
|
|
|
|
|
|
|
|
output = translate[prediction[0]] |
|
|
|
|
|
|
|
|
|
|
|
return render_template('Crop.html', |
|
|
prediction_text=f'{output}', |
|
|
feature_names=FEATURE_NAMES_CROP, |
|
|
form_values=form_values) |
|
|
|
|
|
except KeyError as e: |
|
|
|
|
|
return render_template('Crop.html', |
|
|
error_message=f'Error: Input untuk fitur {str(e)} hilang. Pastikan semua kolom terisi.', |
|
|
feature_names=FEATURE_NAMES_CROP, |
|
|
form_values=request.form.to_dict()), 400 |
|
|
except ValueError: |
|
|
|
|
|
return render_template('Crop.html', |
|
|
error_message='Error: Semua input harus berupa angka.', |
|
|
feature_names=FEATURE_NAMES_CROP, |
|
|
form_values=request.form.to_dict()), 400 |
|
|
except Exception as e: |
|
|
|
|
|
return render_template('Crop.html', |
|
|
error_message=f'Terjadi error tak terduga: {str(e)}', |
|
|
feature_names=FEATURE_NAMES_CROP, |
|
|
form_values=request.form.to_dict()), 500 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/forecast/<kabupaten>') |
|
|
def forecast(kabupaten): |
|
|
try: |
|
|
csv_map = { |
|
|
"Bangkalan": r"static/bangkalan.csv", |
|
|
"Sampang": r"static/sampang.csv", |
|
|
"Pamekasan": r"static/pamekasan.csv", |
|
|
"Sumenep": r"static/sumenep.csv" |
|
|
} |
|
|
|
|
|
model_path = r"static/var_model_multivariate.joblib" |
|
|
|
|
|
if kabupaten not in csv_map: |
|
|
return jsonify({"error": "Kabupaten tidak ditemukan"}), 404 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
df = pd.read_csv(csv_map[kabupaten]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
df['datetime'] = pd.to_datetime(df['datetime']) |
|
|
df.set_index('datetime', inplace=True) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var_model = joblib.load(model_path) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
df['daily_temp'] = df['temp'] |
|
|
df['daily_humidity_diff'] = df['humidity'].diff() |
|
|
df['daily_precipprob_diff'] = df['precipprob'].diff() |
|
|
df = df.dropna() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
model_features = [ |
|
|
'daily_temp', |
|
|
'daily_humidity_diff', |
|
|
'daily_precipprob_diff' |
|
|
] |
|
|
|
|
|
df = df[model_features] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
last_3_days = df.tail(3) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lag_order = var_model.k_ar |
|
|
forecast_values = var_model.forecast( |
|
|
y=df.values[-lag_order:], |
|
|
steps=5 |
|
|
) |
|
|
|
|
|
forecast_dates = [ |
|
|
df.index[-1] + pd.Timedelta(days=i) |
|
|
for i in range(1, 6) |
|
|
] |
|
|
|
|
|
forecast_df = pd.DataFrame( |
|
|
forecast_values, |
|
|
columns=df.columns, |
|
|
index=forecast_dates |
|
|
) |
|
|
|
|
|
return jsonify({ |
|
|
"last_days": { |
|
|
"dates": last_3_days.index.strftime('%Y-%m-%d').tolist(), |
|
|
"values": last_3_days.values.tolist() |
|
|
}, |
|
|
"forecast": { |
|
|
"dates": forecast_df.index.strftime('%Y-%m-%d').tolist(), |
|
|
"values": forecast_df.values.tolist() |
|
|
}, |
|
|
"columns": df.columns.tolist() |
|
|
}) |
|
|
|
|
|
except Exception as e: |
|
|
return jsonify({"error": str(e)}), 500 |
|
|
|
|
|
@app.route('/about') |
|
|
def about(): |
|
|
return render_template('about.html') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/clustering/<kabupaten>') |
|
|
def get_clustering_data(kabupaten): |
|
|
|
|
|
try: |
|
|
|
|
|
tgl = int(request.args.get("tgl", 1)) |
|
|
bln = int(request.args.get("bln", 1)) |
|
|
tahun = int(request.args.get("tahun", 2024)) |
|
|
|
|
|
kabupaten_lower = kabupaten.lower() |
|
|
kabupaten_title = kabupaten.title() |
|
|
|
|
|
if kabupaten_title not in CSV_MAP: |
|
|
return jsonify({"error": "Kabupaten tidak valid"}), 400 |
|
|
|
|
|
|
|
|
model_kmeans = LOADED_MODELS_CLUSTERING.get(kabupaten_lower) |
|
|
scaler = LOADED_SCALERS.get(kabupaten_lower) |
|
|
|
|
|
if model_kmeans is None: |
|
|
return jsonify({"error": f"Model clustering untuk {kabupaten_title} tidak ditemukan/gagal dimuat."}), 500 |
|
|
|
|
|
if scaler is None: |
|
|
return jsonify({"error": f"Scaler untuk {kabupaten_title} tidak ditemukan/gagal dimuat. Tidak dapat melakukan scaling."}), 500 |
|
|
|
|
|
|
|
|
|
|
|
try: |
|
|
df = pd.read_csv(CSV_MAP[kabupaten_title]) |
|
|
except FileNotFoundError: |
|
|
return jsonify({"error": f"Gagal memuat CSV: File data untuk {kabupaten_title} tidak ditemukan."}), 500 |
|
|
|
|
|
df["datetime"] = pd.to_datetime(df["datetime"]) |
|
|
|
|
|
|
|
|
selected = df[ |
|
|
(df["datetime"].dt.day == tgl) & |
|
|
(df["datetime"].dt.month == bln) & |
|
|
(df["datetime"].dt.year == tahun) |
|
|
] |
|
|
|
|
|
if selected.empty: |
|
|
return jsonify({"error": f"Data cuaca untuk {tgl}/{bln}/{tahun} di {kabupaten_title} tidak ditemukan"}), 404 |
|
|
|
|
|
|
|
|
fitur_yang_dipakai_model = [ |
|
|
"tempmax", |
|
|
"precipprob", |
|
|
"humidity", |
|
|
"solarradiation", |
|
|
"windspeed" |
|
|
] |
|
|
|
|
|
|
|
|
fitur_utama_raw = selected[fitur_yang_dipakai_model].values |
|
|
|
|
|
|
|
|
fitur_utama_scaled = scaler.transform(fitur_utama_raw) |
|
|
|
|
|
|
|
|
cluster = int(model_kmeans.predict(fitur_utama_scaled)[0]) |
|
|
|
|
|
return jsonify({ |
|
|
"kabupaten": kabupaten_title, |
|
|
"input_features_raw": selected[fitur_yang_dipakai_model].iloc[0].to_dict(), |
|
|
"feature_names": fitur_yang_dipakai_model, |
|
|
"predicted_cluster": cluster |
|
|
}) |
|
|
|
|
|
except KeyError as e: |
|
|
return jsonify({"error": f"Gagal memprediksi cluster: Kolom fitur hilang atau salah nama: {str(e)}. Pastikan 5 kolom fitur sudah benar."}), 500 |
|
|
except ValueError as e: |
|
|
return jsonify({"error": f"Gagal memprediksi cluster: Kesalahan nilai input atau format data: {str(e)}."}), 500 |
|
|
except Exception as e: |
|
|
return jsonify({"error": f"Gagal memprediksi cluster: Terjadi error tak terduga: {str(e)}"}), 500 |
|
|
|
|
|
@app.route("/cuaca", methods=["GET", "POST"]) |
|
|
def prediksi_cuaca(): |
|
|
if WEATHER_MODEL is None: |
|
|
return "Error: Model CUACA tidak dimuat. Pastikan file model_cuaca.h5 tersedia di folder proyek.", 500 |
|
|
|
|
|
if request.method == "POST": |
|
|
file = request.files.get("image") |
|
|
if not file: |
|
|
return render_template("cuaca.html", hasil=None, error="Tidak ada file yang diunggah.") |
|
|
|
|
|
save_path = os.path.join("static", file.filename) |
|
|
file.save(save_path) |
|
|
|
|
|
|
|
|
img = load_img(save_path, target_size=(100, 100)) |
|
|
img = img_to_array(img) / 255.0 |
|
|
img = np.expand_dims(img, axis=0) |
|
|
|
|
|
pred = WEATHER_MODEL.predict(img) |
|
|
label_index = np.argmax(pred) |
|
|
hasil = WEATHER_CLASSES[label_index] |
|
|
|
|
|
return render_template("cuaca.html", hasil=hasil, img=save_path) |
|
|
|
|
|
return render_template("cuaca.html", hasil=None) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
if __name__ == "__main__": |
|
|
app.run( |
|
|
host="0.0.0.0", |
|
|
port=7860, |
|
|
debug=True |
|
|
) |