Spaces:
Sleeping
Sleeping
Upload 26 files
Browse files- Dockerfile +28 -0
- app/__pycache__/database.cpython-312.pyc +0 -0
- app/__pycache__/database.cpython-314.pyc +0 -0
- app/__pycache__/main.cpython-312.pyc +0 -0
- app/__pycache__/main.cpython-314.pyc +0 -0
- app/__pycache__/models.cpython-312.pyc +0 -0
- app/__pycache__/models.cpython-314.pyc +0 -0
- app/__pycache__/schemas.cpython-312.pyc +0 -0
- app/__pycache__/schemas.cpython-314.pyc +0 -0
- app/data/Sub_skill.json +751 -0
- app/main.py +319 -0
- app/schemas.py +105 -0
- app/services/__pycache__/llm_engine.cpython-312.pyc +0 -0
- app/services/__pycache__/llm_engine.cpython-314.pyc +0 -0
- app/services/__pycache__/psych_service.cpython-312.pyc +0 -0
- app/services/__pycache__/psych_service.cpython-314.pyc +0 -0
- app/services/__pycache__/skill_manager.cpython-312.pyc +0 -0
- app/services/__pycache__/skill_manager.cpython-314.pyc +0 -0
- app/services/llm_engine.py +221 -0
- app/services/psych_service.py +97 -0
- app/services/skill_manager.py +34 -0
- model_artifacts/courses_df.pkl +3 -0
- model_artifacts/smart_course_dataset.csv +0 -0
- model_artifacts/tfidf_matrix.pkl +3 -0
- model_artifacts/tfidf_vectorizer.pkl +3 -0
- requirements.txt +8 -0
Dockerfile
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 1. Gunakan Python 3.9 (Versi paling stabil untuk Scikit-Learn & Pandas)
|
| 2 |
+
FROM python:3.9
|
| 3 |
+
|
| 4 |
+
# 2. Set folder kerja di dalam container (Virtual Computer)
|
| 5 |
+
WORKDIR /code
|
| 6 |
+
|
| 7 |
+
# 3. Copy file requirements.txt terlebih dahulu
|
| 8 |
+
# (Tujuannya agar Docker bisa 'cache' proses install library, biar cepat kalau deploy ulang)
|
| 9 |
+
COPY ./requirements.txt /code/requirements.txt
|
| 10 |
+
|
| 11 |
+
# 4. Install library yang ada di requirements.txt
|
| 12 |
+
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
| 13 |
+
|
| 14 |
+
# 5. Copy seluruh sisa file proyek (folder app, model_artifacts, main.py, dll) ke dalam container
|
| 15 |
+
COPY . /code
|
| 16 |
+
|
| 17 |
+
# 6. Atur izin (Permissions)
|
| 18 |
+
# Hugging Face menjalankan aplikasi sebagai user 'non-root' (user ID 1000).
|
| 19 |
+
# Kita harus memberi izin akses ke folder cache agar aplikasi tidak error saat menulis file sementara.
|
| 20 |
+
RUN mkdir -p /code/cache
|
| 21 |
+
RUN chmod -R 777 /code
|
| 22 |
+
|
| 23 |
+
# Set Environment Variable untuk Cache (biar library ML gak bingung nyimpan cache dimana)
|
| 24 |
+
ENV XDG_CACHE_HOME=/code/cache
|
| 25 |
+
|
| 26 |
+
# 7. Perintah Menyalakan Server
|
| 27 |
+
# PENTING: Hugging Face WAJIB menggunakan port 7860. Jangan diganti ke 8000.
|
| 28 |
+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
|
app/__pycache__/database.cpython-312.pyc
ADDED
|
Binary file (740 Bytes). View file
|
|
|
app/__pycache__/database.cpython-314.pyc
ADDED
|
Binary file (766 Bytes). View file
|
|
|
app/__pycache__/main.cpython-312.pyc
ADDED
|
Binary file (13.3 kB). View file
|
|
|
app/__pycache__/main.cpython-314.pyc
ADDED
|
Binary file (12.4 kB). View file
|
|
|
app/__pycache__/models.cpython-312.pyc
ADDED
|
Binary file (2.77 kB). View file
|
|
|
app/__pycache__/models.cpython-314.pyc
ADDED
|
Binary file (2.9 kB). View file
|
|
|
app/__pycache__/schemas.cpython-312.pyc
ADDED
|
Binary file (4.83 kB). View file
|
|
|
app/__pycache__/schemas.cpython-314.pyc
ADDED
|
Binary file (6.48 kB). View file
|
|
|
app/data/Sub_skill.json
ADDED
|
@@ -0,0 +1,751 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"role_name": "AI Engineer",
|
| 4 |
+
"description": "Profesional yang membangun dan mengoptimalkan model AI.",
|
| 5 |
+
"sub_skills": [
|
| 6 |
+
{
|
| 7 |
+
"id": "python_data_science",
|
| 8 |
+
"name": "Python for Data Science",
|
| 9 |
+
"levels": {
|
| 10 |
+
"beginner": {
|
| 11 |
+
"description": "Memahami sintaks dasar Python dan struktur data.",
|
| 12 |
+
"exam_topics": [
|
| 13 |
+
"Variables",
|
| 14 |
+
"Data Types",
|
| 15 |
+
"List/Dictionary",
|
| 16 |
+
"Control Flow"
|
| 17 |
+
],
|
| 18 |
+
"recommendation": {
|
| 19 |
+
"course_name": "Memulai Pemrograman dengan Python",
|
| 20 |
+
"specific_chapters": [
|
| 21 |
+
"Variable dan Assignment",
|
| 22 |
+
"Tipe Data",
|
| 23 |
+
"Kuis Coding: Variabel dan Assignment"
|
| 24 |
+
]
|
| 25 |
+
}
|
| 26 |
+
},
|
| 27 |
+
"intermediate": {
|
| 28 |
+
"description": "Mampu menggunakan fungsi, loop kompleks, dan OOP dasar.",
|
| 29 |
+
"exam_topics": [
|
| 30 |
+
"Functions",
|
| 31 |
+
"Loops",
|
| 32 |
+
"Object-Oriented Programming (OOP)"
|
| 33 |
+
],
|
| 34 |
+
"recommendation": {
|
| 35 |
+
"course_name": "Memulai Pemrograman dengan Python",
|
| 36 |
+
"specific_chapters": [
|
| 37 |
+
"Fungsi",
|
| 38 |
+
"Perulangan",
|
| 39 |
+
"Pengenalan Kelas",
|
| 40 |
+
"Kuis Object-Oriented Programming (OOP)"
|
| 41 |
+
]
|
| 42 |
+
}
|
| 43 |
+
},
|
| 44 |
+
"advanced": {
|
| 45 |
+
"description": "Menguasai library data, unit testing, dan style guide.",
|
| 46 |
+
"exam_topics": [
|
| 47 |
+
"Unit Testing",
|
| 48 |
+
"Matriks",
|
| 49 |
+
"Library Pengolahan Data"
|
| 50 |
+
],
|
| 51 |
+
"recommendation": {
|
| 52 |
+
"course_name": "Memulai Pemrograman dengan Python",
|
| 53 |
+
"specific_chapters": [
|
| 54 |
+
"Pengantar Unit Testing",
|
| 55 |
+
"Implementasi Matriks pada Python",
|
| 56 |
+
"Library Machine Learning"
|
| 57 |
+
]
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"id": "ml_fundamentals",
|
| 64 |
+
"name": "Machine Learning Fundamentals",
|
| 65 |
+
"levels": {
|
| 66 |
+
"beginner": {
|
| 67 |
+
"description": "Paham konsep dasar AI, Machine Learning, dan Taksonomi.",
|
| 68 |
+
"exam_topics": [
|
| 69 |
+
"AI Taxonomy",
|
| 70 |
+
"Supervised vs Unsupervised",
|
| 71 |
+
"Data Splitting"
|
| 72 |
+
],
|
| 73 |
+
"recommendation": {
|
| 74 |
+
"course_name": "Belajar Dasar AI",
|
| 75 |
+
"specific_chapters": [
|
| 76 |
+
"Taksonomi AI",
|
| 77 |
+
"Tipe-Tipe Machine Learning",
|
| 78 |
+
"Machine Learning Workflow"
|
| 79 |
+
]
|
| 80 |
+
}
|
| 81 |
+
},
|
| 82 |
+
"intermediate": {
|
| 83 |
+
"description": "Mampu menangani regresi, klasifikasi, dan data cleaning.",
|
| 84 |
+
"exam_topics": [
|
| 85 |
+
"Linear Regression",
|
| 86 |
+
"Decision Tree",
|
| 87 |
+
"Handling Missing Value",
|
| 88 |
+
"Overfitting"
|
| 89 |
+
],
|
| 90 |
+
"recommendation": {
|
| 91 |
+
"course_name": "Belajar Machine Learning untuk Pemula",
|
| 92 |
+
"specific_chapters": [
|
| 93 |
+
"Rangkuman Supervised Learning - Regresi",
|
| 94 |
+
"Decision Tree",
|
| 95 |
+
"Penanganan Outlier",
|
| 96 |
+
"Metode Deteksi Overfitting dan Underfitting"
|
| 97 |
+
]
|
| 98 |
+
}
|
| 99 |
+
},
|
| 100 |
+
"advanced": {
|
| 101 |
+
"description": "Menguasai unsupervised learning, tuning, dan evaluasi model kompleks.",
|
| 102 |
+
"exam_topics": [
|
| 103 |
+
"Clustering (K-Means/DBSCAN)",
|
| 104 |
+
"Hyperparameter Tuning (Grid Search)",
|
| 105 |
+
"Dimensionality Reduction (PCA)"
|
| 106 |
+
],
|
| 107 |
+
"recommendation": {
|
| 108 |
+
"course_name": "Belajar Machine Learning untuk Pemula",
|
| 109 |
+
"specific_chapters": [
|
| 110 |
+
"K-Means Clustering",
|
| 111 |
+
"DBSCAN",
|
| 112 |
+
"Grid Search",
|
| 113 |
+
"Dimensionality Reduction : LDA, PCA, dan t-SNE"
|
| 114 |
+
]
|
| 115 |
+
}
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
},
|
| 119 |
+
{
|
| 120 |
+
"id": "computer_vision",
|
| 121 |
+
"name": "Computer Vision",
|
| 122 |
+
"levels": {
|
| 123 |
+
"beginner": {
|
| 124 |
+
"description": "Paham dasar CNN dan pengolahan citra sederhana.",
|
| 125 |
+
"exam_topics": [
|
| 126 |
+
"CNN Architecture",
|
| 127 |
+
"Convolutions",
|
| 128 |
+
"Pooling",
|
| 129 |
+
"Image Preprocessing"
|
| 130 |
+
],
|
| 131 |
+
"recommendation": {
|
| 132 |
+
"course_name": "Belajar Fundamental Deep Learning",
|
| 133 |
+
"specific_chapters": [
|
| 134 |
+
"Dasar-Dasar Convolutional Neural Networks (CNNs)",
|
| 135 |
+
"Pembuatan Model Klasifikasi Gambar dengan CNN",
|
| 136 |
+
"Image Generation"
|
| 137 |
+
]
|
| 138 |
+
}
|
| 139 |
+
},
|
| 140 |
+
"intermediate": {
|
| 141 |
+
"description": "Mampu melakukan Transfer Learning dan Object Detection dasar.",
|
| 142 |
+
"exam_topics": [
|
| 143 |
+
"Transfer Learning",
|
| 144 |
+
"Object Detection Concepts",
|
| 145 |
+
"Dropout/Batch Normalization"
|
| 146 |
+
],
|
| 147 |
+
"recommendation": {
|
| 148 |
+
"course_name": "Machine Learning Terapan",
|
| 149 |
+
"specific_chapters": [
|
| 150 |
+
"Pengenalan Transfer Learning",
|
| 151 |
+
"Pengenalan Object Detection",
|
| 152 |
+
"Teknik-teknik Object Detection"
|
| 153 |
+
]
|
| 154 |
+
}
|
| 155 |
+
},
|
| 156 |
+
"advanced": {
|
| 157 |
+
"description": "Menguasai segmentasi gambar dan kustomisasi model visual.",
|
| 158 |
+
"exam_topics": [
|
| 159 |
+
"Image Segmentation",
|
| 160 |
+
"Advanced Object Detection",
|
| 161 |
+
"Custom Loss for Vision"
|
| 162 |
+
],
|
| 163 |
+
"recommendation": {
|
| 164 |
+
"course_name": "Membangun Proyek Deep Learning Tingkat Mahir",
|
| 165 |
+
"specific_chapters": [
|
| 166 |
+
"Image Segmentation dengan Deep Learning",
|
| 167 |
+
"Klasifikasi Gambar Lanjutan",
|
| 168 |
+
"Berkenalan dengan Object Detection"
|
| 169 |
+
]
|
| 170 |
+
}
|
| 171 |
+
}
|
| 172 |
+
}
|
| 173 |
+
},
|
| 174 |
+
{
|
| 175 |
+
"id": "nlp",
|
| 176 |
+
"name": "Natural Language Processing (NLP)",
|
| 177 |
+
"levels": {
|
| 178 |
+
"beginner": {
|
| 179 |
+
"description": "Dasar pemrosesan teks, tokenisasi, dan klasifikasi teks sederhana.",
|
| 180 |
+
"exam_topics": [
|
| 181 |
+
"Tokenization",
|
| 182 |
+
"Text Cleaning",
|
| 183 |
+
"Binary Classification"
|
| 184 |
+
],
|
| 185 |
+
"recommendation": {
|
| 186 |
+
"course_name": "Belajar Fundamental Deep Learning",
|
| 187 |
+
"specific_chapters": [
|
| 188 |
+
"Pengenalan Natural Language Processing",
|
| 189 |
+
"Latihan Pra-pemrosesan Teks",
|
| 190 |
+
"Binary vs Multiclass vs Multilabel Classification pada Text"
|
| 191 |
+
]
|
| 192 |
+
}
|
| 193 |
+
},
|
| 194 |
+
"intermediate": {
|
| 195 |
+
"description": "Memahami RNN, LSTM, dan Analisis Sentimen.",
|
| 196 |
+
"exam_topics": [
|
| 197 |
+
"RNN",
|
| 198 |
+
"LSTM",
|
| 199 |
+
"Sentiment Analysis",
|
| 200 |
+
"Word Embeddings"
|
| 201 |
+
],
|
| 202 |
+
"recommendation": {
|
| 203 |
+
"course_name": "Belajar Fundamental Deep Learning",
|
| 204 |
+
"specific_chapters": [
|
| 205 |
+
"Pengenalan Recurrent Neural Network",
|
| 206 |
+
"Algoritma RNN",
|
| 207 |
+
"Proyek Analisis Sentimen"
|
| 208 |
+
]
|
| 209 |
+
}
|
| 210 |
+
},
|
| 211 |
+
"advanced": {
|
| 212 |
+
"description": "Menguasai Transformers, Attention Mechanism, dan NLU lanjutan.",
|
| 213 |
+
"exam_topics": [
|
| 214 |
+
"Transformers",
|
| 215 |
+
"Attention Mechanism",
|
| 216 |
+
"Sequence to Sequence",
|
| 217 |
+
"IndoNLU"
|
| 218 |
+
],
|
| 219 |
+
"recommendation": {
|
| 220 |
+
"course_name": "Membangun Proyek Deep Learning Tingkat Mahir",
|
| 221 |
+
"specific_chapters": [
|
| 222 |
+
"Mengenal Transformer dalam NLP",
|
| 223 |
+
"Latihan Membangun Model Transformer Milik Kita Sendiri",
|
| 224 |
+
"Pengenalan IndoNLU"
|
| 225 |
+
]
|
| 226 |
+
}
|
| 227 |
+
}
|
| 228 |
+
}
|
| 229 |
+
},
|
| 230 |
+
{
|
| 231 |
+
"id": "time_series",
|
| 232 |
+
"name": "Time Series Analysis",
|
| 233 |
+
"levels": {
|
| 234 |
+
"beginner": {
|
| 235 |
+
"description": "Konsep dasar data deret waktu dan preprocessing.",
|
| 236 |
+
"exam_topics": [
|
| 237 |
+
"Time Series Basics",
|
| 238 |
+
"Trend & Seasonality",
|
| 239 |
+
"Data Splitting for Time Series"
|
| 240 |
+
],
|
| 241 |
+
"recommendation": {
|
| 242 |
+
"course_name": "Belajar Fundamental Deep Learning",
|
| 243 |
+
"specific_chapters": [
|
| 244 |
+
"Pengenalan Time Series",
|
| 245 |
+
"Data Preprocessing untuk Time Series",
|
| 246 |
+
"Tipe-Tipe Time Series"
|
| 247 |
+
]
|
| 248 |
+
}
|
| 249 |
+
},
|
| 250 |
+
"intermediate": {
|
| 251 |
+
"description": "Membuat model forecasting menggunakan LSTM.",
|
| 252 |
+
"exam_topics": [
|
| 253 |
+
"LSTM for Time Series",
|
| 254 |
+
"Windowing",
|
| 255 |
+
"MAE/Huber Loss"
|
| 256 |
+
],
|
| 257 |
+
"recommendation": {
|
| 258 |
+
"course_name": "Belajar Fundamental Deep Learning",
|
| 259 |
+
"specific_chapters": [
|
| 260 |
+
"Machine Learning pada Time Series",
|
| 261 |
+
"Proyek Kedua : Membuat Model Machine Learning dengan Data Time Series"
|
| 262 |
+
]
|
| 263 |
+
}
|
| 264 |
+
},
|
| 265 |
+
"advanced": {
|
| 266 |
+
"description": "Analisis Time Series tingkat lanjut dengan kustomisasi.",
|
| 267 |
+
"exam_topics": [
|
| 268 |
+
"Advanced Preprocessing",
|
| 269 |
+
"Custom Model for Time Series"
|
| 270 |
+
],
|
| 271 |
+
"recommendation": {
|
| 272 |
+
"course_name": "Membangun Proyek Deep Learning Tingkat Mahir",
|
| 273 |
+
"specific_chapters": [
|
| 274 |
+
"Eksplorasi Data dalam Time Series",
|
| 275 |
+
"Data Preprocessing Lanjutan Untuk Time Series",
|
| 276 |
+
"Latihan Custom Model... pada Time Series Model"
|
| 277 |
+
]
|
| 278 |
+
}
|
| 279 |
+
}
|
| 280 |
+
}
|
| 281 |
+
},
|
| 282 |
+
{
|
| 283 |
+
"id": "recommender_system",
|
| 284 |
+
"name": "Recommender Systems",
|
| 285 |
+
"levels": {
|
| 286 |
+
"beginner": {
|
| 287 |
+
"description": "Memahami konsep Content-Based Filtering.",
|
| 288 |
+
"exam_topics": [
|
| 289 |
+
"Content-Based Filtering",
|
| 290 |
+
"TF-IDF",
|
| 291 |
+
"Cosine Similarity"
|
| 292 |
+
],
|
| 293 |
+
"recommendation": {
|
| 294 |
+
"course_name": "Machine Learning Terapan",
|
| 295 |
+
"specific_chapters": [
|
| 296 |
+
"Pengenalan Sistem Rekomendasi",
|
| 297 |
+
"Content Based Filtering",
|
| 298 |
+
"Feature Engineering dengan TF-IDF"
|
| 299 |
+
]
|
| 300 |
+
}
|
| 301 |
+
},
|
| 302 |
+
"intermediate": {
|
| 303 |
+
"description": "Memahami Collaborative Filtering.",
|
| 304 |
+
"exam_topics": [
|
| 305 |
+
"Collaborative Filtering",
|
| 306 |
+
"User-Item Matrix",
|
| 307 |
+
"Embedding Layers"
|
| 308 |
+
],
|
| 309 |
+
"recommendation": {
|
| 310 |
+
"course_name": "Machine Learning Terapan",
|
| 311 |
+
"specific_chapters": [
|
| 312 |
+
"Collaborative Filtering",
|
| 313 |
+
"Model Development dengan Collaborative Filtering"
|
| 314 |
+
]
|
| 315 |
+
}
|
| 316 |
+
},
|
| 317 |
+
"advanced": {
|
| 318 |
+
"description": "Sistem rekomendasi Hybrid dan Neural Collaborative Filtering.",
|
| 319 |
+
"exam_topics": [
|
| 320 |
+
"Hybrid Recommender",
|
| 321 |
+
"Neural Collaborative Filtering",
|
| 322 |
+
"TensorFlow Recommenders"
|
| 323 |
+
],
|
| 324 |
+
"recommendation": {
|
| 325 |
+
"course_name": "Membangun Proyek Deep Learning Tingkat Mahir",
|
| 326 |
+
"specific_chapters": [
|
| 327 |
+
"Neural Collaborative Filtering",
|
| 328 |
+
"Retrieval dalam Sistem Rekomendasi",
|
| 329 |
+
"Hybrid Recommender System"
|
| 330 |
+
]
|
| 331 |
+
}
|
| 332 |
+
}
|
| 333 |
+
}
|
| 334 |
+
},
|
| 335 |
+
{
|
| 336 |
+
"id": "mlops_deployment",
|
| 337 |
+
"name": "MLOps & Deployment",
|
| 338 |
+
"levels": {
|
| 339 |
+
"beginner": {
|
| 340 |
+
"description": "Menyimpan model dan penggunaan TF Lite.",
|
| 341 |
+
"exam_topics": ["Saving Models (H5/SavedModel)", "TF Lite Basics"],
|
| 342 |
+
"recommendation": {
|
| 343 |
+
"course_name": "Belajar Fundamental Deep Learning",
|
| 344 |
+
"specific_chapters": [
|
| 345 |
+
"Format Penyimpanan Model",
|
| 346 |
+
"Pengenalan TensorFlow Lite",
|
| 347 |
+
"Latihan: Deploy Model ML Menggunakan TensorFlow Lite"
|
| 348 |
+
]
|
| 349 |
+
}
|
| 350 |
+
},
|
| 351 |
+
"intermediate": {
|
| 352 |
+
"description": "Deployment web (TFJS) dan Serving.",
|
| 353 |
+
"exam_topics": ["TensorFlow.js", "TF Serving", "Model Conversion"],
|
| 354 |
+
"recommendation": {
|
| 355 |
+
"course_name": "Belajar Fundamental Deep Learning",
|
| 356 |
+
"specific_chapters": [
|
| 357 |
+
"Pengenalan TensorFlow.js",
|
| 358 |
+
"Pengenalan TensorFlow Serving",
|
| 359 |
+
"Latihan: Deploy Model ML Menggunakan TensorFlow.js"
|
| 360 |
+
]
|
| 361 |
+
}
|
| 362 |
+
},
|
| 363 |
+
"advanced": {
|
| 364 |
+
"description": "Distributed training dan kustomisasi loop training.",
|
| 365 |
+
"exam_topics": [
|
| 366 |
+
"Distributed Training",
|
| 367 |
+
"Custom Training Loops",
|
| 368 |
+
"TensorFlow Extended (TFX) concepts"
|
| 369 |
+
],
|
| 370 |
+
"recommendation": {
|
| 371 |
+
"course_name": "Membangun Proyek Deep Learning Tingkat Mahir",
|
| 372 |
+
"specific_chapters": [
|
| 373 |
+
"Strategi Distributed Training dengan TensorFlow",
|
| 374 |
+
"Custom Training Loop",
|
| 375 |
+
"Reproducibility dalam TensorFlow"
|
| 376 |
+
]
|
| 377 |
+
}
|
| 378 |
+
}
|
| 379 |
+
}
|
| 380 |
+
}
|
| 381 |
+
]
|
| 382 |
+
},
|
| 383 |
+
{
|
| 384 |
+
"role_name": "Front-End Web Developer",
|
| 385 |
+
"description": "Pengembang yang fokus pada antarmuka visual dan interaksi pengguna di browser.",
|
| 386 |
+
"sub_skills": [
|
| 387 |
+
{
|
| 388 |
+
"id": "html_css_fundamentals",
|
| 389 |
+
"name": "HTML & CSS Fundamentals",
|
| 390 |
+
"levels": {
|
| 391 |
+
"beginner": {
|
| 392 |
+
"description": "Struktur dasar HTML5 dan styling CSS dasar.",
|
| 393 |
+
"exam_topics": [
|
| 394 |
+
"Semantic HTML",
|
| 395 |
+
"Box Model",
|
| 396 |
+
"Selectors",
|
| 397 |
+
"Text Styling"
|
| 398 |
+
],
|
| 399 |
+
"recommendation": {
|
| 400 |
+
"course_name": "Belajar Dasar Pemrograman Web",
|
| 401 |
+
"specific_chapters": [
|
| 402 |
+
"Semantic HTML: Header, Footer, Main, dan Nav",
|
| 403 |
+
"Box Model",
|
| 404 |
+
"Selector Dasar"
|
| 405 |
+
]
|
| 406 |
+
}
|
| 407 |
+
},
|
| 408 |
+
"intermediate": {
|
| 409 |
+
"description": "Layouting modern dengan Flexbox dan Positioning.",
|
| 410 |
+
"exam_topics": [
|
| 411 |
+
"Flexbox",
|
| 412 |
+
"Positioning (Relative/Absolute)",
|
| 413 |
+
"Floats"
|
| 414 |
+
],
|
| 415 |
+
"recommendation": {
|
| 416 |
+
"course_name": "Belajar Dasar Pemrograman Web",
|
| 417 |
+
"specific_chapters": [
|
| 418 |
+
"Pengantar Flexbox",
|
| 419 |
+
"Positioning",
|
| 420 |
+
"Latihan: Implementasi Flexbox pada Halaman Profil"
|
| 421 |
+
]
|
| 422 |
+
}
|
| 423 |
+
},
|
| 424 |
+
"advanced": {
|
| 425 |
+
"description": "Layout Grid kompleks dan Desain Responsif.",
|
| 426 |
+
"exam_topics": ["CSS Grid", "Media Queries", "Responsive Design"],
|
| 427 |
+
"recommendation": {
|
| 428 |
+
"course_name": "Belajar Fundamental Front-End Web Development",
|
| 429 |
+
"specific_chapters": [
|
| 430 |
+
"Pengantar CSS Grid",
|
| 431 |
+
"Grid Container dan Grid Item",
|
| 432 |
+
"Media Query",
|
| 433 |
+
"Responsive Layout"
|
| 434 |
+
]
|
| 435 |
+
}
|
| 436 |
+
}
|
| 437 |
+
}
|
| 438 |
+
},
|
| 439 |
+
{
|
| 440 |
+
"id": "javascript_core",
|
| 441 |
+
"name": "JavaScript Core Logic",
|
| 442 |
+
"levels": {
|
| 443 |
+
"beginner": {
|
| 444 |
+
"description": "Sintaks dasar JS, tipe data, dan logika dasar.",
|
| 445 |
+
"exam_topics": [
|
| 446 |
+
"Variables",
|
| 447 |
+
"Data Types",
|
| 448 |
+
"Operators",
|
| 449 |
+
"Functions"
|
| 450 |
+
],
|
| 451 |
+
"recommendation": {
|
| 452 |
+
"course_name": "Belajar Dasar Pemrograman JavaScript",
|
| 453 |
+
"specific_chapters": [
|
| 454 |
+
"Variabel",
|
| 455 |
+
"Tipe Data",
|
| 456 |
+
"Logika Operator dan If Else",
|
| 457 |
+
"Function"
|
| 458 |
+
]
|
| 459 |
+
}
|
| 460 |
+
},
|
| 461 |
+
"intermediate": {
|
| 462 |
+
"description": "Manipulasi struktur data dan OOP dasar.",
|
| 463 |
+
"exam_topics": ["Arrays", "Objects", "Map/Set", "Basic OOP"],
|
| 464 |
+
"recommendation": {
|
| 465 |
+
"course_name": "Belajar Dasar Pemrograman JavaScript",
|
| 466 |
+
"specific_chapters": [
|
| 467 |
+
"Menstrukturkan Data dengan Object",
|
| 468 |
+
"Array",
|
| 469 |
+
"Map",
|
| 470 |
+
"Pengenalan OOP"
|
| 471 |
+
]
|
| 472 |
+
}
|
| 473 |
+
},
|
| 474 |
+
"advanced": {
|
| 475 |
+
"description": "Konsep ES6+, Functional Programming, dan Module.",
|
| 476 |
+
"exam_topics": [
|
| 477 |
+
"ES6 Modules",
|
| 478 |
+
"Arrow Functions",
|
| 479 |
+
"Higher Order Functions",
|
| 480 |
+
"Destructuring"
|
| 481 |
+
],
|
| 482 |
+
"recommendation": {
|
| 483 |
+
"course_name": "Belajar Dasar Pemrograman JavaScript",
|
| 484 |
+
"specific_chapters": [
|
| 485 |
+
"ES6 Module",
|
| 486 |
+
"Arrow Function",
|
| 487 |
+
"Destructuring Object & Array",
|
| 488 |
+
"Functional Programming"
|
| 489 |
+
]
|
| 490 |
+
}
|
| 491 |
+
}
|
| 492 |
+
}
|
| 493 |
+
},
|
| 494 |
+
{
|
| 495 |
+
"id": "dom_events",
|
| 496 |
+
"name": "DOM Manipulation & Interactivity",
|
| 497 |
+
"levels": {
|
| 498 |
+
"beginner": {
|
| 499 |
+
"description": "Memilih elemen dan event dasar.",
|
| 500 |
+
"exam_topics": [
|
| 501 |
+
"getElementById/querySelector",
|
| 502 |
+
"Click Events",
|
| 503 |
+
"Basic DOM Manipulation"
|
| 504 |
+
],
|
| 505 |
+
"recommendation": {
|
| 506 |
+
"course_name": "Belajar Membuat Front-End Web untuk Pemula",
|
| 507 |
+
"specific_chapters": [
|
| 508 |
+
"Mencari DOM",
|
| 509 |
+
"Manipulasi Konten Melalui innerText",
|
| 510 |
+
"Menambahkan Event Handler"
|
| 511 |
+
]
|
| 512 |
+
}
|
| 513 |
+
},
|
| 514 |
+
"intermediate": {
|
| 515 |
+
"description": "Event bubbling, form handling, dan membuat elemen dinamis.",
|
| 516 |
+
"exam_topics": [
|
| 517 |
+
"Event Bubbling",
|
| 518 |
+
"Form Events",
|
| 519 |
+
"Creating Elements"
|
| 520 |
+
],
|
| 521 |
+
"recommendation": {
|
| 522 |
+
"course_name": "Belajar Membuat Front-End Web untuk Pemula",
|
| 523 |
+
"specific_chapters": [
|
| 524 |
+
"Event Bubbling dan Capturing",
|
| 525 |
+
"Form Event",
|
| 526 |
+
"Membuat Elemen HTML"
|
| 527 |
+
]
|
| 528 |
+
}
|
| 529 |
+
},
|
| 530 |
+
"advanced": {
|
| 531 |
+
"description": "Web Storage (Local/Session) dan Custom Events.",
|
| 532 |
+
"exam_topics": [
|
| 533 |
+
"LocalStorage",
|
| 534 |
+
"SessionStorage",
|
| 535 |
+
"JSON Parsing",
|
| 536 |
+
"Custom Events"
|
| 537 |
+
],
|
| 538 |
+
"recommendation": {
|
| 539 |
+
"course_name": "Belajar Membuat Front-End Web untuk Pemula",
|
| 540 |
+
"specific_chapters": [
|
| 541 |
+
"Pengertian dan Fungsi Web Storage",
|
| 542 |
+
"Implementasi Web Storage",
|
| 543 |
+
"Custom Event"
|
| 544 |
+
]
|
| 545 |
+
}
|
| 546 |
+
}
|
| 547 |
+
}
|
| 548 |
+
},
|
| 549 |
+
{
|
| 550 |
+
"id": "async_api",
|
| 551 |
+
"name": "Asynchronous & API",
|
| 552 |
+
"levels": {
|
| 553 |
+
"beginner": {
|
| 554 |
+
"description": "Konsep dasar Async dan Callback.",
|
| 555 |
+
"exam_topics": [
|
| 556 |
+
"Synchronous vs Asynchronous",
|
| 557 |
+
"Callbacks",
|
| 558 |
+
"setTimeout"
|
| 559 |
+
],
|
| 560 |
+
"recommendation": {
|
| 561 |
+
"course_name": "Belajar Dasar Pemrograman JavaScript",
|
| 562 |
+
"specific_chapters": [
|
| 563 |
+
"Apa Itu Asynchronous Process",
|
| 564 |
+
"Penanganan dengan Callback",
|
| 565 |
+
"setTimeout"
|
| 566 |
+
]
|
| 567 |
+
}
|
| 568 |
+
},
|
| 569 |
+
"intermediate": {
|
| 570 |
+
"description": "Promise dan Fetch API dasar.",
|
| 571 |
+
"exam_topics": ["Promise", "Fetch API Basics", "JSON Data"],
|
| 572 |
+
"recommendation": {
|
| 573 |
+
"course_name": "Belajar Fundamental Front-End Web Development",
|
| 574 |
+
"specific_chapters": [
|
| 575 |
+
"Promise",
|
| 576 |
+
"Dasar Penggunaan Fetch",
|
| 577 |
+
"Mengonsumsi Data API"
|
| 578 |
+
]
|
| 579 |
+
}
|
| 580 |
+
},
|
| 581 |
+
"advanced": {
|
| 582 |
+
"description": "Async/Await, Error Handling, dan Chaining.",
|
| 583 |
+
"exam_topics": [
|
| 584 |
+
"Async/Await",
|
| 585 |
+
"Try/Catch",
|
| 586 |
+
"Promise.all",
|
| 587 |
+
"Chaining"
|
| 588 |
+
],
|
| 589 |
+
"recommendation": {
|
| 590 |
+
"course_name": "Belajar Fundamental Front-End Web Development",
|
| 591 |
+
"specific_chapters": [
|
| 592 |
+
"Sintaks Async/Await",
|
| 593 |
+
"Error Handling pada Async",
|
| 594 |
+
"Promise Berantai"
|
| 595 |
+
]
|
| 596 |
+
}
|
| 597 |
+
}
|
| 598 |
+
}
|
| 599 |
+
},
|
| 600 |
+
{
|
| 601 |
+
"id": "web_components",
|
| 602 |
+
"name": "Front-End Architecture (Web Components)",
|
| 603 |
+
"levels": {
|
| 604 |
+
"beginner": {
|
| 605 |
+
"description": "Konsep dasar Custom Elements.",
|
| 606 |
+
"exam_topics": ["Custom Elements", "HTMLElement Class"],
|
| 607 |
+
"recommendation": {
|
| 608 |
+
"course_name": "Belajar Fundamental Front-End Web Development",
|
| 609 |
+
"specific_chapters": [
|
| 610 |
+
"Apa Itu Web Component",
|
| 611 |
+
"Basic Custom Element"
|
| 612 |
+
]
|
| 613 |
+
}
|
| 614 |
+
},
|
| 615 |
+
"intermediate": {
|
| 616 |
+
"description": "Shadow DOM dan Template.",
|
| 617 |
+
"exam_topics": ["Shadow DOM", "Templates", "Encapsulation"],
|
| 618 |
+
"recommendation": {
|
| 619 |
+
"course_name": "Belajar Fundamental Front-End Web Development",
|
| 620 |
+
"specific_chapters": [
|
| 621 |
+
"Pengantar Shadow DOM",
|
| 622 |
+
"Shadow DOM untuk Web Component"
|
| 623 |
+
]
|
| 624 |
+
}
|
| 625 |
+
},
|
| 626 |
+
"advanced": {
|
| 627 |
+
"description": "Lifecycle Callbacks dan Slots.",
|
| 628 |
+
"exam_topics": [
|
| 629 |
+
"connectedCallback",
|
| 630 |
+
"attributeChangedCallback",
|
| 631 |
+
"Slots"
|
| 632 |
+
],
|
| 633 |
+
"recommendation": {
|
| 634 |
+
"course_name": "Belajar Fundamental Front-End Web Development",
|
| 635 |
+
"specific_chapters": [
|
| 636 |
+
"Siklus Hidup (Lifecycle)",
|
| 637 |
+
"Fleksibel dengan Slot Element"
|
| 638 |
+
]
|
| 639 |
+
}
|
| 640 |
+
}
|
| 641 |
+
}
|
| 642 |
+
},
|
| 643 |
+
{
|
| 644 |
+
"id": "pwa_performance",
|
| 645 |
+
"name": "PWA & Performance Optimization",
|
| 646 |
+
"levels": {
|
| 647 |
+
"beginner": {
|
| 648 |
+
"description": "Dasar PWA dan Manifest.",
|
| 649 |
+
"exam_topics": ["Web App Manifest", "Service Worker Basics"],
|
| 650 |
+
"recommendation": {
|
| 651 |
+
"course_name": "Belajar Pengembangan Web Intermediate",
|
| 652 |
+
"specific_chapters": [
|
| 653 |
+
"Pengenalan Progressive Web Apps",
|
| 654 |
+
"Web App Manifest",
|
| 655 |
+
"Registrasi Service Worker"
|
| 656 |
+
]
|
| 657 |
+
}
|
| 658 |
+
},
|
| 659 |
+
"intermediate": {
|
| 660 |
+
"description": "Caching dan Workbox.",
|
| 661 |
+
"exam_topics": [
|
| 662 |
+
"Cache API",
|
| 663 |
+
"Workbox",
|
| 664 |
+
"Caching Strategies (StaleWhileRevalidate)"
|
| 665 |
+
],
|
| 666 |
+
"recommendation": {
|
| 667 |
+
"course_name": "Belajar Pengembangan Web Intermediate",
|
| 668 |
+
"specific_chapters": [
|
| 669 |
+
"Workbox Precaching",
|
| 670 |
+
"Caching Strategies",
|
| 671 |
+
"Latihan: Offline Capability dengan Workbox"
|
| 672 |
+
]
|
| 673 |
+
}
|
| 674 |
+
},
|
| 675 |
+
"advanced": {
|
| 676 |
+
"description": "Optimasi performa, Lazy Loading, dan Web Vitals.",
|
| 677 |
+
"exam_topics": [
|
| 678 |
+
"Image Optimization",
|
| 679 |
+
"Lazy Loading",
|
| 680 |
+
"Web Vitals (LCP, FID, CLS)",
|
| 681 |
+
"Bundle Analyzer"
|
| 682 |
+
],
|
| 683 |
+
"recommendation": {
|
| 684 |
+
"course_name": "Belajar Pengembangan Web Intermediate",
|
| 685 |
+
"specific_chapters": [
|
| 686 |
+
"Image Optimization",
|
| 687 |
+
"Menggunakan Lazy Loading Image",
|
| 688 |
+
"Web Vitals",
|
| 689 |
+
"Bundle Analyzer"
|
| 690 |
+
]
|
| 691 |
+
}
|
| 692 |
+
}
|
| 693 |
+
}
|
| 694 |
+
},
|
| 695 |
+
{
|
| 696 |
+
"id": "testing_automation",
|
| 697 |
+
"name": "Testing & Automation",
|
| 698 |
+
"levels": {
|
| 699 |
+
"beginner": {
|
| 700 |
+
"description": "Dasar pengujian manual dan konsep testing.",
|
| 701 |
+
"exam_topics": [
|
| 702 |
+
"Why Testing?",
|
| 703 |
+
"Manual vs Automated",
|
| 704 |
+
"Types of Testing"
|
| 705 |
+
],
|
| 706 |
+
"recommendation": {
|
| 707 |
+
"course_name": "Belajar Dasar Pemrograman JavaScript",
|
| 708 |
+
"specific_chapters": [
|
| 709 |
+
"Pengenalan JavaScript Testing",
|
| 710 |
+
"Pengujian Program"
|
| 711 |
+
]
|
| 712 |
+
}
|
| 713 |
+
},
|
| 714 |
+
"intermediate": {
|
| 715 |
+
"description": "Unit Testing dan Integration Testing.",
|
| 716 |
+
"exam_topics": [
|
| 717 |
+
"Jest",
|
| 718 |
+
"Unit Testing",
|
| 719 |
+
"Integration Testing",
|
| 720 |
+
"TDD"
|
| 721 |
+
],
|
| 722 |
+
"recommendation": {
|
| 723 |
+
"course_name": "Belajar Pengembangan Web Intermediate",
|
| 724 |
+
"specific_chapters": [
|
| 725 |
+
"TDD Menggunakan Jest",
|
| 726 |
+
"Framework Automation Testing",
|
| 727 |
+
"Menulis Kode Pengujian"
|
| 728 |
+
]
|
| 729 |
+
}
|
| 730 |
+
},
|
| 731 |
+
"advanced": {
|
| 732 |
+
"description": "E2E Testing dan CI/CD.",
|
| 733 |
+
"exam_topics": [
|
| 734 |
+
"End-to-End Testing",
|
| 735 |
+
"CI/CD Concepts",
|
| 736 |
+
"GitHub Actions"
|
| 737 |
+
],
|
| 738 |
+
"recommendation": {
|
| 739 |
+
"course_name": "Belajar Pengembangan Web Intermediate",
|
| 740 |
+
"specific_chapters": [
|
| 741 |
+
"Kasus 5: End-to-End Testing",
|
| 742 |
+
"Pendahuluan Continuous Integration & Continuous Deployment",
|
| 743 |
+
"Latihan: Membuat CI Pipeline menggunakan GitHub Action"
|
| 744 |
+
]
|
| 745 |
+
}
|
| 746 |
+
}
|
| 747 |
+
}
|
| 748 |
+
}
|
| 749 |
+
]
|
| 750 |
+
}
|
| 751 |
+
]
|
app/main.py
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI, HTTPException, Body
|
| 2 |
+
from app import schemas
|
| 3 |
+
from app.services.llm_engine import llm_engine
|
| 4 |
+
from app.services.skill_manager import skill_manager
|
| 5 |
+
import pandas as pd
|
| 6 |
+
import pickle
|
| 7 |
+
import ast
|
| 8 |
+
import os
|
| 9 |
+
from sklearn.metrics.pairwise import linear_kernel
|
| 10 |
+
from app.services.psych_service import psych_service
|
| 11 |
+
from typing import List
|
| 12 |
+
|
| 13 |
+
app = FastAPI(title="MORA - AI Learning Assistant (Final)")
|
| 14 |
+
|
| 15 |
+
# --- GLOBAL MODELS STORE ---
|
| 16 |
+
models = {
|
| 17 |
+
'df': None,
|
| 18 |
+
'tfidf': None,
|
| 19 |
+
'matrix': None
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
# --- 1. STARTUP: LOAD MODEL .PKL ---
|
| 23 |
+
@app.on_event("startup")
|
| 24 |
+
def load_models():
|
| 25 |
+
print("🔄 Loading Pre-trained Models...")
|
| 26 |
+
|
| 27 |
+
# Menggunakan Absolute Path agar aman dijalankan dari mana saja
|
| 28 |
+
current_dir = os.path.dirname(os.path.abspath(__file__))
|
| 29 |
+
base_dir = os.path.dirname(current_dir)
|
| 30 |
+
artifacts_dir = os.path.join(base_dir, "model_artifacts")
|
| 31 |
+
|
| 32 |
+
try:
|
| 33 |
+
with open(os.path.join(artifacts_dir, 'courses_df.pkl'), 'rb') as f:
|
| 34 |
+
models['df'] = pickle.load(f)
|
| 35 |
+
with open(os.path.join(artifacts_dir, 'tfidf_vectorizer.pkl'), 'rb') as f:
|
| 36 |
+
models['tfidf'] = pickle.load(f)
|
| 37 |
+
with open(os.path.join(artifacts_dir, 'tfidf_matrix.pkl'), 'rb') as f:
|
| 38 |
+
models['matrix'] = pickle.load(f)
|
| 39 |
+
print(f"✅ Models Loaded Successfully from: {artifacts_dir}")
|
| 40 |
+
except Exception as e:
|
| 41 |
+
print(f"❌ Error Loading Models: {e}")
|
| 42 |
+
print(f"👉 Pastikan folder 'model_artifacts' ada di: {base_dir}")
|
| 43 |
+
|
| 44 |
+
# --- 2. ENDPOINT REKOMENDASI (ML POWERED) ---
|
| 45 |
+
@app.post("/recommendations")
|
| 46 |
+
def get_recommendations(user: schemas.UserProfile):
|
| 47 |
+
df = models.get('df')
|
| 48 |
+
tfidf = models.get('tfidf')
|
| 49 |
+
matrix = models.get('matrix')
|
| 50 |
+
|
| 51 |
+
# Jika model belum siap, return kosong biar gak crash
|
| 52 |
+
if df is None: return []
|
| 53 |
+
|
| 54 |
+
# Mapping Level agar komputer mengerti urutan
|
| 55 |
+
LEVEL_MAP = {
|
| 56 |
+
'beginner': 1, 'dasar': 1, 'pemula': 1,
|
| 57 |
+
'intermediate': 2, 'menengah': 2,
|
| 58 |
+
'advanced': 3, 'mahir': 3, 'expert': 3, 'profesional': 3
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
final_recs = []
|
| 62 |
+
# Set course yang sudah diambil agar tidak disarankan lagi
|
| 63 |
+
seen_courses = set(user.completed_courses)
|
| 64 |
+
|
| 65 |
+
# --- LOGIKA CORE: Loop setiap 'Gap' Skill User ---
|
| 66 |
+
for gap in user.missing_skills:
|
| 67 |
+
skill_query = gap.skill_name
|
| 68 |
+
target_lvl_str = gap.target_level.lower()
|
| 69 |
+
target_lvl_num = LEVEL_MAP.get(target_lvl_str, 1) # Default 1 (Pemula)
|
| 70 |
+
|
| 71 |
+
try:
|
| 72 |
+
# 1. Transform nama skill jadi vektor angka
|
| 73 |
+
vec = tfidf.transform([skill_query.lower()])
|
| 74 |
+
|
| 75 |
+
# 2. Hitung kemiripan (Cosine Similarity)
|
| 76 |
+
scores = linear_kernel(vec, matrix).flatten()
|
| 77 |
+
|
| 78 |
+
# 3. Ambil Top 15 kandidat
|
| 79 |
+
indices = scores.argsort()[:-15:-1]
|
| 80 |
+
|
| 81 |
+
for idx in indices:
|
| 82 |
+
score = scores[idx]
|
| 83 |
+
# Filter awal: Skip jika kemiripan text terlalu rendah
|
| 84 |
+
if score < 0.1: continue
|
| 85 |
+
|
| 86 |
+
course = df.iloc[idx]
|
| 87 |
+
c_id = int(course['course_id'])
|
| 88 |
+
|
| 89 |
+
if c_id in seen_courses: continue
|
| 90 |
+
|
| 91 |
+
# --- FILTER LEVEL (ADAPTIVE) ---
|
| 92 |
+
c_lvl_str = str(course['level_name']).lower()
|
| 93 |
+
c_lvl_num = LEVEL_MAP.get(c_lvl_str, 1)
|
| 94 |
+
|
| 95 |
+
# Logic: Jangan kasih course yang levelnya DI ATAS target (kejauhan)
|
| 96 |
+
if c_lvl_num > target_lvl_num: continue
|
| 97 |
+
|
| 98 |
+
# Logic Badge (Penanda)
|
| 99 |
+
if c_lvl_num == target_lvl_num:
|
| 100 |
+
badge = "🎯 Target Pas"
|
| 101 |
+
else:
|
| 102 |
+
badge = "↺ Review Dasar"
|
| 103 |
+
|
| 104 |
+
# Parse Tutorial List (karena di CSV formatnya string)
|
| 105 |
+
tuts = course['tutorial_list']
|
| 106 |
+
if isinstance(tuts, str):
|
| 107 |
+
try: tuts = ast.literal_eval(tuts)
|
| 108 |
+
except: tuts = []
|
| 109 |
+
|
| 110 |
+
# Tambahkan ke hasil
|
| 111 |
+
final_recs.append({
|
| 112 |
+
"skill": skill_query,
|
| 113 |
+
"current_level": gap.target_level,
|
| 114 |
+
"course_to_take": course['course_name'],
|
| 115 |
+
"chapters": tuts[:3], # Ambil 3 bab pertama
|
| 116 |
+
"match_score": round(score * 100, 1),
|
| 117 |
+
"badge": badge
|
| 118 |
+
})
|
| 119 |
+
seen_courses.add(c_id)
|
| 120 |
+
|
| 121 |
+
except Exception as e:
|
| 122 |
+
print(f"Error processing {skill_query}: {e}")
|
| 123 |
+
continue
|
| 124 |
+
|
| 125 |
+
# Urutkan berdasarkan skor kecocokan tertinggi
|
| 126 |
+
final_recs = sorted(final_recs, key=lambda x: x['match_score'], reverse=True)
|
| 127 |
+
|
| 128 |
+
return final_recs[:5] # Kembalikan Top 5
|
| 129 |
+
|
| 130 |
+
# --- 3. ENDPOINT CHAT ROUTER ---
|
| 131 |
+
# app/main.py (Bagian process_chat saja)
|
| 132 |
+
|
| 133 |
+
@app.post("/chat/process", response_model=schemas.ChatResponse)
|
| 134 |
+
async def process_chat(req: schemas.ChatRequest):
|
| 135 |
+
# 1. Ambil data role
|
| 136 |
+
role_data = skill_manager.get_role_data(req.role)
|
| 137 |
+
# --- [UPDATE BARU: Ektrak Silabus Lengkap] ---
|
| 138 |
+
# Kita buat string rapi berisi Skill + Topik-topiknya
|
| 139 |
+
syllabus_list = []
|
| 140 |
+
if role_data:
|
| 141 |
+
for s in role_data['sub_skills']:
|
| 142 |
+
# Ambil nama skill utama
|
| 143 |
+
skill_name = s['name']
|
| 144 |
+
|
| 145 |
+
# Kumpulkan semua exam_topics dari level beginner, intermediate, advanced
|
| 146 |
+
all_topics = []
|
| 147 |
+
for lvl_key, lvl_data in s['levels'].items():
|
| 148 |
+
topics = lvl_data.get('exam_topics', [])
|
| 149 |
+
all_topics.extend(topics)
|
| 150 |
+
|
| 151 |
+
# Hapus duplikat topik dan gabungkan jadi string
|
| 152 |
+
unique_topics = list(set(all_topics))
|
| 153 |
+
topic_str = ", ".join(unique_topics)
|
| 154 |
+
|
| 155 |
+
syllabus_list.append(f"TOPIC {skill_name}: [{topic_str}]")
|
| 156 |
+
|
| 157 |
+
# Variable ini yang nanti dikirim ke LLM
|
| 158 |
+
syllabus_context = "\n".join(syllabus_list)
|
| 159 |
+
# ---------------------------------------------
|
| 160 |
+
skill_names = [s['name'] for s in role_data['sub_skills']] if role_data else []
|
| 161 |
+
|
| 162 |
+
# 2. Router
|
| 163 |
+
intent = await llm_engine.process_user_intent(req.message, skill_names)
|
| 164 |
+
|
| 165 |
+
action = intent.get('action')
|
| 166 |
+
# PERUBAHAN 1: Ambil List skills, bukan single skill
|
| 167 |
+
detected_skills_list = intent.get('detected_skills', [])
|
| 168 |
+
|
| 169 |
+
final_reply = ""
|
| 170 |
+
response_data = None
|
| 171 |
+
|
| 172 |
+
# 3. Logic
|
| 173 |
+
if action == "START_EXAM":
|
| 174 |
+
target_skill_ids = []
|
| 175 |
+
|
| 176 |
+
# A. Cari ID untuk SEMUA skill yang dideteksi (Looping)
|
| 177 |
+
if detected_skills_list and role_data:
|
| 178 |
+
for ds in detected_skills_list:
|
| 179 |
+
for s in role_data['sub_skills']:
|
| 180 |
+
# Cek kemiripan nama
|
| 181 |
+
if s['name'].lower() in ds.lower() or ds.lower() in s['name'].lower():
|
| 182 |
+
if s['id'] not in target_skill_ids:
|
| 183 |
+
target_skill_ids.append(s['id'])
|
| 184 |
+
|
| 185 |
+
# B. Jika ada skill yang valid, generate soal untuk MASING-MASING skill
|
| 186 |
+
if target_skill_ids:
|
| 187 |
+
exam_list = []
|
| 188 |
+
|
| 189 |
+
for skid in target_skill_ids:
|
| 190 |
+
# Ambil level user
|
| 191 |
+
user_current_level = req.current_skills.get(skid, "beginner")
|
| 192 |
+
skill_details = skill_manager.get_skill_details(req.role, skid)
|
| 193 |
+
level_data = skill_details['levels'].get(user_current_level, skill_details['levels']['beginner'])
|
| 194 |
+
|
| 195 |
+
# Generate Soal (Sequential)
|
| 196 |
+
llm_res = await llm_engine.generate_question(level_data['exam_topics'], user_current_level)
|
| 197 |
+
|
| 198 |
+
# Masukkan ke list soal
|
| 199 |
+
exam_list.append({
|
| 200 |
+
"skill_id": skid,
|
| 201 |
+
"skill_name": skill_details['name'],
|
| 202 |
+
"level": user_current_level,
|
| 203 |
+
"question": llm_res['question_text'],
|
| 204 |
+
"context": llm_res['grading_rubric']
|
| 205 |
+
})
|
| 206 |
+
|
| 207 |
+
# C. Format Response Baru (Multi-Exam)
|
| 208 |
+
response_data = {
|
| 209 |
+
"mode": "multiple_exams", # Penanda buat frontend
|
| 210 |
+
"exams": exam_list # List soal ada di sini
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
skill_display = ", ".join([x['skill_name'] for x in exam_list])
|
| 214 |
+
final_reply = f"Siap! Saya siapkan {len(exam_list)} ujian untukmu: **{skill_display}**. Silakan kerjakan satu per satu di bawah ini! 👇"
|
| 215 |
+
|
| 216 |
+
else:
|
| 217 |
+
# Jika user mau ujian tapi skill ga jelas
|
| 218 |
+
action = "CASUAL_CHAT"
|
| 219 |
+
final_reply = await llm_engine.casual_chat(req.message, [m.dict() for m in req.history], syllabus_context)
|
| 220 |
+
|
| 221 |
+
elif action == "START_PSYCH_TEST":
|
| 222 |
+
response_data = {"trigger_psych_test": True}
|
| 223 |
+
final_reply = "Tenang, Mora punya tes kepribadian singkat untuk membantumu memilih job role antara **AI Engineer** atau **Front-End Developer**. Yuk coba sekarang! 👇"
|
| 224 |
+
|
| 225 |
+
elif action == "GET_RECOMMENDATION":
|
| 226 |
+
response_data = {"trigger_recommendation": True}
|
| 227 |
+
final_reply = "Sedang menganalisis kebutuhan belajarmu..."
|
| 228 |
+
|
| 229 |
+
elif action == "CASUAL_CHAT":
|
| 230 |
+
final_reply = await llm_engine.casual_chat(req.message, [m.dict() for m in req.history], syllabus_context)
|
| 231 |
+
|
| 232 |
+
return schemas.ChatResponse(
|
| 233 |
+
reply=final_reply,
|
| 234 |
+
action_type=action,
|
| 235 |
+
data=response_data
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
@app.post("/exam/submit", response_model=schemas.EvaluationResponse)
|
| 240 |
+
async def submit_exam(sub: schemas.AnswerSubmission):
|
| 241 |
+
evaluation = await llm_engine.evaluate_answer(
|
| 242 |
+
user_answer=sub.user_answer,
|
| 243 |
+
question_context={
|
| 244 |
+
"question_text": "REFER TO CONTEXT",
|
| 245 |
+
"grading_rubric": sub.question_context
|
| 246 |
+
}
|
| 247 |
+
)
|
| 248 |
+
|
| 249 |
+
is_passed = evaluation['is_correct'] and evaluation['score'] >= 70
|
| 250 |
+
suggested_lvl = "intermediate" if is_passed else None # Logika sederhana
|
| 251 |
+
|
| 252 |
+
return schemas.EvaluationResponse(
|
| 253 |
+
is_correct=evaluation['is_correct'],
|
| 254 |
+
score=evaluation['score'],
|
| 255 |
+
feedback=evaluation['feedback'],
|
| 256 |
+
passed=is_passed,
|
| 257 |
+
suggested_new_level=suggested_lvl
|
| 258 |
+
)
|
| 259 |
+
|
| 260 |
+
# --- 5. ENDPOINT PROGRESS ---
|
| 261 |
+
@app.post("/progress")
|
| 262 |
+
def get_progress(req: schemas.ProgressRequest):
|
| 263 |
+
role_data = skill_manager.get_role_data(req.role)
|
| 264 |
+
if not role_data: return []
|
| 265 |
+
|
| 266 |
+
progress_report = []
|
| 267 |
+
level_weight = {"beginner": 0, "intermediate": 1, "advanced": 2}
|
| 268 |
+
|
| 269 |
+
for skill in role_data['sub_skills']:
|
| 270 |
+
skill_id = skill['id']
|
| 271 |
+
user_level = req.current_skills.get(skill_id, "beginner")
|
| 272 |
+
|
| 273 |
+
# Hitung Persen
|
| 274 |
+
current_stage = level_weight.get(user_level, 0)
|
| 275 |
+
percent = int((current_stage / 3) * 100)
|
| 276 |
+
if user_level == "beginner": percent = 5
|
| 277 |
+
elif user_level == "intermediate": percent = 50
|
| 278 |
+
elif user_level == "advanced": percent = 80
|
| 279 |
+
|
| 280 |
+
# Sisa tutorial (dummy/static logic karena detail ada di rekomendasi)
|
| 281 |
+
remaining = 0
|
| 282 |
+
|
| 283 |
+
progress_report.append({
|
| 284 |
+
"skill_name": skill['name'],
|
| 285 |
+
"current_level": user_level,
|
| 286 |
+
"progress_percent": percent,
|
| 287 |
+
"remaining_tutorials": remaining
|
| 288 |
+
})
|
| 289 |
+
|
| 290 |
+
return progress_report
|
| 291 |
+
|
| 292 |
+
# ==========================================
|
| 293 |
+
# ENDPOINT PSIKOLOGI (JOB ROLE TEST)
|
| 294 |
+
# ==========================================
|
| 295 |
+
|
| 296 |
+
@app.get("/psych/questions", response_model=List[schemas.PsychQuestionItem])
|
| 297 |
+
def get_psych_questions():
|
| 298 |
+
"""Mengambil daftar soal tes kepribadian."""
|
| 299 |
+
return psych_service.get_all_questions()
|
| 300 |
+
|
| 301 |
+
@app.post("/psych/submit", response_model=schemas.PsychResultResponse)
|
| 302 |
+
async def submit_psych_test(req: schemas.PsychSubmitRequest):
|
| 303 |
+
"""Menerima jawaban user, hitung skor, dan minta analisis LLM."""
|
| 304 |
+
|
| 305 |
+
# 1. Hitung Skor secara matematis
|
| 306 |
+
result = psych_service.calculate_result(req.answers)
|
| 307 |
+
|
| 308 |
+
winner = result["winner"]
|
| 309 |
+
scores = result["scores"]
|
| 310 |
+
traits = result["traits"]
|
| 311 |
+
|
| 312 |
+
# 2. Minta LLM buatkan kata-kata mutiara/analisis
|
| 313 |
+
analysis_text = await llm_engine.analyze_psych_result(winner, traits)
|
| 314 |
+
|
| 315 |
+
return schemas.PsychResultResponse(
|
| 316 |
+
suggested_role=winner,
|
| 317 |
+
analysis=analysis_text,
|
| 318 |
+
scores=scores
|
| 319 |
+
)
|
app/schemas.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel
|
| 2 |
+
from typing import List, Optional, Dict, Any
|
| 3 |
+
|
| 4 |
+
# ==========================================
|
| 5 |
+
# 1. CHAT & ROUTING SYSTEM
|
| 6 |
+
# ==========================================
|
| 7 |
+
|
| 8 |
+
class ChatMessage(BaseModel):
|
| 9 |
+
"""Format pesan tunggal untuk riwayat chat (History)."""
|
| 10 |
+
role: str # "user" atau "assistant"
|
| 11 |
+
content: str
|
| 12 |
+
|
| 13 |
+
class ChatRequest(BaseModel):
|
| 14 |
+
"""
|
| 15 |
+
Payload utama yang dikirim Frontend saat user chatting.
|
| 16 |
+
Backend butuh 'current_skills' dan 'role' karena Backend tidak punya Database.
|
| 17 |
+
"""
|
| 18 |
+
message: str # Pesan user saat ini
|
| 19 |
+
role: str # Contoh: "AI Engineer"
|
| 20 |
+
history: List[ChatMessage] = [] # 5 pesan terakhir untuk konteks percakapan
|
| 21 |
+
current_skills: Dict[str, str] = {} # Contoh: {"python": "intermediate", "nlp": "beginner"}
|
| 22 |
+
|
| 23 |
+
class ChatResponse(BaseModel):
|
| 24 |
+
"""Balasan dari Backend ke Frontend."""
|
| 25 |
+
reply: str # Teks balasan bot
|
| 26 |
+
action_type: str # "START_EXAM", "GET_RECOMMENDATION", "CASUAL_CHAT", "START_PSYCH_TEST"
|
| 27 |
+
data: Optional[Dict[str, Any]] = None # Data tambahan (Soal ujian / List Rekomendasi)
|
| 28 |
+
|
| 29 |
+
# ==========================================
|
| 30 |
+
# 2. EXAM SYSTEM (UJIAN)
|
| 31 |
+
# ==========================================
|
| 32 |
+
|
| 33 |
+
class QuestionResponse(BaseModel):
|
| 34 |
+
"""Output soal dari LLM."""
|
| 35 |
+
question_text: str
|
| 36 |
+
question_context: Dict[str, Any] # Kunci jawaban/Rubrik (Frontend wajib simpan ini)
|
| 37 |
+
skill_id: str
|
| 38 |
+
|
| 39 |
+
class AnswerSubmission(BaseModel):
|
| 40 |
+
"""Payload saat user mengirim jawaban ujian."""
|
| 41 |
+
user_answer: str
|
| 42 |
+
question_context: Dict[str, Any] # Kunci jawaban yang dikirim balik oleh Frontend
|
| 43 |
+
|
| 44 |
+
class EvaluationResponse(BaseModel):
|
| 45 |
+
"""Hasil penilaian AI Judge."""
|
| 46 |
+
is_correct: bool
|
| 47 |
+
score: int
|
| 48 |
+
feedback: str
|
| 49 |
+
passed: bool # True jika score >= 70
|
| 50 |
+
suggested_new_level: Optional[str] = None # Saran level baru (misal: "intermediate")
|
| 51 |
+
|
| 52 |
+
# ==========================================
|
| 53 |
+
# 3. RECOMMENDATION SYSTEM (ML POWERED)
|
| 54 |
+
# ==========================================
|
| 55 |
+
|
| 56 |
+
class SkillGap(BaseModel):
|
| 57 |
+
skill_name: str # Contoh: "SQL"
|
| 58 |
+
target_level: str # Contoh: "Pemula"
|
| 59 |
+
|
| 60 |
+
class UserProfile(BaseModel):
|
| 61 |
+
name: str # Contoh: "Siti Adaptive"
|
| 62 |
+
active_path: str # Contoh: "Data Scientist"
|
| 63 |
+
missing_skills: List[SkillGap] # List of objects
|
| 64 |
+
completed_courses: List[int] = []
|
| 65 |
+
|
| 66 |
+
class RecommendationItem(BaseModel):
|
| 67 |
+
skill: str
|
| 68 |
+
current_level: str
|
| 69 |
+
course_to_take: str
|
| 70 |
+
chapters: List[str]
|
| 71 |
+
match_score: float
|
| 72 |
+
badge: str
|
| 73 |
+
# ==========================================
|
| 74 |
+
# 4. PROGRESS SYSTEM
|
| 75 |
+
# ==========================================
|
| 76 |
+
|
| 77 |
+
class ProgressRequest(BaseModel):
|
| 78 |
+
"""Request untuk hitung progress bar."""
|
| 79 |
+
role: str
|
| 80 |
+
current_skills: Dict[str, str]
|
| 81 |
+
|
| 82 |
+
class ProgressItem(BaseModel):
|
| 83 |
+
"""Format satu item progress skill."""
|
| 84 |
+
skill_name: str
|
| 85 |
+
current_level: str
|
| 86 |
+
progress_percent: int # 0 - 100
|
| 87 |
+
remaining_tutorials: int
|
| 88 |
+
|
| 89 |
+
# ==========================================
|
| 90 |
+
# 5. PSYCH TEST SYSTEM (Fitur Baru)
|
| 91 |
+
# ==========================================
|
| 92 |
+
|
| 93 |
+
class PsychQuestionItem(BaseModel):
|
| 94 |
+
id: int
|
| 95 |
+
question: str
|
| 96 |
+
options: Dict[str, str] # {"A": "...", "B": "..."}
|
| 97 |
+
|
| 98 |
+
class PsychSubmitRequest(BaseModel):
|
| 99 |
+
"""Format jawaban yang dikirim user. Key = ID Soal, Value = Pilihan (A/B)"""
|
| 100 |
+
answers: Dict[int, str] # Contoh: {1: "A", 2: "B", 3: "A", ...}
|
| 101 |
+
|
| 102 |
+
class PsychResultResponse(BaseModel):
|
| 103 |
+
suggested_role: str
|
| 104 |
+
analysis: str # Penjelasan dari LLM
|
| 105 |
+
scores: Dict[str, int] # Skor detail (misal: AI=3, Frontend=2)
|
app/services/__pycache__/llm_engine.cpython-312.pyc
ADDED
|
Binary file (11.5 kB). View file
|
|
|
app/services/__pycache__/llm_engine.cpython-314.pyc
ADDED
|
Binary file (8.79 kB). View file
|
|
|
app/services/__pycache__/psych_service.cpython-312.pyc
ADDED
|
Binary file (3.02 kB). View file
|
|
|
app/services/__pycache__/psych_service.cpython-314.pyc
ADDED
|
Binary file (1.52 kB). View file
|
|
|
app/services/__pycache__/skill_manager.cpython-312.pyc
ADDED
|
Binary file (1.96 kB). View file
|
|
|
app/services/__pycache__/skill_manager.cpython-314.pyc
ADDED
|
Binary file (2.49 kB). View file
|
|
|
app/services/llm_engine.py
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
from groq import AsyncGroq
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
|
| 6 |
+
# Load environment variables
|
| 7 |
+
load_dotenv()
|
| 8 |
+
|
| 9 |
+
class LLMEngine:
|
| 10 |
+
def __init__(self):
|
| 11 |
+
self.api_key = os.getenv("GROQ_API_KEY")
|
| 12 |
+
if not self.api_key:
|
| 13 |
+
raise ValueError("GROQ_API_KEY not found in .env file")
|
| 14 |
+
|
| 15 |
+
self.client = AsyncGroq(api_key=self.api_key)
|
| 16 |
+
|
| 17 |
+
async def process_user_intent(self, user_text: str, available_skills: list):
|
| 18 |
+
# Ubah list skill jadi string biar AI tau menu apa aja yang ada
|
| 19 |
+
skills_str = "\n".join([f"- {s}" for s in available_skills])
|
| 20 |
+
|
| 21 |
+
system_prompt = f"""
|
| 22 |
+
ROLE: Kamu adalah 'Router' untuk MORA, sebuah AI Learning Assistant.
|
| 23 |
+
Tugasmu BUKAN menjawab pertanyaan, tapi mengarahkan user ke fitur yang benar.
|
| 24 |
+
|
| 25 |
+
DAFTAR SKILL TERSEDIA DI DATABASE:
|
| 26 |
+
{skills_str}
|
| 27 |
+
|
| 28 |
+
INSTRUKSI UTAMA:
|
| 29 |
+
Analisis pesan user dan tentukan ACTION JSON.
|
| 30 |
+
|
| 31 |
+
1. ACTION: "START_EXAM"
|
| 32 |
+
- Trigger: User ingin "tes", "ujian", "uji kemampuan", "soal", atau menyebut topik teknis (SQL, Python, CV, NLP).
|
| 33 |
+
- TUGAS PENTING (MAPPING): User sering menyebut topik spesifik (misal "SQL"). Kamu WAJIB mencocokkannya dengan "Nama Skill Tersedia" yang paling relevan.
|
| 34 |
+
Contoh:
|
| 35 |
+
- User: "Tes SQL" -> Detected: "Software & Data Foundations"
|
| 36 |
+
- User: "Tes Vision" -> Detected: "Deep Learning & Computer Vision"
|
| 37 |
+
|
| 38 |
+
2. ACTION: "GET_RECOMMENDATION"
|
| 39 |
+
- Trigger: User minta "saran", "belajar apa", "rekomendasi", "bingung mulai mana".
|
| 40 |
+
|
| 41 |
+
3. ACTION: "START_PSYCH_TEST"
|
| 42 |
+
- Trigger: User tanya "karir", "cocok kerja apa", "tes minat".
|
| 43 |
+
|
| 44 |
+
4. ACTION: "CASUAL_CHAT"
|
| 45 |
+
- Trigger: Hanya untuk sapaan ("Halo"), curhat, atau pertanyaan di luar konteks belajar.
|
| 46 |
+
- JANGAN gunakan ini jika user jelas-jelas minta tes/soal.
|
| 47 |
+
|
| 48 |
+
OUTPUT JSON (Hanya JSON, tanpa teks lain):
|
| 49 |
+
{{
|
| 50 |
+
"action": "...",
|
| 51 |
+
"detected_skills": ["Nama Skill Database 1", "Nama Skill Database 2"] (Array berisi String nama skill persis dari daftar diatas. Kosongkan jika tidak ada.)
|
| 52 |
+
}}
|
| 53 |
+
"""
|
| 54 |
+
|
| 55 |
+
try:
|
| 56 |
+
chat_completion = await self.client.chat.completions.create(
|
| 57 |
+
messages=[
|
| 58 |
+
{"role": "system", "content": system_prompt},
|
| 59 |
+
{"role": "user", "content": user_text}
|
| 60 |
+
],
|
| 61 |
+
model="llama-3.3-70b-versatile", # Wajib model 70b biar pinter mapping
|
| 62 |
+
temperature=0.0, # Wajib 0 agar tidak kreatif/halu
|
| 63 |
+
response_format={"type": "json_object"}
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
response_content = chat_completion.choices[0].message.content
|
| 67 |
+
print(f"DEBUG AI MAPPING: {response_content}") # Cek di terminal mappingnya bener gak
|
| 68 |
+
return json.loads(response_content)
|
| 69 |
+
except Exception as e:
|
| 70 |
+
print(f"Error Intent: {e}")
|
| 71 |
+
return {"action": "CASUAL_CHAT", "detected_skills": []}
|
| 72 |
+
|
| 73 |
+
async def generate_question(self, topics: list, level: str):
|
| 74 |
+
topics_str = ", ".join(topics)
|
| 75 |
+
prompt = f"""
|
| 76 |
+
Buatkan 1 soal esai pendek untuk menguji pemahaman user tentang topik: {topics_str}.
|
| 77 |
+
Tingkat Kesulitan: {level}.
|
| 78 |
+
Bahasa: Indonesia.
|
| 79 |
+
|
| 80 |
+
Output JSON:
|
| 81 |
+
{{
|
| 82 |
+
"question_text": "Pertanyaan...",
|
| 83 |
+
"grading_rubric": {{
|
| 84 |
+
"keywords": ["kata1", "kata2"],
|
| 85 |
+
"explanation_focus": "Poin utama yang harus dijelaskan"
|
| 86 |
+
}}
|
| 87 |
+
}}
|
| 88 |
+
"""
|
| 89 |
+
try:
|
| 90 |
+
completion = await self.client.chat.completions.create(
|
| 91 |
+
messages=[{"role": "user", "content": prompt}],
|
| 92 |
+
# UPDATE MODEL
|
| 93 |
+
model="llama-3.3-70b-versatile",
|
| 94 |
+
response_format={"type": "json_object"}
|
| 95 |
+
)
|
| 96 |
+
return json.loads(completion.choices[0].message.content)
|
| 97 |
+
except:
|
| 98 |
+
return None
|
| 99 |
+
|
| 100 |
+
async def evaluate_answer(self, user_answer: str, question_context: dict):
|
| 101 |
+
prompt = f"""
|
| 102 |
+
Bertindaklah sebagai Dosen AI yang menilai jawaban mahasiswa.
|
| 103 |
+
|
| 104 |
+
Soal/Konteks: {json.dumps(question_context)}
|
| 105 |
+
Jawaban Mahasiswa: "{user_answer}"
|
| 106 |
+
|
| 107 |
+
Tugas:
|
| 108 |
+
1. Beri skor 0-100.
|
| 109 |
+
2. Beri feedback singkat & ramah (Bahasa Indonesia).
|
| 110 |
+
3. Tentukan apakah jawaban BENAR secara konsep (is_correct).
|
| 111 |
+
|
| 112 |
+
Output JSON:
|
| 113 |
+
{{
|
| 114 |
+
"score": 85,
|
| 115 |
+
"feedback": "Penjelasanmu bagus, tapi kurang detail di bagian...",
|
| 116 |
+
"is_correct": true
|
| 117 |
+
}}
|
| 118 |
+
"""
|
| 119 |
+
try:
|
| 120 |
+
completion = await self.client.chat.completions.create(
|
| 121 |
+
messages=[{"role": "user", "content": prompt}],
|
| 122 |
+
# UPDATE MODEL
|
| 123 |
+
model="llama-3.3-70b-versatile",
|
| 124 |
+
response_format={"type": "json_object"}
|
| 125 |
+
)
|
| 126 |
+
return json.loads(completion.choices[0].message.content)
|
| 127 |
+
except:
|
| 128 |
+
return {"score": 0, "feedback": "Error menilai.", "is_correct": False}
|
| 129 |
+
|
| 130 |
+
async def casual_chat(self, user_text: str, history: list = [], syllabus_context: str = ""):
|
| 131 |
+
|
| 132 |
+
prompt_template = f"""
|
| 133 |
+
[ROLE]
|
| 134 |
+
Namamu MORA. Kamu adalah Mentor & Asisten Teknis Spesialis.
|
| 135 |
+
|
| 136 |
+
[PENGETAHUAN & SILABUS KAMU]
|
| 137 |
+
Kamu HANYA menguasai materi yang tertera di bawah ini. Gunakan daftar ini sebagai acuan validasi jawabanmu:
|
| 138 |
+
|
| 139 |
+
{syllabus_context}
|
| 140 |
+
|
| 141 |
+
[TUGAS UTAMA]
|
| 142 |
+
1. **JAWAB PERTANYAAN TEKNIS:**
|
| 143 |
+
Jika user bertanya definisi atau konsep tentang topik yang ADA di silabus di atas (misal: "Apa itu List?", "Jelaskan Supervised Learning"), JAWABLAH dengan penjelasan konseptual yang singkat, padat, dan mudah dimengerti (maksimal 3-4 kalimat).
|
| 144 |
+
|
| 145 |
+
2. **GAYA MENGAJAR:**
|
| 146 |
+
Gunakan analogi sederhana jika perlu. Jangan terlalu kaku seperti buku teks, tapi tetap akurat.
|
| 147 |
+
|
| 148 |
+
3. **BATASAN (BLACK LIST):**
|
| 149 |
+
- Jika topik TIDAK ADA di silabus (misal: Hardware, IoT, Masak, Mobil, coding selain yang di sylabus), TOLAK dengan sopan dan pivot ke materi silabus.
|
| 150 |
+
- JANGAN BERIKAN KODE FULL. Jika user minta "Buatkan kodingan", arahkan mereka untuk mengambil Ujian/Tes. Kamu hanya menjelaskan *Konsep* dan *Logika*.
|
| 151 |
+
- Hindari jawaban yang terlalu panjang (lebih dari 4 kalimat).
|
| 152 |
+
- Jika tidak yakin, katakan "Maaf, itu di luar pengetahuan saya."
|
| 153 |
+
- Jangan buat-buat jawaban untuk topik di luar silabus.
|
| 154 |
+
|
| 155 |
+
[CONTOH INTERAKSI]
|
| 156 |
+
User: "Apa itu Supervised Learning?"
|
| 157 |
+
Mora: (Cek silabus... ada!) "Supervised Learning itu ibarat belajar dengan kunci jawaban. Model dilatih pakai data yang sudah ada labelnya, jadi dia tau mana yang benar dan salah. Contohnya kayak filter email spam!"
|
| 158 |
+
|
| 159 |
+
User: "Gimana cara bikin robot?"
|
| 160 |
+
Mora: (Cek silabus... tidak ada!) "Waduh, itu ranah hardware/robotik, di luar silabusku. Tapi kalau kamu mau tau cara memprogram otak robotnya pakai Python (AI), aku bisa jelaskan logikanya!"
|
| 161 |
+
|
| 162 |
+
[PERSONALITY]
|
| 163 |
+
Ramah, Suportif, Emoji secukupnya.
|
| 164 |
+
"""
|
| 165 |
+
|
| 166 |
+
system_msg = {
|
| 167 |
+
"role": "system",
|
| 168 |
+
"content": prompt_template
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
messages = [system_msg]
|
| 172 |
+
for msg in history[-5:]:
|
| 173 |
+
messages.append({"role": msg['role'], "content": msg['content']})
|
| 174 |
+
messages.append({"role": "user", "content": user_text})
|
| 175 |
+
|
| 176 |
+
try:
|
| 177 |
+
completion = await self.client.chat.completions.create(
|
| 178 |
+
messages=messages,
|
| 179 |
+
model="llama-3.3-70b-versatile",
|
| 180 |
+
temperature=0.3
|
| 181 |
+
)
|
| 182 |
+
return completion.choices[0].message.content
|
| 183 |
+
except Exception as e:
|
| 184 |
+
return f"Maaf, otak saya sedang error. (Error: {str(e)})"
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
async def analyze_psych_result(self, role: str, traits: list[str]):
|
| 188 |
+
"""
|
| 189 |
+
Membuat penjelasan psikologis kenapa user cocok di role tersebut.
|
| 190 |
+
"""
|
| 191 |
+
traits_str = "\n".join(traits)
|
| 192 |
+
|
| 193 |
+
prompt = f"""
|
| 194 |
+
Kamu adalah Konsultan Karir IT yang ahli membaca kepribadian.
|
| 195 |
+
|
| 196 |
+
DATA USER:
|
| 197 |
+
User baru saja mengikuti tes kepribadian sederhana.
|
| 198 |
+
Hasil kecocokan tertinggi: **{role}**.
|
| 199 |
+
Kebiasaan/Pilihan User:
|
| 200 |
+
{traits_str}
|
| 201 |
+
|
| 202 |
+
TUGAS:
|
| 203 |
+
Berikan analisis singkat (maksimal 3 kalimat) dan memotivasi.
|
| 204 |
+
Jelaskan hubungan antara kebiasaan user di atas dengan job role {role}.
|
| 205 |
+
Gunakan gaya bahasa santai tapi meyakinkan.
|
| 206 |
+
|
| 207 |
+
Contoh Output:
|
| 208 |
+
"Wah, kamu punya bakat alami jadi AI Engineer! Kebiasaanmu yang suka menganalisis fakta dan mencari review mendalam menunjukkan kamu punya pola pikir analitis yang kuat, modal penting buat ngolah data!"
|
| 209 |
+
"""
|
| 210 |
+
|
| 211 |
+
try:
|
| 212 |
+
chat_completion = self.client.chat.completions.create(
|
| 213 |
+
messages=[{"role": "user", "content": prompt}],
|
| 214 |
+
model="llama-3.1-8b-instant",
|
| 215 |
+
temperature=0.7
|
| 216 |
+
)
|
| 217 |
+
return chat_completion.choices[0].message.content
|
| 218 |
+
except Exception as e:
|
| 219 |
+
return f"Berdasarkan jawabanmu, kamu sangat cocok menjadi {role}! Semangat belajar ya! 🚀"
|
| 220 |
+
|
| 221 |
+
llm_engine = LLMEngine()
|
app/services/psych_service.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app/services/psych_service.py
|
| 2 |
+
from typing import Dict
|
| 3 |
+
|
| 4 |
+
# Bank Soal Psikologis (Bisa ditambah nanti)
|
| 5 |
+
PSYCH_QUESTIONS = [
|
| 6 |
+
{
|
| 7 |
+
"id": 1,
|
| 8 |
+
"question": "Mana kegiatan yang paling relate denganmu di pagi hari?",
|
| 9 |
+
"options": {
|
| 10 |
+
"A": "Baca atau lihat info viral dari berbagai sumber",
|
| 11 |
+
"B": "Coret-coret atau menulis di buku"
|
| 12 |
+
},
|
| 13 |
+
"role_mapping": {"A": "AI Engineer", "B": "Front-End Web Developer"}
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"id": 2,
|
| 17 |
+
"question": "JJika sedang menghadapi masalah, cara mana yang paling mirip denganmu?",
|
| 18 |
+
"options": {
|
| 19 |
+
"A": "Cari tahu masalahnya dari berbagai macam sudut pandang",
|
| 20 |
+
"B": "Ngobrol dengan teman untuk mendapatkan ide baru"
|
| 21 |
+
},
|
| 22 |
+
"role_mapping": {"A": "AI Engineer", "B": "Front-End Web Developer"}
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"id": 3,
|
| 26 |
+
"question": "Kalau lagi bermain sosial media, mana aktivitas yang paling relate denganmu?",
|
| 27 |
+
"options": {
|
| 28 |
+
"A": "Stalking akun-akun yang suka share fakta-fakta seru",
|
| 29 |
+
"B": "Share-share postingan teman sambil comment"
|
| 30 |
+
},
|
| 31 |
+
"role_mapping": {"A": "AI Engineer", "B": "Front-End Web Developer"}
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
"id": 4,
|
| 35 |
+
"question": "Nah, kalau lagi liburan, kegiatan mana yang paling bikin kamu excited?",
|
| 36 |
+
"options": {
|
| 37 |
+
"A": "Mencari review tempat yang akan dikunjungi",
|
| 38 |
+
"B": "Membuat konten blog atau vlog tentang petualangan liburan"
|
| 39 |
+
},
|
| 40 |
+
"role_mapping": {"A": "AI Engineer", "B": "Front-End Web Developer"}
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"id": 5,
|
| 44 |
+
"question": "Kalau lagi kerja bareng tim, mana peran yang paling mirip dengamu?",
|
| 45 |
+
"options": {
|
| 46 |
+
"A": "Jadi orang yang bantu tim ngambil keputusan dengan analisis situasi",
|
| 47 |
+
"B": "Jadi orang yang bikin presentasi buat menyampaikan ide-ide tim"
|
| 48 |
+
},
|
| 49 |
+
"role_mapping": {"A": "AI Engineer", "B": "Front-End Web Developer"}
|
| 50 |
+
}
|
| 51 |
+
]
|
| 52 |
+
|
| 53 |
+
class PsychService:
|
| 54 |
+
def get_all_questions(self):
|
| 55 |
+
"""Mengembalikan soal tanpa kunci jawaban (untuk frontend)"""
|
| 56 |
+
return [
|
| 57 |
+
{
|
| 58 |
+
"id": q["id"],
|
| 59 |
+
"question": q["question"],
|
| 60 |
+
"options": q["options"]
|
| 61 |
+
}
|
| 62 |
+
for q in PSYCH_QUESTIONS
|
| 63 |
+
]
|
| 64 |
+
|
| 65 |
+
def calculate_result(self, user_answers: Dict[int, str]):
|
| 66 |
+
"""
|
| 67 |
+
Menghitung skor berdasarkan jawaban user.
|
| 68 |
+
user_answers contoh: {1: "A", 2: "B"}
|
| 69 |
+
"""
|
| 70 |
+
scores = {"AI Engineer": 0, "Front-End Web Developer": 0}
|
| 71 |
+
|
| 72 |
+
# Simpan trait kepribadian user untuk dikirim ke LLM nanti
|
| 73 |
+
user_traits = []
|
| 74 |
+
|
| 75 |
+
for q in PSYCH_QUESTIONS:
|
| 76 |
+
q_id = q["id"]
|
| 77 |
+
user_choice = user_answers.get(q_id) # "A" atau "B"
|
| 78 |
+
|
| 79 |
+
if user_choice and user_choice in q["role_mapping"]:
|
| 80 |
+
# 1. Tambah Skor
|
| 81 |
+
role = q["role_mapping"][user_choice]
|
| 82 |
+
scores[role] += 1
|
| 83 |
+
|
| 84 |
+
# 2. Catat trait (pilihan user) untuk konteks LLM
|
| 85 |
+
chosen_text = q["options"][user_choice]
|
| 86 |
+
user_traits.append(f"- Lebih suka: {chosen_text}")
|
| 87 |
+
|
| 88 |
+
# Tentukan Pemenang
|
| 89 |
+
winner_role = max(scores, key=scores.get)
|
| 90 |
+
|
| 91 |
+
return {
|
| 92 |
+
"winner": winner_role,
|
| 93 |
+
"scores": scores,
|
| 94 |
+
"traits": user_traits
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
psych_service = PsychService()
|
app/services/skill_manager.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
# Lokasi file JSON
|
| 5 |
+
JSON_PATH = os.path.join(os.path.dirname(__file__), "../data/Sub_skill.json")
|
| 6 |
+
|
| 7 |
+
class SkillManager:
|
| 8 |
+
def __init__(self):
|
| 9 |
+
self.data = self._load_data()
|
| 10 |
+
|
| 11 |
+
def _load_data(self):
|
| 12 |
+
with open(JSON_PATH, 'r') as f:
|
| 13 |
+
return json.load(f)
|
| 14 |
+
|
| 15 |
+
def get_role_data(self, role_name: str):
|
| 16 |
+
"""Mengambil data skill berdasarkan role (AI Engineer / Web Dev)"""
|
| 17 |
+
for role in self.data:
|
| 18 |
+
if role['role_name'].lower() == role_name.lower():
|
| 19 |
+
return role
|
| 20 |
+
return None
|
| 21 |
+
|
| 22 |
+
def get_skill_details(self, role_name: str, skill_id: str):
|
| 23 |
+
"""Mengambil detail satu skill spesifik"""
|
| 24 |
+
role_data = self.get_role_data(role_name)
|
| 25 |
+
if not role_data:
|
| 26 |
+
return None
|
| 27 |
+
|
| 28 |
+
for skill in role_data['sub_skills']:
|
| 29 |
+
if skill['id'] == skill_id:
|
| 30 |
+
return skill
|
| 31 |
+
return None
|
| 32 |
+
|
| 33 |
+
# Instance global
|
| 34 |
+
skill_manager = SkillManager()
|
model_artifacts/courses_df.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dbab867c7952fba0817085133a78cbb87ab5e624f590fda4b0e7931e65880f6a
|
| 3 |
+
size 354601
|
model_artifacts/smart_course_dataset.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
model_artifacts/tfidf_matrix.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f3a5539d76aedad5fa18b3147a84f2e274faf9b2959e403959549540b8618e47
|
| 3 |
+
size 91324
|
model_artifacts/tfidf_vectorizer.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:25e1a1a524f82d96889e5b40e18770ef48262dec554481bed15c5bcf1b939f98
|
| 3 |
+
size 62435
|
requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi
|
| 2 |
+
uvicorn
|
| 3 |
+
pydantic
|
| 4 |
+
groq
|
| 5 |
+
python-dotenv
|
| 6 |
+
pandas
|
| 7 |
+
numpy
|
| 8 |
+
scikit-learn==1.6.1
|