This commit is contained in:
mhilya 2026-07-13 11:34:00 +07:00
parent 2f52a2e70c
commit 3aa8b81285
17 changed files with 471 additions and 57 deletions

Binary file not shown.

32
fastapi/extract.py Normal file
View File

@ -0,0 +1,32 @@
import joblib
import numpy as np
# Load model
model = joblib.load('ml_assets/ml_pipeline_internal.pkl')
# Ekstrak komponen
vec = model.named_steps['tfidf']
clf = model.named_steps['clf']
# Dapatkan vocabulary
feature_names = vec.get_feature_names_out()
# Dapatkan bobot koefisien
coefficients = clf.coef_
intercepts = clf.intercept_
# Tampilkan bobot untuk kata-kata di korpus mini
target_words = ['web', 'develop', 'analyst']
print("BOBOT KOEFISIEN RIIL:")
print("=" * 60)
for word in target_words:
if word in feature_names:
idx = list(feature_names).index(word)
print(f"\nKata: '{word}'")
for i, cls in enumerate(clf.classes_):
print(f" Kelas {cls}: {coefficients[i][idx]:.4f}")
print("\nINTERSEP (Bias):")
for i, cls in enumerate(clf.classes_):
print(f" Kelas {cls}: {intercepts[i]:.4f}")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 114 KiB

View File

@ -1 +0,0 @@
{"status": "error", "message": "Processing failed: 'charmap' codec can't encode character '\\U0001f602' in position 885944: character maps to <undefined>", "processed_rows": 0, "total_rows": 0, "source_type": "unknown"}

View File

@ -1 +0,0 @@
{"status": "error", "message": "Processing failed: 'charmap' codec can't encode character '\\U0001f602' in position 885944: character maps to <undefined>", "processed_rows": 0, "total_rows": 0, "source_type": "unknown"}

View File

@ -1 +0,0 @@
{"status": "error", "message": "Processing failed: 'charmap' codec can't encode character '\\U0001f602' in position 885944: character maps to <undefined>", "processed_rows": 0, "total_rows": 0, "source_type": "unknown"}

File diff suppressed because one or more lines are too long

View File

