fjarsra commited on
Commit
64f0ed1
·
verified ·
1 Parent(s): ac02598

Upload 11 files

Browse files
Dockerfiles 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/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,256 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
11
+ app = FastAPI(title="MORA - AI Learning Assistant (Final)")
12
+
13
+ # --- GLOBAL MODELS STORE ---
14
+ models = {
15
+ 'df': None,
16
+ 'tfidf': None,
17
+ 'matrix': None
18
+ }
19
+
20
+ # --- 1. STARTUP: LOAD MODEL .PKL ---
21
+ @app.on_event("startup")
22
+ def load_models():
23
+ print("🔄 Loading Pre-trained Models...")
24
+
25
+ # Menggunakan Absolute Path agar aman dijalankan dari mana saja
26
+ current_dir = os.path.dirname(os.path.abspath(__file__))
27
+ base_dir = os.path.dirname(current_dir)
28
+ artifacts_dir = os.path.join(base_dir, "model_artifacts")
29
+
30
+ try:
31
+ with open(os.path.join(artifacts_dir, 'courses_df.pkl'), 'rb') as f:
32
+ models['df'] = pickle.load(f)
33
+ with open(os.path.join(artifacts_dir, 'tfidf_vectorizer.pkl'), 'rb') as f:
34
+ models['tfidf'] = pickle.load(f)
35
+ with open(os.path.join(artifacts_dir, 'tfidf_matrix.pkl'), 'rb') as f:
36
+ models['matrix'] = pickle.load(f)
37
+ print(f"✅ Models Loaded Successfully from: {artifacts_dir}")
38
+ except Exception as e:
39
+ print(f"❌ Error Loading Models: {e}")
40
+ print(f"👉 Pastikan folder 'model_artifacts' ada di: {base_dir}")
41
+
42
+ # --- 2. ENDPOINT REKOMENDASI (ML POWERED) ---
43
+ @app.post("/recommendations")
44
+ def get_recommendations(user: schemas.UserProfile):
45
+ df = models.get('df')
46
+ tfidf = models.get('tfidf')
47
+ matrix = models.get('matrix')
48
+
49
+ # Jika model belum siap, return kosong biar gak crash
50
+ if df is None: return []
51
+
52
+ # Mapping Level agar komputer mengerti urutan
53
+ LEVEL_MAP = {
54
+ 'beginner': 1, 'dasar': 1, 'pemula': 1,
55
+ 'intermediate': 2, 'menengah': 2,
56
+ 'advanced': 3, 'mahir': 3, 'expert': 3, 'profesional': 3
57
+ }
58
+
59
+ final_recs = []
60
+ # Set course yang sudah diambil agar tidak disarankan lagi
61
+ seen_courses = set(user.completed_courses)
62
+
63
+ # --- LOGIKA CORE: Loop setiap 'Gap' Skill User ---
64
+ for gap in user.missing_skills:
65
+ skill_query = gap.skill_name
66
+ target_lvl_str = gap.target_level.lower()
67
+ target_lvl_num = LEVEL_MAP.get(target_lvl_str, 1) # Default 1 (Pemula)
68
+
69
+ try:
70
+ # 1. Transform nama skill jadi vektor angka
71
+ vec = tfidf.transform([skill_query.lower()])
72
+
73
+ # 2. Hitung kemiripan (Cosine Similarity)
74
+ scores = linear_kernel(vec, matrix).flatten()
75
+
76
+ # 3. Ambil Top 15 kandidat
77
+ indices = scores.argsort()[:-15:-1]
78
+
79
+ for idx in indices:
80
+ score = scores[idx]
81
+ # Filter awal: Skip jika kemiripan text terlalu rendah
82
+ if score < 0.1: continue
83
+
84
+ course = df.iloc[idx]
85
+ c_id = int(course['course_id'])
86
+
87
+ if c_id in seen_courses: continue
88
+
89
+ # --- FILTER LEVEL (ADAPTIVE) ---
90
+ c_lvl_str = str(course['level_name']).lower()
91
+ c_lvl_num = LEVEL_MAP.get(c_lvl_str, 1)
92
+
93
+ # Logic: Jangan kasih course yang levelnya DI ATAS target (kejauhan)
94
+ if c_lvl_num > target_lvl_num: continue
95
+
96
+ # Logic Badge (Penanda)
97
+ if c_lvl_num == target_lvl_num:
98
+ badge = "🎯 Target Pas"
99
+ else:
100
+ badge = "↺ Review Dasar"
101
+
102
+ # Parse Tutorial List (karena di CSV formatnya string)
103
+ tuts = course['tutorial_list']
104
+ if isinstance(tuts, str):
105
+ try: tuts = ast.literal_eval(tuts)
106
+ except: tuts = []
107
+
108
+ # Tambahkan ke hasil
109
+ final_recs.append({
110
+ "skill": skill_query,
111
+ "current_level": gap.target_level,
112
+ "course_to_take": course['course_name'],
113
+ "chapters": tuts[:3], # Ambil 3 bab pertama
114
+ "match_score": round(score * 100, 1),
115
+ "badge": badge
116
+ })
117
+ seen_courses.add(c_id)
118
+
119
+ except Exception as e:
120
+ print(f"Error processing {skill_query}: {e}")
121
+ continue
122
+
123
+ # Urutkan berdasarkan skor kecocokan tertinggi
124
+ final_recs = sorted(final_recs, key=lambda x: x['match_score'], reverse=True)
125
+
126
+ return final_recs[:5] # Kembalikan Top 5
127
+
128
+ # --- 3. ENDPOINT CHAT ROUTER ---
129
+ @app.post("/chat/process", response_model=schemas.ChatResponse)
130
+ async def process_chat(req: schemas.ChatRequest):
131
+ # Ambil silabus skill berdasarkan role user untuk konteks AI
132
+ role_data = skill_manager.get_role_data(req.role)
133
+ skill_names = [s['name'] for s in role_data['sub_skills']] if role_data else []
134
+
135
+ # Klasifikasi Niat User (Router LLM)
136
+ intent = await llm_engine.process_user_intent(req.message, skill_names)
137
+
138
+ action = intent.get('action')
139
+
140
+ detected_skills = intent.get('detected_skills', [])
141
+
142
+ if action == "START_EXAM":
143
+ target_skill_ids = []
144
+
145
+ # 1. Cari ID untuk SEMUA skill yang dideteksi
146
+ if detected_skills and role_data:
147
+ for ds in detected_skills:
148
+ for s in role_data['sub_skills']:
149
+ # Cek kemiripan nama
150
+ if s['name'].lower() in ds.lower() or ds.lower() in s['name'].lower():
151
+ if s['id'] not in target_skill_ids: # Cegah duplikat
152
+ target_skill_ids.append(s['id'])
153
+
154
+ # 2. Generate Soal untuk SETIAP Skill ID yang ketemu
155
+ if target_skill_ids:
156
+ exam_questions_list = []
157
+
158
+ for skid in target_skill_ids:
159
+ # Ambil level user untuk skill ini
160
+ user_current_level = req.current_skills.get(skid, "beginner")
161
+ skill_details = skill_manager.get_skill_details(req.role, skid)
162
+ level_data = skill_details['levels'].get(user_current_level, skill_details['levels']['beginner'])
163
+
164
+ # Generate Soal via LLM (Tunggu satu-satu)
165
+ llm_res = await llm_engine.generate_question(level_data['exam_topics'], user_current_level)
166
+
167
+ # Masukkan ke list
168
+ exam_questions_list.append({
169
+ "skill_id": skid,
170
+ "skill_name": skill_details['name'],
171
+ "level": user_current_level,
172
+ "question": llm_res['question_text'],
173
+ "context": llm_res['grading_rubric']
174
+ })
175
+
176
+ # 3. Kembalikan List Soal di dalam objek 'data'
177
+ response_data = {
178
+ "mode": "multiple_exams", # Penanda buat frontend
179
+ "exams": exam_questions_list
180
+ }
181
+
182
+ # Buat kalimat sapaan dinamis
183
+ skill_names_str = ", ".join([x['skill_name'] for x in exam_questions_list])
184
+ final_reply = f"Siap! Saya menemukan {len(exam_questions_list)} topik: **{skill_names_str}**. Silakan kerjakan soal-soal berikut di bawah ini! 👇"
185
+
186
+ else:
187
+ # Fallback jika skill tidak dikenali
188
+ action = "CASUAL_CHAT"
189
+ final_reply = await llm_engine.casual_chat(req.message, [m.dict() for m in req.history])
190
+ elif action == "GET_RECOMMENDATION":
191
+ # Frontend yang harus lanjut memanggil endpoint /recommendations
192
+ response_data = {"trigger_recommendation": True}
193
+ final_reply = "Sedang menganalisis kebutuhan belajarmu..."
194
+
195
+ elif action == "CASUAL_CHAT":
196
+ final_reply = await llm_engine.casual_chat(req.message, [m.dict() for m in req.history])
197
+
198
+ return schemas.ChatResponse(
199
+ reply=final_reply,
200
+ action_type=action,
201
+ data=response_data
202
+ )
203
+
204
+
205
+ @app.post("/exam/submit", response_model=schemas.EvaluationResponse)
206
+ async def submit_exam(sub: schemas.AnswerSubmission):
207
+ evaluation = await llm_engine.evaluate_answer(
208
+ user_answer=sub.user_answer,
209
+ question_context={
210
+ "question_text": "REFER TO CONTEXT",
211
+ "grading_rubric": sub.question_context
212
+ }
213
+ )
214
+
215
+ is_passed = evaluation['is_correct'] and evaluation['score'] >= 70
216
+ suggested_lvl = "intermediate" if is_passed else None # Logika sederhana
217
+
218
+ return schemas.EvaluationResponse(
219
+ is_correct=evaluation['is_correct'],
220
+ score=evaluation['score'],
221
+ feedback=evaluation['feedback'],
222
+ passed=is_passed,
223
+ suggested_new_level=suggested_lvl
224
+ )
225
+
226
+ # --- 5. ENDPOINT PROGRESS ---
227
+ @app.post("/progress")
228
+ def get_progress(req: schemas.ProgressRequest):
229
+ role_data = skill_manager.get_role_data(req.role)
230
+ if not role_data: return []
231
+
232
+ progress_report = []
233
+ level_weight = {"beginner": 0, "intermediate": 1, "advanced": 2}
234
+
235
+ for skill in role_data['sub_skills']:
236
+ skill_id = skill['id']
237
+ user_level = req.current_skills.get(skill_id, "beginner")
238
+
239
+ # Hitung Persen
240
+ current_stage = level_weight.get(user_level, 0)
241
+ percent = int((current_stage / 3) * 100)
242
+ if user_level == "beginner": percent = 5
243
+ elif user_level == "intermediate": percent = 50
244
+ elif user_level == "advanced": percent = 80
245
+
246
+ # Sisa tutorial (dummy/static logic karena detail ada di rekomendasi)
247
+ remaining = 0
248
+
249
+ progress_report.append({
250
+ "skill_name": skill['name'],
251
+ "current_level": user_level,
252
+ "progress_percent": percent,
253
+ "remaining_tutorials": remaining
254
+ })
255
+
256
+ return progress_report
app/services/llm_engine.py ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ from groq import Groq
4
+ from dotenv import load_dotenv
5
+
6
+ # Load API Key
7
+ load_dotenv()
8
+
9
+ # Inisialisasi Client Groq
10
+ client = Groq(
11
+ api_key=os.getenv("GROQ_API_KEY"),
12
+ )
13
+
14
+ # Model yang Anda pilih
15
+ MODEL_NAME = "llama-3.3-70b-versatile"
16
+
17
+ class LLMEngine:
18
+
19
+ # ... (kode inisialisasi client Groq tetap sama) ...
20
+
21
+ @staticmethod
22
+ async def process_user_intent(user_text: str, available_skills: list[str]):
23
+ skills_str = ", ".join(available_skills)
24
+
25
+ system_prompt = f"""
26
+ ROLE: Klasifikator Niat (Intent Classifier).
27
+ SKILL TERSEDIA: {skills_str}
28
+
29
+ TUGAS:
30
+ 1. Tentukan kategori aksi.
31
+ 2. Deteksi SEMUA skill yang disebutkan user.
32
+
33
+ OUTPUT JSON:
34
+ {{
35
+ "action": "START_EXAM" | "GET_RECOMMENDATION" | "CASUAL_CHAT",
36
+ "detected_skills": ["Skill A", "Skill B"] (List of strings, kosongkan jika tidak ada)
37
+ }}
38
+ """
39
+
40
+ user_prompt = f'Input: "{user_text}"'
41
+
42
+ try:
43
+ response = client.chat.completions.create(
44
+ messages=[
45
+ {"role": "system", "content": system_prompt},
46
+ {"role": "user", "content": user_prompt}
47
+ ],
48
+ model=MODEL_NAME,
49
+ temperature=0.1, # Sangat kaku
50
+ response_format={"type": "json_object"}
51
+ )
52
+ return json.loads(response.choices[0].message.content)
53
+ except Exception as e:
54
+ print(f"Error intent: {e}")
55
+ return {"action": "CASUAL_CHAT", "detected_skill": None}
56
+
57
+ @staticmethod
58
+ async def casual_chat(user_text: str):
59
+ """Untuk ngobrol santai jika tidak ada action khusus"""
60
+ system_prompt="""
61
+ ROLE: Kamu adalah asisten belajar bernama MORA.
62
+
63
+ TUGAS:
64
+ 1. Hanya jawab pertanyaan terkait belajar pemrograman, AI, dan web development.
65
+
66
+ """
67
+ try:
68
+ response = client.chat.completions.create(
69
+ messages=[
70
+ {"role": "system", "content": "Kamu adalah asisten belajar bernama MORA. Jawab ramah dan singkat."},
71
+ {"role": "user", "content": user_text}
72
+ ],
73
+ model=MODEL_NAME,
74
+ )
75
+ return response.choices[0].message.content
76
+ except:
77
+ return "Maaf saya sedang error."
78
+
79
+ # ... (Fungsi generate_question dan evaluate_answer biarkan tetap ada) ...
80
+
81
+ @staticmethod
82
+ async def generate_question(topics: list[str], level: str):
83
+ topics_str = ", ".join(topics)
84
+
85
+ # System Prompt: Instruksi Dasar
86
+ system_prompt = f"""
87
+ ROLE: Kamu adalah Senior AI Engineer & Penguji Ujian Teknis.
88
+ TARGET LEVEL: {level}
89
+ TOPICS: {topics_str}
90
+
91
+ TUGAS:
92
+ Buatlah SATU soal studi kasus integrasi (gabungan) untuk menguji pemahaman kandidat.
93
+
94
+ INSTRUKSI KHUSUS:
95
+ 1. Jangan menanyakan definisi. Buat skenario nyata.
96
+ 2. Output HARUS dalam format JSON valid.
97
+ 3. Pertanyaan hanya seputar What, How, Why
98
+ """
99
+
100
+ # User Prompt: Trigger Generasi
101
+ user_prompt = """
102
+ Buatkan soal beserta rubrik penilaiannya sekarang.
103
+
104
+ OUTPUT FORMAT (JSON):
105
+ {
106
+ "question_text": "Teks pertanyaan untuk user...",
107
+ "grading_rubric": {
108
+ "key_concept": "Konsep utama...",
109
+ "must_have_keywords": ["keyword1", "keyword2"],
110
+ "explanation_focus": "Fokus penjelasan..."
111
+ }
112
+ }
113
+ """
114
+
115
+ try:
116
+ # Panggil API Groq
117
+ response = client.chat.completions.create(
118
+ messages=[
119
+ {"role": "system", "content": system_prompt},
120
+ {"role": "user", "content": user_prompt}
121
+ ],
122
+ model=MODEL_NAME,
123
+ temperature=0.7,
124
+ # FITUR PENTING: Memaksa output JSON
125
+ response_format={"type": "json_object"}
126
+ )
127
+
128
+ # Parse string JSON ke Dictionary Python
129
+ return json.loads(response.choices[0].message.content)
130
+
131
+ except Exception as e:
132
+ print(f"Error generating question via Groq: {e}")
133
+ return None
134
+
135
+ @staticmethod
136
+ async def evaluate_answer(user_answer: str, question_context: dict):
137
+ rubric = question_context.get('grading_rubric')
138
+ question = question_context.get('question_text')
139
+
140
+ system_prompt = f"""
141
+ ROLE: Kamu adalah Penilai Ujian (Grader) yang objektif.
142
+
143
+ KONTEKS SOAL: "{question}"
144
+
145
+ RUBRIK (KUNCI JAWABAN):
146
+ - Konsep: {rubric['key_concept']}
147
+ - Keyword Wajib: {rubric['must_have_keywords']}
148
+ - Fokus: {rubric['explanation_focus']}
149
+
150
+ TUGAS:
151
+ Nilai jawaban user. Analisis maknanya secara semantik.
152
+ Output HARUS JSON.
153
+ """
154
+
155
+ user_prompt = f"""
156
+ JAWABAN USER: "{user_answer}"
157
+
158
+ Berikan penilaianmu dalam format JSON berikut:
159
+ {{
160
+ "is_correct": boolean,
161
+ "score": integer (0-100),
162
+ "feedback": "Penjelasan singkat (maks 2 kalimat) kenapa benar/salah",
163
+ "missing_concepts": ["list konsep yang kurang"]
164
+ }}
165
+ """
166
+
167
+ try:
168
+ response = client.chat.completions.create(
169
+ messages=[
170
+ {"role": "system", "content": system_prompt},
171
+ {"role": "user", "content": user_prompt}
172
+ ],
173
+ model=MODEL_NAME,
174
+ temperature=0.5, # Lebih rendah agar penilaian konsisten
175
+ response_format={"type": "json_object"}
176
+ )
177
+
178
+ return json.loads(response.choices[0].message.content)
179
+
180
+ except Exception as e:
181
+ print(f"Error evaluating answer via Groq: {e}")
182
+ # Return nilai default agar tidak crash
183
+ return {"is_correct": False, "score": 0, "feedback": "Terjadi kesalahan sistem evaluasi.", "missing_concepts": []}
184
+
185
+ @staticmethod
186
+ async def analyze_psych_answer(user_answer: str, question_data: dict):
187
+ """
188
+ Menentukan user condong ke Opsi A atau B berdasarkan ketikan mereka.
189
+ """
190
+ prompt = f"""
191
+ ROLE: Psikolog Penjurusan Karir IT.
192
+
193
+ PERTANYAAN: "{question_data['question']}"
194
+ OPSI A: "{question_data['options']['A']}"
195
+ OPSI B: "{question_data['options']['B']}"
196
+
197
+ JAWABAN USER: "{user_answer}"
198
+
199
+ TUGAS:
200
+ Analisis jawaban user. Apakah makna kalimatnya lebih dekat ke Opsi A atau Opsi B?
201
+
202
+ OUTPUT JSON:
203
+ {{
204
+ "choice": "A" | "B",
205
+ "reason": "Alasan singkat kenapa masuk kategori itu"
206
+ }}
207
+ """
208
+
209
+ try:
210
+ response = client.chat.completions.create(
211
+ messages=[{"role": "user", "content": prompt}],
212
+ model=MODEL_NAME,
213
+ temperature=0.1, # Harus tegas
214
+ response_format={"type": "json_object"}
215
+ )
216
+ return json.loads(response.choices[0].message.content)
217
+ except Exception as e:
218
+ return {"choice": "A", "reason": "Error, default ke A"}
219
+
220
+ # Instance global
221
+ llm_engine = LLMEngine()
app/services/psych_service.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app/services/psych_service.py
2
+
3
+ # Bank Soal Psikologis (Bisa ditambah nanti)
4
+ PSYCH_QUESTIONS = [
5
+ {
6
+ "id": 1,
7
+ "question": "Mana kegiatan yang paling relate denganmu di pagi hari?",
8
+ "options": {
9
+ "A": "Baca atau lihat info viral dari berbagai sumber (Cari Pola/Data)",
10
+ "B": "Coret-coret ide atau menulis jurnal di buku (Visual/Desain)"
11
+ },
12
+ "role_mapping": {"A": "AI Engineer", "B": "Front-End Web Developer"}
13
+ },
14
+ {
15
+ "id": 2,
16
+ "question": "Jika kamu melihat sebuah website yang jelek, apa yang pertama kali kamu pikirkan?",
17
+ "options": {
18
+ "A": "Ini fitur search-nya lambat banget, pasti database-nya berantakan.",
19
+ "B": "Ini warnanya nabrak banget, font-nya juga susah dibaca."
20
+ },
21
+ "role_mapping": {"A": "AI Engineer", "B": "Front-End Web Developer"}
22
+ },
23
+ {
24
+ "id": 3,
25
+ "question": "Saat memecahkan masalah, gaya kamu lebih seperti apa?",
26
+ "options": {
27
+ "A": "Mengumpulkan banyak data dulu, baru menyimpulkan solusi.",
28
+ "B": "Mencoba menggambar sketsa solusi dulu, baru diperbaiki sambil jalan."
29
+ },
30
+ "role_mapping": {"A": "AI Engineer", "B": "Front-End Web Developer"}
31
+ }
32
+ ]
33
+
34
+ def get_psych_question(index: int):
35
+ if index < len(PSYCH_QUESTIONS):
36
+ return PSYCH_QUESTIONS[index]
37
+ return None
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