From c54e3c2346d367c6b4ef0df9e450d6981188ca9e Mon Sep 17 00:00:00 2001 From: Sangga123 Date: Mon, 14 Jul 2025 13:31:13 +0700 Subject: [PATCH] ADD GIT --- 5-fold cross_validation.py | 299 +++++++++++++++++++++++++++++++++ hit_rate_119.py | 221 +++++++++++++++++++++++++ hit_rate_20.py | 188 +++++++++++++++++++++ job_profiles.csv | 21 +++ jobs.xlsx | Bin 0 -> 13306 bytes requirements.txt | Bin 0 -> 128 bytes server.py | 240 +++++++++++++++++++++++++++ student.csv | 120 ++++++++++++++ templates/index.html | 330 +++++++++++++++++++++++++++++++++++++ templates/result.html | 159 ++++++++++++++++++ 10 files changed, 1578 insertions(+) create mode 100644 5-fold cross_validation.py create mode 100644 hit_rate_119.py create mode 100644 hit_rate_20.py create mode 100644 job_profiles.csv create mode 100644 jobs.xlsx create mode 100644 requirements.txt create mode 100644 server.py create mode 100644 student.csv create mode 100644 templates/index.html create mode 100644 templates/result.html diff --git a/5-fold cross_validation.py b/5-fold cross_validation.py new file mode 100644 index 0000000..24d1035 --- /dev/null +++ b/5-fold cross_validation.py @@ -0,0 +1,299 @@ +import pandas as pd +import numpy as np +from sklearn.preprocessing import OneHotEncoder, MinMaxScaler, LabelEncoder +from sklearn.metrics.pairwise import cosine_similarity +from sklearn.model_selection import KFold +import warnings +warnings.filterwarnings('ignore') + +class JobRecommendationSystem: + def __init__(self): + self.skill_encoder = OneHotEncoder(sparse_output=False, handle_unknown='ignore') + self.interest_encoder = OneHotEncoder(sparse_output=False, handle_unknown='ignore') + self.major_encoder = OneHotEncoder(sparse_output=False, handle_unknown='ignore') + self.job_encoder = LabelEncoder() + self.ipk_scaler = MinMaxScaler() + self.is_fitted = False + + def preprocess_data(self, df): + """Preprocess data untuk feature engineering""" + # Copy data untuk menghindari modifikasi asli + data = df.copy() + + # Normalisasi nama kolom + data.columns = data.columns.str.strip() + + # Clean dan standardisasi text + data['keterampilan'] = data['keterampilan'].str.strip().str.lower() + data['minat'] = data['minat'].str.strip().str.lower() + data['Jurusan'] = data['Jurusan'].str.strip().str.lower() + data['dream job'] = data['dream job'].str.strip() + + return data + + def create_features(self, data, fit=False): + """Membuat feature matrix dari data""" + # Reshape untuk encoder + skills = data['keterampilan'].values.reshape(-1, 1) + interests = data['minat'].values.reshape(-1, 1) + majors = data['Jurusan'].values.reshape(-1, 1) + ipk = data['IPK'].values.reshape(-1, 1) + + if fit: + # Fit encoders + skill_features = self.skill_encoder.fit_transform(skills) + interest_features = self.interest_encoder.fit_transform(interests) + major_features = self.major_encoder.fit_transform(majors) + ipk_features = self.ipk_scaler.fit_transform(ipk) + else: + # Transform only + skill_features = self.skill_encoder.transform(skills) + interest_features = self.interest_encoder.transform(interests) + major_features = self.major_encoder.transform(majors) + ipk_features = self.ipk_scaler.transform(ipk) + + # Gabungkan semua features + features = np.hstack([ + ipk_features, + skill_features, + interest_features, + major_features + ]) + + return features + + def fit(self, X_train, y_train): + """Fit model dengan training data""" + self.train_features = self.create_features(X_train, fit=True) + self.train_jobs = self.job_encoder.fit_transform(y_train) + self.train_job_names = y_train.values + self.is_fitted = True + + def predict(self, X_test, k=3): + """Predict jobs untuk test data menggunakan cosine similarity""" + if not self.is_fitted: + raise ValueError("Model belum di-fit!") + + test_features = self.create_features(X_test, fit=False) + + # Hitung cosine similarity + similarities = cosine_similarity(test_features, self.train_features) + + predictions = [] + + for i, sim_scores in enumerate(similarities): + # Ambil k tetangga terdekat + top_k_indices = np.argsort(sim_scores)[::-1][:k] + + # Ambil job dari tetangga terdekat + neighbor_jobs = [self.train_job_names[idx] for idx in top_k_indices] + + # Hitung frequency-based recommendation + job_counts = {} + for job in neighbor_jobs: + job_counts[job] = job_counts.get(job, 0) + 1 + + # Ambil job dengan frekuensi tertinggi + recommended_job = max(job_counts.keys(), key=job_counts.get) + predictions.append(recommended_job) + + return predictions + + def predict_top_k(self, X_test, k=3, top_jobs=3): + """Predict top-k jobs untuk test data""" + if not self.is_fitted: + raise ValueError("Model belum di-fit!") + + test_features = self.create_features(X_test, fit=False) + similarities = cosine_similarity(test_features, self.train_features) + + predictions = [] + + for i, sim_scores in enumerate(similarities): + top_k_indices = np.argsort(sim_scores)[::-1][:k] + neighbor_jobs = [self.train_job_names[idx] for idx in top_k_indices] + + # Hitung job frequency + job_counts = {} + for job in neighbor_jobs: + job_counts[job] = job_counts.get(job, 0) + 1 + + # Ambil top jobs berdasarkan frequency + sorted_jobs = sorted(job_counts.items(), key=lambda x: x[1], reverse=True) + top_recommended = [job for job, count in sorted_jobs[:top_jobs]] + + predictions.append(top_recommended) + + return predictions + +def evaluate_model(df, n_splits=5, k_neighbors=5): + """Evaluasi model menggunakan 5-fold cross validation""" + + # Preprocess data + model = JobRecommendationSystem() + processed_data = model.preprocess_data(df) + + # Inisialisasi KFold + kf = KFold(n_splits=n_splits, shuffle=True, random_state=42) + + # Metrics untuk setiap fold + fold_results = [] + + print("=" * 80) + print("EVALUASI 5-FOLD CROSS-VALIDATION DENGAN COSINE SIMILARITY") + print("=" * 80) + print() + + for fold, (train_idx, test_idx) in enumerate(kf.split(processed_data), 1): + print(f"Processing Fold {fold}...") + + # Split data + train_data = processed_data.iloc[train_idx] + test_data = processed_data.iloc[test_idx] + + X_train = train_data[['IPK', 'Jurusan', 'keterampilan', 'minat']] + y_train = train_data['dream job'] + X_test = test_data[['IPK', 'Jurusan', 'keterampilan', 'minat']] + y_test = test_data['dream job'] + + # Fit dan predict + fold_model = JobRecommendationSystem() + fold_model.fit(X_train, y_train) + + # Single prediction (top-1) + predictions_top1 = fold_model.predict(X_test, k=k_neighbors) + + # Top-3 predictions + predictions_top3 = fold_model.predict_top_k(X_test, k=k_neighbors, top_jobs=3) + + # Hitung metrics + # Accuracy (exact match) + exact_matches = sum(1 for pred, actual in zip(predictions_top1, y_test) + if pred.lower() == actual.lower()) + accuracy = exact_matches / len(y_test) + + # Top-3 accuracy + top3_matches = 0 + for pred_list, actual in zip(predictions_top3, y_test): + if any(pred.lower() == actual.lower() for pred in pred_list): + top3_matches += 1 + top3_accuracy = top3_matches / len(y_test) + + # Partial match (similar to original hit rate) + partial_matches = 0 + for pred, actual in zip(predictions_top1, y_test): + pred_lower = pred.lower() + actual_lower = actual.lower() + if pred_lower in actual_lower or actual_lower in pred_lower: + partial_matches += 1 + partial_accuracy = partial_matches / len(y_test) + + # Simpan hasil fold + fold_result = { + 'fold': fold, + 'train_size': len(train_data), + 'test_size': len(test_data), + 'exact_accuracy': accuracy, + 'partial_accuracy': partial_accuracy, + 'top3_accuracy': top3_accuracy, + 'predictions': predictions_top1, + 'actual': y_test.tolist() + } + fold_results.append(fold_result) + + # Print hasil fold + print(f"Fold {fold} Results:") + print(f" Train Size: {len(train_data)}, Test Size: {len(test_data)}") + print(f" Exact Accuracy: {accuracy:.3f} ({accuracy*100:.1f}%)") + print(f" Partial Accuracy: {partial_accuracy:.3f} ({partial_accuracy*100:.1f}%)") + print(f" Top-3 Accuracy: {top3_accuracy:.3f} ({top3_accuracy*100:.1f}%)") + print() + + return fold_results + +def analyze_results(fold_results): + """Analisis hasil cross-validation""" + + print("=" * 80) + print("RINGKASAN HASIL 5-FOLD CROSS-VALIDATION") + print("=" * 80) + + # Hitung rata-rata metrics + exact_accs = [result['exact_accuracy'] for result in fold_results] + partial_accs = [result['partial_accuracy'] for result in fold_results] + top3_accs = [result['top3_accuracy'] for result in fold_results] + + print(f"\nRata-rata Metrics:") + print(f"Exact Accuracy: {np.mean(exact_accs):.3f} ± {np.std(exact_accs):.3f}") + print(f"Partial Accuracy: {np.mean(partial_accs):.3f} ± {np.std(partial_accs):.3f}") + print(f"Top-3 Accuracy: {np.mean(top3_accs):.3f} ± {np.std(top3_accs):.3f}") + + print(f"\nDetail per Fold:") + print(f"{'Fold':<6} {'Exact':<8} {'Partial':<8} {'Top-3':<8}") + print("-" * 35) + for result in fold_results: + print(f"{result['fold']:<6} {result['exact_accuracy']:.3f} {result['partial_accuracy']:.3f} {result['top3_accuracy']:.3f}") + + # Analisis error + print(f"\nAnalisis Kesalahan Prediksi:") + all_predictions = [] + all_actual = [] + + for result in fold_results: + all_predictions.extend(result['predictions']) + all_actual.extend(result['actual']) + + # Hitung confusion untuk job yang sering salah + error_analysis = {} + for pred, actual in zip(all_predictions, all_actual): + if pred.lower() != actual.lower(): + key = f"'{actual}' → '{pred}'" + error_analysis[key] = error_analysis.get(key, 0) + 1 + + print("\nKesalahan Prediksi Terbanyak:") + sorted_errors = sorted(error_analysis.items(), key=lambda x: x[1], reverse=True) + for error, count in sorted_errors[:10]: + print(f" {error}: {count} kali") + + return { + 'mean_exact_accuracy': np.mean(exact_accs), + 'std_exact_accuracy': np.std(exact_accs), + 'mean_partial_accuracy': np.mean(partial_accs), + 'std_partial_accuracy': np.std(partial_accs), + 'mean_top3_accuracy': np.mean(top3_accs), + 'std_top3_accuracy': np.std(top3_accs), + 'fold_results': fold_results + } + +def main(): + """Fungsi utama untuk menjalankan evaluasi""" + + # Load data + try: + df = pd.read_csv('student.csv') + print(f"Data berhasil dimuat: {len(df)} records") + print(f"Kolom: {list(df.columns)}") + print() + except FileNotFoundError: + print("File student.csv tidak ditemukan!") + return + + # Jalankan evaluasi + fold_results = evaluate_model(df, n_splits=5, k_neighbors=5) + + # Analisis hasil + summary = analyze_results(fold_results) + + print("\n" + "=" * 80) + print("KESIMPULAN") + print("=" * 80) + print(f"Model Cosine Similarity dengan 5-fold CV menunjukkan:") + print(f"• Exact Match Accuracy: {summary['mean_exact_accuracy']:.1%} ± {summary['std_exact_accuracy']:.1%}") + print(f"• Partial Match Accuracy: {summary['mean_partial_accuracy']:.1%} ± {summary['std_partial_accuracy']:.1%}") + print(f"• Top-3 Accuracy: {summary['mean_top3_accuracy']:.1%} ± {summary['std_top3_accuracy']:.1%}") + print() + print("Metode ini menggunakan kesamaan profil mahasiswa (IPK, jurusan, keterampilan, minat)") + print("untuk merekomendasikan pekerjaan berdasarkan tetangga terdekat.") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/hit_rate_119.py b/hit_rate_119.py new file mode 100644 index 0000000..6f2808d --- /dev/null +++ b/hit_rate_119.py @@ -0,0 +1,221 @@ +import pandas as pd +import random + +# Data jurusan dan rekomendasi (tetap sama) +jurusan_data = { + "teknik informatika": { + "keterampilan": [ + "spreadsheet", "python", "analisis data", "visualisasi data", + "pengujian perangkat lunak", "dokumentasi kasus uji", "manajemen waktu", + "html", "css", "javascript", "java", "node.js", "sql", "devops", "aws", "azure", + "react", "angular", "statistik", "machine learning", "cloud operation", "ruby", + "database", "desain", "pemrograman", "keamanan digital", "troubleshooting", + "kotlin", "swift", "objective-c", "r", "preprocessing data", "penyebaran model", + "manajemen proyek", "pemahaman operasi bisnis", "unity", "unreal engine", "c#", "c++" + ], + "minat": [ + "investigative (analitis)", "conventional (terstruktur)", "artistic (kreatif)", + "realistic (praktis, teknis)", "enterprising (memimpin)", "social (sosial)" + ] + }, + "sistem informasi": { + "keterampilan": [ + "spreadsheet", "python", "analisis data", "visualisasi data", + "database", "sql", "manajemen proyek", "pemahaman operasi bisnis", + "administrasi jaringan", "troubleshooting", "dukungan teknis perangkat keras dan perangkat lunak" + ], + "minat": ["investigative (analitis)", "conventional (terstruktur)", "realistic (praktis, teknis)", "social (sosial)"] + }, + "matematika": { + "keterampilan": ["spreadsheet", "python", "analisis data", "visualisasi data", "statistik", "machine learning", "r"], + "minat": ["investigative (analitis)"] + }, + "teknik komputer": { + "keterampilan": [ + "html", "css", "javascript", "python", "java", "node.js", "sql", "devops", "aws", "azure", + "cloud operation", "ruby", "pemrograman", "keamanan digital", "troubleshooting", + "kotlin", "swift", "objective-c", "administrasi jaringan" + ], + "minat": ["conventional (terstruktur)", "artistic (kreatif)", "realistic (praktis, teknis)", "enterprising (memimpin)"] + }, + "desain komunikasi visual": { + "keterampilan": ["html", "css", "javascript", "react", "angular", "desain"], + "minat": ["artistic (kreatif)"] + }, + "statistik": { + "keterampilan": ["statistik", "machine learning", "python", "r"], + "minat": ["investigative (analitis)"] + }, + "ilmu komputer": { + "keterampilan": ["database", "sql"], + "minat": ["investigative (analitis)"] + } +} + +def get_recommendation(ipk, jurusan): + """Generate recommendation based on IPK and major""" + jurusan_lower = jurusan.lower() + + if jurusan_lower in ["teknik informatika", "sistem informasi"]: + if ipk >= 3.7: + return ["Data Scientist", "Machine Learning Engineer", "Software Engineer"] + elif ipk >= 3.5: + return ["Data Analyst", "Web Developer", "Software Engineer"] + else: + return ["Junior Developer", "IT Support", "System Administrator"] + elif jurusan_lower == "matematika": + return ["Data Scientist", "Statistician", "Data Analyst"] + elif jurusan_lower == "desain komunikasi visual": + return ["UI/UX Designer", "Graphic Designer", "Web Designer"] + elif jurusan_lower == "teknik komputer": + return ["System Administrator", "Network Engineer", "DevOps Engineer"] + elif jurusan_lower == "statistik": + return ["Data Analyst", "Statistician", "Research Analyst"] + else: + return ["Database Administrator", "Data Analyst", "System Analyst"] + +def check_hit(target, recommendations): + """Check if target job matches any recommendation""" + target_lower = target.lower() + for rec in recommendations: + rec_lower = rec.lower() + # Check if target contains recommendation or vice versa + if target_lower in rec_lower or rec_lower in target_lower: + return True + return False + +# Load CSV data +try: + df = pd.read_csv('student.csv') + print(f"Data berhasil dimuat: {len(df)} records") +except FileNotFoundError: + print("File student.csv tidak ditemukan!") + exit() + +# Process data +uji_coba = [] +total = len(df) +hit = 0 + +print("=" * 120) +print("PENGUJIAN HIT RATE SISTEM IPKMATCHER - MENGGUNAKAN DATASET CSV") +print("=" * 120) +print() + +print("{:<3} {:<5} {:<5} {:<20} {:<20} {:<25} {:<35} {:<18} {:<4}".format( + "No", "NIM", "IPK", "Jurusan", "Keterampilan", "Minat", "Rekomendasi Sistem", "Target", "Hit")) +print("-" * 120) + +for index, row in df.iterrows(): + nim = row['NIM'] + ipk = row['IPK'] + jurusan = row['Jurusan'] + target = row['dream job'] + keterampilan = row['keterampilan'] + minat = row['minat'] + + # Generate recommendations + rekomendasi = get_recommendation(ipk, jurusan) + + # Check hit + is_hit = check_hit(target, rekomendasi) + if is_hit: + hit += 1 + + # Store for analysis + uji_coba.append({ + "nim": nim, + "ipk": ipk, + "jurusan": jurusan, + "keterampilan": keterampilan, + "minat": minat, + "rekomendasi": rekomendasi, + "target": target, + "hit": is_hit + }) + + # Format for display + jurusan_short = (jurusan[:17] + "...") if len(jurusan) > 20 else jurusan + keterampilan_short = (keterampilan[:17] + "...") if len(keterampilan) > 20 else keterampilan + minat_short = (minat[:22] + "...") if len(minat) > 25 else minat + rekom_short = (", ".join(rekomendasi)[:32] + "...") if len(", ".join(rekomendasi)) > 35 else ", ".join(rekomendasi) + target_short = (target[:15] + "...") if len(target) > 18 else target + + print("{:<3} {:<5} {:<5} {:<20} {:<20} {:<25} {:<35} {:<18} {:<4}".format( + index + 1, nim, ipk, jurusan_short, keterampilan_short, minat_short, + rekom_short, target_short, "✓" if is_hit else "✗")) + +# Calculate hit rate +hit_rate = hit / total +print("-" * 120) +print(f"HASIL PENGUJIAN:") +print(f"Total Hit: {hit} dari {total} kasus") +print(f"Hit Rate: {hit_rate:.3f} ({hit_rate * 100:.1f}%)") +print() + +# Analysis per major +print("ANALISIS PER JURUSAN:") +print("-" * 50) +jurusan_stats = {} +for case in uji_coba: + jurusan = case["jurusan"] + if jurusan not in jurusan_stats: + jurusan_stats[jurusan] = {"total": 0, "hit": 0} + + jurusan_stats[jurusan]["total"] += 1 + jurusan_stats[jurusan]["hit"] += case["hit"] + +for jurusan, stats in jurusan_stats.items(): + rate = stats["hit"] / stats["total"] + print(f"{jurusan:<25}: {stats['hit']}/{stats['total']} ({rate*100:.1f}%)") + +# Analysis per IPK range +print("\nANALISIS PER RENTANG IPK:") +print("-" * 50) +ipk_ranges = { + "3.8-4.0": {"min": 3.8, "max": 4.0, "total": 0, "hit": 0}, + "3.5-3.79": {"min": 3.5, "max": 3.79, "total": 0, "hit": 0}, + "3.0-3.49": {"min": 3.0, "max": 3.49, "total": 0, "hit": 0}, + "<3.0": {"min": 0, "max": 2.99, "total": 0, "hit": 0} +} + +for case in uji_coba: + ipk = case["ipk"] + for range_name, range_info in ipk_ranges.items(): + if range_info["min"] <= ipk <= range_info["max"]: + range_info["total"] += 1 + range_info["hit"] += case["hit"] + break + +for range_name, stats in ipk_ranges.items(): + if stats["total"] > 0: + rate = stats["hit"] / stats["total"] + print(f"IPK {range_name:<10}: {stats['hit']}/{stats['total']} ({rate*100:.1f}%)") + +print("=" * 120) + +# Detailed results +# print("\nDETAIL LENGKAP HASIL PENGUJIAN:") +# print("=" * 80) +# for i, case in enumerate(uji_coba, 1): +# print(f"\nTest Case {i}:") +# print(f" NIM: {case['nim']}") +# print(f" IPK: {case['ipk']}") +# print(f" Jurusan: {case['jurusan']}") +# print(f" Keterampilan: {case['keterampilan']}") +# print(f" Minat: {case['minat']}") +# print(f" Rekomendasi: {', '.join(case['rekomendasi'])}") +# print(f" Target (Dream Job): {case['target']}") +# print(f" Status: {'✓ HIT' if case['hit'] else '✗ MISS'}") +# print(f" {'-'*40}") + +# Summary of missed predictions +print("\nANALISIS PREDIKSI YANG MELESET:") +print("=" * 80) +missed_cases = [case for case in uji_coba if not case['hit']] +print(f"Total kasus yang meleset: {len(missed_cases)}") + +if missed_cases: + print("\nContoh kasus yang meleset:") + for i, case in enumerate(missed_cases[:10], 1): # Show first 10 missed cases + print(f"{i}. NIM {case['nim']}: Target '{case['target']}' vs Rekomendasi {case['rekomendasi']}") \ No newline at end of file diff --git a/hit_rate_20.py b/hit_rate_20.py new file mode 100644 index 0000000..e9ae9fc --- /dev/null +++ b/hit_rate_20.py @@ -0,0 +1,188 @@ +import random +jurusan_data = { + "teknik informatika": { + "keterampilan": [ + "spreadsheet", "python", "analisis data", "visualisasi data", + "pengujian perangkat lunak", "dokumentasi kasus uji", "manajemen waktu", + "html", "css", "javascript", "java", "node.js", "sql", "devops", "aws", "azure", + "react", "angular", "statistik", "machine learning", "cloud operation", "ruby", + "database", "desain", "pemrograman", "keamanan digital", "troubleshooting", + "kotlin", "swift", "objective-c", "r", "preprocessing data", "penyebaran model", + "manajemen proyek", "pemahaman operasi bisnis", "unity", "unreal engine", "c#", "c++" + ], + "minat": [ + "investigative (analitis)", "conventional (terstruktur)", "artistic (kreatif)", + "realistic (praktis, teknis)", "enterprising (memimpin)", "social (sosial)" + ] + }, + "sistem informasi": { + "keterampilan": [ + "spreadsheet", "python", "analisis data", "visualisasi data", + "database", "sql", "manajemen proyek", "pemahaman operasi bisnis", + "administrasi jaringan", "troubleshooting", "dukungan teknis perangkat keras dan perangkat lunak" + ], + "minat": ["investigative (analitis)", "conventional (terstruktur)", "realistic (praktis, teknis)", "social (sosial)"] + }, + "matematika": { + "keterampilan": ["spreadsheet", "python", "analisis data", "visualisasi data", "statistik", "machine learning", "r"], + "minat": ["investigative (analitis)"] + }, + "teknik komputer": { + "keterampilan": [ + "html", "css", "javascript", "python", "java", "node.js", "sql", "devops", "aws", "azure", + "cloud operation", "ruby", "pemrograman", "keamanan digital", "troubleshooting", + "kotlin", "swift", "objective-c", "administrasi jaringan" + ], + "minat": ["conventional (terstruktur)", "artistic (kreatif)", "realistic (praktis, teknis)", "enterprising (memimpin)"] + }, + "desain komunikasi visual": { + "keterampilan": ["html", "css", "javascript", "react", "angular", "desain"], + "minat": ["artistic (kreatif)"] + }, + "statistik": { + "keterampilan": ["statistik", "machine learning", "python", "r"], + "minat": ["investigative (analitis)"] + }, + "ilmu komputer": { + "keterampilan": ["database", "sql"], + "minat": ["investigative (analitis)"] + } +} + +def get_random_skills_interests(jurusan): + if jurusan in jurusan_data: + keterampilan = random.choice(jurusan_data[jurusan]["keterampilan"]) + minat = random.choice(jurusan_data[jurusan]["minat"]) + return keterampilan, minat + return "python", "investigative (analitis)" # default + +random.seed(42) + +uji_coba = [] + +test_cases_data = [ + {"nama": "Ahmad", "ipk": 3.78, "jurusan": "teknik informatika", "target": "Web Developer"}, + {"nama": "Sari", "ipk": 3.50, "jurusan": "sistem informasi", "target": "Data Analyst"}, + {"nama": "Budi", "ipk": 3.83, "jurusan": "teknik informatika", "target": "Data Scientist"}, + {"nama": "Rina", "ipk": 3.91, "jurusan": "teknik informatika", "target": "Quality Assurance"}, + {"nama": "Doni", "ipk": 3.83, "jurusan": "sistem informasi", "target": "Data Analyst"}, + {"nama": "Lisa", "ipk": 3.78, "jurusan": "teknik informatika", "target": "Web Developer"}, + {"nama": "Agus", "ipk": 3.71, "jurusan": "teknik informatika", "target": "Frontend Developer"}, + {"nama": "Maya", "ipk": 3.74, "jurusan": "desain komunikasi visual", "target": "UI/UX Designer"}, + {"nama": "Rudi", "ipk": 3.92, "jurusan": "teknik informatika", "target": "Machine Learning Engineer"}, + {"nama": "Dewi", "ipk": 3.95, "jurusan": "matematika", "target": "Data Scientist"}, + {"nama": "Andi", "ipk": 3.25, "jurusan": "teknik komputer", "target": "System Administrator"}, + {"nama": "Nina", "ipk": 3.65, "jurusan": "statistik", "target": "Data Analyst"}, + {"nama": "Hadi", "ipk": 3.45, "jurusan": "ilmu komputer", "target": "Database Administrator"}, + {"nama": "Tina", "ipk": 3.88, "jurusan": "teknik informatika", "target": "Software Engineer"}, + {"nama": "Bayu", "ipk": 3.55, "jurusan": "sistem informasi", "target": "Business Analyst"}, + {"nama": "Sinta", "ipk": 3.72, "jurusan": "desain komunikasi visual", "target": "UI/UX Designer"}, + {"nama": "Dika", "ipk": 3.33, "jurusan": "teknik komputer", "target": "Network Engineer"}, + {"nama": "Lusi", "ipk": 3.67, "jurusan": "matematika", "target": "Statistician"}, + {"nama": "Yoga", "ipk": 3.81, "jurusan": "teknik informatika", "target": "DevOps Engineer"}, + {"nama": "Eka", "ipk": 3.59, "jurusan": "sistem informasi", "target": "IT Support"} +] + +for case_data in test_cases_data: + keterampilan, minat = get_random_skills_interests(case_data["jurusan"]) + + if case_data["jurusan"] in ["teknik informatika", "sistem informasi"]: + if case_data["ipk"] >= 3.7: + rekomendasi = ["Data Scientist", "Machine Learning Engineer", "Software Engineer"] + elif case_data["ipk"] >= 3.5: + rekomendasi = ["Data Analyst", "Web Developer", "Software Engineer"] + else: + rekomendasi = ["Junior Developer", "IT Support", "System Administrator"] + elif case_data["jurusan"] == "matematika": + rekomendasi = ["Data Scientist", "Statistician", "Data Analyst"] + elif case_data["jurusan"] == "desain komunikasi visual": + rekomendasi = ["UI/UX Designer", "Graphic Designer", "Web Designer"] + elif case_data["jurusan"] == "teknik komputer": + rekomendasi = ["System Administrator", "Network Engineer", "DevOps Engineer"] + elif case_data["jurusan"] == "statistik": + rekomendasi = ["Data Analyst", "Statistician", "Research Analyst"] + else: + rekomendasi = ["Database Administrator", "Data Analyst", "System Analyst"] + + uji_coba.append({ + "nama": case_data["nama"], + "ipk": case_data["ipk"], + "jurusan": case_data["jurusan"], + "keterampilan": keterampilan, + "minat": minat, + "rekomendasi": rekomendasi, + "target": case_data["target"] + }) + +total = len(uji_coba) +hit = 0 + +print("=" * 120) +print("PENGUJIAN HIT RATE SISTEM IPKMATCHER") +print("=" * 120) +print() + +print("{:<3} {:<8} {:<5} {:<20} {:<20} {:<25} {:<35} {:<18} {:<4}".format( + "No", "Nama", "IPK", "Jurusan", "Keterampilan", "Minat", "Rekomendasi Sistem", "Target", "Hit")) +print("-" * 120) + +for i, case in enumerate(uji_coba, 1): + nama = case["nama"] + ipk = case["ipk"] + jurusan = case["jurusan"] + keterampilan = case["keterampilan"] + minat = case["minat"] + rekom = case["rekomendasi"] + target = case["target"] + + is_hit = 1 if any(target.lower() in rec.lower() or rec.lower() in target.lower() for rec in rekom) else 0 + hit += is_hit + + jurusan_short = (jurusan[:17] + "...") if len(jurusan) > 20 else jurusan + keterampilan_short = (keterampilan[:17] + "...") if len(keterampilan) > 20 else keterampilan + minat_short = (minat[:22] + "...") if len(minat) > 25 else minat + rekom_short = (", ".join(rekom)[:32] + "...") if len(", ".join(rekom)) > 35 else ", ".join(rekom) + target_short = (target[:15] + "...") if len(target) > 18 else target + + print("{:<3} {:<8} {:<5} {:<20} {:<20} {:<25} {:<35} {:<18} {:<4}".format( + i, nama, ipk, jurusan_short, keterampilan_short, minat_short, rekom_short, target_short, "✓" if is_hit else "✗")) + +hit_rate = hit / total +print("-" * 120) +print(f"HASIL PENGUJIAN:") +print(f"Total Hit: {hit} dari {total} kasus") +print(f"Hit Rate: {hit_rate:.3f} ({hit_rate * 100:.1f}%)") +print() + +print("ANALISIS PER JURUSAN:") +print("-" * 50) +jurusan_stats = {} +for case in uji_coba: + jurusan = case["jurusan"] + if jurusan not in jurusan_stats: + jurusan_stats[jurusan] = {"total": 0, "hit": 0} + + jurusan_stats[jurusan]["total"] += 1 + is_hit = 1 if any(case["target"].lower() in rec.lower() or rec.lower() in case["target"].lower() for rec in case["rekomendasi"]) else 0 + jurusan_stats[jurusan]["hit"] += is_hit + +for jurusan, stats in jurusan_stats.items(): + rate = stats["hit"] / stats["total"] + print(f"{jurusan.title():<25}: {stats['hit']}/{stats['total']} ({rate*100:.1f}%)") + +print("=" * 120) + +print("\nDETAIL LENGKAP HASIL PENGUJIAN:") +print("=" * 80) +for i, case in enumerate(uji_coba, 1): + print(f"\nTest Case {i}:") + print(f" Nama: {case['nama']}") + print(f" IPK: {case['ipk']}") + print(f" Jurusan: {case['jurusan'].title()}") + print(f" Keterampilan: {case['keterampilan'].title()}") + print(f" Minat: {case['minat'].title()}") + print(f" Rekomendasi: {', '.join(case['rekomendasi'])}") + print(f" Target: {case['target']}") + is_hit = 1 if any(case["target"].lower() in rec.lower() or rec.lower() in case["target"].lower() for rec in case["rekomendasi"]) else 0 + print(f" Status: {'✓ HIT' if is_hit else '✗ MISS'}") + print(f" {'-'*40}") \ No newline at end of file diff --git a/job_profiles.csv b/job_profiles.csv new file mode 100644 index 0000000..1514563 --- /dev/null +++ b/job_profiles.csv @@ -0,0 +1,21 @@ +dream job,IPK,keterampilan_Administrasi Jaringan,keterampilan_Algoritma Machine Learning,keterampilan_Analisis Data,keterampilan_Azure,keterampilan_C#,keterampilan_C++,keterampilan_CSS,keterampilan_Cloud Operation,keterampilan_Database,keterampilan_Desain,keterampilan_Dokumentasi Kasus Uji,keterampilan_Dukungan Teknis Perangkat Keras dan Perangkat Lunak,keterampilan_HTML,keterampilan_Java,keterampilan_JavaScript,keterampilan_Javascript,keterampilan_Keamanan Digital,keterampilan_Kotlin,keterampilan_Kotlin ,keterampilan_Machine Learning,keterampilan_Manajemen Penyimpanan Database,keterampilan_Manajemen Proyek,keterampilan_Manajemen Waktu,keterampilan_Node.js,keterampilan_Pemahaman Operasi Bisnis,keterampilan_Pemrograman,keterampilan_Pengujian Perangkat Lunak,keterampilan_Preprocessing Data,keterampilan_Python,keterampilan_React,keterampilan_Ruby,keterampilan_SQL,keterampilan_Spreadsheet,keterampilan_Statistik,keterampilan_Swift,keterampilan_Troubleshooting,keterampilan_Unity,keterampilan_Unreal Engine,keterampilan_Visualisasi Data,minat_Artistic (Kreatif),minat_Conventional (Terstruktur),minat_Enterprising (Memimpin),minat_Investigative (Analitis),"minat_Realistic (Praktis, Teknis)",minat_Social (Sosial) +Backend Developer,0.5707079106380618,0.0,0.0,0.0,0.16666666666666666,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.3333333333333333,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.3333333333333333,0.0,0.0,0.0,0.0,0.16666666666666666,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.6666666666666666,0.0,0.16666666666666666,0.16666666666666666,0.0 +Business Intelligence Developer,0.7181107600199566,0.0,0.0,0.16666666666666666,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.3333333333333333,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.5,0.0,0.0,0.0,1.0,0.0,0.0 +Cloud Engineer,0.6885913853317809,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.16666666666666666,0.0,0.0,0.8333333333333334,0.0 +Cybersecurity Analyst,0.7101557736016407,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.5,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.5,0.0,0.0,0.0,0.0,0.16666666666666666,0.3333333333333333,0.3333333333333333,0.16666666666666666,0.0 +Data Analyst,0.735953054097075,0.0,0.0,0.42857142857142855,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.14285714285714285,0.0,0.0,0.0,0.14285714285714285,0.0,0.0,0.0,0.0,0.0,0.2857142857142857,0.0,0.0,0.0,1.0,0.0,0.0 +Data Engineer,0.7873219136315758,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.6666666666666666,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.3333333333333333,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.16666666666666666,0.0,0.5,0.3333333333333333,0.0 +Data Scientist,0.7581905870613669,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.3333333333333333,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.16666666666666666,0.0,0.0,0.0,0.0,0.5,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0 +Database Administrator,0.5515383336105103,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.2,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.8,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0 +DevOps Engineer,0.7336049670159097,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.3333333333333333,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.16666666666666666,0.0,0.5,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0 +Frontend Developer,0.6585730916347909,0.0,0.0,0.0,0.0,0.0,0.0,0.16666666666666666,0.0,0.0,0.0,0.0,0.0,0.16666666666666666,0.0,0.5,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.16666666666666666,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0 +Game Developer,0.637091856533067,0.0,0.0,0.0,0.0,0.3333333333333333,0.3333333333333333,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.16666666666666666,0.16666666666666666,0.0,1.0,0.0,0.0,0.0,0.0,0.0 +IT Project Manager,0.7553356616220409,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.3333333333333333,0.0,0.0,0.6666666666666666 +IT Support Specialist,0.7002605465934916,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0 +Machine Learning Engineer,0.7346859582016739,0.0,0.3333333333333333,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.16666666666666666,0.5,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0 +Mobile App Developer,0.6177726038028714,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.3333333333333333,0.0,0.0,0.0,0.3333333333333333,0.16666666666666666,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.16666666666666666,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0 +Network Administrator,0.745689894118299,0.6666666666666666,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.3333333333333333,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0 +Quality Assurance,0.6567104606685512,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.2,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.6,0.0,0.0,0.0,0.2,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0 +Software Engineer,0.7200510006097897,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.5,0.5,0.0,0.0,0.0,0.0 +UI/UX Designer,0.6349021564388267,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0 +Web Developer,0.6734297910083704,0.0,0.0,0.0,0.0,0.0,0.0,0.16666666666666666,0.0,0.0,0.0,0.0,0.0,0.16666666666666666,0.0,0.0,0.6666666666666666,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0 diff --git a/jobs.xlsx b/jobs.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..bb465c488770dc32e6a3e33193c6bb25dc585c76 GIT binary patch literal 13306 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 literal 0 HcmV?d00001 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..67359ca1f1a56ccc9a748855b6eb7a1aad18865d GIT binary patch literal 128 zcmXwyI|_g>6a=RhJc?ps?P0_Z1T~1!!pp0h2eNE6`+1+4m7c^xCwXcb*-=uki$q4_ lnL3fyoN44@Q|7j2X5JFsx`~0H)xb%mk4Y + + + + + IPKMatcher + + + +
+

