293 lines
9.4 KiB
Python
293 lines
9.4 KiB
Python
from flask import Flask, request, jsonify
|
|
from sklearn.metrics import confusion_matrix, classification_report, accuracy_score
|
|
from flask_cors import CORS
|
|
from sklearn.neighbors import KNeighborsClassifier
|
|
import pandas as pd
|
|
import os
|
|
import mysql.connector
|
|
|
|
# Konfigurasi koneksi database MySQL
|
|
DB_CONFIG = {
|
|
'host': 'localhost',
|
|
'user': 'root',
|
|
'password': '',
|
|
'database': 'game_rating_db'
|
|
}
|
|
|
|
|
|
app = Flask(__name__)
|
|
CORS(app)
|
|
|
|
|
|
DATASET_FILE = os.path.join(os.path.dirname(__file__), "dataset.xlsx")
|
|
|
|
# Mapping angka ke kategori jawaban
|
|
ANSWER_MAPPING = {
|
|
0: "Tidak Mengandung",
|
|
1: "Sangat Sedikit Mengandung",
|
|
2: "Agak Mengandung",
|
|
3: "Cukup Mengandung",
|
|
4: "Banyak Mengandung",
|
|
5: "Sangat Banyak Mengandung"
|
|
}
|
|
|
|
REVERSE_MAPPING = {v: k for k, v in ANSWER_MAPPING.items()}
|
|
|
|
# Fungsi untuk membaca dataset
|
|
def load_dataset():
|
|
if os.path.exists(DATASET_FILE):
|
|
try:
|
|
data = pd.read_excel(DATASET_FILE)
|
|
|
|
if data.empty:
|
|
data = pd.DataFrame(columns=["Nama Game", "Kekerasan", "Bahasa Kasar", "Tema Seksual", "Darah/Gore", "Elemen Perjudian", "Rating"])
|
|
data.to_excel(DATASET_FILE, index=False)
|
|
except Exception as e:
|
|
print("Error saat membaca dataset:", e)
|
|
data = pd.DataFrame(columns=["Nama Game", "Kekerasan", "Bahasa Kasar", "Tema Seksual", "Darah/Gore", "Elemen Perjudian", "Rating"])
|
|
data.to_excel(DATASET_FILE, index=False)
|
|
else:
|
|
data = pd.DataFrame(columns=["Nama Game", "Kekerasan", "Bahasa Kasar", "Tema Seksual", "Darah/Gore", "Elemen Perjudian", "Rating"])
|
|
data.to_excel(DATASET_FILE, index=False)
|
|
|
|
print("Kolom dalam dataset:", data.columns.tolist())
|
|
|
|
required_columns = ["Nama Game", "Kekerasan", "Bahasa Kasar", "Tema Seksual", "Darah/Gore", "Elemen Perjudian", "Rating"]
|
|
for col in required_columns:
|
|
if col not in data.columns:
|
|
raise ValueError(f"Kolom '{col}' tidak ditemukan dalam dataset!")
|
|
|
|
X = data.iloc[:, 1:-1].values
|
|
y = data.iloc[:, -1].values
|
|
|
|
return X, y, data
|
|
|
|
# Fungsi untuk menambahkan data ke dataset
|
|
def add_to_dataset(name, features, rating):
|
|
data = pd.read_excel(DATASET_FILE)
|
|
|
|
if name not in data['Nama Game'].values:
|
|
new_row = [name] + features + [rating]
|
|
data.loc[len(data)] = new_row
|
|
data.to_excel(DATASET_FILE, index=False)
|
|
|
|
# Fungsi untuk menyimpan ke database MySQL
|
|
def save_rating_to_db(game_name, rating):
|
|
kategori_map = {
|
|
"SU": "Semua Umur",
|
|
"3+": "Untuk usia 3 tahun ke atas",
|
|
"7+": "Untuk usia 7 tahun ke atas",
|
|
"13+": "Untuk usia 13 tahun ke atas",
|
|
"18+": "Untuk usia 18 tahun ke atas"
|
|
}
|
|
kategori = kategori_map.get(rating, "Tidak diketahui")
|
|
|
|
try:
|
|
conn = mysql.connector.connect(**DB_CONFIG)
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute("""
|
|
CREATE TABLE IF NOT EXISTS riwayat (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
nama_game VARCHAR(255),
|
|
rating VARCHAR(10),
|
|
kategori VARCHAR(100)
|
|
)
|
|
""")
|
|
|
|
cursor.execute("""
|
|
INSERT INTO riwayat (nama_game, rating, kategori)
|
|
VALUES (%s, %s, %s)
|
|
""", (game_name, rating, kategori))
|
|
|
|
conn.commit()
|
|
cursor.close()
|
|
conn.close()
|
|
except mysql.connector.Error as err:
|
|
print("ERROR saat menyimpan ke database:", err)
|
|
|
|
|
|
# Endpoint untuk mengecek apakah game sudah ada dalam dataset
|
|
@app.route('/check_game', methods=['POST'])
|
|
def check_game():
|
|
try:
|
|
data = request.json
|
|
game_name = data['game_name']
|
|
|
|
_, _, dataset = load_dataset()
|
|
|
|
if game_name in dataset["Nama Game"].values:
|
|
rating = dataset.loc[dataset["Nama Game"] == game_name, "Rating"].values[0]
|
|
return jsonify({'exists': True, 'rating': rating})
|
|
|
|
return jsonify({'exists': False})
|
|
except Exception as e:
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
# Endpoint untuk menerima jawaban dan memberikan rating
|
|
@app.route('/submit_answers', methods=['POST'])
|
|
def submit_answers():
|
|
try:
|
|
data = request.json
|
|
print("=== DATA DITERIMA DARI FRONTEND ===")
|
|
print(data)
|
|
|
|
game_name = data.get('game_name', '').strip()
|
|
answers = data.get('answers', [])
|
|
|
|
if not game_name or not answers:
|
|
print("ERROR: Data tidak lengkap")
|
|
return jsonify({"error": "Data tidak lengkap"}), 400
|
|
|
|
print("Nama Game:", game_name)
|
|
print("Jawaban:", answers)
|
|
|
|
# Cek apakah jawaban valid
|
|
features = []
|
|
for answer in answers:
|
|
if answer not in REVERSE_MAPPING:
|
|
print(f"ERROR: Jawaban tidak valid -> {answer}")
|
|
return jsonify({"error": f"Jawaban tidak valid: {answer}"}), 400
|
|
features.append(REVERSE_MAPPING[answer])
|
|
|
|
print("Features (dikonversi ke angka):", features)
|
|
|
|
|
|
X, y, dataset = load_dataset()
|
|
|
|
# Jika game sudah ada di dataset, kembalikan rating yang sudah ada
|
|
if game_name in dataset["Nama Game"].values:
|
|
existing_rating = dataset.loc[dataset["Nama Game"] == game_name, "Rating"].values[0]
|
|
print("Game ditemukan dalam dataset. Rating:", existing_rating)
|
|
return jsonify({'rating': existing_rating})
|
|
|
|
# Prediksi rating dengan KNN jika dataset cukup besar
|
|
if len(X) > 3:
|
|
knn = KNeighborsClassifier(n_neighbors=5)
|
|
knn.fit(X, y)
|
|
predicted_rating = knn.predict([features])[0]
|
|
else:
|
|
total_score = sum(features)
|
|
if total_score <= 5:
|
|
predicted_rating = "SU"
|
|
elif total_score <= 10:
|
|
predicted_rating = "3+"
|
|
elif total_score <= 15:
|
|
predicted_rating = "7+"
|
|
elif total_score <= 20:
|
|
predicted_rating = "13+"
|
|
else:
|
|
predicted_rating = "18+"
|
|
|
|
print("Prediksi Rating:", predicted_rating)
|
|
|
|
add_to_dataset(game_name, features, predicted_rating)
|
|
save_rating_to_db(game_name, predicted_rating)
|
|
return jsonify({'rating': predicted_rating})
|
|
|
|
except Exception as e:
|
|
print("ERROR di backend:", str(e))
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
@app.route('/autocomplete', methods=['GET'])
|
|
def autocomplete():
|
|
try:
|
|
query = request.args.get('query', '').strip().lower()
|
|
|
|
if not query:
|
|
return jsonify([])
|
|
|
|
_, _, dataset = load_dataset()
|
|
|
|
# Mencari game yang mengandung teks yang diketik pengguna
|
|
suggestions = dataset[dataset["Nama Game"].str.lower().str.contains(query, na=False)]["Nama Game"].tolist()
|
|
|
|
return jsonify(suggestions)
|
|
except Exception as e:
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
|
|
@app.route('/latest_games', methods=['GET'])
|
|
def latest_games():
|
|
_, _, dataset = load_dataset()
|
|
|
|
latest_games = dataset["Nama Game"].dropna().values[-4:].tolist()
|
|
|
|
return jsonify(latest_games)
|
|
|
|
@app.route('/all_games', methods=['GET'])
|
|
def all_games():
|
|
try:
|
|
_, _, dataset = load_dataset()
|
|
|
|
if "Nama Game" in dataset.columns and "Rating" in dataset.columns:
|
|
games = dataset[["Nama Game", "Rating"]].to_dict(orient="records")
|
|
return jsonify(games)
|
|
|
|
return jsonify({"error": "Kolom Nama Game atau Rating tidak ditemukan"}), 500
|
|
except Exception as e:
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
# @app.route('/evaluate_model', methods=['GET'])
|
|
# def evaluate():
|
|
# try:
|
|
# X, y, _ = load_dataset()
|
|
|
|
# if len(X) < 2:
|
|
# return jsonify({"error": "Dataset terlalu kecil untuk evaluasi"}), 400
|
|
|
|
# knn = KNeighborsClassifier(n_neighbors=5)
|
|
# knn.fit(X, y)
|
|
# y_pred = knn.predict(X)
|
|
|
|
# labels = sorted(list(set(y)))
|
|
# cm = confusion_matrix(y, y_pred, labels=labels)
|
|
# accuracy = accuracy_score(y, y_pred)
|
|
|
|
# # Hitung metrik per kelas secara manual
|
|
# results = {}
|
|
# total_TP = total_FP = total_FN = total_TN = 0
|
|
|
|
# for i, label in enumerate(labels):
|
|
# TP = cm[i][i]
|
|
# FN = sum(cm[i]) - TP
|
|
# FP = sum(cm[:, i]) - TP
|
|
# TN = cm.sum() - (TP + FN + FP)
|
|
|
|
# recall = TP / (TP + FN) if (TP + FN) else 0
|
|
# precision = TP / (TP + FP) if (TP + FP) else 0
|
|
|
|
# results[label] = {
|
|
# "TP": int(TP),
|
|
# "FN": int(FN),
|
|
# "FP": int(FP),
|
|
# "TN": int(TN),
|
|
# "recall": round(float(recall), 4),
|
|
# "recall_formula": f"{int(TP)} / ({int(TP)} + {int(FN)})",
|
|
# "precision": round(float(precision), 4),
|
|
# "precision_formula": f"{int(TP)} / ({int(TP)} + {int(FP)})",
|
|
# }
|
|
|
|
|
|
# total_TP += TP
|
|
# total_FP += FP
|
|
# total_FN += FN
|
|
# total_TN += TN
|
|
|
|
# acc_formula = f"({total_TP} + {total_TN}) / ({total_TP} + {total_TN} + {total_FP} + {total_FN})"
|
|
# acc_value = (total_TP + total_TN) / (total_TP + total_TN + total_FP + total_FN)
|
|
|
|
# return jsonify({
|
|
# "accuracy": round(float(acc_value), 4),
|
|
# "accuracy_formula": acc_formula,
|
|
|
|
# "per_class_metrics": results
|
|
# })
|
|
# except Exception as e:
|
|
# return jsonify({"error": str(e)}), 500
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
app.run(debug=True)
|