@ -1,29 +0,0 @@
{"timestamp": "2026-06-05T19:08:42.267450", "level": "INFO", "logger": "retrain_worker", "message": "[init] Log file tersimpan di: D:\\msaif\\Project\\Laravel\\sista_mif_ta\\fastapi\\ml_assets\\logs\\retrain_log_20260605_190842.jsonl"}
{"timestamp": "2026-06-05T19:08:42.267450", "level": "INFO", "logger": "retrain_worker", "message": "Retraining session dimulai. Log file: retrain_log_20260605_190842.jsonl", "stage": "started", "event_type": "session_start", "metadata": {"extra_csv_provided": true}}
{"timestamp": "2026-06-05T19:08:42.268452", "level": "INFO", "logger": "retrain_worker", "message": "[started] Worker dimulai", "stage": "started"}
{"timestamp": "2026-06-05T19:08:42.269454", "level": "INFO", "logger": "retrain_worker", "message": "[backup] Membuat backup model lama...", "stage": "backup"}
{"timestamp": "2026-06-05T19:08:42.271473", "level": "INFO", "logger": "retrain_worker", "message": "Backup tersimpan: D:\\msaif\\Project\\Laravel\\sista_mif_ta\\fastapi\\ml_assets\\ml_pipeline_internal.pkl.bak"}
{"timestamp": "2026-06-05T19:08:42.271473", "level": "INFO", "logger": "retrain_worker", "message": "[loading_data] Memuat dan menggabungkan data training...", "stage": "loading_data"}
{"timestamp": "2026-06-05T19:08:42.275472", "level": "INFO", "logger": "retrain_worker", "message": "Corpus asli: 250 baris"}
{"timestamp": "2026-06-05T19:08:42.445530", "level": "INFO", "logger": "retrain_worker", "message": "Data manual_override: 1 baris | weight=6.6648x (log-damped) | influence aktual=2.6% (target nominal 30%)"}
{"timestamp": "2026-06-05T19:08:42.465047", "level": "INFO", "logger": "retrain_worker", "message": "Total setelah merge (tanpa deduplicate): 251 baris"}
{"timestamp": "2026-06-05T19:08:42.465047", "level": "INFO", "logger": "retrain_worker", "message": "Data training siap: 251 baris", "stage": "loading_data", "event_type": "data_merged", "metadata": {"corpus_rows": 250, "override_rows": 1, "override_weight": 6.6648, "influence_pct": 2.6, "total_merged": 251}}
{"timestamp": "2026-06-05T19:08:42.479066", "level": "INFO", "logger": "retrain_worker", "message": "Dynamic split: 175 train | 76 test"}
{"timestamp": "2026-06-05T19:08:42.480074", "level": "INFO", "logger": "retrain_worker", "message": "[evaluating] Mengevaluasi model lama pada test set baru...", "stage": "evaluating"}
{"timestamp": "2026-06-05T19:08:42.492120", "level": "INFO", "logger": "retrain_worker", "message": "Baseline F1 (old model, same test set): 0.9059"}
{"timestamp": "2026-06-05T19:08:42.494119", "level": "INFO", "logger": "retrain_worker", "message": "[training] K-Fold validation & training... (175 sampel)", "stage": "training"}
{"timestamp": "2026-06-05T19:08:42.534414", "level": "INFO", "logger": "retrain_worker", "message": "[training] K-Fold selesai: fold 1/5 | acc=0.5143", "stage": "training"}
{"timestamp": "2026-06-05T19:08:42.542922", "level": "INFO", "logger": "retrain_worker", "message": "[training] K-Fold selesai: fold 2/5 | acc=0.6857", "stage": "training"}
{"timestamp": "2026-06-05T19:08:42.552262", "level": "INFO", "logger": "retrain_worker", "message": "[training] K-Fold selesai: fold 3/5 | acc=0.5429", "stage": "training"}
{"timestamp": "2026-06-05T19:08:42.561273", "level": "INFO", "logger": "retrain_worker", "message": "[training] K-Fold selesai: fold 4/5 | acc=0.5714", "stage": "training"}
{"timestamp": "2026-06-05T19:08:42.569782", "level": "INFO", "logger": "retrain_worker", "message": "[training] K-Fold selesai: fold 5/5 | acc=0.7143", "stage": "training"}
{"timestamp": "2026-06-05T19:08:42.569782", "level": "INFO", "logger": "retrain_worker", "message": "K-Fold 5 lipatan selesai. Akurasi per fold: [0.5143, 0.6857, 0.5429, 0.5714, 0.7143]", "stage": "training", "event_type": "kfold_completed", "metadata": {"fold_accuracies": [0.5143, 0.6857, 0.5429, 0.5714, 0.7143], "mean_accuracy": 0.6057, "std_accuracy": 0.0796}}
{"timestamp": "2026-06-05T19:08:42.570788", "level": "INFO", "logger": "retrain_worker", "message": "[training] Training model final pada seluruh data training...", "stage": "training"}
{"timestamp": "2026-06-05T19:08:42.579235", "level": "INFO", "logger": "retrain_worker", "message": "Candidate model tersimpan: D:\\msaif\\Project\\Laravel\\sista_mif_ta\\fastapi\\ml_assets\\ml_pipeline_candidate.pkl"}
{"timestamp": "2026-06-05T19:08:42.589948", "level": "INFO", "logger": "retrain_worker", "message": "[evaluating] Mengevaluasi model baru vs model lama (test set sama)...", "stage": "evaluating"}
{"timestamp": "2026-06-05T19:08:42.593983", "level": "INFO", "logger": "retrain_worker", "message": "Baseline F1 (old model, same test) : 0.9059"}
{"timestamp": "2026-06-05T19:08:42.593983", "level": "INFO", "logger": "retrain_worker", "message": "Candidate F1 (new model, same test): 0.7011 (delta: -0.2048)"}
{"timestamp": "2026-06-05T19:08:42.593983", "level": "INFO", "logger": "retrain_worker", "message": "Evaluasi selesai. Delta F1: -0.2048 (rollback)", "stage": "evaluating", "event_type": "evaluation_completed", "metadata": {"baseline_f1": 0.9059, "candidate_f1": 0.7011, "delta": -0.2048, "delta_pct": -20.48, "threshold_min": 0.01, "decision": "rollback", "reason": "Model baru lebih buruk secara signifikan (-20.48% weighted F1)", "new_accuracy": 0.6711, "per_class_f1": {"Programmer": 0.8, "Data Analyst": 0.19, "Wirausaha Informatika": 0.474, "Non-IT": 0.897}}}
{"timestamp": "2026-06-05T19:08:42.593983", "level": "INFO", "logger": "retrain_worker", "message": "Rollback berhasil: pkl lama dipulihkan dari .bak"}
{"timestamp": "2026-06-05T19:08:42.593983", "level": "INFO", "logger": "retrain_worker", "message": "Model lama dipertahankan. Model baru lebih buruk secara signifikan (-20.48% weighted F1)", "stage": "rolled_back", "event_type": "rollback", "metadata": {"reason": "Model baru lebih buruk secara signifikan (-20.48% weighted F1)", "old_f1": 0.9059, "new_f1": 0.7011}}
{"timestamp": "2026-06-05T19:08:42.594976", "level": "INFO", "logger": "retrain_worker", "message": "[rolled_back] Model lama dipertahankan. Model baru lebih buruk secara signifikan (-20.48% weighted F1)", "stage": "rolled_back"}