IPKMatcher

+

Mulai tentukan karier Anda.

+ +
+ + + + + + + + + + + + + + + + +
+ + {% if error %} +
{{ error }}
+ {% endif %} +
+ + + + \ No newline at end of file diff --git a/templates/result.html b/templates/result.html new file mode 100644 index 0000000..1636a8c --- /dev/null +++ b/templates/result.html @@ -0,0 +1,159 @@ + + + + + + Hasil Rekomendasi - IPKMatcher + + + +
+

IPKMatcher

+ + + +
+

Rekomendasi Pekerjaan

+ + {% if recommendations %} + {% for job in recommendations %} +
+
{{ job.job }}
+
{{ job.job_desc }}
+
+ IPK Minimum: {{ job.min_ipk }} | + Jurusan: {{ job.jurusan|title }} | + Keterampilan: {{ job.keterampilan|join(', ')|title }} | + Minat: {{ job.minat|title }} +
+
+ Kesesuaian: {{ "%.1f"|format(job.similarity_score * 100) }}% +
+
+ {% endfor %} + {% else %} +
+

Maaf, tidak ada pekerjaan yang sesuai dengan profil Anda.

+

Cobalah meningkatkan IPK atau mengembangkan keterampilan lain.

+
+ {% endif %} +
+ + Kembali ke Beranda +
+ + \ No newline at end of file