aidlmrza commited on
Commit
2b74789
·
verified ·
1 Parent(s): 03e4a4c

Upload 26 files

Browse files
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