View File

@ -1,10 +0,0 @@
{
"stage": "rolled_back",
"message": "Model lama dipertahankan. Model baru lebih buruk secara signifikan (-20.44% weighted F1)",
"timestamp": "2026-06-08T11:35:03.809787",
"result": "rolled_back",
"reason": "Model baru lebih buruk secara signifikan (-20.44% weighted F1)",
"old_f1": 0.9064,
"new_f1": 0.702,
"_reloaded": true
}

409
fastapi/test.py Normal file
View File

@ -0,0 +1,409 @@
import pandas as pd
import numpy as np
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
import math
# Buat workbook
wb = Workbook()
# ============================================
# SHEET 1: KORPUS & TOKENISASI
# ============================================
ws1 = wb.active
ws1.title = "1. Korpus & Tokenisasi"
# Header
ws1['A1'] = "DOKUMEN"
ws1['B1'] = "TEKS ASLI"
ws1['C1'] = "TEKS SETEMAH STEMMING (Sastrawi)"
ws1['D1'] = "KETERANGAN"
# Styling header
header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
header_font = Font(bold=True, color="FFFFFF", size=11)
for cell in ws1[1]:
cell.fill = header_fill
cell.font = header_font
cell.alignment = Alignment(horizontal="center", vertical="center")
# Data
data_korpus = [
["D1", "web developer", "web develop", "Stemming: developer → develop"],
["D2", "web analyst", "web analyst", "Tidak berubah"],
["D3", "web developer analyst", "web develop analyst", "Stemming: developer → develop"]
]
for i, row in enumerate(data_korpus, start=2):
for j, val in enumerate(row, start=1):
ws1.cell(row=i, column=j, value=val)
# Catatan penting
ws1['A6'] = "CATATAN PENTING:"
ws1['A6'].font = Font(bold=True, color="FF0000")
ws1['A7'] = "1. Korpus ini dirancang agar setiap kata muncul minimal 2x (kompatibel dengan min_df=2)"
ws1['A8'] = "2. Stemming Sastrawi mengubah 'developer' menjadi 'develop'"
ws1['A9'] = "3. Total dokumen N = 3"
# Set column width
ws1.column_dimensions['A'].width = 12
ws1.column_dimensions['B'].width = 25
ws1.column_dimensions['C'].width = 30
ws1.column_dimensions['D'].width = 40
# ============================================
# SHEET 2: MATRIKS TF & SUBLINEAR TF
# ============================================
ws2 = wb.create_sheet("2. Matriks TF")
# Matriks TF Mentah
ws2['A1'] = "MATRIKS TF MENTAH (Frekuensi Term)"
ws2['A1'].font = Font(bold=True, size=12)
ws2['A2'] = "Dokumen"
ws2['B2'] = "web"
ws2['C2'] = "develop"
ws2['D2'] = "analyst"
for cell in ws2[2]:
cell.fill = header_fill
cell.font = header_font
cell.alignment = Alignment(horizontal="center")
# Data TF
tf_data = [
["D1", 1, 1, 0],
["D2", 1, 0, 1],
["D3", 1, 1, 1]
]
for i, row in enumerate(tf_data, start=3):
for j, val in enumerate(row, start=1):
ws2.cell(row=i, column=j, value=val)
# Sublinear TF
ws2['A7'] = "SUBLINEAR TF (1 + ln(tf))"
ws2['A7'].font = Font(bold=True, size=12)
ws2['A8'] = "Dokumen"
ws2['B8'] = "web"
ws2['C8'] = "develop"
ws2['D8'] = "analyst"
for cell in ws2[8]:
cell.fill = PatternFill(start_color="70AD47", end_color="70AD47", fill_type="solid")
cell.font = header_font
cell.alignment = Alignment(horizontal="center")
# Hitung Sublinear TF
sublinear_data = [
["D1", 1 + math.log(1), 1 + math.log(1), 0],
["D2", 1 + math.log(1), 0, 1 + math.log(1)],
["D3", 1 + math.log(1), 1 + math.log(1), 1 + math.log(1)]
]
for i, row in enumerate(sublinear_data, start=9):
for j, val in enumerate(row, start=1):
if j == 1:
ws2.cell(row=i, column=j, value=val)
else:
ws2.cell(row=i, column=j, value=round(val, 4))
ws2['A13'] = "CATATAN:"
ws2['A13'].font = Font(bold=True)
ws2['A14'] = "Karena semua TF bernilai 0 atau 1, dan ln(1) = 0, maka Sublinear TF = TF mentah"
# Set column width
for col in ['A', 'B', 'C', 'D']:
ws2.column_dimensions[col].width = 15
# ============================================
# SHEET 3: PERHITUNGAN DF & SMOOTH IDF
# ============================================
ws3 = wb.create_sheet("3. Smooth IDF")
ws3['A1'] = "PERHITUNGAN DOCUMENT FREQUENCY (DF) & SMOOTH IDF"
ws3['A1'].font = Font(bold=True, size=12)
ws3['A3'] = "Kata (Vocabulary)"
ws3['B3'] = "DF (Jumlah Dokumen)"
ws3['C3'] = "Rumus Smooth IDF Scikit-Learn"
ws3['D3'] = "Nilai IDF"
for cell in ws3[3]:
cell.fill = header_fill
cell.font = header_font
cell.alignment = Alignment(horizontal="center")
# Data DF dan IDF
N = 3
df_data = [
["web", 3],
["develop", 2],
["analyst", 2]
]
for i, (word, df) in enumerate(df_data, start=4):
ws3.cell(row=i, column=1, value=word)
ws3.cell(row=i, column=2, value=df)
# Rumus: ln((1+N)/(1+df)) + 1
rumus = f"=LN((1+{N})/(1+{df}))+1"
ws3.cell(row=i, column=3, value=rumus)
# Hitung nilai
idf_value = math.log((1 + N) / (1 + df)) + 1
ws3.cell(row=i, column=4, value=round(idf_value, 4))
ws3.cell(row=i, column=4).fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid")
ws3['A8'] = "CATATAN:"
ws3['A8'].font = Font(bold=True)
ws3['A9'] = f"Total Dokumen N = {N}"
ws3['A10'] = "Rumus Smooth IDF: ln((1+N)/(1+df)) + 1"
ws3['A11'] = "Smooth IDF mencegah pembagian dengan nol dan menstabilkan bobot untuk term langka"
# Highlight contoh perhitungan
ws3['A13'] = "CONTOH PERHITUNGAN MANUAL untuk kata 'web':"
ws3['A13'].font = Font(bold=True, color="FF0000")
ws3['A14'] = f"IDF = ln((1+3)/(1+3)) + 1 = ln(4/4) + 1 = ln(1) + 1 = 0 + 1 = 1.0000"
ws3['A15'] = f"IDF = ln((1+3)/(1+2)) + 1 = ln(4/3) + 1 = 0.2877 + 1 = 1.2877"
for col in ['A', 'B', 'C', 'D']:
ws3.column_dimensions[col].width = 30
# ============================================
# SHEET 4: MATRIKS TF-IDF & NORMALISASI L2
# ============================================
ws4 = wb.create_sheet("4. TF-IDF & L2 Norm")
ws4['A1'] = "MATRIKS TF-IDF MENTAH (Sublinear TF × IDF)"
ws4['A1'].font = Font(bold=True, size=12)
ws4['A3'] = "Dokumen"
ws4['B3'] = "web"
ws4['C3'] = "develop"
ws4['D3'] = "analyst"
for cell in ws4[3]:
cell.fill = header_fill
cell.font = header_font
cell.alignment = Alignment(horizontal="center")
# Hitung TF-IDF mentah
idf_values = {
"web": 1.0000,
"develop": 1.2877,
"analyst": 1.2877
}
tfidf_data = [
["D1", 1 * idf_values["web"], 1 * idf_values["develop"], 0],
["D2", 1 * idf_values["web"], 0, 1 * idf_values["analyst"]],
["D3", 1 * idf_values["web"], 1 * idf_values["develop"], 1 * idf_values["analyst"]]
]
for i, row in enumerate(tfidf_data, start=4):
for j, val in enumerate(row, start=1):
if j == 1:
ws4.cell(row=i, column=j, value=val)
else:
ws4.cell(row=i, column=j, value=round(val, 4))
# L2 Norm
ws4['A8'] = "L2 NORM (Panjang Vektor per Dokumen)"
ws4['A8'].font = Font(bold=True, size=12)
ws4['A10'] = "Dokumen"
ws4['B10'] = "L2 Norm = √(x₁² + x₂² + x₃²)"
for cell in ws4[10]:
cell.fill = PatternFill(start_color="70AD47", end_color="70AD47", fill_type="solid")
cell.font = header_font
cell.alignment = Alignment(horizontal="center")
# Hitung L2 Norm
l2_norms = []
for row in tfidf_data:
values = [v for v in row[1:] if isinstance(v, (int, float))]
l2 = math.sqrt(sum(v**2 for v in values))
l2_norms.append(l2)
ws4.cell(row=11 + tfidf_data.index(row), column=1, value=row[0])
ws4.cell(row=11 + tfidf_data.index(row), column=2, value=round(l2, 4))
ws4.cell(row=11 + tfidf_data.index(row), column=2).fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid")
# Vektor Fitur Akhir (Ternormalisasi L2)
ws4['A15'] = "VEKTOR FITUR AKHIR (TF-IDF Ternormalisasi L2)"
ws4['A15'].font = Font(bold=True, size=12, color="FF0000")
ws4['A17'] = "Dokumen"
ws4['B17'] = "web"
ws4['C17'] = "develop"
ws4['D17'] = "analyst"
for cell in ws4[17]:
cell.fill = PatternFill(start_color="FF0000", end_color="FF0000", fill_type="solid")
cell.font = Font(bold=True, color="FFFFFF")
cell.alignment = Alignment(horizontal="center")
# Hitung vektor ternormalisasi
for i, (row, l2) in enumerate(zip(tfidf_data, l2_norms), start=18):
ws4.cell(row=i, column=1, value=row[0])
for j, val in enumerate(row[1:], start=2):
if isinstance(val, (int, float)) and val > 0:
normalized = val / l2
ws4.cell(row=i, column=j, value=round(normalized, 4))
else:
ws4.cell(row=i, column=j, value=0)
ws4['A22'] = "CATATAN:"
ws4['A22'].font = Font(bold=True)
ws4['A23'] = "Normalisasi L2 memastikan panjang vektor = 1 (standar Scikit-Learn)"
ws4['A24'] = "Vektor ini yang akan dikalikan dengan bobot koefisien model Logistic Regression"
for col in ['A', 'B', 'C', 'D']:
ws4.column_dimensions[col].width = 20
# ============================================
# SHEET 5: SIMULASI INFERENSI LOGISTIC REGRESSION
# ============================================
ws5 = wb.create_sheet("5. Inferensi Manual")
ws5['A1'] = "SIMULASI FORWARD PASS LOGISTIC REGRESSION"
ws5['A1'].font = Font(bold=True, size=12)
ws5['A3'] = "Dokumen Uji: D1 ('web develop')"
ws5['A3'].font = Font(bold=True, color="FF0000")
# Vektor fitur dari Sheet 4
ws5['A5'] = "VEKTOR FITUR (x)"
ws5['A5'].font = Font(bold=True)
ws5['A6'] = "Fitur"
ws5['B6'] = "Nilai (x)"
for cell in ws5[6]:
cell.fill = header_fill
cell.font = header_font
# Ambil vektor D1 yang sudah dinormalisasi
vec_d1 = {
"web": round(1.0000 / l2_norms[0], 4),
"develop": round(1 * idf_values["develop"] / l2_norms[0], 4),
"analyst": 0
}
features = ["web", "develop", "analyst"]
for i, feat in enumerate(features, start=7):
ws5.cell(row=i, column=1, value=feat)
ws5.cell(row=i, column=2, value=vec_d1[feat])
# Bobot koefisien (simulasi berdasarkan output model)
ws5['A11'] = "BOBOT KOEFISIEN MODEL (w)"
ws5['A11'].font = Font(bold=True)
ws5['A12'] = "Fitur"
ws5['B12'] = "Kelas Programmer"
ws5['C12'] = "Kelas Non-IT"
ws5['D12'] = "Kelas Data Analyst"
ws5['E12'] = "Kelas Wirausaha"
for cell in ws5[12]:
cell.fill = PatternFill(start_color="70AD47", end_color="70AD47", fill_type="solid")
cell.font = header_font
cell.alignment = Alignment(horizontal="center")
# Bobot koefisien (disesuaikan dengan output model)
weights = {
"web": [0.7727, -0.5, -0.3, 0.2],
"develop": [1.2192, -0.4, -0.2464, 0.1],
"analyst": [-1.0188, -0.3, 2.8400, -0.6561]
}
for i, feat in enumerate(features, start=13):
ws5.cell(row=i, column=1, value=feat)
for j, w in enumerate(weights[feat], start=2):
ws5.cell(row=i, column=j, value=w)
# Intersep (bias)
ws5['A17'] = "Intersep (b)"
ws5['B17'] = -0.5
ws5['C17'] = -0.895
ws5['D17'] = -1.2
ws5['E17'] = -0.45
for cell in ws5[17]:
cell.fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid")
# Hitung skor linear z = w^T * x + b
ws5['A19'] = "SKOR LINEAR (z = w^T × x + b)"
ws5['A19'].font = Font(bold=True, size=12)
ws5['A20'] = "Kelas"
ws5['B20'] = "Skor (z)"
for cell in ws5[20]:
cell.fill = header_fill
cell.font = header_font
classes = ["Programmer", "Non-IT", "Data Analyst", "Wirausaha"]
z_scores = []
for i, cls in enumerate(classes, start=21):
ws5.cell(row=i, column=1, value=cls)
# Hitung z = sum(w * x) + b
z = 0
for feat in features:
z += weights[feat][i-21] * vec_d1[feat]
z += [-0.5, -0.895, -1.2, -0.45][i-21]
z_scores.append(z)
ws5.cell(row=i, column=2, value=round(z, 4))
# Hitung Softmax
ws5['A26'] = "PERHITUNGAN SOFTMAX (Probabilitas Posterior)"
ws5['A26'].font = Font(bold=True, size=12)
ws5['A27'] = "Kelas"
ws5['B27'] = "z"
ws5['C27'] = "e^z"
ws5['D27'] = "Probabilitas"
for cell in ws5[27]:
cell.fill = PatternFill(start_color="FF0000", end_color="FF0000", fill_type="solid")
cell.font = Font(bold=True, color="FFFFFF")
cell.alignment = Alignment(horizontal="center")
# Hitung e^z
exp_z = [math.exp(z) for z in z_scores]
sum_exp_z = sum(exp_z)
for i, (cls, z, e_z) in enumerate(zip(classes, z_scores, exp_z), start=28):
ws5.cell(row=i, column=1, value=cls)
ws5.cell(row=i, column=2, value=round(z, 4))
ws5.cell(row=i, column=3, value=round(e_z, 4))
prob = e_z / sum_exp_z
ws5.cell(row=i, column=4, value=f"{prob:.4f} ({prob*100:.1f}%)")
ws5.cell(row=i, column=4).fill = PatternFill(start_color="00FF00", end_color="00FF00", fill_type="solid")
ws5['A33'] = f"Total Penyebut (Σe^z): {sum_exp_z:.4f}"
ws5['A33'].font = Font(bold=True)
ws5['A35'] = "KESIMPULAN:"
ws5['A35'].font = Font(bold=True, color="FF0000", size=12)
ws5['A36'] = f"Probabilitas tertinggi: {max(exp_z)/sum_exp_z*100:.1f}% untuk kelas {classes[exp_z.index(max(exp_z))]}"
ws5['A37'] = "Karena probabilitas > 0.50 (threshold), status = auto_classified"
for col in ['A', 'B', 'C', 'D', 'E']:
ws5.column_dimensions[col].width = 18
# Simpan file
filename = "Perhitungan_Manual_TFIDF_LogReg.xlsx"
wb.save(filename)
print(f"File Excel berhasil dibuat: {filename}")
print(f"\nSheet yang dibuat:")
for sheet in wb.sheetnames:
print(f" - {sheet}")

View File

@ -15,7 +15,7 @@ class AdminUserSeeder extends Seeder
public function run() public function run()
{ {
User::updateOrCreate( User::updateOrCreate(
['email' => 'test@example.com'], ['email' => 'admin@gmail.com'],
[ [
'name' => 'Admin', 'name' => 'Admin',
'password' => Hash::make('admin1234'), 'password' => Hash::make('admin1234'),

View File

@ -644,7 +644,7 @@ class="inline-flex items-center gap-2 px-6 py-3 rounded-xl text-sm font-semibold
type: 'doughnut', type: 'doughnut',
data: { data: {
labels: Object.keys(chartData.profile), labels: Object.keys(chartData.profile),
datasets: [{ data: Object.values(chartData.profile), backgroundColor: ['#3b82f6', '#10b981', '#f59e0b', '#64748b'] }] datasets: [{ data: Object.values(chartData.profile), backgroundColor: ['#2563EB', '#DC2626', '#F59E0B', '#7C3AED', '#DB2777'] }]
}, },
options: pieOptions options: pieOptions
}); });
@ -653,7 +653,7 @@ class="inline-flex items-center gap-2 px-6 py-3 rounded-xl text-sm font-semibold
type: 'pie', type: 'pie',
data: { data: {
labels: Object.keys(chartData.method), labels: Object.keys(chartData.method),
datasets: [{ data: Object.values(chartData.method), backgroundColor: ['#10b981', '#3b82f6', '#f59e0b'] }] datasets: [{ data: Object.values(chartData.method), backgroundColor: ['#845EC2', '#D65DB1', '#FF9671', '#FFC75F'] }]
}, },
options: pieOptions options: pieOptions
}); });
@ -662,7 +662,7 @@ class="inline-flex items-center gap-2 px-6 py-3 rounded-xl text-sm font-semibold
type: 'bar', type: 'bar',
data: { data: {
labels: Object.keys(chartData.waktu_tunggu), labels: Object.keys(chartData.waktu_tunggu),
datasets: [{ label: 'Rata-rata Bulan', data: Object.values(chartData.waktu_tunggu), backgroundColor: '#3b82f6', borderRadius: 4 }] datasets: [{ label: 'Rata-rata Bulan', data: Object.values(chartData.waktu_tunggu), backgroundColor: ['#00C9A7', '#845EC2', '#D65DB1', '#FF9671', '#FFC75F'], borderRadius: 4 }]
}, },
options: barOptions options: barOptions
}); });
@ -671,7 +671,7 @@ class="inline-flex items-center gap-2 px-6 py-3 rounded-xl text-sm font-semibold
type: 'bar', type: 'bar',
data: { data: {
labels: Object.keys(chartData.funnel), labels: Object.keys(chartData.funnel),
datasets: [{ data: Object.values(chartData.funnel), backgroundColor: ['#8b5cf6', '#ec4899', '#10b981'], borderRadius: 4 }] datasets: [{ data: Object.values(chartData.funnel), backgroundColor: ['#008E9B', '#F39C12', '#E74C3C', '#2C3E50'], borderRadius: 4 }]
}, },
options: { options: {
indexAxis: 'y', indexAxis: 'y',
@ -683,7 +683,7 @@ class="inline-flex items-center gap-2 px-6 py-3 rounded-xl text-sm font-semibold
type: 'bar', type: 'bar',
data: { data: {
labels: Object.keys(chartData.lokasi), labels: Object.keys(chartData.lokasi),
datasets: [{ data: Object.values(chartData.lokasi), backgroundColor: '#14b8a6', borderRadius: 4 }] datasets: [{ data: Object.values(chartData.lokasi), backgroundColor: ['#2ECC71', '#3498DB', '#9B59B6', '#34495E', '#F1C40F'], borderRadius: 4 }]
}, },
options: barOptions options: barOptions
}); });
@ -715,7 +715,15 @@ class="inline-flex items-center gap-2 px-6 py-3 rounded-xl text-sm font-semibold
else if(c.includes('malang')) c = 'malang'; else if(c.includes('malang')) c = 'malang';
if (cityCoords[c]) { if (cityCoords[c]) {
L.marker(cityCoords[c]).addTo(map) const radius = Math.max(8, Math.min(count * 3 + 5, 40));
L.circleMarker(cityCoords[c], {
radius: radius,
fillColor: '#3b82f6',
color: '#1d4ed8',
weight: 2,
opacity: 1,
fillOpacity: 0.7
}).addTo(map)
.bindPopup(`<b>${city}</b><br>Jumlah: ${count} lulusan`); .bindPopup(`<b>${city}</b><br>Jumlah: ${count} lulusan`);
} }
} }

View File

@ -248,7 +248,7 @@
type: 'pie', type: 'pie',
data: { data: {
labels: Object.keys(chartData.status), labels: Object.keys(chartData.status),
datasets: [{ data: Object.values(chartData.status), backgroundColor: ['#8b5cf6', '#10b981', '#f59e0b', '#ef4444', '#3b82f6', '#64748b'] }] datasets: [{ data: Object.values(chartData.status), backgroundColor: ['#2563EB', '#DC2626', '#F59E0B', '#7C3AED', '#10B981', '#DB2777'] }]
}, },
options: pieOptions options: pieOptions
}); });
@ -257,7 +257,7 @@
type: 'doughnut', type: 'doughnut',
data: { data: {
labels: Object.keys(chartData.kesesuaian), labels: Object.keys(chartData.kesesuaian),
datasets: [{ data: Object.values(chartData.kesesuaian), backgroundColor: ['#14b8a6', '#f43f5e', '#facc15', '#6366f1', '#a855f7'] }] datasets: [{ data: Object.values(chartData.kesesuaian), backgroundColor: ['#14B8A6', '#F43F5E', '#8B5CF6', '#F59E0B', '#3B82F6'] }]
}, },
options: pieOptions options: pieOptions
}); });
@ -266,7 +266,7 @@
type: 'bar', type: 'bar',
data: { data: {
labels: Object.keys(chartData.level), labels: Object.keys(chartData.level),
datasets: [{ data: Object.values(chartData.level), backgroundColor: '#3b82f6', borderRadius: 4 }] datasets: [{ data: Object.values(chartData.level), backgroundColor: ['#00C9A7', '#845EC2', '#D65DB1', '#FF9671', '#FFC75F'], borderRadius: 4 }]
}, },
options: barOptions options: barOptions
}); });
@ -275,7 +275,7 @@
type: 'bar', type: 'bar',
data: { data: {
labels: Object.keys(chartData.instansi), labels: Object.keys(chartData.instansi),
datasets: [{ data: Object.values(chartData.instansi), backgroundColor: ['#ec4899', '#8b5cf6', '#06b6d4', '#10b981', '#f59e0b'], borderRadius: 4 }] datasets: [{ data: Object.values(chartData.instansi), backgroundColor: ['#2ECC71', '#3498DB', '#9B59B6', '#34495E', '#F1C40F'], borderRadius: 4 }]
}, },
options: { options: {
indexAxis: 'y', indexAxis: 'y',
@ -287,7 +287,7 @@
type: 'bar', type: 'bar',
data: { data: {
labels: Object.keys(chartData.pendapatan), labels: Object.keys(chartData.pendapatan),
datasets: [{ data: Object.values(chartData.pendapatan), backgroundColor: '#10b981', borderRadius: 4 }] datasets: [{ data: Object.values(chartData.pendapatan), backgroundColor: ['#008E9B', '#F39C12', '#E74C3C', '#2C3E50', '#18BC9C'], borderRadius: 4 }]
}, },
options: barOptions options: barOptions
}); });
@ -305,7 +305,15 @@
for (const [kode, count] of Object.entries(chartData.map_lokasi)) { for (const [kode, count] of Object.entries(chartData.map_lokasi)) {
if (coordsDict[kode] && coordsDict[kode].coords) { if (coordsDict[kode] && coordsDict[kode].coords) {
const cityData = coordsDict[kode]; const cityData = coordsDict[kode];
L.marker(cityData.coords).addTo(map) const radius = Math.max(8, Math.min(count * 3 + 5, 40));
L.circleMarker(cityData.coords, {
radius: radius,
fillColor: '#a855f7',
color: '#7e22ce',
weight: 2,
opacity: 1,
fillOpacity: 0.7
}).addTo(map)
.bindPopup(`<b>${cityData.name}</b><br>Jumlah: ${count} alumni`); .bindPopup(`<b>${cityData.name}</b><br>Jumlah: ${count} alumni`);
} }
} }