diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8d536ab --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +data/ +__pycache__/ +**/__pycache__/ +*.pyc +.git/ +tools/ +*.docx +*.xlsx +RANCANGAN.md +README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7163fa8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# data lokal (nilai mahasiswa!) — jangan pernah ikut ke repositori +data/ + +__pycache__/ +*.pyc + +# pengaturan lokal Claude Code +.claude/ diff --git a/Contoh Portofolio MK 040126.xlsx b/Contoh Portofolio MK 040126.xlsx new file mode 100644 index 0000000..a97beb6 Binary files /dev/null and b/Contoh Portofolio MK 040126.xlsx differ diff --git a/Dockerfile b/Dockerfile index 073e4d4..cd9b051 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,25 @@ -FROM nginx:alpine +FROM python:3.12-slim -# Dashboard static single-page (tanpa backend) disajikan di root "/" -COPY Dashboard_Pemetaan_CPMK.html /usr/share/nginx/html/index.html +# Opsional: aktifkan baris berikut agar tombol "PDF" berfungsi di server +# (menambah ukuran image ±700 MB): +# RUN apt-get update && apt-get install -y --no-install-recommends \ +# libreoffice-writer && rm -rf /var/lib/apt/lists/* -# Target routing / health check Coolify -EXPOSE 80 +WORKDIR /srv +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY app ./app +COPY seed ./seed + +ENV DB_PATH=/data/app.db \ + PYTHONUNBUFFERED=1 +VOLUME /data + +EXPOSE 8000 +HEALTHCHECK --interval=30s --timeout=5s --start-period=15s \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/login')" || exit 1 + +# --proxy-headers: hormati X-Forwarded-* dari reverse proxy Coolify (Traefik) +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", \ + "--proxy-headers", "--forwarded-allow-ips=*"] diff --git a/RANCANGAN.md b/RANCANGAN.md new file mode 100644 index 0000000..cbfa763 --- /dev/null +++ b/RANCANGAN.md @@ -0,0 +1,227 @@ +# Rancangan Aplikasi Penyusunan RPS & Portofolio Mata Kuliah + +**Prodi Informatika (S-1) · Universitas Tanjungpura — Kurikulum OBE** + +## 1. Tujuan + +Membantu dosen menyusun **RPS (format APTIKOM 2024)** dan **Portofolio Mata Kuliah** +melalui form web, lalu menghasilkan dokumen **.docx yang formatnya identik dengan +template resmi** — tanpa dosen menyentuh Word sama sekali. + +Nilai tambah utama: data kurikulum yang sudah ada (10 CPL, 34 CPMK, 49 MK, +549 Sub-CPMK — sumber: dashboard pemetaan) dipakai untuk **mengisi otomatis** +bagian CPL/CPMK/Sub-CPMK begitu dosen memilih mata kuliah. Dosen hanya melengkapi +bagian yang memang pengetahuannya: tabel mingguan, penilaian, referensi. + +## 2. Keputusan desain (sudah disepakati) + +| Aspek | Keputusan | +|---|---| +| Deployment | Web app + server, deploy via Coolify (Dockerfile), seperti dashboard | +| Backend | Python: FastAPI + SQLite + python-docx | +| Autentikasi | **Login dengan NIP** — username = NIP, password awal = NIP (revisi 14 Jul 2026 — dibutuhkan karena aplikasi menyimpan nilai per mahasiswa; kredensial dibuat sesederhana mungkin atas keputusan user). Dua peran: **kaprodi** (kelola akun, penugasan MK, impor daftar mahasiswa, pengaturan, cadangan) dan **dosen** (edit RPS/porto & lihat nilai hanya pada MK yang ditugaskan; RPS MK lain terbuka baca). Keputusan awal "tanpa login" dibatalkan. | +| Template RPS | `Template_RPS_APTIKOM2024.docx` (kode generatornya sudah ada: `buat_template_rps.py`) | +| Template Portofolio | Akan diberikan user — generator dibuat setelah file diterima | + +## 3. Arsitektur + +``` +┌──────────────────────────── Docker (Coolify) ────────────────────────────┐ +│ FastAPI (uvicorn, port 8000) │ +│ ├── UI: Jinja2 templates + sedikit JS vanilla (tanpa build step) │ +│ ├── API: CRUD RPS/Portofolio, generate .docx │ +│ ├── Generator dokumen: python-docx (refactor dari buat_template_rps.py) │ +│ └── SQLite ←── volume persisten Coolify (/data/app.db) │ +└──────────────────────────────────────────────────────────────────────────┘ +Seed data: JSON kurikulum diekstrak dari dashboard-deploy/index.html (baris 237) +``` + +Prinsip: **satu container, tanpa build frontend, tanpa dependensi eksternal saat runtime.** +Form dirender server-side; interaktivitas (tambah baris minggu, hitung total bobot) +pakai JS vanilla inline. + +## 4. Model data + +Entitas kurikulum (read-only, di-seed sekali dari JSON dashboard, bisa di-reseed): + +- `cpl` — kode, deskripsi +- `mk` — kode, nama, sks, semester +- `cpmk` — kode, deskripsi, relasi ke CPL +- `sub_cpmk` — kode, uraian, relasi ke CPMK & MK + +Entitas isian dosen: + +- `rps` — satu per MK per tahun ajaran: + - identitas (bobot teori/praktikum, prasyarat, dosen pengampu, koordinator, kaprodi) + - deskripsi singkat MK + - pilihan CPL/CPMK/Sub-CPMK yang dibebankan (default: semua hasil pemetaan, bisa dikurangi) + - `rps_minggu` — 16 baris (minggu, sub-CPMK, indikator, kriteria & bentuk penilaian, + metode & estimasi waktu, materi & referensi, bobot %) — minggu 8 = UTS, 16 = UAS + - `rps_bobot` — rekap komponen penilaian (Tugas/Kuis, Praktikum, UTS, UAS) + - `rps_referensi` — daftar pustaka (APA) + - status: `draft` / `selesai`, `updated_at`, `updated_by` (nama diketik bebas) +- `portofolio` — satu per MK per semester berjalan; **struktur field mengikuti template + yang akan diberikan** (umumnya: realisasi pembelajaran, ketercapaian CPMK, distribusi + nilai, refleksi, rencana perbaikan) + +Entitas pengukuran (lihat §7): + +- `nilai_komponen` — realisasi skor per komponen penilaian per MK per semester + (rata-rata kelas 0–100; opsional per mahasiswa via impor CSV pada tahap lanjut) +- Bobot penilaian per Sub-CPMK **tidak perlu entitas baru** — sudah terkandung di + `rps_minggu.bobot` (tiap baris minggu terikat ke Sub-CPMK) dan `rps_bobot` + +## 5. Arah visual (UI) + +Kekhawatiran user: tampilan tidak boleh terasa "template buatan AI". Kesepakatan kerja: + +- **Tanpa framework CSS** (tanpa Tailwind/Bootstrap) — CSS ditulis tangan mengikuti + satu arah visual yang dipilih dari mockup. +- Tiga kandidat arah sudah dibuat sebagai mockup nyata (artifact + `Arah Visual — Aplikasi RPS & Portofolio`): + - **A · Dokumen Dinas** — tata naskah resmi: kop bergaris ganda, Times New Roman, + tabel bergaris penuh, biru `#1F4E79` dari template Word, status berupa stempel. + - **B · Lembar Kerja Teknik** — kertas milimeter, blok judul gambar teknik, + monospace untuk data, aksen oranye drafting. + - **C · Papan Prodi** — modernisme layanan publik: blok warna datar hijau + pinus + marigold, huruf Bahnschrift besar, sudut siku, tanpa bayangan. +- Orientasi layout **landscape/lebar** (keputusan user, 14 Jul 2026): "kertas" + aplikasi selebar `min(96vw, 1720px)` — memudahkan pengisian tabel mingguan + dan selaras dengan format RPS UNTAN yang memang landscape. +- Arah terpilih: **A · Dokumen Dinas** (keputusan user, 13 Jul 2026) — palet, tipografi, + dan komponen (tabel, chip status, tombol, form) dibakukan jadi style guide kecil + di `app/static/` dan dipakai konsisten di seluruh halaman. + +## 6. Alur pengguna + +1. **Beranda** — daftar 49 MK + status RPS/Portofolio (belum ada / draft / selesai). + Ini sekaligus jadi tampilan pantauan untuk kaprodi. +2. **Pilih MK** → form RPS terbuka dengan bagian B/C/D **sudah terisi** dari pemetaan. +3. Dosen melengkapi identitas, deskripsi, tabel mingguan, bobot, referensi. + Validasi ringan: total bobot harus 100 %, minggu 8/16 terkunci sebagai UTS/UAS, + saran kata kerja operasional Bloom (C1–C6) pada uraian. +4. **Simpan** kapan saja (draft) → **Unduh .docx** kapan saja; dokumen selalu + digenerate ulang dari data terkini sehingga format pasti konsisten. +5. Alur portofolio serupa: pilih MK → sebagian terisi dari RPS-nya → lengkapi → unduh. + +## 7. Pengukuran ketercapaian CPL & CPMK — inti OBE + +Dashboard yang ada (`dashboard-deploy/index.html`) memuat **graf relasi lengkap** +CPL → CPMK → MK → Sub-CPMK (549 Sub-CPMK) + matriks MK × CPL, tapi **tanpa bobot dan +tanpa nilai** — ia memetakan desain kurikulum, belum mengukur realisasinya. +Aplikasi ini melengkapi dua bahan yang hilang itu: + +| Bahan | Sumber di aplikasi | +|---|---| +| Relasi Sub-CPMK–CPMK–CPL–MK | Seed dari JSON dashboard (read-only) | +| Bobot penilaian per Sub-CPMK | Tabel mingguan (F) & rekap bobot (G) pada form RPS | +| Nilai realisasi | Form portofolio: rata-rata kelas per komponen penilaian | + +**Rumus penggulungan (roll-up):** + +1. **Ketercapaian Sub-CPMK** = skor komponen penilaian yang menilainya + (bila dinilai >1 komponen: rata-rata tertimbang bobot). +2. **Ketercapaian CPMK dalam satu MK** = Σ(bobot × skor Sub-CPMK pendukung) / Σ bobot. +3. **Ketercapaian CPMK tingkat prodi** = rata-rata ketercapaian CPMK itu lintas + semua MK yang mengukurnya (**bagi rata**, default — lihat catatan di bawah). +4. **Ketercapaian CPL** = rata-rata ketercapaian CPMK pendukungnya (**bagi rata**). + +> **Catatan bobot antar-MK** (hasil audit spreadsheet `Draft Kurikulum Terbaru +> 2025.xlsx`, 13 Jul 2026): tidak ada matriks bobot kontribusi MK→CPL di dokumen +> kurikulum. Sheet `(%)Pemetaan CPL-MK` hanya biner (1 = memikul) + kolom "beban +> CPL" (SKS + jumlah CPL) untuk cek beban, dan sheet 18 hanya distribusi MK per +> semester. Karena itu default mengikuti praktik umum panduan KPT/OBE Dikti: +> **rata-rata sederhana dua tahap** (MK → CPMK → CPL). Opsi **tertimbang SKS** +> disediakan sebagai toggle pengaturan prodi; matriks kontribusi manual = fitur +> lanjutan bila prodi kelak menetapkannya. +5. Ambang ketercapaian (mis. ≥ 70 = tercapai) dapat diatur di pengaturan prodi. + +Default bobot komponen penilaian untuk prefill rekap bobot (G) di form RPS — +diambil dari sheet `Asessement` draft kurikulum (Tabel 25): Kuis 15 %, +UTS 20 %, UAS 30 %, Tugas 35 %. Dosen bebas mengubahnya per MK. + +**Keluaran pengukuran:** + +- Halaman **Rekap Ketercapaian** — skor CPL & CPMK tingkat prodi per semester, + daftar CPL/CPMK di bawah ambang, dan MK mana penyumbangnya (pelengkap dashboard + pemetaan yang sudah ada). +- Tabel ketercapaian CPMK **otomatis masuk ke dokumen portofolio** tiap MK. +- Validasi di form RPS: setiap Sub-CPMK yang dipetakan ke MK harus ternilai oleh + minimal satu komponen — jika tidak, CPMK/CPL-nya tidak akan pernah terukur + (deteksi celah sejak perencanaan). + +**Keputusan final (disepakati user, 13 Jul 2026, hasil kajian "auditor OBE"):** + +1. **Bobot antar-MK: bagi rata dua tahap + terdokumentasi.** Aplikasi menyediakan + halaman **Metodologi Pengukuran** yang bisa dicetak (rumus roll-up, ambang, + periode) sebagai jawaban baku untuk asesor. Toggle SKS ada tapi baru diaktifkan + bila ada keputusan resmi prodi. +2. **Nilai per mahasiswa sejak awal di lapisan data.** Pintu masuk utama: unggah + Excel nilai (yang sudah dibuat dosen untuk SIAKAD). Ketik rata-rata kelas tetap + boleh sebagai transisi, tapi MK tersebut ditandai **"bukti agregat"** di rekap. + Rekap ketercapaian menampilkan **kelengkapan bukti** per CPL (x dari y MK + pemikul sudah setor nilai) berdampingan dengan skor. +3. **Kisi-kisi komponen × Sub-CPMK tampil**, terisi otomatis dari tabel mingguan + (bagi rata), dapat dikoreksi dosen, dan tercetak sebagai lampiran di dokumen + RPS/portofolio (bukti constructive alignment). +4. **Pengaturan prodi** (ambang kelulusan, skala huruf mutu, bobot default + komponen) jadi halaman sendiri — menggantikan kebijakan pinjaman dari pedoman + kampus lain yang ditemukan di sheet `Asessement`. + +Temuan data yang memengaruhi UI rekap: distribusi pemikul timpang (CPL-03: 27 MK +vs CPL-10: 6 MK; CPMK-043/101/103 hanya diukur 2 MK) → indikator kelengkapan bukti +wajib tampil; granularitas tinggi (median 10, maks 24 Sub-CPMK/MK) → kisi-kisi +wajib prefill otomatis. + +## 8. Generator dokumen + +**Keputusan format (user, 13 Jul 2026):** RPS memakai **format resmi UNTAN** +(`RPS Template.docx`, standar Dikti SN-Dikti Permendikbud 3/2020 — landscape, +satu tabel besar berlogo UNTAN); portofolio dibuat sebagai **.docx** dengan +struktur mengikuti `Contoh Portofolio MK 040126.xlsx` (10 bagian, termasuk +Evaluasi Ketercapaian yang diisi mesin roll-up tahap 2). + +- `generator/rps_untan.py` — pendekatan **isi-template-asli**: file template resmi + dimuat, sel diisi data, baris CPL/CPMK/Sub-CPMK/mingguan digandakan dari baris + prototipe (merge & gaya ikut tersalin), teks petunjuk pengisian dibuang, kop + diganti identitas prodi (fakultas/prodi via env `FAKULTAS` / `PRODI`), plus + lampiran kisi-kisi asesmen. Logo UNTAN bawaan template ikut tersalin otomatis; + logo yang sama dipakai di kop aplikasi web (`static/logo-untan.png`). +- `generator/rps_docx.py` — generator format APTIKOM 2024 (versi awal), disimpan + sebagai alternatif; tidak dipakai rute unduh. +- `generator/portofolio_docx.py` — tahap 2, meniru struktur contoh xlsx dalam + bentuk .docx. + +## 9. Struktur proyek + +``` +rps-portofolio-app/ +├── app/ +│ ├── main.py # FastAPI, routing +│ ├── db.py # SQLite (sqlite3/SQLModel), skema & seed +│ ├── generator/ +│ │ ├── rps_docx.py +│ │ └── portofolio_docx.py +│ ├── templates/ # Jinja2: beranda, form RPS, form portofolio +│ └── static/ # css + js vanilla +├── seed/kurikulum.json # hasil ekstraksi dari dashboard +├── Dockerfile +├── requirements.txt +└── RANCANGAN.md +``` + +## 10. Tahapan pengerjaan + +1. **MVP RPS** — seed data kurikulum, beranda status, form RPS, generate .docx, Dockerfile. +2. **Portofolio + Pengukuran** — setelah template diterima: analisis struktur, form portofolio, input nilai komponen, mesin roll-up ketercapaian CPMK/CPL, halaman Rekap Ketercapaian, generator .docx portofolio. +3. **Penyempurnaan** — duplikasi RPS antar tahun ajaran, ekspor PDF, rekap + ketercapaian CPMK lintas MK, backup/ekspor seluruh database ke JSON. + +## 11. Risiko & mitigasi + +- **Tanpa login → bisa saling timpa**: setiap simpan mencatat nama & waktu; riwayat + versi sederhana (simpan snapshot JSON tiap kali status berubah ke `selesai`). +- **Data kurikulum berubah**: seed dipisah sebagai JSON; ada perintah re-seed yang + tidak menyentuh data isian dosen. +- **SQLite di container**: wajib volume persisten di Coolify (`/data`), plus tombol + ekspor database dari UI sebagai cadangan. diff --git a/README.md b/README.md index f9fad2f..6e3acf6 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,137 @@ -# OBE-Mapping — Dashboard Pemetaan CPMK +# Aplikasi Penyusunan RPS & Portofolio — Prodi Informatika UNTAN -Dashboard interaktif **one-page** untuk pemetaan capaian pembelajaran Kurikulum OBE -**Prodi Informatika (S-1) · Fakultas Teknik · Universitas Tanjungpura**. +Web app untuk dosen menyusun **RPS (APTIKOM 2024)** dan **Portofolio MK** dengan +keluaran .docx berformat identik template resmi, sekaligus mesin pengukuran +ketercapaian CPL/CPMK (Kurikulum OBE). Rancangan lengkap: `RANCANGAN.md`. -Sumber data: `Dokumen_9_RPS_Rumusan_Pemetaan_CPMK.docx` (BAB IX — RPS). - -## Isi data - -| Entitas | Jumlah | -|-----------|--------| -| CPL | 10 | -| CPMK | 34 | -| Mata Kuliah | 49 | -| Sub-CPMK | 549 | - -## Fitur - -- **Kartu ringkasan (KPI)** — total CPL / CPMK / MK / Sub-CPMK. -- **Diagram alur Sankey** CPL → CPMK → MK (SVG murni, offline). -- **Matriks pemetaan MK × CPL** interaktif (filter, sorot kolom). -- **Explorer bertingkat** CPL → CPMK → Sub-CPMK. -- **Pencarian Mata Kuliah** + modal detail CPMK & Sub-CPMK. -- **Analisis cakupan & deteksi celah** pemetaan. -- **Linked highlighting** lintas panel, **pencarian global**, **mode terang/gelap**, - **ekspor CSV/PDF**, **deep-link** (URL hash). - -Seluruh data ter-_embed_ di dalam `Dashboard_Pemetaan_CPMK.html` — **berjalan tanpa internet** -dan tanpa server backend. - -## Struktur repo +## Menjalankan lokal +```bash +pip install -r requirements.txt +uvicorn app.main:app --reload +# buka http://localhost:8000 ``` -Dashboard_Pemetaan_CPMK.html # dashboard (file utama, semua data ter-embed) -Dockerfile # nginx menyajikan dashboard di "/" -README.md + +Database SQLite dibuat otomatis di `data/app.db` (atau path di env `DB_PATH`). +Tahun ajaran aktif diatur lewat env `TAHUN_AJARAN` (default: "2026/2027 Ganjil"). + +## Memperbarui data kurikulum + +Data CPL/CPMK/MK/Sub-CPMK di-seed dari dashboard pemetaan: + +```bash +python tools/extract_seed.py # baca ../dashboard-deploy/index.html +python tools/extract_seed.py # atau sumber lain +``` + +Re-seed tidak menyentuh isian RPS dosen (tersimpan terpisah di SQLite). + +Dashboard Pemetaan CPMK (buatan user) ikut disajikan di rute **`/dashboard`** +dari salinan `app/static/dashboard.html`. Bila dashboard sumber diperbarui, +salin ulang dan re-seed sekaligus: + +```bash +cp ../dashboard-deploy/index.html app/static/dashboard.html +python tools/extract_seed.py ``` ## Deploy via Coolify -1. **New Resource → Application** dari repo Git ini (`OBE-Mapping`, branch `main`). -2. **Build Pack: Dockerfile** (otomatis terdeteksi dari `Dockerfile` di root). -3. **Port: 80**. -4. Set **Domain** sesuai kebutuhan, lalu **Deploy**. +Prasyarat: kode berada di repositori Git yang bisa diakses Coolify +(mis. GitHub private repo). `data/` di-gitignore — nilai mahasiswa tidak +pernah ikut ke repositori. -Setiap `git push` ke `main` akan memicu rebuild & redeploy otomatis bila webhook Coolify aktif. +1. Push repo ini ke GitHub (private), hubungkan ke Coolify: + **New Resource → Application → Private Repository**, Build Pack: + **Dockerfile**, Port **8000**. +2. Tambahkan **volume persisten** ke `/data` (wajib — tempat SQLite; + tanpa ini data hilang tiap redeploy). +3. Env (opsional, ada default): `TAHUN_AJARAN` (mis. `2026/2027 Ganjil`; + hanya fallback awal — tahun aktif selanjutnya diatur di `/metodologi`), + `FAKULTAS`, `PRODI` (kop dokumen .docx). +4. Deploy, buka domainnya → kunjungan pertama membuka `/setup` untuk + membuat akun kaprodi. +5. **Memindahkan data lokal ke server** (bila isian lokal mau dibawa): + unduh `/cadangan.db` dari instalasi lokal, lalu salin ke volume server + sebagai `/data/app.db` (`docker cp cadangan.db :/data/app.db` + lalu restart), atau mulai bersih dari `/setup`. +6. Tombol PDF butuh LibreOffice — aktifkan baris `apt-get install + libreoffice-writer` di Dockerfile bila diperlukan (image +±700 MB); + tanpa itu pengguna diarahkan unduh .docx. -### Alternatif (tanpa Dockerfile) +Healthcheck sudah terpasang di Dockerfile (`/login`); uvicorn berjalan +dengan `--proxy-headers` agar cocok di belakang proxy Coolify. -Coolify juga bisa pakai **Static Site** build pack: salin/rename file menjadi `index.html` -dan arahkan _publish directory_ ke root repo. +## Status fitur -## Pengembangan lokal - -Cukup buka `Dashboard_Pemetaan_CPMK.html` langsung di browser, atau jalankan server statis: - -```bash -python -m http.server 8080 -# buka http://localhost:8080/Dashboard_Pemetaan_CPMK.html -``` +- [x] Tahap 1 — MVP RPS: beranda status 49 MK, form RPS (CPL/CPMK/Sub-CPMK + otomatis dari pemetaan, validasi bobot 100 %, cek cakupan Sub-CPMK), + unduh .docx **format resmi UNTAN** (template asli diisi langsung — logo, + merge, dan tata letak terjaga; + lampiran kisi-kisi asesmen), arsip versi + saat "selesai". Fakultas/prodi pada kop diatur env `FAKULTAS` / `PRODI`. +- [x] Tahap 2 — Portofolio + Pengukuran: input nilai per MK di `/nilai/` + (unggah Excel per mahasiswa dari template yang digenerate aplikasi, atau + ketik rata-rata → ditandai "bukti agregat"); mesin roll-up bagi rata + Sub-CPMK → CPMK → CPL (`app/hitung.py`); halaman `/rekap` (skor CPL/CPMK + prodi, status vs ambang, kelengkapan bukti, penyumbang terlemah); + `/metodologi` (dokumentasi rumus + pengaturan ambang/skala huruf, bisa + dicetak); form portofolio `/portofolio/` + unduh .docx (Cover, + Pengesahan, CPL, korelasi, rencana penilaian, matriks nilai mahasiswa, + evaluasi ketercapaian, statistik & simpulan — struktur mengikuti + `Contoh Portofolio MK 040126.xlsx`). +- [x] Tahap 3 — Penyempurnaan: + - **Tahun ajaran dinamis** — diatur di `/metodologi`; semua form & rekap + mengikuti tahun aktif, data tahun lama tetap tersimpan. + - **Duplikasi RPS antar tahun ajaran** — tombol "Salin dari " + muncul di form RPS yang masih kosong; hasil salinan berstatus draft + dan tidak pernah menimpa isian tahun aktif. + - **Data SKS + semester per MK** (`seed/sks.json`, dari sheet + `14. Susunan MK`; perbarui: `python tools/extract_sks.py`) — dipakai + untuk prefill form RPS dan mengaktifkan **roll-up tertimbang SKS** + (opsi di `/metodologi`). + - **Riwayat lintas semester** (`/rekap/riwayat`) — matriks skor CPL × + tahun ajaran + kelengkapan bukti, untuk melihat tren. + - **Cadangan** — `/cadangan.json` (seluruh isian, termasuk arsip & + pengaturan) dan `/cadangan.db` (file SQLite mentah). + - **Ekspor PDF** — `/rps//unduh.pdf` & `/portofolio//unduh.pdf` + via LibreOffice bila terpasang di server; bila tidak, pengguna diarahkan + unduh .docx (lihat catatan Dockerfile). +- [x] Tahap 4 — Akun & privasi nilai (14 Jul 2026): + - **Login dengan NIP** (username = NIP, password awal = NIP; hash PBKDF2, + cookie sesi HttpOnly). Kunjungan pertama saat belum ada akun membuka + `/setup` untuk membuat akun kaprodi. "Reset password" di `/admin` + mengembalikan password ke NIP. + - **Dua peran** — *kaprodi*: kelola akun & reset password (`/admin`), + penugasan MK per dosen, pengaturan prodi, cadangan; *dosen*: mengedit + RPS/portofolio dan mengakses nilai hanya pada MK yang ditugaskan + (RPS/porto MK lain tetap terbuka baca; unduhan portofolio yang memuat + nilai mahasiswa ikut dibatasi). Nama penyimpan (`updated_by`) otomatis + dari akun login. + - **Impor daftar mahasiswa** — kaprodi mengunggah satu Excel kontrak + semester (NPM, Nama, Kode MK, Kelas; template dari `/admin`); daftar ini + mengisi otomatis template nilai tiap MK. +- [x] Dasbor monitoring (`/pantau`, 18 Jul 2026): grafik ketercapaian CPL & + CPMK semester berjalan vs ambang (SVG tanpa library, warna mengikuti + status), kelengkapan proses dokumen RPS → Nilai → Portofolio, tren skor + CPL lintas tahun ajaran (sparkline, tampil bila ≥ 2 tahun berdata), dan + daftar "perlu perhatian": CPL di bawah ambang + penyumbang terlemahnya, + serta MK yang belum setor nilai lengkap dengan pengampunya. +- [x] Kerapian navigasi & team teaching (18 Jul 2026): + - **Menu global** di semua halaman (base.html) menggantikan tautan + ad-hoc per halaman; halaman aktif ditandai; Cadangan pindah ke `/admin`. + - **Filter semester di beranda** (kolom Smt dari `seed/sks.json`) dengan + KPI yang menghitung ulang sesuai filter + persentase berwarna + (hijau ≥ 75 %, kuning 40–74 %, merah < 40 %). + - **Semua tabel bisa diurut** klik header naik/turun (`static/tabel.js`; + tabel di dalam form dilewati, baris total tetap di bawah). + - **Team teaching nilai**: unggahan Excel **menggabung** per sel — tiap + dosen mengunggah bagiannya (kolom Sub-CPMK sebelum/sesudah UTS atau + baris mahasiswa kelasnya) tanpa menimpa dosen lain; riwayat unggahan + tercatat per file+dosen; tombol "Mulai Ulang" menghapus total; mode + rata-rata ditolak bila data per-mahasiswa sudah ada. Halaman nilai + dirapikan: satu metodologi, cara agregat dilipat sebagai alternatif. + - **Input nilai langsung di aplikasi** (cara utama): tabel mahasiswa × + Sub-CPMK yang bisa diketik, terisi otomatis dari impor kaprodi + nilai + tersimpan; menyimpan mengganti seluruh isi tabel dengan yang tampil. + Unggah Excel tetap tersedia (dilipat) untuk nilai yang sudah direkap + atau team teaching yang mengisi terpisah. diff --git a/RPS Template.docx b/RPS Template.docx new file mode 100644 index 0000000..68a89f1 Binary files /dev/null and b/RPS Template.docx differ diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/auth.py b/app/auth.py new file mode 100644 index 0000000..c58ec22 --- /dev/null +++ b/app/auth.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +"""Autentikasi: hashing password (PBKDF2) & token sesi.""" +import hashlib +import hmac +import secrets + +_ITERASI = 200_000 + + +def hash_password(password: str) -> str: + salt = secrets.token_hex(16) + h = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), + bytes.fromhex(salt), _ITERASI) + return f"{salt}${h.hex()}" + + +def verifikasi_password(password: str, tersimpan: str) -> bool: + try: + salt, h = tersimpan.split("$", 1) + calon = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), + bytes.fromhex(salt), _ITERASI) + return hmac.compare_digest(calon.hex(), h) + except (ValueError, TypeError): + return False + + +def token_baru() -> str: + return secrets.token_urlsafe(32) diff --git a/app/db.py b/app/db.py new file mode 100644 index 0000000..5b008e2 --- /dev/null +++ b/app/db.py @@ -0,0 +1,486 @@ +# -*- coding: utf-8 -*- +"""Lapisan data: kurikulum (read-only, dari seed JSON) + isian RPS (SQLite).""" +import json +import os +import pathlib +import sqlite3 +from datetime import datetime, timezone, timedelta + +BASE = pathlib.Path(__file__).resolve().parent.parent +SEED = BASE / "seed" / "kurikulum.json" +DB_PATH = os.environ.get("DB_PATH", str(BASE / "data" / "app.db")) + +WIB = timezone(timedelta(hours=7)) + +TAHUN_AJARAN_AKTIF = os.environ.get("TAHUN_AJARAN", "2026/2027 Ganjil") + + +def tahun_aktif() -> str: + """Tahun ajaran aktif — dari pengaturan prodi, fallback env TAHUN_AJARAN.""" + return ambil_pengaturan().get("tahun_aktif") or TAHUN_AJARAN_AKTIF + + +# ── kurikulum (dimuat sekali saat start) ───────────────────────────────────── + +_kur = None +_sks = None + + +def kurikulum() -> dict: + global _kur + if _kur is None: + _kur = json.loads(SEED.read_text(encoding="utf-8")) + return _kur + + +def sks_mk() -> dict: + """{kode: {'sks': x, 'semester': n}} dari seed/sks.json (boleh kosong).""" + global _sks + if _sks is None: + p = SEED.parent / "sks.json" + _sks = json.loads(p.read_text(encoding="utf-8")) if p.exists() else {} + return _sks + + +def daftar_mk() -> list[dict]: + """Semua MK terurut nomor kurikulum, dengan ringkasan pemetaan.""" + k = kurikulum() + out = [] + for kode, mk in k["mk"].items(): + sub_mk = k["sub"].get(kode, {"cpmks": {}}) + n_sub = sum(len(c["subs"]) for c in sub_mk["cpmks"].values()) + out.append( + { + "kode": kode, + "nama": mk["nama"], + "no": mk.get("no", 999), + "cpls": sorted(mk.get("cpls", [])), + "n_cpmk": len(sub_mk["cpmks"]), + "n_sub": n_sub, + } + ) + return sorted(out, key=lambda m: m["no"]) + + +def peta_mk(kode: str) -> dict | None: + """Pemetaan lengkap satu MK: CPL, CPMK, dan Sub-CPMK yang dipikulnya.""" + k = kurikulum() + mk = k["mk"].get(kode) + if not mk: + return None + sub_mk = k["sub"].get(kode, {"cpmks": {}}) + + cpmks = [] + for ckey, cval in sub_mk["cpmks"].items(): + info = k["cpmk"].get(ckey, {}) + cpmks.append( + { + "kode": info.get("kode", ckey), + "key": ckey, + "desk": cval["desk"], + "cpl": info.get("cpl", []), + "subs": cval["subs"], + } + ) + cpmks.sort(key=lambda c: c["kode"]) + + cpl_kode = sorted({cp for c in cpmks for cp in c["cpl"]} | set(mk.get("cpls", []))) + cpls = [{"kode": cp, "desk": k["cpl"][cp]["desk"]} for cp in cpl_kode if cp in k["cpl"]] + + subs = [ + {"kode": s["kode"], "uraian": s["uraian"], "cpmk": c["kode"]} + for c in cpmks + for s in c["subs"] + ] + return {"kode": kode, "nama": mk["nama"], "cpls": cpls, "cpmks": cpmks, "subs": subs} + + +# ── SQLite: isian RPS ──────────────────────────────────────────────────────── + + +def get_conn() -> sqlite3.Connection: + pathlib.Path(DB_PATH).parent.mkdir(parents=True, exist_ok=True) + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row + return conn + + +def init_db() -> None: + with get_conn() as conn: + for tabel in ("rps", "portofolio", "nilai"): + conn.execute( + f"""CREATE TABLE IF NOT EXISTS {tabel} ( + mk_kode TEXT NOT NULL, + tahun TEXT NOT NULL, + data TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'draft', + updated_at TEXT NOT NULL, + updated_by TEXT NOT NULL DEFAULT '', + PRIMARY KEY (mk_kode, tahun) + )""" + ) + conn.execute( + """CREATE TABLE IF NOT EXISTS rps_arsip ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + mk_kode TEXT NOT NULL, + tahun TEXT NOT NULL, + data TEXT NOT NULL, + status TEXT NOT NULL, + updated_at TEXT NOT NULL, + updated_by TEXT NOT NULL + )""" + ) + conn.execute( + """CREATE TABLE IF NOT EXISTS pengaturan ( + kunci TEXT PRIMARY KEY, + nilai TEXT NOT NULL + )""" + ) + conn.execute( + """CREATE TABLE IF NOT EXISTS pengguna ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT NOT NULL UNIQUE, + nama TEXT NOT NULL, + role TEXT NOT NULL CHECK (role IN ('kaprodi','dosen')), + pw_hash TEXT NOT NULL, + dibuat TEXT NOT NULL + )""" + ) + conn.execute( + """CREATE TABLE IF NOT EXISTS sesi ( + token TEXT PRIMARY KEY, + user_id INTEGER NOT NULL, + dibuat TEXT NOT NULL + )""" + ) + conn.execute( + """CREATE TABLE IF NOT EXISTS penugasan ( + user_id INTEGER NOT NULL, + mk_kode TEXT NOT NULL, + PRIMARY KEY (user_id, mk_kode) + )""" + ) + conn.execute( + """CREATE TABLE IF NOT EXISTS mahasiswa ( + npm TEXT NOT NULL, + nama TEXT NOT NULL, + mk_kode TEXT NOT NULL, + kelas TEXT NOT NULL DEFAULT '', + tahun TEXT NOT NULL, + PRIMARY KEY (npm, mk_kode, tahun) + )""" + ) + + +# ── pengaturan prodi ───────────────────────────────────────────────────────── + +PENGATURAN_DEFAULT = { + "tahun_aktif": TAHUN_AJARAN_AKTIF, + "ambang": "70", + "metode_antar_mk": "rata", # 'rata' (bagi rata) | 'sks' (tertimbang SKS) + # skala huruf mutu — dari contoh portofolio; format: HURUF|MIN|MAX|ANGKA + "skala_huruf": ( + "A+|91|100|4.00\nA|86|90|3.75\nA-|81|85|3.50\nB+|76|80|3.25\n" + "B|71|75|3.00\nB-|66|70|2.75\nC+|61|65|2.50\nC|56|60|2.00\n" + "D|41|55|1.00\nE|0|40|0.00" + ), +} + + +def ambil_pengaturan() -> dict: + with get_conn() as conn: + rows = conn.execute("SELECT kunci, nilai FROM pengaturan").fetchall() + out = dict(PENGATURAN_DEFAULT) + out.update({r["kunci"]: r["nilai"] for r in rows}) + return out + + +def simpan_pengaturan(d: dict) -> None: + with get_conn() as conn: + for k, v in d.items(): + if k in PENGATURAN_DEFAULT: + conn.execute( + "INSERT INTO pengaturan (kunci, nilai) VALUES (?,?) " + "ON CONFLICT(kunci) DO UPDATE SET nilai=excluded.nilai", + (k, str(v)), + ) + + +_TABEL_DOK = ("rps", "portofolio", "nilai") + + +def simpan_dok(tabel: str, mk_kode: str, data: dict, status: str, updated_by: str, + tahun: str | None = None) -> None: + assert tabel in _TABEL_DOK + tahun = tahun or tahun_aktif() + now = datetime.now(WIB).strftime("%Y-%m-%d %H:%M") + payload = json.dumps(data, ensure_ascii=False) + with get_conn() as conn: + conn.execute( + f"""INSERT INTO {tabel} (mk_kode, tahun, data, status, updated_at, updated_by) + VALUES (?,?,?,?,?,?) + ON CONFLICT(mk_kode, tahun) DO UPDATE SET + data=excluded.data, status=excluded.status, + updated_at=excluded.updated_at, updated_by=excluded.updated_by""", + (mk_kode, tahun, payload, status, now, updated_by), + ) + # snapshot arsip RPS setiap kali status ditetapkan 'selesai' + if tabel == "rps" and status == "selesai": + conn.execute( + """INSERT INTO rps_arsip (mk_kode, tahun, data, status, updated_at, updated_by) + VALUES (?,?,?,?,?,?)""", + (mk_kode, tahun, payload, status, now, updated_by), + ) + + +def hapus_dok(tabel: str, mk_kode: str, tahun: str | None = None) -> None: + assert tabel in _TABEL_DOK + tahun = tahun or tahun_aktif() + with get_conn() as conn: + conn.execute(f"DELETE FROM {tabel} WHERE mk_kode=? AND tahun=?", + (mk_kode, tahun)) + + +def ambil_dok(tabel: str, mk_kode: str, tahun: str | None = None) -> dict | None: + assert tabel in _TABEL_DOK + tahun = tahun or tahun_aktif() + with get_conn() as conn: + row = conn.execute( + f"SELECT * FROM {tabel} WHERE mk_kode=? AND tahun=?", (mk_kode, tahun) + ).fetchone() + if not row: + return None + return { + "data": json.loads(row["data"]), + "status": row["status"], + "updated_at": row["updated_at"], + "updated_by": row["updated_by"], + } + + +def status_semua(tabel: str = "rps", tahun: str | None = None) -> dict[str, dict]: + assert tabel in _TABEL_DOK + tahun = tahun or tahun_aktif() + with get_conn() as conn: + rows = conn.execute( + f"SELECT mk_kode, status, updated_at, updated_by FROM {tabel} WHERE tahun=?", + (tahun,), + ).fetchall() + return {r["mk_kode"]: dict(r) for r in rows} + + +def semua_nilai(tahun: str | None = None) -> dict[str, dict]: + tahun = tahun or tahun_aktif() + with get_conn() as conn: + rows = conn.execute( + "SELECT mk_kode, data FROM nilai WHERE tahun=?", (tahun,) + ).fetchall() + return {r["mk_kode"]: json.loads(r["data"]) for r in rows} + + +def daftar_tahun() -> list[str]: + """Semua tahun ajaran yang pernah punya data (RPS/nilai/portofolio).""" + tahun = {tahun_aktif()} + with get_conn() as conn: + for tabel in _TABEL_DOK: + for r in conn.execute(f"SELECT DISTINCT tahun FROM {tabel}"): + tahun.add(r["tahun"]) + return sorted(tahun) + + +def tahun_rps_lain(mk_kode: str) -> list[str]: + """Tahun lain (selain aktif) yang punya RPS untuk MK ini — bahan duplikasi.""" + aktif = tahun_aktif() + with get_conn() as conn: + rows = conn.execute( + "SELECT tahun FROM rps WHERE mk_kode=? AND tahun<>? ORDER BY tahun DESC", + (mk_kode, aktif), + ).fetchall() + return [r["tahun"] for r in rows] + + +def dump_semua() -> dict: + """Seluruh isi database sebagai dict (untuk cadangan JSON).""" + out = {} + with get_conn() as conn: + for tabel in (*_TABEL_DOK, "rps_arsip", "pengaturan", + "pengguna", "penugasan", "mahasiswa"): + rows = conn.execute(f"SELECT * FROM {tabel}").fetchall() + out[tabel] = [dict(r) for r in rows] + for tabel in _TABEL_DOK + ("rps_arsip",): + for r in out.get(tabel, []): + r["data"] = json.loads(r["data"]) + return out + + +# ── pengguna, sesi, penugasan ──────────────────────────────────────────────── + + +def ada_pengguna() -> bool: + with get_conn() as conn: + return conn.execute("SELECT 1 FROM pengguna LIMIT 1").fetchone() is not None + + +def buat_pengguna(username: str, nama: str, role: str, pw_hash: str) -> int | None: + now = datetime.now(WIB).strftime("%Y-%m-%d %H:%M") + try: + with get_conn() as conn: + cur = conn.execute( + "INSERT INTO pengguna (username, nama, role, pw_hash, dibuat) " + "VALUES (?,?,?,?,?)", + (username, nama, role, pw_hash, now), + ) + return cur.lastrowid + except sqlite3.IntegrityError: + return None + + +def pengguna_by_username(username: str) -> dict | None: + with get_conn() as conn: + row = conn.execute( + "SELECT * FROM pengguna WHERE username=?", (username,) + ).fetchone() + return dict(row) if row else None + + +def pengguna_by_id(user_id: int) -> dict | None: + with get_conn() as conn: + row = conn.execute("SELECT * FROM pengguna WHERE id=?", (user_id,)).fetchone() + return dict(row) if row else None + + +def daftar_pengguna() -> list[dict]: + with get_conn() as conn: + rows = conn.execute( + "SELECT id, username, nama, role, dibuat FROM pengguna " + "ORDER BY role, nama" + ).fetchall() + tugas = conn.execute( + "SELECT user_id, mk_kode FROM penugasan ORDER BY mk_kode" + ).fetchall() + per_user: dict[int, list[str]] = {} + for t in tugas: + per_user.setdefault(t["user_id"], []).append(t["mk_kode"]) + return [{**dict(r), "mk": per_user.get(r["id"], [])} for r in rows] + + +def hapus_pengguna(user_id: int) -> None: + with get_conn() as conn: + conn.execute("DELETE FROM pengguna WHERE id=?", (user_id,)) + conn.execute("DELETE FROM sesi WHERE user_id=?", (user_id,)) + conn.execute("DELETE FROM penugasan WHERE user_id=?", (user_id,)) + + +def ubah_password(user_id: int, pw_hash: str) -> None: + with get_conn() as conn: + conn.execute("UPDATE pengguna SET pw_hash=? WHERE id=?", (pw_hash, user_id)) + # sesi lama gugur agar pemegang password lama langsung keluar + conn.execute("DELETE FROM sesi WHERE user_id=?", (user_id,)) + + +def set_penugasan(user_id: int, kode_list: list[str]) -> None: + with get_conn() as conn: + conn.execute("DELETE FROM penugasan WHERE user_id=?", (user_id,)) + conn.executemany( + "INSERT OR IGNORE INTO penugasan (user_id, mk_kode) VALUES (?,?)", + [(user_id, k) for k in kode_list], + ) + + +def mk_diampu(user_id: int) -> set[str]: + with get_conn() as conn: + rows = conn.execute( + "SELECT mk_kode FROM penugasan WHERE user_id=?", (user_id,) + ).fetchall() + return {r["mk_kode"] for r in rows} + + +def pengampu_per_mk() -> dict[str, list[str]]: + """{mk_kode: [nama dosen, ...]} — untuk kolom pengampu di beranda.""" + with get_conn() as conn: + rows = conn.execute( + "SELECT p.mk_kode, u.nama FROM penugasan p " + "JOIN pengguna u ON u.id = p.user_id ORDER BY u.nama" + ).fetchall() + out: dict[str, list[str]] = {} + for r in rows: + out.setdefault(r["mk_kode"], []).append(r["nama"]) + return out + + +def buat_sesi(user_id: int, token: str) -> None: + now = datetime.now(WIB).strftime("%Y-%m-%d %H:%M") + with get_conn() as conn: + conn.execute( + "INSERT INTO sesi (token, user_id, dibuat) VALUES (?,?,?)", + (token, user_id, now), + ) + + +def user_dari_sesi(token: str) -> dict | None: + with get_conn() as conn: + row = conn.execute( + "SELECT u.* FROM sesi s JOIN pengguna u ON u.id = s.user_id " + "WHERE s.token=?", + (token,), + ).fetchone() + return dict(row) if row else None + + +def hapus_sesi(token: str) -> None: + with get_conn() as conn: + conn.execute("DELETE FROM sesi WHERE token=?", (token,)) + + +# ── mahasiswa (kontrak MK per kelas, diimpor kaprodi) ──────────────────────── + + +def ganti_mahasiswa(baris: list[dict], tahun: str | None = None) -> None: + """Ganti seluruh daftar mahasiswa tahun tsb dengan hasil impor terbaru.""" + tahun = tahun or tahun_aktif() + with get_conn() as conn: + conn.execute("DELETE FROM mahasiswa WHERE tahun=?", (tahun,)) + conn.executemany( + "INSERT OR REPLACE INTO mahasiswa (npm, nama, mk_kode, kelas, tahun) " + "VALUES (?,?,?,?,?)", + [(b["npm"], b["nama"], b["mk_kode"], b.get("kelas", ""), tahun) + for b in baris], + ) + + +def mahasiswa_mk(mk_kode: str, tahun: str | None = None) -> list[dict]: + tahun = tahun or tahun_aktif() + with get_conn() as conn: + rows = conn.execute( + "SELECT npm, nama, kelas FROM mahasiswa WHERE mk_kode=? AND tahun=? " + "ORDER BY kelas, npm", + (mk_kode, tahun), + ).fetchall() + return [dict(r) for r in rows] + + +def ringkasan_mahasiswa(tahun: str | None = None) -> dict[str, dict]: + """{mk_kode: {'n': jumlah, 'kelas': [daftar kelas]}} tahun aktif.""" + tahun = tahun or tahun_aktif() + with get_conn() as conn: + rows = conn.execute( + "SELECT mk_kode, kelas, COUNT(*) AS n FROM mahasiswa WHERE tahun=? " + "GROUP BY mk_kode, kelas", + (tahun,), + ).fetchall() + out: dict[str, dict] = {} + for r in rows: + d = out.setdefault(r["mk_kode"], {"n": 0, "kelas": []}) + d["n"] += r["n"] + if r["kelas"]: + d["kelas"].append(r["kelas"]) + return out + + +# kompatibilitas rute lama +def simpan_rps(mk_kode, data, status, updated_by, tahun=None): + simpan_dok("rps", mk_kode, data, status, updated_by, tahun) + + +def ambil_rps(mk_kode, tahun=None): + return ambil_dok("rps", mk_kode, tahun) diff --git a/app/generator/__init__.py b/app/generator/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/generator/portofolio_docx.py b/app/generator/portofolio_docx.py new file mode 100644 index 0000000..d4dfeef --- /dev/null +++ b/app/generator/portofolio_docx.py @@ -0,0 +1,438 @@ +# -*- coding: utf-8 -*- +""" +Generator Portofolio Mata Kuliah (.docx). +Struktur mengikuti `Contoh Portofolio MK 040126.xlsx`: +Cover · Lembar Pengesahan · A CPL · B CPMK & Sub-CPMK · C Korelasi CPL–Sub-CPMK +· D Rencana Penilaian & Tugas · E Realisasi Pembelajaran · F Penilaian +· G Evaluasi Ketercapaian · H Simpulan & Tindak Lanjut. +""" +import io +import os +import pathlib + +from docx import Document +from docx.enum.section import WD_ORIENT +from docx.enum.table import WD_ALIGN_VERTICAL +from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_BREAK +from docx.oxml import OxmlElement +from docx.oxml.ns import qn +from docx.shared import Cm, Pt, RGBColor + +DARK_BLUE = "1F4E79" +LIGHT_BLUE = "D6E4F0" +RED = "9A3324" +WHITE = "FFFFFF" + +LOGO = pathlib.Path(__file__).resolve().parent.parent / "static" / "logo-untan.png" +FAKULTAS = os.environ.get("FAKULTAS", "FAKULTAS TEKNIK") +PRODI = os.environ.get("PRODI", "PROGRAM STUDI INFORMATIKA") + + +def _shd(cell, hex_color): + tc_pr = cell._tc.get_or_add_tcPr() + el = OxmlElement("w:shd") + el.set(qn("w:val"), "clear") + el.set(qn("w:color"), "auto") + el.set(qn("w:fill"), hex_color) + tc_pr.append(el) + + +def _ct(cell, text, bold=False, italic=False, size=9, color=None, + align=WD_ALIGN_PARAGRAPH.LEFT): + p = cell.paragraphs[0] + p.alignment = align + p.paragraph_format.space_before = Pt(1) + p.paragraph_format.space_after = Pt(1) + run = p.add_run() + for i, line in enumerate(str(text or "").split("\n")): + if i: + run.add_break() + run.add_text(line) + run.bold = bold + run.italic = italic + run.font.name = "Times New Roman" + run.font.size = Pt(size) + if color: + run.font.color.rgb = RGBColor.from_string(color) + cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER + + +def _hdr(cell, text, size=9): + _shd(cell, DARK_BLUE) + _ct(cell, text, bold=True, color=WHITE, size=size, align=WD_ALIGN_PARAGRAPH.CENTER) + + +def _borders(table): + tbl_pr = table._tbl.tblPr + el = OxmlElement("w:tblBorders") + for sisi in ("top", "left", "bottom", "right", "insideH", "insideV"): + b = OxmlElement(f"w:{sisi}") + b.set(qn("w:val"), "single") + b.set(qn("w:sz"), "4") + b.set(qn("w:color"), "000000") + el.append(b) + tbl_pr.append(el) + + +def _f(x, nd=2): + return f"{x:.{nd}f}" if isinstance(x, (int, float)) else "—" + + +def build_portofolio(d: dict) -> bytes: + doc = Document() + for sec in doc.sections: + sec.orientation = WD_ORIENT.LANDSCAPE + sec.page_width, sec.page_height = Cm(29.7), Cm(21.0) + sec.top_margin = sec.bottom_margin = Cm(2) + sec.left_margin = sec.right_margin = Cm(2) + + ident = d.get("identitas", {}) + dok = d.get("dokumen", {}) + + def para(text, size=11, bold=False, italic=False, align=WD_ALIGN_PARAGRAPH.LEFT, + before=6, after=3): + p = doc.add_paragraph() + p.alignment = align + p.paragraph_format.space_before = Pt(before) + p.paragraph_format.space_after = Pt(after) + r = p.add_run(text) + r.bold, r.italic = bold, italic + r.font.name = "Times New Roman" + r.font.size = Pt(size) + return p + + def bagian(judul): + para(judul, size=12, bold=True, before=14) + + # ── COVER ── + for _ in range(3): + doc.add_paragraph() + para("PORTOFOLIO MATA KULIAH", size=22, bold=True, align=WD_ALIGN_PARAGRAPH.CENTER) + para(f"{ident.get('nama', '')} ({ident.get('kode', '')})", size=16, bold=True, + align=WD_ALIGN_PARAGRAPH.CENTER) + doc.add_paragraph() + if LOGO.exists(): + p = doc.add_paragraph() + p.alignment = WD_ALIGN_PARAGRAPH.CENTER + p.add_run().add_picture(str(LOGO), width=Cm(4.5)) + doc.add_paragraph() + para(PRODI, size=13, bold=True, align=WD_ALIGN_PARAGRAPH.CENTER, after=0) + para(FAKULTAS, size=12, align=WD_ALIGN_PARAGRAPH.CENTER, before=0, after=0) + para("UNIVERSITAS TANJUNGPURA", size=12, align=WD_ALIGN_PARAGRAPH.CENTER, + before=0, after=0) + para(f"TAHUN AJARAN {d.get('tahun', '')}", size=12, align=WD_ALIGN_PARAGRAPH.CENTER, + before=0) + doc.paragraphs[-1].runs[-1].add_break(WD_BREAK.PAGE) + + # ── LEMBAR PENGESAHAN ── + para("LEMBAR PENGESAHAN", size=14, bold=True, align=WD_ALIGN_PARAGRAPH.CENTER) + para(f"PORTOFOLIO MATA KULIAH — KURIKULUM OBE {dok.get('kurikulum_tahun', '')}", + size=11, align=WD_ALIGN_PARAGRAPH.CENTER) + doc.add_paragraph() + ti = doc.add_table(rows=5, cols=3) + _borders(ti) + isian = [ + ("Nama Dokumen", f"Portofolio MK {ident.get('nama', '')}"), + ("Nomor Dokumen", dok.get("nomor", "") or "—"), + ("No. Revisi", dok.get("revisi", "") or "—"), + ("Tanggal Terbit", dok.get("tgl_terbit", "") or "—"), + ("Status Dokumen", dok.get("status_dokumen", "Master")), + ] + for i, (k, v) in enumerate(isian): + _shd(ti.cell(i, 0), LIGHT_BLUE) + _ct(ti.cell(i, 0), k, bold=True, size=10) + _ct(ti.cell(i, 1), ":", size=10, align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(ti.cell(i, 2), v, size=10) + ti.rows[i].cells[0].width = Cm(5) + ti.rows[i].cells[1].width = Cm(0.7) + ti.rows[i].cells[2].width = Cm(12) + doc.add_paragraph() + tt = doc.add_table(rows=4, cols=3) + _borders(tt) + for ci, (peran, nama) in enumerate([ + ("DISUSUN OLEH\nKoordinator Mata Kuliah", ident.get("koordinator", "")), + ("DIPERIKSA OLEH\nKetua Program Studi", ident.get("kaprodi", "")), + ("DISAHKAN OLEH\nDekan", dok.get("dekan", "")), + ]): + _hdr(tt.cell(0, ci), peran, size=10) + _ct(tt.cell(1, ci), "\n\n\n") + _ct(tt.cell(2, ci), f"( {nama or '______________________'} )", + align=WD_ALIGN_PARAGRAPH.CENTER, size=10) + _ct(tt.cell(3, ci), "NIP. ......................................", + align=WD_ALIGN_PARAGRAPH.CENTER, size=10) + doc.paragraphs[-1].runs[-1].add_break(WD_BREAK.PAGE) if doc.paragraphs[-1].runs else doc.add_page_break() + + # ── A. CPL ── + bagian("A. CAPAIAN PEMBELAJARAN LULUSAN (CPL) YANG DIBEBANKAN PADA MK") + cpls = d.get("cpl", []) + ta = doc.add_table(rows=1 + len(cpls), cols=2) + _borders(ta) + _hdr(ta.cell(0, 0), "Kode CPL") + _hdr(ta.cell(0, 1), "Uraian CPL") + for i, c in enumerate(cpls, 1): + _ct(ta.cell(i, 0), c["kode"], align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(ta.cell(i, 1), c["desk"]) + ta.rows[i].cells[0].width = Cm(3) + ta.rows[i].cells[1].width = Cm(22) + + # ── B. CPMK & Sub-CPMK ── + bagian("B. CPMK DAN SUB-CPMK") + cpmks = d.get("cpmk", []) + tb = doc.add_table(rows=1 + len(cpmks), cols=3) + _borders(tb) + _hdr(tb.cell(0, 0), "Kode CPMK") + _hdr(tb.cell(0, 1), "Uraian CPMK") + _hdr(tb.cell(0, 2), "CPL Didukung") + for i, c in enumerate(cpmks, 1): + _ct(tb.cell(i, 0), c["kode"], align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(tb.cell(i, 1), c["desk"]) + _ct(tb.cell(i, 2), ", ".join(c.get("cpl", [])), align=WD_ALIGN_PARAGRAPH.CENTER) + doc.add_paragraph() + subs = d.get("sub", []) + ts = doc.add_table(rows=1 + len(subs), cols=3) + _borders(ts) + _hdr(ts.cell(0, 0), "Kode Sub-CPMK") + _hdr(ts.cell(0, 1), "Kemampuan Akhir Tiap Tahapan Belajar") + _hdr(ts.cell(0, 2), "CPMK") + for i, s in enumerate(subs, 1): + _ct(ts.cell(i, 0), s["kode"], align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(ts.cell(i, 1), s["uraian"]) + _ct(ts.cell(i, 2), s.get("cpmk", ""), align=WD_ALIGN_PARAGRAPH.CENTER) + + # ── C. korelasi CPL–Sub-CPMK ── + bagian("C. KORELASI CPL TERHADAP SUB-CPMK (BOBOT PENILAIAN, %)") + korelasi = d.get("korelasi", {}) # {sub: {cpl: porsi}} + bobot_sub = d.get("bobot_sub", {}) + minggu_sub = d.get("minggu_sub", {}) + kode_cpl = [c["kode"] for c in cpls] + tc = doc.add_table(rows=1 + len(subs) + 1, cols=3 + len(kode_cpl)) + _borders(tc) + _hdr(tc.cell(0, 0), "Sub-CPMK") + for j, cp in enumerate(kode_cpl): + _hdr(tc.cell(0, 1 + j), f"{cp}\n(%)") + _hdr(tc.cell(0, 1 + len(kode_cpl)), "Bobot\nPenilaian (%)") + _hdr(tc.cell(0, 2 + len(kode_cpl)), "Jumlah\nMinggu") + tot_cpl = {cp: 0.0 for cp in kode_cpl} + tot_bobot = 0.0 + for i, s in enumerate(subs, 1): + _ct(tc.cell(i, 0), s["kode"], align=WD_ALIGN_PARAGRAPH.CENTER) + for j, cp in enumerate(kode_cpl): + v = korelasi.get(s["kode"], {}).get(cp) + _ct(tc.cell(i, 1 + j), _f(v, 1) if v else "", align=WD_ALIGN_PARAGRAPH.CENTER) + tot_cpl[cp] += v or 0 + b = bobot_sub.get(s["kode"]) + tot_bobot += b or 0 + _ct(tc.cell(i, 1 + len(kode_cpl)), _f(b, 1) if b else "—", + align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(tc.cell(i, 2 + len(kode_cpl)), str(minggu_sub.get(s["kode"], "")), + align=WD_ALIGN_PARAGRAPH.CENTER) + last = len(subs) + 1 + _shd(tc.cell(last, 0), LIGHT_BLUE) + _ct(tc.cell(last, 0), "Total", bold=True, align=WD_ALIGN_PARAGRAPH.RIGHT) + for j, cp in enumerate(kode_cpl): + _shd(tc.cell(last, 1 + j), LIGHT_BLUE) + _ct(tc.cell(last, 1 + j), _f(tot_cpl[cp], 1), bold=True, + align=WD_ALIGN_PARAGRAPH.CENTER) + _shd(tc.cell(last, 1 + len(kode_cpl)), LIGHT_BLUE) + _ct(tc.cell(last, 1 + len(kode_cpl)), _f(tot_bobot, 1), bold=True, + align=WD_ALIGN_PARAGRAPH.CENTER) + _shd(tc.cell(last, 2 + len(kode_cpl)), LIGHT_BLUE) + + # ── D. rencana penilaian & tugas (dari RPS) ── + bagian("D. RENCANA PENILAIAN DAN RENCANA TUGAS (DARI RPS)") + rencana = d.get("rencana", []) + td = doc.add_table(rows=1 + max(len(rencana), 1), cols=4) + _borders(td) + for ci, h in enumerate(["Minggu ke-", "Sub-CPMK", "Bentuk Asesmen", "Bobot (%)"]): + _hdr(td.cell(0, ci), h) + for i, r in enumerate(rencana, 1): + _ct(td.cell(i, 0), str(r["minggu"]), align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(td.cell(i, 1), r["sub"]) + _ct(td.cell(i, 2), r["asesmen"]) + _ct(td.cell(i, 3), str(r["bobot"]), align=WD_ALIGN_PARAGRAPH.CENTER) + para("RPS lengkap diterbitkan sebagai dokumen terpisah dari aplikasi yang sama.", + size=9, italic=True) + + # ── E. realisasi ── + bagian("E. REALISASI PEMBELAJARAN") + te = doc.add_table(rows=1, cols=1) + _borders(te) + _ct(te.cell(0, 0), d.get("realisasi", "") or "—", size=10) + + # ── F. penilaian ── + bagian("F. PENILAIAN MATA KULIAH") + nilai = d.get("nilai", {}) + skor_sub = d.get("skor_sub", {}) + if nilai.get("mode") == "per_mahasiswa": + mhs = nilai.get("mahasiswa", []) + akhir = {a["npm"]: a["akhir"] for a in d.get("akhir", [])} + kol_sub = [s["kode"] for s in subs] + tf = doc.add_table(rows=2 + len(mhs) + 3, cols=3 + len(kol_sub) + 1) + _borders(tf) + _hdr(tf.cell(0, 0), "No.") + _hdr(tf.cell(0, 1), "NPM") + _hdr(tf.cell(0, 2), "Nama") + for j, sk in enumerate(kol_sub): + _hdr(tf.cell(0, 3 + j), sk, size=7) + _hdr(tf.cell(0, 3 + len(kol_sub)), "Nilai\nAkhir", size=7) + # baris bobot + _shd(tf.cell(1, 0), LIGHT_BLUE) + _ct(tf.cell(1, 0), "Bobot", bold=True, size=8) + for j, sk in enumerate(kol_sub): + _shd(tf.cell(1, 3 + j), LIGHT_BLUE) + b = d.get("bobot_sub", {}).get(sk) + _ct(tf.cell(1, 3 + j), _f((b or 0) / 100, 2), size=8, + align=WD_ALIGN_PARAGRAPH.CENTER) + for i, m in enumerate(mhs, 2): + _ct(tf.cell(i, 0), str(i - 1), size=8, align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(tf.cell(i, 1), m.get("npm", ""), size=8) + _ct(tf.cell(i, 2), m.get("nama", ""), size=8) + for j, sk in enumerate(kol_sub): + v = (m.get("skor") or {}).get(sk) + _ct(tf.cell(i, 3 + j), _f(v, 0) if v is not None else "", + size=8, align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(tf.cell(i, 3 + len(kol_sub)), _f(akhir.get(m.get("npm", "")), 1), + size=8, bold=True, align=WD_ALIGN_PARAGRAPH.CENTER) + # baris statistik per sub + base = 2 + len(mhs) + for k, (label, kunci) in enumerate([("Rata-rata", "rata"), + ("Maksimum", "max"), ("Minimum", "min")]): + _shd(tf.cell(base + k, 0), LIGHT_BLUE) + _ct(tf.cell(base + k, 0), label, bold=True, size=8) + for j, sk in enumerate(kol_sub): + info = skor_sub.get(sk) + if not info: + continue + if kunci == "rata": + v = info["rata"] + else: + xs = [(m.get("skor") or {}).get(sk) for m in mhs] + xs = [x for x in xs if x is not None] + v = (max(xs) if kunci == "max" else min(xs)) if xs else None + _ct(tf.cell(base + k, 3 + j), _f(v, 1), size=8, + align=WD_ALIGN_PARAGRAPH.CENTER) + else: + para("Mode input: rata-rata kelas per Sub-CPMK (BUKTI AGREGAT — tidak " + "memuat data per mahasiswa).", size=10, italic=True) + tf = doc.add_table(rows=1 + len(subs), cols=2) + _borders(tf) + _hdr(tf.cell(0, 0), "Sub-CPMK") + _hdr(tf.cell(0, 1), "Rata-rata Kelas") + for i, s in enumerate(subs, 1): + info = skor_sub.get(s["kode"]) + _ct(tf.cell(i, 0), s["kode"], align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(tf.cell(i, 1), _f(info["rata"], 2) if info else "—", + align=WD_ALIGN_PARAGRAPH.CENTER) + + doc.add_paragraph() + skala = d.get("skala", []) + tsx = doc.add_table(rows=1 + len(skala), cols=3) + _borders(tsx) + _hdr(tsx.cell(0, 0), "Huruf") + _hdr(tsx.cell(0, 1), "Rentang Nilai") + _hdr(tsx.cell(0, 2), "Angka Mutu") + for i, s in enumerate(skala, 1): + _ct(tsx.cell(i, 0), s["huruf"], align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(tsx.cell(i, 1), f"{s['lo']:g} – {s['hi']:g}", align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(tsx.cell(i, 2), _f(s["angka"], 2) if s["angka"] is not None else "—", + align=WD_ALIGN_PARAGRAPH.CENTER) + + # ── G. evaluasi ketercapaian ── + bagian("G. EVALUASI KETERCAPAIAN CPL DAN CPMK") + rollup = d.get("rollup", {}) + ambang = d.get("ambang", 70) + baris_g = [] + for cp in cpls: + subs_cp = [s for s in subs + if any(c["kode"] == s.get("cpmk") and cp["kode"] in c.get("cpl", []) + for c in cpmks)] + for k, s in enumerate(subs_cp): + info = skor_sub.get(s["kode"]) + baris_g.append({ + "cpl": cp["kode"] if k == 0 else "", + "sub": s["kode"], + "rata_sub": info["rata"] if info else None, + "rata_cpl": rollup.get("cpl", {}).get(cp["kode"], {}).get("skor") + if k == 0 else None, + "lulus": info["lulus_pct"] if info else None, + "pertama": k == 0, + "kode_cpl": cp["kode"], + }) + tg = doc.add_table(rows=1 + max(len(baris_g), 1), cols=6) + _borders(tg) + for ci, h in enumerate(["CPL", "Sub-CPMK", "Rata-rata Nilai\nSub-CPMK", + "Rata-rata\nNilai CPL", "% Kelulusan\n(≥ ambang)", + "Kesimpulan"]): + _hdr(tg.cell(0, ci), h) + for i, b in enumerate(baris_g, 1): + _ct(tg.cell(i, 0), b["cpl"], bold=True, align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(tg.cell(i, 1), b["sub"], align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(tg.cell(i, 2), _f(b["rata_sub"]), align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(tg.cell(i, 3), _f(b["rata_cpl"]) if b["pertama"] else "", + bold=True, align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(tg.cell(i, 4), _f(b["lulus"], 1) if b["lulus"] is not None else "—", + align=WD_ALIGN_PARAGRAPH.CENTER) + if b["pertama"]: + skor = b["rata_cpl"] + if skor is None: + ket, warna = "Belum terukur", None + elif skor >= ambang: + ket, warna = "TERCAPAI", None + else: + ket, warna = "BELUM TERCAPAI", RED + _ct(tg.cell(i, 5), ket, bold=True, color=warna, + align=WD_ALIGN_PARAGRAPH.CENTER) + para(f"Ambang ketercapaian: {ambang:g}. Metode: rata-rata sederhana (bagi rata) " + "sesuai Metodologi Pengukuran prodi.", size=9, italic=True) + + # ── H. simpulan ── + bagian("H. SIMPULAN HASIL EVALUASI DAN TINDAK LANJUT") + stat = d.get("statistik") + if stat: + th = doc.add_table(rows=7, cols=2) + _borders(th) + _hdr(th.cell(0, 0), "Statistik Deskriptif (Nilai Akhir)") + _hdr(th.cell(0, 1), "Nilai") + for i, (k, v) in enumerate([ + ("Jumlah mahasiswa", stat["n"]), ("Mean", _f(stat["mean"])), + ("Median", _f(stat["median"])), ("Standar deviasi", _f(stat["stdev"])), + ("Minimum", _f(stat["min"])), ("Maksimum – rentang", + f"{_f(stat['max'])} – {_f(stat['range'])}"), + ], 1): + _ct(th.cell(i, 0), k) + _ct(th.cell(i, 1), str(v), align=WD_ALIGN_PARAGRAPH.CENTER) + doc.add_paragraph() + frek = stat["frekuensi"] + tfr = doc.add_table(rows=2, cols=1 + len(frek)) + _borders(tfr) + _hdr(tfr.cell(0, 0), "Huruf") + _shd(tfr.cell(1, 0), LIGHT_BLUE) + _ct(tfr.cell(1, 0), "Frekuensi", bold=True) + for j, (h, n) in enumerate(frek.items(), 1): + _hdr(tfr.cell(0, j), h) + _ct(tfr.cell(1, j), str(n), align=WD_ALIGN_PARAGRAPH.CENTER) + else: + para("Statistik nilai akhir tidak tersedia (mode bukti agregat).", + size=10, italic=True) + + doc.add_paragraph() + kendala = [k for k in d.get("kendala", []) if k.get("kendala")] + tk = doc.add_table(rows=1 + max(len(kendala), 1), cols=3) + _borders(tk) + _hdr(tk.cell(0, 0), "No.") + _hdr(tk.cell(0, 1), "Kendala") + _hdr(tk.cell(0, 2), "Tindak Lanjut") + for i, k in enumerate(kendala, 1): + _ct(tk.cell(i, 0), str(i), align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(tk.cell(i, 1), k.get("kendala", "")) + _ct(tk.cell(i, 2), k.get("tindak", "")) + + if d.get("simpulan"): + doc.add_paragraph() + ts2 = doc.add_table(rows=1, cols=1) + _borders(ts2) + _ct(ts2.cell(0, 0), d["simpulan"], size=10) + + buf = io.BytesIO() + doc.save(buf) + return buf.getvalue() diff --git a/app/generator/rps_docx.py b/app/generator/rps_docx.py new file mode 100644 index 0000000..306d0ca --- /dev/null +++ b/app/generator/rps_docx.py @@ -0,0 +1,335 @@ +# -*- coding: utf-8 -*- +""" +Generator RPS Format APTIKOM 2024 — Prodi Informatika S1 UNTAN. +Diadaptasi dari buat_template_rps.py; gaya visual dipertahankan persis, +placeholder diganti data isian dosen. +""" +import io +import pathlib + +from docx import Document +from docx.enum.table import WD_ALIGN_VERTICAL +from docx.enum.text import WD_ALIGN_PARAGRAPH +from docx.oxml import OxmlElement +from docx.oxml.ns import qn +from docx.shared import Cm, Pt, RGBColor + +DARK_BLUE = "1F4E79" +MID_BLUE = "2E75B6" +LIGHT_BLUE = "D6E4F0" +GREEN_LITE = "E2EFDA" +YELLOW = "FFF2CC" +WHITE = "FFFFFF" + +LOGO = pathlib.Path(__file__).resolve().parent.parent / "static" / "logo-untan.png" + + +def _shd(cell, hex_color): + tc_pr = cell._tc.get_or_add_tcPr() + el = OxmlElement("w:shd") + el.set(qn("w:val"), "clear") + el.set(qn("w:color"), "auto") + el.set(qn("w:fill"), hex_color) + tc_pr.append(el) + + +def _ct(cell, text, bold=False, italic=False, size=10, color=None, + align=WD_ALIGN_PARAGRAPH.LEFT): + p = cell.paragraphs[0] + p.alignment = align + p.paragraph_format.space_before = Pt(1) + p.paragraph_format.space_after = Pt(1) + run = p.add_run(text or "") + run.bold = bold + run.italic = italic + run.font.name = "Times New Roman" + run.font.size = Pt(size) + if color: + run.font.color.rgb = RGBColor.from_string(color) + cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER + + +def _hdr(cell, text, bg=DARK_BLUE, fg=WHITE): + _shd(cell, bg) + _ct(cell, text, bold=True, color=fg, align=WD_ALIGN_PARAGRAPH.CENTER) + + +def _widths(table, cms): + for row in table.rows: + for ci, w in enumerate(cms): + try: + row.cells[ci].width = Cm(w) + except IndexError: + pass + + +def build_rps(d: dict) -> bytes: + """d = data RPS tersimpan (lihat main.py: bentuk_data_form).""" + doc = Document() + for sec in doc.sections: + sec.top_margin = Cm(2.5) + sec.bottom_margin = Cm(2.5) + sec.left_margin = Cm(3) + sec.right_margin = Cm(2) + + def section_head(text): + p = doc.add_paragraph() + p.paragraph_format.space_before = Pt(8) + p.paragraph_format.space_after = Pt(3) + r = p.add_run(text) + r.bold = True + r.font.name = "Times New Roman" + r.font.size = Pt(11) + + ident = d.get("identitas", {}) + + # ── logo UNTAN (dari RPS Template.docx resmi) ── + if LOGO.exists(): + p = doc.add_paragraph() + p.alignment = WD_ALIGN_PARAGRAPH.CENTER + p.paragraph_format.space_after = Pt(4) + p.add_run().add_picture(str(LOGO), width=Cm(2.6)) + + # ── judul ── + for txt, sz, bd in [ + ("RENCANA PEMBELAJARAN SEMESTER (RPS)", 14, True), + ("Program Studi Informatika", 12, True), + ("Fakultas Matematika dan Ilmu Pengetahuan Alam", 11, False), + ("Universitas Tanjungpura", 11, False), + ]: + p = doc.add_paragraph() + p.alignment = WD_ALIGN_PARAGRAPH.CENTER + p.paragraph_format.space_before = Pt(0) + p.paragraph_format.space_after = Pt(2) + r = p.add_run(txt) + r.bold = bd + r.font.name = "Times New Roman" + r.font.size = Pt(sz) + doc.add_paragraph() + + # ── A. identitas ── + section_head("A. IDENTITAS MATA KULIAH") + sks = ident.get("sks", "") + sks_t = ident.get("sks_teori", "") + sks_p = ident.get("sks_praktikum", "") + sks_txt = f"{sks} SKS" + if sks_t or sks_p: + sks_txt += f" (Teori: {sks_t or '-'} SKS | Praktikum: {sks_p or '-'} SKS)" + rows_id = [ + ("Nama Mata Kuliah", ident.get("nama", "")), + ("Kode Mata Kuliah", ident.get("kode", "")), + ("Bobot SKS", sks_txt), + ("Semester", ident.get("semester", "")), + ("MK Prasyarat", ident.get("prasyarat", "") or "—"), + ("Dosen Pengampu", ident.get("pengampu", "")), + ("Koordinator MK", ident.get("koordinator", "")), + ("Ketua Program Studi", ident.get("kaprodi", "")), + ("Tahun Ajaran", d.get("tahun", "")), + ] + ta = doc.add_table(rows=len(rows_id), cols=3) + ta.style = "Table Grid" + for i, (lbl, val) in enumerate(rows_id): + c = ta.rows[i].cells + _shd(c[0], LIGHT_BLUE) + _ct(c[0], lbl, bold=True) + _ct(c[1], ":", align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(c[2], val) + _widths(ta, [5.5, 0.5, 9.5]) + doc.add_paragraph() + + # ── B. CPL ── + section_head("B. CAPAIAN PEMBELAJARAN LULUSAN (CPL) YANG DIBEBANKAN PADA MK") + cpls = d.get("cpl", []) + tb = doc.add_table(rows=1 + max(len(cpls), 1), cols=2) + tb.style = "Table Grid" + _hdr(tb.cell(0, 0), "Kode CPL") + _hdr(tb.cell(0, 1), "Deskripsi CPL") + for i, cpl in enumerate(cpls, 1): + _ct(tb.cell(i, 0), cpl["kode"], align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(tb.cell(i, 1), cpl["desk"]) + _widths(tb, [2.5, 13]) + doc.add_paragraph() + + # ── C. CPMK ── + section_head("C. CAPAIAN PEMBELAJARAN MATA KULIAH (CPMK)") + cpmks = d.get("cpmk", []) + tc = doc.add_table(rows=1 + max(len(cpmks), 1), cols=3) + tc.style = "Table Grid" + _hdr(tc.cell(0, 0), "Kode CPMK") + _hdr(tc.cell(0, 1), "Deskripsi CPMK") + _hdr(tc.cell(0, 2), "CPL yang Didukung") + for i, c in enumerate(cpmks, 1): + _ct(tc.cell(i, 0), c["kode"], align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(tc.cell(i, 1), c["desk"]) + _ct(tc.cell(i, 2), ", ".join(c.get("cpl", [])), align=WD_ALIGN_PARAGRAPH.CENTER) + _widths(tc, [2.5, 9, 4]) + doc.add_paragraph() + + # ── D. Sub-CPMK ── + section_head("D. SUB-CPMK (Kemampuan Akhir Tiap Tahapan Belajar)") + subs = d.get("sub", []) + td = doc.add_table(rows=1 + max(len(subs), 1), cols=3) + td.style = "Table Grid" + _hdr(td.cell(0, 0), "Kode Sub-CPMK") + _hdr(td.cell(0, 1), "Uraian Sub-CPMK") + _hdr(td.cell(0, 2), "CPMK yang\nDidukung") + for i, s in enumerate(subs, 1): + _ct(td.cell(i, 0), s["kode"], align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(td.cell(i, 1), s["uraian"]) + _ct(td.cell(i, 2), s.get("cpmk", ""), align=WD_ALIGN_PARAGRAPH.CENTER) + _widths(td, [3, 9, 3.5]) + doc.add_paragraph() + + # ── E. deskripsi ── + section_head("E. DESKRIPSI SINGKAT MATA KULIAH") + te = doc.add_table(rows=1, cols=1) + te.style = "Table Grid" + _ct(te.cell(0, 0), d.get("deskripsi", "")) + te.cell(0, 0).width = Cm(15.5) + doc.add_paragraph() + + # ── F. tabel mingguan ── + section_head("F. RENCANA PEMBELAJARAN SEMESTER — Tabel F APTIKOM 2024") + minggu = d.get("minggu", []) + nrow = 2 + len(minggu) + 1 + tf = doc.add_table(rows=nrow, cols=7) + tf.style = "Table Grid" + _hdr(tf.cell(0, 0), "Minggu\nke-") + _hdr(tf.cell(0, 1), "Sub-CPMK\n(Kemampuan Akhir\nyang Direncanakan)") + _hdr(tf.cell(0, 2), "Penilaian") + _hdr(tf.cell(0, 4), "Bentuk & Metode\nPembelajaran\n[Estimasi Waktu]") + _hdr(tf.cell(0, 5), "Materi Pembelajaran\n[Bahan Kajian]\n[Referensi]") + _hdr(tf.cell(0, 6), "Bobot\n(%)") + tf.cell(0, 2).merge(tf.cell(0, 3)) + _hdr(tf.cell(1, 2), "Indikator\nPencapaian", bg=MID_BLUE) + _hdr(tf.cell(1, 3), "Kriteria &\nBentuk Penilaian", bg=MID_BLUE) + for ci in (0, 1, 4, 5, 6): + tf.cell(0, ci).merge(tf.cell(1, ci)) + + total_bobot = 0.0 + for idx, m in enumerate(minggu): + ri = idx + 2 + w = m.get("ke", idx + 1) + cells = [tf.cell(ri, ci) for ci in range(7)] + if w == 8: + for c in cells: + _shd(c, GREEN_LITE) + elif w == 16: + for c in cells: + _shd(c, YELLOW) + _ct(cells[0], str(w), align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(cells[1], m.get("sub", "")) + _ct(cells[2], m.get("indikator", "")) + _ct(cells[3], m.get("kriteria", "")) + _ct(cells[4], m.get("metode", "")) + _ct(cells[5], m.get("materi", "")) + bobot = m.get("bobot", "") + _ct(cells[6], str(bobot) if bobot != "" else "", align=WD_ALIGN_PARAGRAPH.CENTER) + try: + total_bobot += float(bobot) + except (TypeError, ValueError): + pass + + last = nrow - 1 + tf.cell(last, 0).merge(tf.cell(last, 5)) + _shd(tf.cell(last, 0), LIGHT_BLUE) + _ct(tf.cell(last, 0), "Total Bobot Penilaian", bold=True, align=WD_ALIGN_PARAGRAPH.RIGHT) + _shd(tf.cell(last, 6), LIGHT_BLUE) + total_txt = f"{total_bobot:g}%" + _ct(tf.cell(last, 6), total_txt, bold=True, align=WD_ALIGN_PARAGRAPH.CENTER) + _widths(tf, [1.0, 3.0, 2.5, 2.5, 3.0, 3.0, 1.0]) + doc.add_paragraph() + + # ── G. rekap bobot ── + section_head("G. REKAP BOBOT PENILAIAN") + komponen = d.get("komponen", []) + tg = doc.add_table(rows=1 + len(komponen) + 1, cols=3) + tg.style = "Table Grid" + _hdr(tg.cell(0, 0), "Komponen Penilaian") + _hdr(tg.cell(0, 1), "Sub-CPMK yang Dinilai") + _hdr(tg.cell(0, 2), "Bobot (%)") + tot_komp = 0.0 + for i, k in enumerate(komponen, 1): + _ct(tg.cell(i, 0), k.get("nama", "")) + _ct(tg.cell(i, 1), ", ".join(k.get("subs", []))) + b = k.get("bobot", "") + _ct(tg.cell(i, 2), str(b), align=WD_ALIGN_PARAGRAPH.CENTER) + try: + tot_komp += float(b) + except (TypeError, ValueError): + pass + li = len(komponen) + 1 + for ci in range(3): + _shd(tg.cell(li, ci), LIGHT_BLUE) + _ct(tg.cell(li, 0), "Total", bold=True) + _ct(tg.cell(li, 2), f"{tot_komp:g}", bold=True, align=WD_ALIGN_PARAGRAPH.CENTER) + _widths(tg, [4, 9, 2.5]) + doc.add_paragraph() + + # ── H. referensi ── + section_head("H. DAFTAR REFERENSI") + refs = d.get("referensi", []) + th = doc.add_table(rows=1 + max(len(refs), 1), cols=2) + th.style = "Table Grid" + _hdr(th.cell(0, 0), "No.") + _hdr(th.cell(0, 1), "Referensi (Format APA)") + for i, ref in enumerate(refs, 1): + _ct(th.cell(i, 0), str(i), align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(th.cell(i, 1), ref) + _widths(th, [1, 14.5]) + doc.add_paragraph() + + # ── I. otorisasi ── + section_head("I. OTORISASI") + ti = doc.add_table(rows=4, cols=2) + ti.style = "Table Grid" + _shd(ti.cell(0, 0), LIGHT_BLUE) + _shd(ti.cell(0, 1), LIGHT_BLUE) + _ct(ti.cell(0, 0), "Koordinator Mata Kuliah", bold=True, align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(ti.cell(0, 1), "Ketua Program Studi", bold=True, align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(ti.cell(1, 0), "\n\n\n\n") + _ct(ti.cell(1, 1), "\n\n\n\n") + koor = ident.get("koordinator", "").strip() + kapr = ident.get("kaprodi", "").strip() + _ct(ti.cell(2, 0), f"( {koor or '_____________________________'} )", + align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(ti.cell(2, 1), f"( {kapr or '_____________________________'} )", + align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(ti.cell(3, 0), "NIP. ....................................", align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(ti.cell(3, 1), "NIP. ....................................", align=WD_ALIGN_PARAGRAPH.CENTER) + doc.add_paragraph() + + # ── J. lampiran kisi-kisi (bukti constructive alignment) ── + section_head("J. LAMPIRAN — KISI-KISI ASESMEN (Komponen × Sub-CPMK)") + tj = doc.add_table(rows=1 + max(len(komponen), 1), cols=4) + tj.style = "Table Grid" + _hdr(tj.cell(0, 0), "Komponen") + _hdr(tj.cell(0, 1), "Sub-CPMK yang Dinilai") + _hdr(tj.cell(0, 2), "Porsi per Sub-CPMK (%)") + _hdr(tj.cell(0, 3), "Bobot Komponen (%)") + for i, k in enumerate(komponen, 1): + subs_k = k.get("subs", []) + try: + bobot = float(k.get("bobot", 0) or 0) + except (TypeError, ValueError): + bobot = 0.0 + porsi = f"{bobot / len(subs_k):.1f}" if subs_k else "—" + _ct(tj.cell(i, 0), k.get("nama", "")) + _ct(tj.cell(i, 1), ", ".join(subs_k) or "—") + _ct(tj.cell(i, 2), f"{porsi} (bagi rata)" if subs_k else "—", + align=WD_ALIGN_PARAGRAPH.CENTER) + _ct(tj.cell(i, 3), str(k.get("bobot", "")), align=WD_ALIGN_PARAGRAPH.CENTER) + _widths(tj, [3.5, 7, 3, 2]) + p = doc.add_paragraph() + r = p.add_run( + "Porsi per Sub-CPMK dihitung bagi rata dari bobot komponen " + "(kebijakan default prodi); penyesuaian porsi per butir soal dicatat " + "pada dokumen portofolio mata kuliah." + ) + r.italic = True + r.font.name = "Times New Roman" + r.font.size = Pt(9) + + buf = io.BytesIO() + doc.save(buf) + return buf.getvalue() diff --git a/app/generator/rps_untan.py b/app/generator/rps_untan.py new file mode 100644 index 0000000..e25123f --- /dev/null +++ b/app/generator/rps_untan.py @@ -0,0 +1,254 @@ +# -*- coding: utf-8 -*- +""" +Generator RPS format resmi UNTAN (standar Dikti, SN Dikti Permendikbud 3/2020). + +Pendekatan: file template resmi (`templates/rps_untan.docx`) dimuat apa adanya, +sel-selnya diisi data, dan baris digandakan seperlunya — sehingga tata letak, +merge sel, dan logo dijamin identik dengan template. Ditambah satu lampiran +kisi-kisi asesmen (bukti constructive alignment) setelah tabel utama. + +Peta baris tabel template (39 baris x 18 kolom): + 0 kop (logo | identitas institusi | kode dokumen) + 2/3 label & isian: MK, kode, rumpun, bobot T/P, semester, tgl + 4/5 otorisasi: pengembang, koordinator, kaprodi + 6..11 CPL (header + 5 baris contoh) + 12..14 CPMK (header + 2 baris contoh) + 15..17 Sub-CPMK (header + 2 baris contoh) + 18..27 peta CPL-CPMK, deskripsi, bahan kajian, pustaka U/P, media, + pengampu, MK syarat + 28..30 header tabel mingguan + 31..37 baris contoh mingguan (31 normal, 33 UTS, 37 UAS) + 38 total bobot +""" +import copy +import io +import os +import pathlib + +from docx import Document +from docx.enum.text import WD_ALIGN_PARAGRAPH +from docx.oxml import OxmlElement +from docx.oxml.ns import qn +from docx.shared import Pt +from docx.table import _Row + +TEMPLATE = pathlib.Path(__file__).resolve().parent / "templates" / "rps_untan.docx" + +FAKULTAS = os.environ.get("FAKULTAS", "FAKULTAS TEKNIK") +PRODI = os.environ.get("PRODI", "PROGRAM STUDI INFORMATIKA") + + +def _set_text(cell, text, align=None, bold=None): + """Ganti isi sel; gaya paragraf/tabel template dipertahankan.""" + for p in cell.paragraphs[1:]: + p._p.getparent().remove(p._p) + p = cell.paragraphs[0] + for r in list(p.runs): + r._r.getparent().remove(r._r) + run = p.add_run() + for i, line in enumerate((text or "").split("\n")): + if i: + run.add_break() + run.add_text(line) + if bold is not None: + run.bold = bold + if align is not None: + p.alignment = align + + +def _unique_cells(row): + seen, out = None, [] + for c in row.cells: + if c._tc is seen: + continue + seen = c._tc + out.append(c) + return out + + +def _clone_after(table, proto_tr, ref_tr): + """Salin baris prototipe, kosongkan teksnya, sisipkan setelah ref_tr.""" + new_tr = copy.deepcopy(proto_tr) + ref_tr.addnext(new_tr) + row = _Row(new_tr, table) + for c in _unique_cells(row): + _set_text(c, "") + return row + + +def _fmt(v, suffix=""): + return f"{v}{suffix}" if v not in (None, "") else "" + + +def _grid_borders(table): + """Beri garis penuh pada tabel (template tidak punya style 'Table Grid').""" + tbl_pr = table._tbl.tblPr + borders = OxmlElement("w:tblBorders") + for sisi in ("top", "left", "bottom", "right", "insideH", "insideV"): + el = OxmlElement(f"w:{sisi}") + el.set(qn("w:val"), "single") + el.set(qn("w:sz"), "4") + el.set(qn("w:color"), "000000") + borders.append(el) + tbl_pr.append(borders) + + +def build_rps(d: dict) -> bytes: + doc = Document(str(TEMPLATE)) + t = doc.tables[0] + ident = d.get("identitas", {}) + + # ── kop & identitas ── + _set_text( + t.cell(0, 3), + f"UNIVERSITAS TANJUNGPURA\n{FAKULTAS}\n{PRODI}", + align=WD_ALIGN_PARAGRAPH.CENTER, bold=True, + ) + _set_text(t.cell(0, 16), "Kode Dokumen\n" + (ident.get("kode_dokumen") or "—")) + + _set_text(t.cell(3, 0), ident.get("nama", ""), bold=True) + _set_text(t.cell(3, 5), ident.get("kode", "")) + _set_text(t.cell(3, 7), ident.get("rumpun", "")) + _set_text(t.cell(3, 10), "T = " + (ident.get("sks_teori") or "-")) + _set_text(t.cell(3, 11), "P = " + (ident.get("sks_praktikum") or "-")) + _set_text(t.cell(3, 13), ident.get("semester", "")) + _set_text(t.cell(3, 15), ident.get("tgl_penyusunan", "")) + + _set_text(t.cell(5, 5), ident.get("pengembang") or ident.get("pengampu", "")) + _set_text(t.cell(5, 10), ident.get("koordinator", "")) + _set_text(t.cell(5, 13), ident.get("kaprodi", "")) + + trs = [r._tr for r in t.rows] # indeks baris template asli + + def isi_daftar(proto_idx, hapus_idx, items, kolom_kode=2, kolom_isi=5): + """Isi blok daftar (CPL/CPMK/Sub-CPMK): prototipe digandakan per item.""" + proto = trs[proto_idx] + ref = proto + for kode, isi in items: + row = _clone_after(t, proto, ref) + _set_text(row.cells[kolom_kode], kode, bold=True) + _set_text(row.cells[kolom_isi], isi) + ref = row._tr + for i in hapus_idx: + trs[i].getparent().remove(trs[i]) + proto.getparent().remove(proto) + + # ── CPL / CPMK / Sub-CPMK (baris digandakan sesuai data) ── + isi_daftar(7, [8, 9, 10, 11], [(c["kode"], c["desk"]) for c in d.get("cpl", [])]) + isi_daftar(13, [14], [(c["kode"], c["desk"]) for c in d.get("cpmk", [])]) + isi_daftar(16, [17], [(s["kode"], s["uraian"]) for s in d.get("sub", [])]) + + # ── peta CPL–CPMK & blok naratif ── + peta_txt = "\n".join( + f"{c['kode']} → {', '.join(c.get('cpl', [])) or '-'}" + for c in d.get("cpmk", []) + ) + _set_text(t.cell(18, 2), peta_txt) + _set_text(t.cell(19, 2), d.get("deskripsi", "")) + _set_text(t.cell(20, 2), d.get("bahan_kajian", "")) + + utama = d.get("referensi", []) + pendukung = d.get("referensi_pendukung", []) + _set_text(t.cell(22, 2), "\n".join(f"{i}. {r}" for i, r in enumerate(utama, 1))) + _set_text(t.cell(24, 2), "\n".join(f"{i}. {r}" for i, r in enumerate(pendukung, 1))) + _set_text(t.cell(25, 2), d.get("media", "")) + _set_text(t.cell(26, 2), ident.get("pengampu", "")) + _set_text(t.cell(27, 2), ident.get("prasyarat") or "—") + + # ── tabel mingguan ── + proto_norm, proto_uts, proto_uas = trs[31], trs[33], trs[37] + total = 0.0 + ref = trs[30] # sisipkan setelah baris nomor kolom (1)(2)...(9) + for m in d.get("minggu", []): + w = m.get("ke") + try: + total += float(m.get("bobot") or 0) + except (TypeError, ValueError): + pass + if w in (8, 16): + row = _clone_after(t, proto_uts if w == 8 else proto_uas, ref) + cells = _unique_cells(row) + _set_text(cells[0], str(w), align=WD_ALIGN_PARAGRAPH.CENTER, bold=True) + nama_ujian = "Ujian Tengah Semester (UTS)" if w == 8 else "Ujian Akhir Semester (UAS)" + gab = [nama_ujian] + if m.get("sub"): + gab.append(f"Cakupan: {m['sub']}") + if m.get("indikator"): + gab.append(f"Indikator: {m['indikator']}") + if m.get("metode"): + gab.append(f"Bentuk: {m['metode']}") + _set_text(cells[1], "\n".join(gab)) + _set_text(cells[2], _fmt(m.get("bobot")), align=WD_ALIGN_PARAGRAPH.CENTER) + else: + row = _clone_after(t, proto_norm, ref) + cells = _unique_cells(row) + _set_text(cells[0], str(w), align=WD_ALIGN_PARAGRAPH.CENTER) + _set_text(cells[1], m.get("sub", "")) + _set_text(cells[2], m.get("indikator", "")) + _set_text(cells[3], m.get("kriteria", "")) + _set_text(cells[4], m.get("metode", "")) # tatap muka + _set_text(cells[5], m.get("daring", "")) + _set_text(cells[6], m.get("kegiatan", "")) + _set_text(cells[7], m.get("materi", "")) + _set_text(cells[8], _fmt(m.get("bobot")), align=WD_ALIGN_PARAGRAPH.CENTER) + ref = row._tr + + for i in (31, 32, 33, 34, 35, 36, 37): # buang seluruh baris contoh + trs[i].getparent().remove(trs[i]) + + _set_text( + _unique_cells(_Row(trs[38], t))[-1], + f"{total:g}", align=WD_ALIGN_PARAGRAPH.CENTER, bold=True, + ) + + # ── buang paragraf petunjuk pengisian (catatan SN Dikti) setelah tabel ── + body = doc.element.body + tbl_seen = False + for el in list(body): + if el.tag == qn("w:tbl"): + tbl_seen = True + continue + if tbl_seen and el.tag == qn("w:p"): + body.remove(el) + + # ── lampiran kisi-kisi asesmen ── + doc.add_page_break() + p = doc.add_paragraph() + r = p.add_run("LAMPIRAN — KISI-KISI ASESMEN (Komponen Penilaian × Sub-CPMK)") + r.bold = True + r.font.size = Pt(11) + + komponen = d.get("komponen", []) + tk = doc.add_table(rows=1 + max(len(komponen), 1), cols=4) + _grid_borders(tk) + for ci, judul in enumerate( + ["Komponen", "Sub-CPMK yang Dinilai", "Porsi per Sub-CPMK (%)", "Bobot (%)"] + ): + _set_text(tk.cell(0, ci), judul, bold=True, align=WD_ALIGN_PARAGRAPH.CENTER) + for i, k in enumerate(komponen, 1): + subs_k = k.get("subs", []) + try: + bobot = float(k.get("bobot") or 0) + except (TypeError, ValueError): + bobot = 0.0 + _set_text(tk.cell(i, 0), k.get("nama", "")) + _set_text(tk.cell(i, 1), ", ".join(subs_k) or "—") + _set_text( + tk.cell(i, 2), + f"{bobot / len(subs_k):.1f} (bagi rata)" if subs_k else "—", + align=WD_ALIGN_PARAGRAPH.CENTER, + ) + _set_text(tk.cell(i, 3), str(k.get("bobot", "")), align=WD_ALIGN_PARAGRAPH.CENTER) + + p = doc.add_paragraph() + r = p.add_run( + "Porsi per Sub-CPMK dihitung bagi rata dari bobot komponen (kebijakan " + "default prodi); penyesuaian porsi per butir soal dicatat pada dokumen " + "portofolio mata kuliah." + ) + r.italic = True + r.font.size = Pt(9) + + buf = io.BytesIO() + doc.save(buf) + return buf.getvalue() diff --git a/app/generator/templates/rps_untan.docx b/app/generator/templates/rps_untan.docx new file mode 100644 index 0000000..68a89f1 Binary files /dev/null and b/app/generator/templates/rps_untan.docx differ diff --git a/app/hitung.py b/app/hitung.py new file mode 100644 index 0000000..49eefd7 --- /dev/null +++ b/app/hitung.py @@ -0,0 +1,226 @@ +# -*- coding: utf-8 -*- +""" +Mesin pengukuran ketercapaian CPL/CPMK (inti OBE). + +Kebijakan (RANCANGAN.md §7, disepakati 13 Jul 2026): +- Nilai dicatat per mahasiswa per Sub-CPMK (mengikuti format contoh portofolio); + mode 'agregat' (rata-rata kelas per Sub-CPMK) diperbolehkan tapi ditandai. +- Roll-up dalam MK : Sub-CPMK → CPMK → CPL, rata-rata sederhana (bagi rata). +- Roll-up prodi : CPMK prodi = rata-rata lintas MK pengukurnya; + CPL = rata-rata CPMK pendukungnya. Opsi tertimbang SKS + tersedia via pengaturan 'metode_antar_mk' = 'sks' + (butuh data SKS; tanpa data SKS jatuh ke bagi rata). +- % kelulusan = persentase mahasiswa dengan skor ≥ ambang (hanya mode + per-mahasiswa; mode agregat tidak menghasilkan % kelulusan). +""" +import statistics + +from . import db + + +def _rata(xs): + xs = [x for x in xs if x is not None] + return sum(xs) / len(xs) if xs else None + + +def parse_skala(teks: str) -> list[dict]: + """'A+|91|100|4.00' per baris → [{'huruf','lo','hi','angka'}] terurut turun.""" + out = [] + for line in (teks or "").splitlines(): + bag = [b.strip() for b in line.split("|")] + if len(bag) >= 3: + try: + out.append({ + "huruf": bag[0], "lo": float(bag[1]), "hi": float(bag[2]), + "angka": float(bag[3]) if len(bag) > 3 else None, + }) + except ValueError: + continue + return sorted(out, key=lambda s: -s["lo"]) + + +def huruf_mutu(skor: float, skala: list[dict]) -> str: + for s in skala: + if s["lo"] <= skor <= s["hi"]: + return s["huruf"] + return "?" + + +def skor_sub(nilai_data: dict, ambang: float) -> dict: + """Skor per Sub-CPMK dari data nilai tersimpan. + + nilai_data = {"mode": "per_mahasiswa"|"agregat", + "mahasiswa": [{"npm","nama","skor":{sub: nilai}}] | + "agregat": {sub: rata}} + → {sub: {"rata": x, "n": jumlah_mhs, "lulus_pct": y|None}} + """ + out = {} + if nilai_data.get("mode") == "per_mahasiswa": + per_sub: dict[str, list[float]] = {} + for m in nilai_data.get("mahasiswa", []): + for sub, v in (m.get("skor") or {}).items(): + if v is not None: + per_sub.setdefault(sub, []).append(float(v)) + for sub, xs in per_sub.items(): + out[sub] = { + "rata": _rata(xs), + "n": len(xs), + "lulus_pct": 100.0 * sum(1 for x in xs if x >= ambang) / len(xs), + } + else: + for sub, v in (nilai_data.get("agregat") or {}).items(): + if v in (None, ""): + continue + out[sub] = {"rata": float(v), "n": None, "lulus_pct": None} + return out + + +def bobot_sub_dari_rps(rps_data: dict | None) -> dict[str, float]: + """Bobot (%) tiap Sub-CPMK = jumlah porsi bagi-rata dari komponen RPS (kisi-kisi).""" + bobot: dict[str, float] = {} + if not rps_data: + return bobot + for k in rps_data.get("komponen", []): + subs = k.get("subs", []) + try: + b = float(k.get("bobot") or 0) + except (TypeError, ValueError): + continue + for s in subs: + bobot[s] = bobot.get(s, 0.0) + b / len(subs) + return bobot + + +def nilai_akhir_mahasiswa(nilai_data: dict, bobot_sub: dict[str, float]) -> list[dict]: + """Nilai akhir tertimbang bobot Sub-CPMK per mahasiswa (mode per_mahasiswa).""" + out = [] + for m in nilai_data.get("mahasiswa", []): + skor = m.get("skor") or {} + pasangan = [(bobot_sub.get(s), v) for s, v in skor.items() if v is not None] + berbobot = [(b, v) for b, v in pasangan if b] + if berbobot: + total_b = sum(b for b, _ in berbobot) + akhir = sum(b * v for b, v in berbobot) / total_b + else: # RPS belum mengisi bobot → rata-rata sederhana + akhir = _rata([v for _, v in pasangan]) + if akhir is not None: + out.append({"npm": m.get("npm", ""), "nama": m.get("nama", ""), + "akhir": akhir}) + return out + + +def statistik_akhir(akhir: list[dict], skala: list[dict]) -> dict | None: + xs = [a["akhir"] for a in akhir] + if not xs: + return None + frek = {s["huruf"]: 0 for s in skala} + for x in xs: + h = huruf_mutu(x, skala) + frek[h] = frek.get(h, 0) + 1 + return { + "n": len(xs), + "mean": statistics.mean(xs), + "median": statistics.median(xs), + "stdev": statistics.stdev(xs) if len(xs) > 1 else 0.0, + "min": min(xs), + "max": max(xs), + "range": max(xs) - min(xs), + "frekuensi": frek, + } + + +def rollup_mk(peta: dict, nilai_data: dict, ambang: float) -> dict: + """Ketercapaian satu MK: per Sub-CPMK → CPMK → CPL (bagi rata).""" + sub = skor_sub(nilai_data, ambang) + + cpmk: dict[str, dict] = {} + for c in peta["cpmks"]: + skor = _rata([sub[s["kode"]]["rata"] for s in c["subs"] if s["kode"] in sub]) + n_ternilai = sum(1 for s in c["subs"] if s["kode"] in sub) + cpmk[c["kode"]] = {"skor": skor, "cpl": c["cpl"], + "n_sub": len(c["subs"]), "n_ternilai": n_ternilai} + + cpl: dict[str, dict] = {} + for cp in peta["cpls"]: + skor = _rata([v["skor"] for v in cpmk.values() + if cp["kode"] in v["cpl"] and v["skor"] is not None]) + cpl[cp["kode"]] = {"skor": skor} + + return {"sub": sub, "cpmk": cpmk, "cpl": cpl, + "mode": nilai_data.get("mode", "agregat")} + + +def rollup_prodi(tahun: str | None = None) -> dict: + """Ketercapaian tingkat prodi + kelengkapan bukti per CPL/CPMK.""" + tahun = tahun or db.tahun_aktif() + pengaturan = db.ambil_pengaturan() + ambang = float(pengaturan["ambang"]) + pakai_sks = pengaturan.get("metode_antar_mk") == "sks" + sks = db.sks_mk() if pakai_sks else {} + k = db.kurikulum() + semua = db.semua_nilai(tahun) + + def rata_antar_mk(entri): + """Skor CPMK lintas MK: bagi rata, atau tertimbang SKS bila diatur.""" + if not entri: + return None + if pakai_sks and sks: + bobot = [(float(sks.get(mk, {}).get("sks") or 0), s) for mk, s, _ in entri] + total = sum(b for b, _ in bobot if b) + if total: + return sum(b * s for b, s in bobot if b) / total + return _rata([s for _, s, _ in entri]) + + # skor CPMK per MK + per_cpmk: dict[str, list[tuple[str, float, str]]] = {} # key cpmk → [(mk, skor, mode)] + for mk_kode, nilai_data in semua.items(): + peta = db.peta_mk(mk_kode) + if not peta: + continue + hasil = rollup_mk(peta, nilai_data, ambang) + for kode_cpmk, v in hasil["cpmk"].items(): + if v["skor"] is not None: + per_cpmk.setdefault(kode_cpmk, []).append( + (mk_kode, v["skor"], hasil["mode"]) + ) + + # CPMK prodi = bagi rata lintas MK pengukur; kelengkapan = pengukur/pemikul + cpmk_prodi: dict[str, dict] = {} + for ckey, info in k["cpmk"].items(): + kode = info.get("kode", ckey) + pemikul = info.get("mk", []) + entri = per_cpmk.get(kode, []) + cpmk_prodi[kode] = { + "kode": kode, + "cpl": info.get("cpl", []), + "skor": rata_antar_mk(entri), + "n_pemikul": len(pemikul), + "n_setor": len(entri), + "ada_agregat": any(m == "agregat" for _, _, m in entri), + "mk_terlemah": min(entri, key=lambda e: e[1])[:2] if entri else None, + } + + # CPL = bagi rata CPMK pendukung + cpl_prodi: dict[str, dict] = {} + for kode_cpl, info in k["cpl"].items(): + pendukung = [c for c in cpmk_prodi.values() if kode_cpl in c["cpl"]] + ternilai = [c for c in pendukung if c["skor"] is not None] + skor = _rata([c["skor"] for c in ternilai]) + terlemah = min(ternilai, key=lambda c: c["skor"]) if ternilai else None + cpl_prodi[kode_cpl] = { + "kode": kode_cpl, + "desk": info["desk"], + "skor": skor, + "tercapai": (skor is not None and skor >= ambang), + "n_cpmk": len(pendukung), + "n_cpmk_ternilai": len(ternilai), + "n_mk_pemikul": sum(c["n_pemikul"] for c in pendukung), + "n_mk_setor": sum(c["n_setor"] for c in pendukung), + "ada_agregat": any(c["ada_agregat"] for c in ternilai), + "cpmk_terlemah": terlemah["kode"] if terlemah else None, + "mk_terlemah": (terlemah["mk_terlemah"] if terlemah else None), + } + + return {"cpl": cpl_prodi, "cpmk": cpmk_prodi, "ambang": ambang, + "n_mk_setor": len(semua), "tahun": tahun, + "metode": "sks" if (pakai_sks and sks) else "rata"} diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..db811e8 --- /dev/null +++ b/app/main.py @@ -0,0 +1,1011 @@ +# -*- coding: utf-8 -*- +"""Aplikasi Penyusunan RPS & Portofolio — Prodi Informatika UNTAN.""" +import pathlib +import re + +from fastapi import FastAPI, Request +from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse, Response +from fastapi.staticfiles import StaticFiles +from fastapi.templating import Jinja2Templates + +from . import auth, db, hitung, nilai_xlsx +from .generator.portofolio_docx import build_portofolio +from .generator.rps_untan import build_rps + +BASE = pathlib.Path(__file__).resolve().parent + +app = FastAPI(title="RPS & Portofolio — Informatika UNTAN") +app.mount("/static", StaticFiles(directory=str(BASE / "static")), name="static") +templates = Jinja2Templates(directory=str(BASE / "templates")) + +db.init_db() +db.kurikulum() + + +# ── autentikasi: semua halaman wajib login ─────────────────────────────────── + +PUBLIK = {"/login", "/setup", "/favicon.ico"} + + +@app.middleware("http") +async def wajib_login(request: Request, call_next): + path = request.url.path + if path in PUBLIK or path.startswith("/static/"): + return await call_next(request) + user = db.user_dari_sesi(request.cookies.get("sesi", "")) + if user is None: + tujuan = "/setup" if not db.ada_pengguna() else "/login" + return RedirectResponse(tujuan, status_code=303) + request.state.user = user + return await call_next(request) + + +def render(request: Request, nama: str, ctx: dict): + """TemplateResponse + user login otomatis tersedia di semua template.""" + ctx.setdefault("user", getattr(request.state, "user", None)) + return templates.TemplateResponse(request, nama, ctx) + + +def boleh_kelola(user: dict, kode: str) -> bool: + """Pengampu MK ini atau kaprodi — syarat edit & akses nilai.""" + return user["role"] == "kaprodi" or kode in db.mk_diampu(user["id"]) + + +def _masuk(user_id: int) -> RedirectResponse: + token = auth.token_baru() + db.buat_sesi(user_id, token) + r = RedirectResponse("/", status_code=303) + r.set_cookie("sesi", token, httponly=True, samesite="lax", + max_age=60 * 60 * 24 * 30) + return r + + +@app.get("/setup", response_class=HTMLResponse) +def setup_form(request: Request): + if db.ada_pengguna(): + return RedirectResponse("/login", status_code=303) + return render(request, "login.html", + {"mode": "setup", "pesan": "", "tahun": db.tahun_aktif()}) + + +def _rapikan_nip(mentah: str) -> str: + """NIP sebagai username: buang spasi/titik agar seragam.""" + return "".join(ch for ch in (mentah or "") if ch.isalnum()) + + +@app.post("/setup") +async def setup_simpan(request: Request): + if db.ada_pengguna(): + return RedirectResponse("/login", status_code=303) + form = await request.form() + nip = _rapikan_nip(form.get("nip") or "") + nama = (form.get("nama") or "").strip() + if not nip or not nama: + return render(request, "login.html", + {"mode": "setup", "tahun": db.tahun_aktif(), + "pesan": "Nama dan NIP wajib diisi."}) + # password awal = NIP (kesepakatan prodi: kredensial cukup NIP) + uid = db.buat_pengguna(nip, nama, "kaprodi", auth.hash_password(nip)) + return _masuk(uid) + + +@app.get("/login", response_class=HTMLResponse) +def login_form(request: Request): + if not db.ada_pengguna(): + return RedirectResponse("/setup", status_code=303) + return render(request, "login.html", + {"mode": "login", "pesan": "", "tahun": db.tahun_aktif()}) + + +@app.post("/login") +async def login_proses(request: Request): + form = await request.form() + nip = _rapikan_nip(form.get("nip") or "") + pw = form.get("password") or "" + p = db.pengguna_by_username(nip) + if not p or not auth.verifikasi_password(pw, p["pw_hash"]): + return render(request, "login.html", + {"mode": "login", "tahun": db.tahun_aktif(), + "pesan": "NIP atau password salah."}) + return _masuk(p["id"]) + + +@app.get("/keluar") +def keluar(request: Request): + db.hapus_sesi(request.cookies.get("sesi", "")) + r = RedirectResponse("/login", status_code=303) + r.delete_cookie("sesi") + return r + + +# ── admin kaprodi: akun, penugasan MK, daftar mahasiswa ───────────────────── + + +def _bukan_kaprodi(request: Request): + if request.state.user["role"] != "kaprodi": + return RedirectResponse("/", status_code=303) + return None + + +@app.get("/admin", response_class=HTMLResponse) +def admin(request: Request, pesan: str = ""): + tolak = _bukan_kaprodi(request) + if tolak: + return tolak + mhs = db.ringkasan_mahasiswa() + return render(request, "admin.html", + {"pengguna": db.daftar_pengguna(), "daftar": db.daftar_mk(), + "mhs": mhs, "n_mhs_mk": len(mhs), + "tahun": db.tahun_aktif(), "pesan": pesan}) + + +@app.post("/admin/pengguna") +async def admin_buat_pengguna(request: Request): + tolak = _bukan_kaprodi(request) + if tolak: + return tolak + form = await request.form() + nip = _rapikan_nip(form.get("nip") or "") + nama = (form.get("nama") or "").strip() + role = "kaprodi" if form.get("role") == "kaprodi" else "dosen" + if not nip or not nama: + return RedirectResponse( + "/admin?pesan=Nama+dan+NIP+wajib+diisi.", status_code=303) + uid = db.buat_pengguna(nip, nama, role, auth.hash_password(nip)) + if uid is None: + return RedirectResponse( + f"/admin?pesan=NIP+{nip}+sudah+terdaftar.", status_code=303) + return RedirectResponse( + f"/admin?pesan=Akun+{nama.split(',')[0].replace(' ', '+')}+({role})+dibuat+-+login+dengan+NIP+{nip},+password+=+NIP.", + status_code=303) + + +@app.post("/admin/pengguna/{uid}/hapus") +async def admin_hapus_pengguna(request: Request, uid: int): + tolak = _bukan_kaprodi(request) + if tolak: + return tolak + if uid == request.state.user["id"]: + return RedirectResponse( + "/admin?pesan=Tidak+bisa+menghapus+akun+sendiri.", status_code=303) + db.hapus_pengguna(uid) + return RedirectResponse("/admin?pesan=Akun+dihapus.", status_code=303) + + +@app.post("/admin/pengguna/{uid}/reset") +async def admin_reset_password(request: Request, uid: int): + tolak = _bukan_kaprodi(request) + if tolak: + return tolak + p = db.pengguna_by_id(uid) + if not p: + return RedirectResponse("/admin?pesan=Akun+tidak+ditemukan.", status_code=303) + db.ubah_password(uid, auth.hash_password(p["username"])) + return RedirectResponse( + "/admin?pesan=Password+dikembalikan+ke+NIP+-+sesi+lama+pengguna+tsb+ditutup.", + status_code=303) + + +@app.post("/admin/pengguna/{uid}/tugas") +async def admin_penugasan(request: Request, uid: int): + tolak = _bukan_kaprodi(request) + if tolak: + return tolak + form = await request.form() + kode_valid = {m["kode"] for m in db.daftar_mk()} + pilihan = [k for k in form.getlist("mk") if k in kode_valid] + db.set_penugasan(uid, pilihan) + p = db.pengguna_by_id(uid) + return RedirectResponse( + f"/admin?pesan={len(pilihan)}+MK+ditugaskan+ke+{p['username'] if p else uid}.", + status_code=303) + + +@app.get("/admin/mahasiswa/template.xlsx") +def admin_template_mahasiswa(request: Request): + tolak = _bukan_kaprodi(request) + if tolak: + return tolak + isi = nilai_xlsx.template_mahasiswa(db.daftar_mk(), db.tahun_aktif()) + return Response( + content=isi, + media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + headers={"Content-Disposition": + 'attachment; filename="Template_Daftar_Mahasiswa.xlsx"'}, + ) + + +@app.post("/admin/mahasiswa") +async def admin_impor_mahasiswa(request: Request): + tolak = _bukan_kaprodi(request) + if tolak: + return tolak + form = await request.form() + berkas = form.get("berkas") + if berkas is None or not getattr(berkas, "filename", ""): + return RedirectResponse( + "/admin?pesan=Pilih+file+.xlsx+terlebih+dahulu.", status_code=303) + isi = await berkas.read() + kode_valid = {m["kode"] for m in db.daftar_mk()} + try: + baris, peringatan = nilai_xlsx.parse_mahasiswa(isi, kode_valid) + except Exception: + return RedirectResponse( + "/admin?pesan=File+tidak+terbaca+sebagai+.xlsx.", status_code=303) + if not baris: + pesan = "+".join((peringatan or ["Tidak ada baris yang terbaca."])[0].split()) + return RedirectResponse(f"/admin?pesan={pesan}", status_code=303) + db.ganti_mahasiswa(baris) + n_mk = len({b["mk_kode"] for b in baris}) + pesan = f"{len(baris)}+baris+kontrak+({n_mk}+MK)+diimpor+-+menggantikan+daftar+lama+tahun+ini." + if peringatan: + pesan += "+Catatan:+" + "+".join(" ".join(peringatan[:3]).split()) + return RedirectResponse(f"/admin?pesan={pesan}", status_code=303) + + +# bobot default komponen — dari sheet `Asessement` draft kurikulum (Tabel 25) +KOMPONEN_DEFAULT = [ + {"nama": "Kuis", "subs": [], "bobot": 15}, + {"nama": "Ujian Tengah Semester (UTS)", "subs": [], "bobot": 20}, + {"nama": "Ujian Akhir Semester (UAS)", "subs": [], "bobot": 30}, + {"nama": "Tugas / Praktikum / Proyek", "subs": [], "bobot": 35}, +] + + +def minggu_default() -> list[dict]: + rows = [] + for w in range(1, 17): + m = {"ke": w, "sub": "", "indikator": "", "kriteria": "", "metode": "", + "daring": "", "kegiatan": "", "materi": "", "bobot": ""} + if w == 8: + m.update(sub="__uts__", kriteria="Ujian Tengah Semester (UTS)", + metode="Ujian tulis [2 x 50 menit]", materi="Materi minggu 1–7") + elif w == 16: + m.update(sub="__uas__", kriteria="Ujian Akhir Semester (UAS)", + metode="Ujian tulis [2 x 50 menit]", materi="Materi minggu 9–15") + rows.append(m) + return rows + + +def form_kosong() -> dict: + return { + "identitas": {"sks": "", "sks_teori": "", "sks_praktikum": "", "semester": "", + "prasyarat": "", "pengampu": "", "koordinator": "", "kaprodi": "", + "rumpun": "", "tgl_penyusunan": "", "pengembang": "", + "kode_dokumen": ""}, + "deskripsi": "", + "bahan_kajian": "", + "media": "", + "minggu": minggu_default(), + "komponen": [dict(k) for k in KOMPONEN_DEFAULT], + "referensi_teks": "", + "referensi_pendukung_teks": "", + } + + +@app.get("/", response_class=HTMLResponse) +def beranda(request: Request, semua: int = 0): + daftar = db.daftar_mk() + n_total = len(daftar) + mk_saya = db.mk_diampu(request.state.user["id"]) + # dosen: bawaan hanya MK yang diampu; ?semua=1 menampilkan seluruh MK + hanya_saya = (request.state.user["role"] == "dosen" + and bool(mk_saya) and not semua) + if hanya_saya: + daftar = [m for m in daftar if m["kode"] in mk_saya] + status = db.status_semua("rps") + st_porto = db.status_semua("portofolio") + st_nilai = db.status_semua("nilai") + n_selesai = sum(1 for s in status.values() if s["status"] == "selesai") + n_draft = sum(1 for s in status.values() if s["status"] == "draft") + return render( + request, "beranda.html", + {"daftar": daftar, "status": status, "st_porto": st_porto, + "st_nilai": st_nilai, "n_selesai": n_selesai, + "n_draft": n_draft, "tahun": db.tahun_aktif(), + "pengampu": db.pengampu_per_mk(), + "smt": {k: v.get("semester") for k, v in db.sks_mk().items()}, + "mk_saya": mk_saya, "hanya_saya": hanya_saya, "n_total": n_total}, + ) + + +@app.get("/dashboard") +def dashboard(): + """Dashboard Pemetaan CPMK (buatan user; self-contained, data ter-embed).""" + return FileResponse(str(BASE / "static" / "dashboard.html"), media_type="text/html") + + +@app.get("/rps/{kode}", response_class=HTMLResponse) +def form_rps(request: Request, kode: str, pesan: str = ""): + peta = db.peta_mk(kode) + if not peta: + return RedirectResponse("/", status_code=303) + tersimpan = db.ambil_rps(kode) + if tersimpan: + f = tersimpan["data"] + f.setdefault("referensi_teks", "\n".join(f.get("referensi", []))) + ctx_status = tersimpan + else: + f = form_kosong() + # prefill SKS & semester dari data kurikulum (sheet '14. Susunan MK') + info = db.sks_mk().get(kode) + if info: + f["identitas"]["sks"] = f"{info['sks']:g}" + if info.get("semester"): + f["identitas"]["semester"] = str(info["semester"]) + ctx_status = {"status": "", "updated_at": "", "updated_by": ""} + return render( + request, "form_rps.html", + {"peta": peta, "f": f, "status": ctx_status["status"], + "updated_at": ctx_status["updated_at"], "updated_by": ctx_status["updated_by"], + "tahun": db.tahun_aktif(), "pesan": pesan, + "boleh_edit": boleh_kelola(request.state.user, kode), + "tahun_lain": db.tahun_rps_lain(kode) if not tersimpan else []}, + ) + + +@app.post("/rps/{kode}/salin") +async def salin_rps(request: Request, kode: str): + """Duplikasi RPS dari tahun ajaran lain ke tahun aktif (status: draft).""" + if not boleh_kelola(request.state.user, kode): + return RedirectResponse( + f"/rps/{kode}?pesan=Anda+bukan+pengampu+MK+ini.", status_code=303) + form = await request.form() + dari = form.get("dari") or "" + sumber = db.ambil_rps(kode, tahun=dari) if dari else None + if not sumber: + return RedirectResponse(f"/rps/{kode}?pesan=RPS+sumber+tidak+ditemukan.", + status_code=303) + if db.ambil_rps(kode): + return RedirectResponse( + f"/rps/{kode}?pesan=RPS+tahun+aktif+sudah+ada+-+tidak+ditimpa.", + status_code=303) + db.simpan_rps(kode, sumber["data"], "draft", request.state.user["nama"]) + return RedirectResponse( + f"/rps/{kode}?pesan=RPS+disalin+dari+{dari.replace(' ', '+')}+sebagai+draft.", + status_code=303) + + +@app.post("/rps/{kode}") +async def simpan_rps(request: Request, kode: str): + peta = db.peta_mk(kode) + if not peta: + return RedirectResponse("/", status_code=303) + if not boleh_kelola(request.state.user, kode): + return RedirectResponse( + f"/rps/{kode}?pesan=Anda+bukan+pengampu+MK+ini.", status_code=303) + form = await request.form() + + identitas = { + k: (form.get(k) or "").strip() + for k in ("sks", "sks_teori", "sks_praktikum", "semester", "prasyarat", + "pengampu", "koordinator", "kaprodi", "rumpun", + "tgl_penyusunan", "pengembang", "kode_dokumen") + } + minggu = [] + for w in range(1, 17): + minggu.append({ + "ke": w, + "sub": (form.get(f"m{w}_sub") or "").strip(), + "indikator": (form.get(f"m{w}_indikator") or "").strip(), + "kriteria": (form.get(f"m{w}_kriteria") or "").strip(), + "metode": (form.get(f"m{w}_metode") or "").strip(), + "daring": (form.get(f"m{w}_daring") or "").strip(), + "kegiatan": (form.get(f"m{w}_kegiatan") or "").strip(), + "materi": (form.get(f"m{w}_materi") or "").strip(), + "bobot": (form.get(f"m{w}_bobot") or "").strip(), + }) + komponen = [] + for i in range(1, len(KOMPONEN_DEFAULT) + 1): + nama = (form.get(f"k{i}_nama") or "").strip() + if not nama: + continue + komponen.append({ + "nama": nama, + "subs": [s for s in form.getlist(f"k{i}_subs")], + "bobot": (form.get(f"k{i}_bobot") or "").strip(), + }) + referensi_teks = (form.get("referensi") or "").strip() + referensi = [r.strip() for r in referensi_teks.splitlines() if r.strip()] + pendukung_teks = (form.get("referensi_pendukung") or "").strip() + pendukung = [r.strip() for r in pendukung_teks.splitlines() if r.strip()] + + data = { + "identitas": identitas, + "deskripsi": (form.get("deskripsi") or "").strip(), + "bahan_kajian": (form.get("bahan_kajian") or "").strip(), + "media": (form.get("media") or "").strip(), + "minggu": minggu, + "komponen": komponen, + "referensi": referensi, + "referensi_teks": referensi_teks, + "referensi_pendukung": pendukung, + "referensi_pendukung_teks": pendukung_teks, + } + aksi = form.get("aksi") or "draft" + status = "selesai" if aksi == "selesai" else "draft" + db.simpan_rps(kode, data, status, request.state.user["nama"]) + return RedirectResponse(f"/rps/{kode}?pesan=Tersimpan+sebagai+{status}.", status_code=303) + + +@app.get("/rps/{kode}/unduh") +def unduh_rps(kode: str): + peta = db.peta_mk(kode) + tersimpan = db.ambil_rps(kode) + if not peta or not tersimpan: + return RedirectResponse(f"/rps/{kode}", status_code=303) + f = tersimpan["data"] + + # resolusi kode Sub-CPMK → teks uraian untuk dokumen + uraian = {s["kode"]: s["uraian"] for s in peta["subs"]} + + def sub_teks(kode_sub: str) -> str: + if kode_sub == "__uts__": + return "Sub-CPMK minggu 1–7 (diujikan pada UTS)" + if kode_sub == "__uas__": + return "Sub-CPMK minggu 9–15 (diujikan pada UAS)" + if kode_sub in uraian: + return f"{kode_sub}: {uraian[kode_sub]}" + return kode_sub + + d = { + "tahun": db.tahun_aktif(), + "identitas": {**f.get("identitas", {}), "nama": peta["nama"], "kode": peta["kode"]}, + "deskripsi": f.get("deskripsi", ""), + "bahan_kajian": f.get("bahan_kajian", ""), + "media": f.get("media", ""), + "cpl": peta["cpls"], + "cpmk": [{"kode": c["kode"], "desk": c["desk"], "cpl": c["cpl"]} for c in peta["cpmks"]], + "sub": [{"kode": s["kode"], "uraian": s["uraian"], "cpmk": s["cpmk"]} for s in peta["subs"]], + "minggu": [{**m, "sub": sub_teks(m.get("sub", ""))} for m in f.get("minggu", [])], + "komponen": f.get("komponen", []), + "referensi": f.get("referensi", []), + "referensi_pendukung": f.get("referensi_pendukung", []), + } + isi = build_rps(d) + nama_file = re.sub(r"[^A-Za-z0-9_-]+", "_", f"RPS_{peta['kode']}_{peta['nama']}") + ".docx" + return Response( + content=isi, + media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document", + headers={"Content-Disposition": f'attachment; filename="{nama_file}"'}, + ) + + +# ═════════════════════════ TAHAP 2: NILAI & PENGUKURAN ═════════════════════════ + + +def _konteks_nilai(request, kode, pesan="", peringatan=None): + peta = db.peta_mk(kode) + tersimpan = db.ambil_dok("nilai", kode) + pengaturan = db.ambil_pengaturan() + ambang = float(pengaturan["ambang"]) + hasil, akhir = None, {} + if tersimpan: + hasil = hitung.rollup_mk(peta, tersimpan["data"], ambang) + if tersimpan["data"].get("mode") == "per_mahasiswa": + rps = db.ambil_rps(kode) + bobot_sub = hitung.bobot_sub_dari_rps(rps["data"] if rps else None) + akhir = {a["npm"]: a["akhir"] + for a in hitung.nilai_akhir_mahasiswa(tersimpan["data"], bobot_sub)} + # grid input langsung: gabungan daftar impor kaprodi + nilai tersimpan + terdaftar = db.mahasiswa_mk(kode) + skor_map, nama_map = {}, {} + if tersimpan and tersimpan["data"].get("mode") == "per_mahasiswa": + for m in tersimpan["data"]["mahasiswa"]: + skor_map[m.get("npm", "")] = m.get("skor") or {} + nama_map[m.get("npm", "")] = m.get("nama", "") + grid, ada = [], set() + for m in terdaftar: + grid.append({"npm": m["npm"], "nama": m["nama"], "kelas": m.get("kelas") or "", + "skor": skor_map.get(m["npm"], {})}) + ada.add(m["npm"]) + for npm, sk in skor_map.items(): + if npm not in ada: + grid.append({"npm": npm, "nama": nama_map.get(npm, ""), "kelas": "", + "skor": sk}) + return render( + request, "form_nilai.html", + {"peta": peta, "tersimpan": tersimpan, "hasil": hasil, "ambang": ambang, + "akhir": akhir, "tahun": db.tahun_aktif(), "pesan": pesan, + "terdaftar": terdaftar, "grid": grid, + "peringatan": peringatan or []}, + ) + + +def _gabung_nilai(lama: dict, baru: list[dict], sumber: str, oleh: str) -> dict: + """Gabungkan unggahan ke data per-mahasiswa tersimpan (team teaching): + sel terisi memperbarui, sel kosong tidak menghapus nilai dosen lain, + mahasiswa yang tidak ada di file dibiarkan utuh.""" + urut = [dict(m, skor=dict(m.get("skor") or {})) + for m in lama.get("mahasiswa", [])] + per_npm = {m["npm"]: m for m in urut if m.get("npm")} + for m in baru: + ada = per_npm.get(m.get("npm")) + if ada is None: + urut.append(m) + if m.get("npm"): + per_npm[m["npm"]] = m + else: + if m.get("nama"): + ada["nama"] = m["nama"] + ada["skor"].update(m.get("skor") or {}) + riwayat = lama.get("sumber") or [] + if isinstance(riwayat, str): + riwayat = [riwayat] + riwayat.append(f"{sumber} ({oleh})") + return {"mode": "per_mahasiswa", "mahasiswa": urut, "sumber": riwayat[-8:]} + + +@app.get("/nilai/{kode}", response_class=HTMLResponse) +def form_nilai(request: Request, kode: str, pesan: str = ""): + if not db.peta_mk(kode): + return RedirectResponse("/", status_code=303) + if not boleh_kelola(request.state.user, kode): + return RedirectResponse( + f"/rps/{kode}?pesan=Nilai+mahasiswa+hanya+dibuka+untuk+pengampu+MK+dan+kaprodi.", + status_code=303) + return _konteks_nilai(request, kode, pesan) + + +@app.get("/nilai/{kode}/template.xlsx") +def template_nilai(request: Request, kode: str): + peta = db.peta_mk(kode) + if not peta: + return RedirectResponse("/", status_code=303) + if not boleh_kelola(request.state.user, kode): + return RedirectResponse(f"/rps/{kode}", status_code=303) + isi = nilai_xlsx.buat_template(peta, db.tahun_aktif(), db.mahasiswa_mk(kode)) + return Response( + content=isi, + media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + headers={"Content-Disposition": + f'attachment; filename="Template_Nilai_{kode}.xlsx"'}, + ) + + +@app.post("/nilai/{kode}") +async def simpan_nilai(request: Request, kode: str): + peta = db.peta_mk(kode) + if not peta: + return RedirectResponse("/", status_code=303) + if not boleh_kelola(request.state.user, kode): + return RedirectResponse( + f"/rps/{kode}?pesan=Nilai+mahasiswa+hanya+dibuka+untuk+pengampu+MK+dan+kaprodi.", + status_code=303) + form = await request.form() + aksi = form.get("aksi") + updated_by = request.state.user["nama"] + kode_sub = {s["kode"] for s in peta["subs"]} + + if aksi == "hapus": + db.hapus_dok("nilai", kode) + return _konteks_nilai( + request, kode, + pesan="Seluruh nilai tersimpan MK ini dihapus — silakan mulai dari awal.") + + if aksi == "grid": + # input langsung: menyimpan persis isi tabel yang tampil + mahasiswa = [] + i = 0 + while f"npm_{i}" in form: + npm = (form.get(f"npm_{i}") or "").strip() + nama = (form.get(f"nama_{i}") or "").strip() + skor = {} + for s in peta["subs"]: + v = (form.get(f"skor_{i}_{s['kode']}") or "").strip() + if not v: + continue + try: + x = float(v) + except ValueError: + continue + if 0 <= x <= 100: + skor[s["kode"]] = x + if skor and (npm or nama): + mahasiswa.append({"npm": npm, "nama": nama, "skor": skor}) + i += 1 + if not mahasiswa: + return _konteks_nilai(request, kode, + peringatan=["Tidak ada nilai yang diisi."]) + lama = db.ambil_dok("nilai", kode) + riwayat = (lama["data"].get("sumber") if lama else None) or [] + if isinstance(riwayat, str): + riwayat = [riwayat] + riwayat.append(f"input langsung ({updated_by})") + data = {"mode": "per_mahasiswa", "mahasiswa": mahasiswa, + "sumber": riwayat[-8:]} + db.simpan_dok("nilai", kode, data, "selesai", updated_by) + return _konteks_nilai( + request, kode, + pesan=f"Nilai {len(mahasiswa)} mahasiswa tersimpan (input langsung).") + + if aksi == "unggah": + berkas = form.get("berkas") + if berkas is None or not getattr(berkas, "filename", ""): + return _konteks_nilai(request, kode, + peringatan=["Pilih file .xlsx terlebih dahulu."]) + isi = await berkas.read() + try: + mahasiswa, peringatan = nilai_xlsx.parse_unggahan(isi, kode_sub) + except Exception: + return _konteks_nilai(request, kode, + peringatan=["File tidak terbaca sebagai .xlsx."]) + if not mahasiswa: + return _konteks_nilai(request, kode, peringatan=peringatan) + lama = db.ambil_dok("nilai", kode) + if lama and lama["data"].get("mode") == "per_mahasiswa": + data = _gabung_nilai(lama["data"], mahasiswa, berkas.filename, updated_by) + pesan = (f"Unggahan {berkas.filename} digabung: {len(mahasiswa)} mahasiswa " + f"diperbarui, total {len(data['mahasiswa'])} mahasiswa tersimpan.") + else: + data = {"mode": "per_mahasiswa", "mahasiswa": mahasiswa, + "sumber": [f"{berkas.filename} ({updated_by})"]} + pesan = f"Nilai {len(mahasiswa)} mahasiswa tersimpan dari {berkas.filename}." + db.simpan_dok("nilai", kode, data, "selesai", updated_by) + return _konteks_nilai(request, kode, pesan=pesan, peringatan=peringatan) + + # aksi == 'agregat' — ketik rata-rata per Sub-CPMK + lama = db.ambil_dok("nilai", kode) + if lama and lama["data"].get("mode") == "per_mahasiswa": + return _konteks_nilai(request, kode, peringatan=[ + "Data per-mahasiswa sudah tersimpan — menyimpan rata-rata akan membuang " + 'rinciannya. Bila memang ingin beralih, hapus dulu lewat tombol "Mulai Ulang".']) + agregat = {} + for s in peta["subs"]: + v = (form.get(f"rata_{s['kode']}") or "").strip() + if v: + try: + x = float(v) + except ValueError: + continue + if 0 <= x <= 100: + agregat[s["kode"]] = x + if not agregat: + return _konteks_nilai(request, kode, + peringatan=["Tidak ada rata-rata yang diisi."]) + data = {"mode": "agregat", "agregat": agregat} + db.simpan_dok("nilai", kode, data, "selesai", updated_by) + return _konteks_nilai( + request, kode, + pesan=f"Rata-rata {len(agregat)} Sub-CPMK tersimpan (mode BUKTI AGREGAT).", + ) + + +# ═════════════════════════ TAHAP 2: PORTOFOLIO ═════════════════════════ + + +PORTO_KOSONG = { + "dokumen": {"nomor": "", "revisi": "0", "tgl_terbit": "", + "kurikulum_tahun": "2025", "dekan": "", "status_dokumen": "Master"}, + "realisasi": "", + "kendala": [{"kendala": "", "tindak": ""} for _ in range(5)], + "simpulan": "", +} + + +@app.get("/portofolio/{kode}", response_class=HTMLResponse) +def form_porto(request: Request, kode: str, pesan: str = ""): + peta = db.peta_mk(kode) + if not peta: + return RedirectResponse("/", status_code=303) + tersimpan = db.ambil_dok("portofolio", kode) + if tersimpan: + f = tersimpan["data"] + else: + f = {"dokumen": dict(PORTO_KOSONG["dokumen"]), + "realisasi": "", "simpulan": "", + "kendala": [{"kendala": "", "tindak": ""} for _ in range(5)]} + while len(f.get("kendala", [])) < 5: + f.setdefault("kendala", []).append({"kendala": "", "tindak": ""}) + rps = db.ambil_rps(kode) + nilai = db.ambil_dok("nilai", kode) + return render( + request, "form_porto.html", + {"peta": peta, "f": f, "tahun": db.tahun_aktif(), "pesan": pesan, + "status": tersimpan["status"] if tersimpan else "", + "updated_at": tersimpan["updated_at"] if tersimpan else "", + "updated_by": tersimpan["updated_by"] if tersimpan else "", + "boleh_edit": boleh_kelola(request.state.user, kode), + "ada_rps": bool(rps), "ada_nilai": bool(nilai)}, + ) + + +@app.post("/portofolio/{kode}") +async def simpan_porto(request: Request, kode: str): + if not db.peta_mk(kode): + return RedirectResponse("/", status_code=303) + if not boleh_kelola(request.state.user, kode): + return RedirectResponse( + f"/portofolio/{kode}?pesan=Anda+bukan+pengampu+MK+ini.", status_code=303) + form = await request.form() + data = { + "dokumen": { + k: (form.get(k) or "").strip() + for k in ("nomor", "revisi", "tgl_terbit", "kurikulum_tahun", + "dekan", "status_dokumen") + }, + "realisasi": (form.get("realisasi") or "").strip(), + "kendala": [ + {"kendala": (form.get(f"kendala_{i}") or "").strip(), + "tindak": (form.get(f"tindak_{i}") or "").strip()} + for i in range(1, 6) + ], + "simpulan": (form.get("simpulan") or "").strip(), + } + status = "selesai" if form.get("aksi") == "selesai" else "draft" + db.simpan_dok("portofolio", kode, data, status, request.state.user["nama"]) + return RedirectResponse(f"/portofolio/{kode}?pesan=Tersimpan+sebagai+{status}.", + status_code=303) + + +def _bahan_porto(kode: str) -> dict | None: + """Kumpulkan semua bahan dokumen portofolio satu MK.""" + peta = db.peta_mk(kode) + if not peta: + return None + porto = db.ambil_dok("portofolio", kode) + rps = db.ambil_rps(kode) + nilai = db.ambil_dok("nilai", kode) + pengaturan = db.ambil_pengaturan() + ambang = float(pengaturan["ambang"]) + skala = hitung.parse_skala(pengaturan["skala_huruf"]) + + f = porto["data"] if porto else PORTO_KOSONG + rps_data = rps["data"] if rps else None + nilai_data = nilai["data"] if nilai else {"mode": "agregat", "agregat": {}} + + bobot_sub = hitung.bobot_sub_dari_rps(rps_data) + # korelasi sub -> CPL: bobot sub dibagi rata ke CPL yang didukung CPMK-nya + cpl_per_cpmk = {c["kode"]: c["cpl"] for c in peta["cpmks"]} + korelasi = {} + for s in peta["subs"]: + cpls = cpl_per_cpmk.get(s["cpmk"], []) + b = bobot_sub.get(s["kode"]) + if b and cpls: + korelasi[s["kode"]] = {cp: b / len(cpls) for cp in cpls} + # jumlah minggu per sub & rencana penilaian dari RPS + minggu_sub, rencana = {}, [] + if rps_data: + uraian = {s["kode"]: s for s in peta["subs"]} + for m in rps_data.get("minggu", []): + sk = m.get("sub", "") + if sk in uraian: + minggu_sub[sk] = minggu_sub.get(sk, 0) + 1 + asesmen = "; ".join(x for x in (m.get("kriteria"), m.get("metode")) if x) + if sk in uraian: + label = sk + elif sk == "__uts__": + label = "UTS" + elif sk == "__uas__": + label = "UAS" + else: + label = sk + rencana.append({"minggu": m.get("ke"), "sub": label, + "asesmen": asesmen, "bobot": m.get("bobot", "")}) + + skor_sub = hitung.skor_sub(nilai_data, ambang) + rollup = hitung.rollup_mk(peta, nilai_data, ambang) + akhir = (hitung.nilai_akhir_mahasiswa(nilai_data, bobot_sub) + if nilai_data.get("mode") == "per_mahasiswa" else []) + statistik = hitung.statistik_akhir(akhir, skala) if akhir else None + + return { + "tahun": db.tahun_aktif(), + "identitas": {**(rps_data or {}).get("identitas", {}), + "nama": peta["nama"], "kode": peta["kode"]}, + "dokumen": f.get("dokumen", {}), + "cpl": peta["cpls"], + "cpmk": peta["cpmks"], + "sub": [{"kode": s["kode"], "uraian": s["uraian"], "cpmk": s["cpmk"]} + for s in peta["subs"]], + "korelasi": korelasi, "bobot_sub": bobot_sub, "minggu_sub": minggu_sub, + "rencana": rencana, + "realisasi": f.get("realisasi", ""), + "nilai": nilai_data, "skor_sub": skor_sub, "rollup": rollup, + "akhir": akhir, "statistik": statistik, "skala": skala, "ambang": ambang, + "kendala": f.get("kendala", []), "simpulan": f.get("simpulan", ""), + } + + +@app.get("/portofolio/{kode}/unduh") +def unduh_porto(request: Request, kode: str): + # dokumen memuat matriks nilai per mahasiswa — batasi pengampu + kaprodi + if not boleh_kelola(request.state.user, kode): + return RedirectResponse( + f"/portofolio/{kode}?pesan=Unduhan+portofolio+memuat+nilai+mahasiswa+-+hanya+pengampu+dan+kaprodi.", + status_code=303) + bahan = _bahan_porto(kode) + if not bahan: + return RedirectResponse("/", status_code=303) + isi = build_portofolio(bahan) + nama = re.sub(r"[^A-Za-z0-9_-]+", "_", + f"Portofolio_{kode}_{bahan['identitas']['nama']}") + ".docx" + return Response( + content=isi, + media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document", + headers={"Content-Disposition": f'attachment; filename="{nama}"'}, + ) + + +# ═════════════════════════ TAHAP 2: REKAP & METODOLOGI ═════════════════════════ + + +@app.get("/rekap", response_class=HTMLResponse) +def rekap(request: Request): + hasil = hitung.rollup_prodi() + cpl_urut = sorted(hasil["cpl"].values(), key=lambda c: c["kode"]) + cpmk_urut = sorted(hasil["cpmk"].values(), key=lambda c: c["kode"]) + nama_mk = {m["kode"]: m["nama"] for m in db.daftar_mk()} + return render( + request, "rekap.html", + {"hasil": hasil, "cpl_urut": cpl_urut, "cpmk_urut": cpmk_urut, + "nama_mk": nama_mk, "tahun": db.tahun_aktif()}, + ) + + +@app.get("/pantau", response_class=HTMLResponse) +def pantau(request: Request): + """Dasbor monitoring prodi: ketercapaian CPL/CPMK semester berjalan, + kelengkapan proses dokumen, dan daftar tindak lanjut untuk kaprodi.""" + hasil = hitung.rollup_prodi() + daftar = db.daftar_mk() + nama_mk = {m["kode"]: m["nama"] for m in daftar} + + cpl_urut = sorted(hasil["cpl"].values(), key=lambda c: c["kode"]) + cpmk_urut = sorted(hasil["cpmk"].values(), key=lambda c: c["kode"]) + terukur = [c for c in cpl_urut if c["skor"] is not None] + rata_cpl = sum(c["skor"] for c in terukur) / len(terukur) if terukur else None + + # kelengkapan proses dokumen per tahap (dari status 49 MK) + proses = [] + for label, tabel in (("RPS", "rps"), ("Nilai", "nilai"), + ("Portofolio", "portofolio")): + s = db.status_semua(tabel) + n_sel = sum(1 for v in s.values() if v["status"] == "selesai") + n_draft = sum(1 for v in s.values() if v["status"] == "draft") + proses.append({"label": label, "selesai": n_sel, "draft": n_draft, + "belum": len(daftar) - n_sel - n_draft}) + + # MK belum setor nilai + pengampunya — bahan tindak lanjut + st_nilai = db.status_semua("nilai") + pengampu = db.pengampu_per_mk() + belum_nilai = [{"kode": m["kode"], "nama": m["nama"], + "pengampu": pengampu.get(m["kode"], [])} + for m in daftar if m["kode"] not in st_nilai] + + # tren skor CPL lintas tahun ajaran (sparkline; hanya bila >1 tahun) + daftar_tahun = db.daftar_tahun() + tren = {} + if len(daftar_tahun) > 1: + per_tahun = {t: hitung.rollup_prodi(t) for t in daftar_tahun} + for c in cpl_urut: + titik = [(t, per_tahun[t]["cpl"][c["kode"]]["skor"]) + for t in daftar_tahun] + if sum(1 for _, s in titik if s is not None) > 1: + tren[c["kode"]] = titik + + return render( + request, "pantau.html", + {"hasil": hasil, "cpl_urut": cpl_urut, "cpmk_urut": cpmk_urut, + "rata_cpl": rata_cpl, "proses": proses, "n_mk": len(daftar), + "belum_nilai": belum_nilai, "tren": tren, "nama_mk": nama_mk, + "tahun": db.tahun_aktif()}, + ) + + +@app.get("/metodologi", response_class=HTMLResponse) +def metodologi(request: Request, pesan: str = ""): + pengaturan = db.ambil_pengaturan() + skala = hitung.parse_skala(pengaturan["skala_huruf"]) + return render( + request, "metodologi.html", + {"p": pengaturan, "skala": skala, "tahun": db.tahun_aktif(), + "boleh_ubah": request.state.user["role"] == "kaprodi", + "pesan": pesan}, + ) + + +@app.post("/metodologi") +async def simpan_metodologi(request: Request): + tolak = _bukan_kaprodi(request) + if tolak: + return tolak + form = await request.form() + db.simpan_pengaturan({ + "tahun_aktif": (form.get("tahun_aktif") or "").strip() + or db.PENGATURAN_DEFAULT["tahun_aktif"], + "ambang": (form.get("ambang") or "70").strip(), + "metode_antar_mk": form.get("metode_antar_mk") or "rata", + "skala_huruf": (form.get("skala_huruf") or "").strip() + or db.PENGATURAN_DEFAULT["skala_huruf"], + }) + return RedirectResponse("/metodologi?pesan=Pengaturan+tersimpan.", status_code=303) + + +# ═════════════════════════ TAHAP 3: RIWAYAT, CADANGAN, PDF ═════════════════════════ + + +@app.get("/rekap/riwayat", response_class=HTMLResponse) +def rekap_riwayat(request: Request): + """Tren ketercapaian CPL lintas tahun ajaran.""" + daftar_tahun = db.daftar_tahun() + per_tahun = {t: hitung.rollup_prodi(t) for t in daftar_tahun} + kode_cpl = sorted(db.kurikulum()["cpl"].keys()) + return render( + request, "riwayat.html", + {"daftar_tahun": daftar_tahun, "per_tahun": per_tahun, + "kode_cpl": kode_cpl, "tahun": db.tahun_aktif()}, + ) + + +@app.get("/cadangan.json") +def cadangan_json(request: Request): + tolak = _bukan_kaprodi(request) + if tolak: + return tolak + import json as _json + isi = _json.dumps(db.dump_semua(), ensure_ascii=False, indent=1) + stempel = db.tahun_aktif().replace("/", "-").replace(" ", "_") + return Response( + content=isi.encode("utf-8"), media_type="application/json", + headers={"Content-Disposition": + f'attachment; filename="cadangan_rps_porto_{stempel}.json"'}, + ) + + +@app.get("/cadangan.db") +def cadangan_db(request: Request): + tolak = _bukan_kaprodi(request) + if tolak: + return tolak + return FileResponse(db.DB_PATH, media_type="application/octet-stream", + filename="app.db") + + +def _docx_ke_pdf(docx_bytes: bytes) -> bytes | None: + """Konversi via LibreOffice (soffice) bila tersedia; None bila tidak.""" + import shutil, subprocess, tempfile, pathlib as _pl + soffice = shutil.which("soffice") or shutil.which("soffice.exe") + if not soffice: + return None + with tempfile.TemporaryDirectory() as tmp: + src = _pl.Path(tmp) / "dokumen.docx" + src.write_bytes(docx_bytes) + try: + subprocess.run([soffice, "--headless", "--convert-to", "pdf", + "--outdir", tmp, str(src)], + check=True, capture_output=True, timeout=120) + except (subprocess.SubprocessError, OSError): + return None + pdf = src.with_suffix(".pdf") + return pdf.read_bytes() if pdf.exists() else None + + +@app.get("/rps/{kode}/unduh.pdf") +def unduh_rps_pdf(kode: str): + r = unduh_rps(kode) + if not isinstance(r, Response) or r.media_type is None or "wordprocessingml" not in r.media_type: + return r + pdf = _docx_ke_pdf(r.body) + if pdf is None: + return RedirectResponse( + f"/rps/{kode}?pesan=Konverter+PDF+(LibreOffice)+tidak+tersedia+di+server+-+unduh+.docx+lalu+Save+as+PDF+di+Word.", + status_code=303) + return Response(content=pdf, media_type="application/pdf", + headers={"Content-Disposition": + f'attachment; filename="RPS_{kode}.pdf"'}) + + +@app.get("/portofolio/{kode}/unduh.pdf") +def unduh_porto_pdf(request: Request, kode: str): + r = unduh_porto(request, kode) + if not isinstance(r, Response) or r.media_type is None or "wordprocessingml" not in r.media_type: + return r + pdf = _docx_ke_pdf(r.body) + if pdf is None: + return RedirectResponse( + f"/portofolio/{kode}?pesan=Konverter+PDF+(LibreOffice)+tidak+tersedia+di+server+-+unduh+.docx+lalu+Save+as+PDF+di+Word.", + status_code=303) + return Response(content=pdf, media_type="application/pdf", + headers={"Content-Disposition": + f'attachment; filename="Portofolio_{kode}.pdf"'}) diff --git a/app/nilai_xlsx.py b/app/nilai_xlsx.py new file mode 100644 index 0000000..43b9025 --- /dev/null +++ b/app/nilai_xlsx.py @@ -0,0 +1,207 @@ +# -*- coding: utf-8 -*- +"""Template & parser Excel nilai per mahasiswa per Sub-CPMK.""" +import io + +import openpyxl +from openpyxl.styles import Alignment, Font, PatternFill +from openpyxl.utils import get_column_letter + +BIRU = "1F4E79" +BIRU_MUDA = "D6E4F0" + + +def buat_template(peta: dict, tahun: str, mahasiswa: list[dict] | None = None) -> bytes: + """Template xlsx: kolom NPM, Nama, Kelas, lalu satu kolom per Sub-CPMK. + + Bila daftar mahasiswa (impor kaprodi) tersedia, baris NPM/Nama/Kelas + sudah terisi — dosen tinggal mengisi skornya. + """ + wb = openpyxl.Workbook() + ws = wb.active + ws.title = "Nilai" + + subs = peta["subs"] + ws.append([f"NILAI PER SUB-CPMK — {peta['nama']} ({peta['kode']}) — {tahun}"]) + ws.append(["Petunjuk: isi skor 0–100 per mahasiswa per Sub-CPMK. " + "Jangan mengubah baris kode Sub-CPMK."]) + ws.append([]) + header = ["NPM", "Nama Mahasiswa", "Kelas"] + [s["kode"] for s in subs] + ws.append(header) + uraian = ["", "", ""] + [s["uraian"][:80] for s in subs] + ws.append(uraian) + for m in mahasiswa or []: + ws.append([m["npm"], m["nama"], m.get("kelas", "")]) + + for ci, _ in enumerate(header, 1): + c = ws.cell(row=4, column=ci) + c.font = Font(bold=True, color="FFFFFF") + c.fill = PatternFill("solid", fgColor=BIRU) + c.alignment = Alignment(horizontal="center") + u = ws.cell(row=5, column=ci) + u.font = Font(italic=True, size=8) + u.alignment = Alignment(wrap_text=True, vertical="top") + ws.column_dimensions[get_column_letter(ci)].width = ( + 14 if ci == 1 else 28 if ci == 2 else 9 if ci == 3 else 14) + ws.freeze_panes = "D6" + + buf = io.BytesIO() + wb.save(buf) + return buf.getvalue() + + +def template_mahasiswa(daftar_mk: list[dict], tahun: str) -> bytes: + """Template impor daftar mahasiswa: NPM | Nama | Kode MK | Kelas.""" + wb = openpyxl.Workbook() + ws = wb.active + ws.title = "Mahasiswa" + ws.append([f"DAFTAR MAHASISWA MENGONTRAK MK — {tahun}"]) + ws.append(["Petunjuk: satu baris per kontrak (mahasiswa yang mengambil 5 MK " + "berarti 5 baris). Kode MK harus sesuai sheet 'Kode MK'."]) + ws.append([]) + ws.append(["NPM", "Nama Mahasiswa", "Kode MK", "Kelas"]) + for ci, lebar in enumerate((16, 30, 14, 10), 1): + c = ws.cell(row=4, column=ci) + c.font = Font(bold=True, color="FFFFFF") + c.fill = PatternFill("solid", fgColor=BIRU) + c.alignment = Alignment(horizontal="center") + ws.column_dimensions[get_column_letter(ci)].width = lebar + ws.freeze_panes = "A5" + + rujukan = wb.create_sheet("Kode MK") + rujukan.append(["Kode MK", "Nama Mata Kuliah"]) + for ci in (1, 2): + c = rujukan.cell(row=1, column=ci) + c.font = Font(bold=True, color="FFFFFF") + c.fill = PatternFill("solid", fgColor=BIRU) + for mk in daftar_mk: + rujukan.append([mk["kode"], mk["nama"]]) + rujukan.column_dimensions["A"].width = 14 + rujukan.column_dimensions["B"].width = 46 + + buf = io.BytesIO() + wb.save(buf) + return buf.getvalue() + + +def parse_mahasiswa(isi: bytes, kode_mk_valid: set[str]) -> tuple[list[dict], list[str]]: + """Baca xlsx daftar mahasiswa → (baris kontrak, peringatan). + + Header dicari otomatis (baris yang memuat 'NPM'); kolom dikenali dari + judulnya: NPM, Nama, Kode MK, Kelas. + """ + wb = openpyxl.load_workbook(io.BytesIO(isi), read_only=True, data_only=True) + ws = wb["Mahasiswa"] if "Mahasiswa" in wb.sheetnames else wb.active + rows = list(ws.iter_rows(values_only=True)) + + kol = {} + header_i = None + for i, row in enumerate(rows): + vals = [str(v).strip().lower() if v is not None else "" for v in row] + if "npm" in vals: + header_i = i + for ci, v in enumerate(vals): + if v == "npm": + kol["npm"] = ci + elif "nama" in v: + kol.setdefault("nama", ci) + elif "kode" in v or v == "mk": + kol.setdefault("mk", ci) + elif "kelas" in v: + kol.setdefault("kelas", ci) + break + if header_i is None or "npm" not in kol or "mk" not in kol: + return [], ["Header tidak dikenali: pastikan ada kolom NPM, Nama, " + "Kode MK, dan Kelas (pakai template dari aplikasi)."] + + def ambil(vals, kunci): + ci = kol.get(kunci) + if ci is None or ci >= len(vals) or vals[ci] is None: + return "" + v = vals[ci] + # NPM/kelas numerik dari Excel → buang '.0' + if isinstance(v, float) and v.is_integer(): + v = int(v) + return str(v).strip() + + baris, peringatan, tak_dikenal = [], [], set() + terlihat = set() + for row in rows[header_i + 1:]: + vals = list(row) + npm = ambil(vals, "npm") + mk = ambil(vals, "mk").upper() + if not npm or not mk: + continue + if mk not in kode_mk_valid: + tak_dikenal.add(mk) + continue + if (npm, mk) in terlihat: + continue + terlihat.add((npm, mk)) + baris.append({"npm": npm, "nama": ambil(vals, "nama"), + "mk_kode": mk, "kelas": ambil(vals, "kelas")}) + + if tak_dikenal: + peringatan.append( + f"{len(tak_dikenal)} kode MK tidak dikenal dan dilewati: " + + ", ".join(sorted(tak_dikenal)[:6]) + + ("…" if len(tak_dikenal) > 6 else "")) + if not baris: + peringatan.append("Tidak ada baris kontrak yang terbaca.") + return baris, peringatan + + +def parse_unggahan(isi: bytes, kode_sub_valid: set[str]) -> tuple[list[dict], list[str]]: + """Baca xlsx nilai → (mahasiswa, peringatan). + + Baris header dicari otomatis: baris yang memuat 'NPM'. Kolom Sub-CPMK + dicocokkan dengan kode yang valid untuk MK tsb. + """ + wb = openpyxl.load_workbook(io.BytesIO(isi), read_only=True, data_only=True) + ws = wb.active + rows = list(ws.iter_rows(values_only=True)) + + header_i, kolom_sub = None, {} + for i, row in enumerate(rows): + vals = [str(v).strip() if v is not None else "" for v in row] + if "NPM" in vals: + header_i = i + for ci, v in enumerate(vals): + if v in kode_sub_valid: + kolom_sub[ci] = v + npm_i = vals.index("NPM") + nama_i = npm_i + 1 + break + if header_i is None or not kolom_sub: + return [], ["Header tidak dikenali: pastikan memakai template dari aplikasi " + "(ada kolom NPM dan kode Sub-CPMK)."] + + mahasiswa, peringatan = [], [] + for row in rows[header_i + 1:]: + vals = list(row) + npm = str(vals[npm_i]).strip() if npm_i < len(vals) and vals[npm_i] is not None else "" + nama = str(vals[nama_i]).strip() if nama_i < len(vals) and vals[nama_i] is not None else "" + skor = {} + for ci, sub in kolom_sub.items(): + v = vals[ci] if ci < len(vals) else None + if v is None or v == "": + continue + try: + x = float(v) + except (TypeError, ValueError): + continue + if 0 <= x <= 100: + skor[sub] = x + else: + peringatan.append(f"{npm or nama}: nilai {sub} = {x} di luar 0–100, dilewati.") + if skor and (npm or nama): + mahasiswa.append({"npm": npm, "nama": nama, "skor": skor}) + + if not mahasiswa: + peringatan.append("Tidak ada baris nilai yang terbaca.") + hilang = kode_sub_valid - {s for m in mahasiswa for s in m["skor"]} + if mahasiswa and hilang: + peringatan.append( + f"{len(hilang)} Sub-CPMK tanpa nilai sama sekali: " + + ", ".join(sorted(hilang)[:8]) + ("…" if len(hilang) > 8 else "") + ) + return mahasiswa, peringatan diff --git a/Dashboard_Pemetaan_CPMK.html b/app/static/dashboard.html similarity index 100% rename from Dashboard_Pemetaan_CPMK.html rename to app/static/dashboard.html diff --git a/app/static/logo-untan.png b/app/static/logo-untan.png new file mode 100644 index 0000000..c78d26e Binary files /dev/null and b/app/static/logo-untan.png differ diff --git a/app/static/style.css b/app/static/style.css new file mode 100644 index 0000000..9feaee1 --- /dev/null +++ b/app/static/style.css @@ -0,0 +1,265 @@ +/* ─── Arah visual A · Dokumen Dinas ─────────────────────────────── + Bahasa visual tata naskah resmi: kop bergaris ganda, Times New + Roman, tabel bergaris penuh, biru 1F4E79 dari template Word RPS, + status berupa stempel. Tanpa framework. */ + +:root { + --paper: #ffffff; + --desk: #ececec; /* latar di luar "kertas" */ + --ink: #1a1a1a; + --blue: #1f4e79; + --blue-fill: #d6e4f0; + --row-alt: #f3f7fb; + --red: #9a3324; + --grey: #767676; + --green-fill: #e2efda; + --yellow-fill: #fff2cc; +} + +* { box-sizing: border-box; } + +body { + margin: 0; + background: var(--desk); + color: var(--ink); + font-family: "Times New Roman", Times, serif; + font-size: 15px; + line-height: 1.45; +} + +/* "kertas" landscape penuh — memakai seluruh lebar layar */ +.kertas { + max-width: none; + margin: 12px 16px 44px; + background: var(--paper); + border: 1px solid #c9c9c9; + padding: 26px 40px 40px; +} +.kop-teks, .kop { max-width: none; } + +/* ── kop: logo kiri, teks tengah (tata naskah dinas) ── */ +.kop { display: flex; align-items: center; gap: 16px; text-align: center; } +.kop-logo { flex: 0 0 74px; } +.kop-imbang { flex: 0 0 74px; } /* penyeimbang agar teks tepat di tengah */ +.kop-teks { flex: 1; } +.kop .uni { font-size: 15px; letter-spacing: .06em; } +.kop .prodi{ font-size: 21px; font-weight: bold; letter-spacing: .02em; } +.kop .app { font-size: 13px; font-style: italic; margin-top: 2px; } +.kop-rule { border: none; border-top: 2.5px solid var(--ink); margin: 14px 0 2px; } +.kop-rule2 { border: none; border-top: 1px solid var(--ink); margin: 0 0 24px; } + +/* ── menu utama (di bawah kop) ── */ +.menu-baris { + display: flex; justify-content: space-between; align-items: baseline; + gap: 16px; flex-wrap: wrap; margin: -16px 0 22px; + padding: 7px 0; border-bottom: 1px solid #c9c9c9; + font-size: 14px; +} +.menu-kiri { display: flex; gap: 4px 18px; flex-wrap: wrap; } +.menu-baris a { color: var(--blue); text-decoration: none; } +.menu-baris a:hover { text-decoration: underline; } +.menu-baris a.aktif { + color: var(--ink); font-weight: bold; + border-bottom: 2px solid var(--blue); text-decoration: none; +} +.menu-kanan { font-size: 13px; color: var(--grey); } + +/* ── kotak login ── */ +.kertas { min-height: calc(100vh - 56px); } +/* halaman login: kotak di tengah vertikal, kaki menempel di dasar kertas */ +.kertas:has(.login-kotak) { display: flex; flex-direction: column; } +.login-kotak { + width: 100%; max-width: 460px; margin: 40px auto 60px; + border: 1px solid var(--ink); padding: 26px 30px 30px; +} +.kertas:has(.login-kotak) .login-kotak { margin: auto; } +.login-kotak h1 { text-align: center; } +.login-kotak label { display: block; font-size: 14px; margin: 12px 0 3px; } + +/* ── navigasi & meta ── */ +.nav-baris { + display: flex; justify-content: space-between; align-items: baseline; + gap: 16px; flex-wrap: wrap; margin-bottom: 18px; font-size: 14px; +} +.nav-baris a { color: var(--blue); } +.kpi-baris { display: flex; gap: 26px; flex-wrap: wrap; margin-bottom: 20px; font-size: 14px; } +.kpi-baris b { font-size: 17px; } +.kpi-baris span { color: var(--grey); } +.kpi-pct { + font-family: "Segoe UI", Arial, sans-serif; font-size: 11px; font-weight: 700; + padding: 1px 6px; border: 1.5px solid; white-space: nowrap; +} +.kpi-baris span.kpi-hijau { color: #205020; border-color: #205020; background: var(--green-fill); } +.kpi-baris span.kpi-kuning { color: #7a5c00; border-color: #7a5c00; background: var(--yellow-fill); } +.kpi-baris span.kpi-merah { color: var(--red); border-color: var(--red); background: #f6ddd8; } + +/* ── judul bagian (meniru section_head dokumen) ── */ +h1 { font-size: 19px; margin: 0 0 14px; } +h2.bagian { + font-size: 15px; font-weight: bold; margin: 26px 0 8px; +} +.keterangan { font-size: 13px; font-style: italic; color: var(--grey); margin: 4px 0 10px; } + +/* ── tabel gaya "Table Grid" Word ── */ +table.grid { border-collapse: collapse; width: 100%; font-size: 14px; } +table.grid th { + background: var(--blue); color: #fff; font-weight: bold; + padding: 6px 9px; border: 1px solid var(--ink); text-align: left; +} +table.grid th.sub { background: #2e75b6; } +table.grid td { border: 1px solid var(--ink); padding: 6px 9px; vertical-align: top; } +table.grid tr:nth-child(even) td { background: var(--row-alt); } +table.grid td.tengah, table.grid th.tengah { text-align: center; } +table.grid td.label { background: var(--blue-fill); font-weight: bold; width: 220px; } +table.grid tr.uts td { background: var(--green-fill); } +table.grid tr.uas td { background: var(--yellow-fill); } +table.grid tr.total td { background: var(--blue-fill); font-weight: bold; } +.gulir { overflow-x: auto; } + +/* header tabel yang bisa diurut (tabel.js) */ +table.grid th.bisa-urut { cursor: pointer; user-select: none; } +table.grid th.bisa-urut:hover { background: #2e75b6; } +table.grid th.bisa-urut[data-arah="asc"]::after { content: " ▲"; font-size: 10px; } +table.grid th.bisa-urut[data-arah="desc"]::after { content: " ▼"; font-size: 10px; } + +/* ── cara input alternatif (dilipat) ── */ +details.cara-alt { margin-top: 26px; } +details.cara-alt summary { + font-size: 15px; font-weight: bold; cursor: pointer; margin-bottom: 8px; +} +details.cara-alt summary:hover { color: var(--blue); } + +/* ── baris filter tabel ── */ +.filter-baris { + display: flex; align-items: center; gap: 10px; flex-wrap: wrap; + margin: 0 0 10px; font-size: 14px; +} + +/* ── stempel status ── */ +.stamp { + display: inline-block; font-family: "Segoe UI", Arial, sans-serif; + font-size: 10.5px; font-weight: 700; letter-spacing: .14em; + padding: 2px 9px; transform: rotate(-2deg); white-space: nowrap; +} +.stamp-ok { color: var(--blue); border: 2px solid var(--blue); } +.stamp-draft { color: var(--grey); border: 2px dashed var(--grey); } +.stamp-no { color: var(--red); border: 2px solid var(--red); } + +/* ── form ── */ +input[type="text"], input[type="number"], input[type="password"], textarea, select { + font-family: "Times New Roman", Times, serif; + font-size: 14px; color: var(--ink); + border: 1px solid #999; background: #fff; + padding: 4px 6px; width: 100%; +} +textarea { resize: vertical; } +input:focus, textarea:focus, select:focus { + outline: 2px solid var(--blue); outline-offset: 0; +} +td.isian { padding: 2px !important; background: #fff !important; } +td.isian input, td.isian textarea, td.isian select { border: none; } +td.isian input:focus, td.isian textarea:focus, td.isian select:focus { + outline: 2px solid var(--blue); outline-offset: -2px; +} + +.tombol { + font-family: "Times New Roman", Times, serif; font-size: 14px; + background: var(--blue); color: #fff; border: 1px solid var(--blue); + padding: 7px 18px; cursor: pointer; +} +.tombol.garis { background: transparent; color: var(--blue); } +.tombol.selesai { background: #205020; border-color: #205020; } +.tombol:focus-visible { outline: 2px solid var(--red); outline-offset: 2px; } +a.tombol { text-decoration: none; display: inline-block; } + +.aksi-baris { + display: flex; gap: 12px; align-items: center; flex-wrap: wrap; + margin-top: 26px; padding-top: 16px; border-top: 1px solid #c9c9c9; +} +.aksi-baris .spacer { flex: 1; } + +/* peringatan validasi */ +.peringatan { + border: 2px solid var(--red); color: var(--red); + padding: 8px 12px; font-size: 14px; margin: 12px 0; +} +.info { + border: 1px solid var(--blue); background: var(--blue-fill); + padding: 8px 12px; font-size: 14px; margin: 12px 0; +} +#total-bobot-f.salah, #total-bobot-g.salah { color: var(--red); font-weight: bold; } + +/* kelompok checkbox sub-cpmk */ +.pilih-sub { border: 1px solid #999; padding: 6px 8px; max-height: 150px; overflow-y: auto; background: #fff; } +.pilih-sub label { display: block; font-size: 13px; margin: 2px 0; } +.pilih-sub input { width: auto; margin-right: 6px; } + +/* ── dasbor monitoring (/pantau) ── */ +.grafik { width: 100%; max-width: 940px; height: auto; display: block; margin: 6px 0 10px; } +.grafik text.sumbu { + font-family: "Segoe UI", Arial, sans-serif; font-size: 11px; fill: #555; +} +.grafik text.sumbu.pudar { fill: #999; } +.grafik text.nilai-bar { + font-family: "Segoe UI", Arial, sans-serif; font-size: 11px; fill: var(--ink); + paint-order: stroke; stroke: #fff; stroke-width: 3px; stroke-linejoin: round; +} +.grafik g.bar rect[fill="transparent"] { transition: fill .1s; } +.grafik g.bar:hover rect[fill="transparent"] { fill: var(--row-alt); } + +.legenda { + display: flex; gap: 18px; flex-wrap: wrap; align-items: center; + font-family: "Segoe UI", Arial, sans-serif; font-size: 12px; color: #555; + margin: 2px 0 4px; +} +.legenda .chip { + display: inline-block; width: 12px; height: 12px; border-radius: 2px; + vertical-align: -1px; margin-right: 5px; +} +.legenda .chip-kosong { + display: inline-block; width: 12px; height: 12px; border-radius: 2px; + border: 1px dashed var(--grey); vertical-align: -1px; margin-right: 5px; +} +.legenda .chip-garis { + display: inline-block; width: 18px; border-top: 2px dashed var(--red); + vertical-align: 3px; margin-right: 5px; +} + +.proses-baris { display: flex; align-items: center; gap: 10px; margin: 6px 0; max-width: 940px; } +.proses-label { flex: 0 0 90px; font-size: 14px; } +.proses-jalur { flex: 1; display: flex; gap: 2px; height: 26px; } +.proses-jalur .seg { + display: flex; align-items: center; justify-content: center; + font-family: "Segoe UI", Arial, sans-serif; font-size: 12px; + border-radius: 2px; min-width: 20px; overflow: hidden; +} +.proses-jalur .seg-kosong { border: 1px dashed #bbb; color: var(--grey); } + +.tren-kisi { + display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 10px; margin: 8px 0 4px; +} +.tren-kartu { + border: 1px solid #ccc; padding: 8px 10px 4px; + display: flex; align-items: center; gap: 10px; + font-family: "Segoe UI", Arial, sans-serif; font-size: 12px; +} +.tren-kartu b { flex: 0 0 46px; font-size: 12px; } +.tren-svg { flex: 1; height: 46px; } +.tren-akhir { font-weight: 700; font-size: 13px; } + +.perhatian-kisi { + display: grid; grid-template-columns: 1fr 1fr; gap: 24px; align-items: start; +} +@media (max-width: 900px) { .perhatian-kisi { grid-template-columns: 1fr; } } + +.kaki { + margin-top: 28px; display: flex; justify-content: space-between; gap: 12px; + flex-wrap: wrap; font-size: 12.5px; color: var(--grey); font-style: italic; +} + +@media print { + body { background: #fff; } + .kertas { border: none; margin: 0; padding: 0; } + .aksi-baris, .nav-baris { display: none; } +} diff --git a/app/static/tabel.js b/app/static/tabel.js new file mode 100644 index 0000000..5ac4ee2 --- /dev/null +++ b/app/static/tabel.js @@ -0,0 +1,58 @@ +/* Urutkan tabel.grid dengan klik header (naik/turun). + Tabel di dalam
dilewati (baris berisi isian, urutan tak relevan); + baris .total selalu tetap di bawah. */ +(function () { + "use strict"; + + function nilaiSel(tr, i) { + var td = tr.cells[i]; + return td ? td.textContent.trim() : ""; + } + + function sebagaiAngka(t) { + // "76,2", "86 %", "18/49" → angka; "—"/kosong → null + var m = t.replace(",", ".").match(/-?\d+(\.\d+)?/); + return m ? parseFloat(m[0]) : null; + } + + function pasang(tabel) { + var thead = tabel.tHead; + var tbody = tabel.tBodies[0]; + if (!thead || !tbody || thead.rows.length !== 1) return; + + Array.prototype.forEach.call(thead.rows[0].cells, function (th, i) { + th.classList.add("bisa-urut"); + th.addEventListener("click", function () { + var naik = th.dataset.arah !== "asc"; + Array.prototype.forEach.call(thead.rows[0].cells, function (h) { + delete h.dataset.arah; + h.removeAttribute("aria-sort"); + }); + th.dataset.arah = naik ? "asc" : "desc"; + th.setAttribute("aria-sort", naik ? "ascending" : "descending"); + + var baris = Array.prototype.slice.call(tbody.rows); + var biasa = baris.filter(function (tr) { return !tr.classList.contains("total"); }); + var total = baris.filter(function (tr) { return tr.classList.contains("total"); }); + + biasa.sort(function (a, b) { + var ta = nilaiSel(a, i), tb = nilaiSel(b, i); + var na = sebagaiAngka(ta), nb = sebagaiAngka(tb); + var hasil; + if (na !== null && nb !== null) hasil = na - nb; + else if (na === null && nb === null) hasil = ta.localeCompare(tb, "id"); + else hasil = na === null ? 1 : -1; // sel kosong/— selalu di akhir + return naik ? hasil : -hasil; + }); + + biasa.concat(total).forEach(function (tr) { tbody.appendChild(tr); }); + }); + }); + } + + document.addEventListener("DOMContentLoaded", function () { + document.querySelectorAll("table.grid").forEach(function (t) { + if (!t.closest("form")) pasang(t); + }); + }); +})(); diff --git a/app/templates/admin.html b/app/templates/admin.html new file mode 100644 index 0000000..10b123e --- /dev/null +++ b/app/templates/admin.html @@ -0,0 +1,121 @@ +{% extends "base.html" %} +{% block judul %}Kelola Akun & Mahasiswa — RPS & Portofolio UNTAN{% endblock %} +{% block isi %} +

Kelola Akun & Mahasiswa

+ {% if pesan %}
{{ pesan }}
{% endif %} + +

A. Akun Pengguna & Penugasan Mata Kuliah

+

Dosen hanya dapat mengedit RPS/portofolio dan melihat nilai + mahasiswa pada MK yang ditugaskan kepadanya. Kaprodi dapat mengakses semuanya.

+
+ + + + + + + + + + + + {% for p in pengguna %} + + + + + + + + {% endfor %} + +
NamaNIPPeranMK yang DiampuTindakan
{{ p.nama }}{{ p.username }}{{ 'Kaprodi' if p.role == 'kaprodi' else 'Dosen' }} + {% if p.role == 'kaprodi' %} + semua MK + {% else %} + {{ p.mk|join(', ') if p.mk else '—' }} +
+ Ubah penugasan… + +
+ {% for mk in daftar %} + + {% endfor %} +
+ + +
+ {% endif %} +
+
+ +
+ {% if p.id != user.id %} +
+ +
+ {% endif %} +
+
+ +

B. Buat Akun Baru

+

NIP menjadi username sekaligus password awal — dosen langsung + bisa masuk dengan NIP-nya.

+
+ + + + +
+ +

C. Daftar Mahasiswa Mengontrak MK — {{ tahun }}

+

Unggah satu file Excel berisi seluruh kontrak semester ini + (kolom: NPM, Nama, Kode MK, Kelas — satu baris per kontrak). Impor baru + menggantikan seluruh daftar tahun ajaran aktif. Daftar ini dipakai untuk + mengisi otomatis template nilai tiap MK.

+
+ Unduh Template Daftar Mahasiswa (.xlsx) +
+ + +
+
+ {% if mhs %} +
+ + + + + + {% for mk in daftar if mk.kode in mhs %} + + + + + + {% endfor %} + +
Kode MKMahasiswaKelas
{{ mk.kode }} — {{ mk.nama }}{{ mhs[mk.kode].n }}{{ mhs[mk.kode].kelas|join(', ') if mhs[mk.kode].kelas else '—' }}
+
+ {% else %} +

Belum ada daftar mahasiswa untuk tahun ajaran ini.

+ {% endif %} + +

D. Cadangan Data

+

Unduh salinan seluruh isian secara berkala dan simpan di + tempat aman — terutama sebelum pemutakhiran aplikasi atau pergantian server.

+ Unduh cadangan JSON (seluruh isian) + Unduh cadangan SQLite (file DB mentah) +{% endblock %} diff --git a/app/templates/base.html b/app/templates/base.html new file mode 100644 index 0000000..cee2a58 --- /dev/null +++ b/app/templates/base.html @@ -0,0 +1,53 @@ + + + + + + {% block judul %}RPS & Portofolio — Informatika UNTAN{% endblock %} + + + + +
+
+ +
+
UNIVERSITAS TANJUNGPURA — FAKULTAS TEKNIK
+
PROGRAM STUDI INFORMATIKA
+
Sistem Penyusunan RPS & Portofolio Mata Kuliah — Kurikulum OBE · {{ tahun }}
+
+ +
+

+ + {% if user %} + {% set path = request.url.path %} + {% macro tautan(href, label) -%} + {{ label }} + {%- endmacro %} + + {% endif %} + + {% block isi %}{% endblock %} + +
+ Format keluaran: RPS APTIKOM 2024 (.docx) · Portofolio MK (.docx) + Pemetaan: 10 CPL · 34 CPMK · 49 MK · 549 Sub-CPMK +
+
+ + diff --git a/app/templates/beranda.html b/app/templates/beranda.html new file mode 100644 index 0000000..fae536d --- /dev/null +++ b/app/templates/beranda.html @@ -0,0 +1,140 @@ +{% extends "base.html" %} +{% block isi %} +
+
{{ daftar|length }} mata kuliah
+
{{ n_selesai }} RPS selesai
+
{{ n_draft }} RPS draft
+
{{ st_nilai|length }} MK setor nilai
+
{{ st_porto.values()|selectattr('status', 'equalto', 'selesai')|list|length }} portofolio selesai
+
+ + {% if user.role == 'dosen' %} + {% if hanya_saya %} +
Menampilkan {{ daftar|length }} MK yang Anda ampu. + RPS mata kuliah lain tetap terbuka baca — tampilkan seluruh {{ n_total }} MK.
+ {% elif mk_saya %} +
Menampilkan seluruh {{ n_total }} MK — + kembali ke MK yang Anda ampu saja.
+ {% else %} +
Belum ada MK yang ditugaskan kepada + Anda — hubungi kaprodi agar menetapkan penugasan di halaman Kelola Akun. + Sementara itu seluruh MK ditampilkan (hanya-baca).
+ {% endif %} + {% endif %} + +
+ + + +
+ + {% macro stempel(st) -%} + {% if st and st.status == 'selesai' %}SELESAI + {% elif st %}DRAFT + {% else %}BELUM{% endif %} + {%- endmacro %} + +
+ + + + + + + + + + + + + + + + + {% for mk in daftar %} + {% set st = status.get(mk.kode) %} + {% set sn = st_nilai.get(mk.kode) %} + {% set sp = st_porto.get(mk.kode) %} + + + + + + + + + + + + + {% endfor %} + +
No.KodeSmtMata KuliahPengampuCPL DipikulRPSNilaiPortofolioPemutakhiran Terakhir
{{ mk.no }}{{ mk.kode }}{{ smt.get(mk.kode) or '—' }}{{ mk.nama }}
+ {{ mk.n_cpmk }} CPMK · {{ mk.n_sub }} Sub-CPMK +
{{ pengampu.get(mk.kode, [])|join(', ') or '—' }}{{ mk.cpls|map('replace','CPL-','')|join(', ') }}{{ stempel(st) }}{{ stempel(sn) }}{{ stempel(sp) }} + {% set terakhir = [st, sn, sp]|select|sort(attribute='updated_at')|last %} + {% if terakhir %}{{ terakhir.updated_at }}{% if terakhir.updated_by %} — {{ terakhir.updated_by }}{% endif %}{% else %}—{% endif %} +
+
+ +

Klik stempel untuk membuka form terkait. Alur per MK: RPS (rencana & bobot) → Nilai (unggah Excel / rata-rata) → Portofolio (refleksi & dokumen akhir). Rekap prodi menggulung otomatis dari nilai yang masuk.

+{% endblock %} diff --git a/app/templates/form_nilai.html b/app/templates/form_nilai.html new file mode 100644 index 0000000..6fb1e00 --- /dev/null +++ b/app/templates/form_nilai.html @@ -0,0 +1,189 @@ +{% extends "base.html" %} +{% block judul %}Nilai {{ peta.nama }} — Informatika UNTAN{% endblock %} +{% block isi %} + + +

Input Nilai — {{ peta.nama }} ({{ peta.kode }})

+ +

Apa pun cara inputnya, pengukuran memakai satu metodologi + yang sama: skor per Sub-CPMK digulung ke CPMK lalu CPL + (rincian di halaman Metodologi). Pilih satu cara + di bawah — menyimpan lewat cara mana pun menggantikan data nilai MK ini.

+ + {% if pesan %}
{{ pesan }}
{% endif %} + {% for w in peringatan %}
{{ w }}
{% endfor %} + + {% if tersimpan %} +
+ Data tersimpan: mode + {% if tersimpan.data.mode == 'per_mahasiswa' %}PER MAHASISWA ({{ tersimpan.data.mahasiswa|length }} mahasiswa){% else %}BUKTI AGREGAT (rata-rata kelas){% endif %} + — terakhir disimpan {{ tersimpan.updated_at }}{% if tersimpan.updated_by %} oleh {{ tersimpan.updated_by }}{% endif %}. + {% if tersimpan.data.sumber %}
Riwayat unggahan: + {{ tersimpan.data.sumber if tersimpan.data.sumber is string else tersimpan.data.sumber|join(' · ') }}.{% endif %} +
+ +
+
+ {% endif %} + + {% if grid %} + {% set per_mhs = tersimpan and tersimpan.data.mode == 'per_mahasiswa' %} +

NILAI PER MAHASISWA (ketik langsung di sel, 0–100; kolom Nilai Akhir & baris rata-rata dihitung dari data tersimpan)

+

+ Daftar mahasiswa dari impor kaprodi. Kosongkan sel yang belum dinilai; + Simpan Nilai menyimpan persis isi tabel. Nilai Akhir = tertimbang bobot + komponen RPS; angkanya diperbarui setelah disimpan. +

+
+
+ {% set ada_kelas = grid|selectattr('kelas')|list|length > 0 %} + {% set n_kiri = 4 if ada_kelas else 3 %} + + + + + + + {% if ada_kelas %}{% endif %} + {% for s in peta.subs %}{% endfor %} + + + + + {% for m in grid %} + {% set i = loop.index0 %} + + + + + {% if ada_kelas %}{% endif %} + {% for s in peta.subs %} + + {% endfor %} + + + {% endfor %} + {% if per_mhs and hasil %} + + + {% for s in peta.subs %} + {% set info = hasil.sub.get(s.kode) %} + + {% endfor %} + + + + + {% for s in peta.subs %} + {% set info = hasil.sub.get(s.kode) %} + + {% endfor %} + + + {% endif %} + +
No.NPMNamaKelas{{ s.kode }}Nilai Akhir
{{ loop.index }} + + {{ m.npm }}{{ m.nama }}{{ m.kelas or '—' }}{{ '%.1f'|format(akhir.get(m.npm)) if akhir.get(m.npm) is not none else '—' }}
Rata-rata kelas (tersimpan){{ '%.1f'|format(info.rata) if info else '—' }}{{ '%.1f'|format(akhir.values()|sum / akhir|length) if akhir else '—' }}
% lulus (≥ {{ ambang|round(0)|int }}){{ '%.0f'|format(info.lulus_pct) ~ '%' if info and info.lulus_pct is not none else '—' }}
+
+
+ Disimpan atas nama {{ user.nama }} + + +
+
+ {% endif %} + +
+ {{ 'ALTERNATIF — ' if grid }}UNGGAH EXCEL PER MAHASISWA (cocok bila nilai sudah direkap di Excel, atau team teaching mengisi terpisah) +

+ Unduh template berkolom Sub-CPMK MK ini, isi nilai 0–100 per mahasiswa, lalu unggah kembali. + {% if terdaftar %}Template sudah berisi {{ terdaftar|length }} mahasiswa yang + mengontrak MK ini (dari daftar impor kaprodi). + {% else %}Daftar mahasiswa MK ini belum diimpor kaprodi — template kosong, + isi NPM & nama secara manual.{% endif %} +
Team teaching: tiap dosen cukup mengunggah bagiannya sendiri — kolom + Sub-CPMK yang diampunya (mis. sebelum/sesudah UTS) atau baris mahasiswa kelasnya. + Unggahan digabung: sel terisi memperbarui nilai, sel kosong tidak menghapus + nilai yang sudah diunggah dosen lain. +

+ +
+
+ + +
+
+
+ +
+ ALTERNATIF — KETIK RATA-RATA KELAS PER SUB-CPMK (hanya bila nilai per mahasiswa tidak tersedia; ditandai "bukti agregat" pada rekap) +
+
+ + + + {% for s in peta.subs %} + + + + + + {% endfor %} + +
Sub-CPMKUraianRata-rata (0–100)
{{ s.kode }}{{ s.uraian[:110] }}{% if s.uraian|length > 110 %}…{% endif %}
+
+
+ Disimpan atas nama {{ user.nama }} + + +
+
+
+ + {% if hasil %} +

HASIL KETERCAPAIAN MK INI (ambang {{ ambang|round(0)|int }}; roll-up bagi rata)

+
+ + + + {% for kode_c, v in hasil.cpmk.items() %} + + + + + + + {% endfor %} + +
CPMKSkorSub TernilaiCPL Didukung
{{ kode_c }}{% if v.skor is not none %}{{ '%.2f'|format(v.skor) }}{% else %}—{% endif %}{{ v.n_ternilai }}/{{ v.n_sub }}{{ v.cpl|join(', ') }}
+
+
+ + + + {% for kode_c, v in hasil.cpl.items() %} + + + + + + {% endfor %} + +
CPL (dalam MK ini)SkorStatus
{{ kode_c }}{% if v.skor is not none %}{{ '%.2f'|format(v.skor) }}{% else %}—{% endif %} + {% if v.skor is none %}BELUM TERUKUR + {% elif v.skor >= ambang %}TERCAPAI + {% else %}DI BAWAH AMBANG{% endif %} +
+
+ {% endif %} +{% endblock %} diff --git a/app/templates/form_porto.html b/app/templates/form_porto.html new file mode 100644 index 0000000..44e339e --- /dev/null +++ b/app/templates/form_porto.html @@ -0,0 +1,83 @@ +{% extends "base.html" %} +{% block judul %}Portofolio {{ peta.nama }} — Informatika UNTAN{% endblock %} +{% block isi %} + + +

Portofolio Mata Kuliah — {{ peta.nama }} ({{ peta.kode }})

+ + {% if pesan %}
{{ pesan }}
{% endif %} + + {% if not boleh_edit %} +
Anda bukan pengampu MK ini — form terbuka hanya untuk dibaca; + unduhan dokumen (memuat nilai mahasiswa) juga dibatasi untuk pengampu dan kaprodi.
+ {% endif %} + + {% if not ada_rps %} +
RPS MK ini belum disusun — bagian Rencana Penilaian, korelasi bobot, dan nilai akhir tertimbang akan kosong. Susun RPS dulu →
+ {% endif %} + {% if not ada_nilai %} +
Nilai MK ini belum diinput — bagian Penilaian, Evaluasi Ketercapaian, dan Simpulan statistik akan kosong. Input nilai dulu →
+ {% endif %} + {% if ada_rps and ada_nilai %} +
RPS ✓ dan Nilai ✓ tersedia — tabel korelasi, penilaian, evaluasi ketercapaian, dan statistik akan diisi otomatis di dokumen. Anda hanya melengkapi bagian di bawah.
+ {% endif %} + +
+

IDENTITAS DOKUMEN (LEMBAR PENGESAHAN)

+ + + + + + + + +
Nomor Dokumen
No. Revisi
Tanggal Terbit
Kurikulum Tahun
Nama Dekan
Status Dokumen + +
+ +

REALISASI PEMBELAJARAN

+

Ceritakan pelaksanaan nyata semester ini: penyesuaian jadwal/materi/metode terhadap rencana di RPS, kehadiran, dan hal penting lain.

+ + +

KENDALA & TINDAK LANJUT

+ + + + {% for k in f.kendala[:5] %} + + + + + + {% endfor %} + +
No.KendalaTindak Lanjut
{{ loop.index }}
+ +

SIMPULAN HASIL EVALUASI

+

Statistik nilai, frekuensi huruf mutu, dan tabel ketercapaian CPL/CPMK dihitung dan dicetak otomatis; tuliskan simpulan naratifnya di sini.

+ + +
+ {% if boleh_edit %} + Disimpan atas nama {{ user.nama }} + + + + {% if status %}Unduh .docx + PDF{% endif %} + {% endif %} +
+
+{% endblock %} diff --git a/app/templates/form_rps.html b/app/templates/form_rps.html new file mode 100644 index 0000000..a3d36ec --- /dev/null +++ b/app/templates/form_rps.html @@ -0,0 +1,228 @@ +{% extends "base.html" %} +{% block judul %}RPS {{ peta.nama }} — Informatika UNTAN{% endblock %} +{% block isi %} + + +

Rencana Pembelajaran Semester — {{ peta.nama }} ({{ peta.kode }})

+ + {% if pesan %}
{{ pesan }}
{% endif %} + + {% if not boleh_edit %} +
Anda bukan pengampu MK ini — form terbuka hanya untuk dibaca. + Hubungi kaprodi bila penugasan MK Anda perlu diubah.
+ {% endif %} + + {% if tahun_lain and boleh_edit %} +
+ MK ini punya RPS pada tahun ajaran lain. Salin sebagai titik awal (status draft, bisa diedit): + {% for t in tahun_lain %} +
+ + +
+ {% endfor %} +
+ {% endif %} + +
+ +

A. IDENTITAS MATA KULIAH

+ + + + + + + + + + + + + + +
Nama Mata Kuliah{{ peta.nama }}
Kode Mata Kuliah{{ peta.kode }}
Bobot SKS + SKS +  (Teori: + | Praktikum: ) +
Semester
Rumpun MK
MK Prasyarat
Dosen Pengampu
Dosen Pengembang RPS
Koordinator MK
Ketua Program Studi
Tgl Penyusunan
Kode Dokumen
+ +

B. CPL YANG DIBEBANKAN PADA MK — otomatis dari pemetaan kurikulum

+ + + + {% for c in peta.cpls %} + + {% endfor %} + +
KodeDeskripsi CPL
{{ c.kode }}{{ c.desk }}
+ +

C. CPMK — otomatis dari pemetaan kurikulum

+ + + + {% for c in peta.cpmks %} + + {% endfor %} + +
KodeDeskripsi CPMKCPL Didukung
{{ c.kode }}{{ c.desk }}{{ c.cpl|join(', ') }}
+ +

D. SUB-CPMK — otomatis dari pemetaan kurikulum ({{ peta.subs|length }} butir)

+
+ + + + {% for s in peta.subs %} + + {% endfor %} + +
KodeUraian Sub-CPMKCPMK
{{ s.kode }}{{ s.uraian }}{{ s.cpmk }}
+
+ +

E. DESKRIPSI SINGKAT MATA KULIAH

+ + +

E2. BAHAN KAJIAN — MATERI PEMBELAJARAN

+ + +

E3. MEDIA PEMBELAJARAN

+ + +

F. RENCANA MINGGUAN — total bobot: 0%

+

Minggu 8 = UTS, minggu 16 = UAS. Total bobot seluruh minggu harus 100 %.

+
+ + + + + + + + + + + + + + + + + + + + {% for m in f.minggu %} + + + + + + + + + + + + {% endfor %} + +
MgSub-CPMKPenilaianBentuk/Metode Pembelajaran [Estimasi Waktu]Kegiatan PembelajaranMateri [Pustaka]Bobot (%)
IndikatorKriteria & TeknikTatap MukaDaring
{{ m.ke }} + +
+
+ +

G. REKAP BOBOT PENILAIAN — total: 0%

+

Centang Sub-CPMK yang dinilai tiap komponen. Setiap Sub-CPMK harus tercakup minimal satu komponen agar CPMK/CPL-nya terukur.

+
+ + + + + + {% for k in f.komponen %} + {% set ki = loop.index %} + + + + + + {% endfor %} + +
KomponenSub-CPMK yang DinilaiBobot (%)
+
+ {% for s in peta.subs %} + + {% endfor %} +
+
+
+ + + +

H. PUSTAKA UTAMA

+

Satu referensi per baris, format APA.

+ + +

H2. PUSTAKA PENDUKUNG

+ + +
+ {% if boleh_edit %} + Disimpan atas nama {{ user.nama }} + + + + {% else %} + + {% endif %} + {% if status %}Unduh .docx + PDF{% endif %} +
+
+ + +{% endblock %} diff --git a/app/templates/login.html b/app/templates/login.html new file mode 100644 index 0000000..3905146 --- /dev/null +++ b/app/templates/login.html @@ -0,0 +1,39 @@ +{% extends "base.html" %} +{% block judul %}{{ 'Penyiapan Awal' if mode == 'setup' else 'Masuk' }} — RPS & Portofolio UNTAN{% endblock %} +{% block isi %} + +{% endblock %} diff --git a/app/templates/metodologi.html b/app/templates/metodologi.html new file mode 100644 index 0000000..275efde --- /dev/null +++ b/app/templates/metodologi.html @@ -0,0 +1,82 @@ +{% extends "base.html" %} +{% block judul %}Metodologi Pengukuran — Informatika UNTAN{% endblock %} +{% block isi %} + + +

Metodologi Pengukuran Ketercapaian CPL/CPMK

+

Halaman ini adalah dokumentasi resmi metode pengukuran — jawaban baku untuk asesor atas pertanyaan "angka ketercapaian ini dihitung dari mana?".

+ + {% if pesan %}
{{ pesan }}
{% endif %} + +

1. SUMBER DATA

+ + + + +
Relasi CPL–CPMK–MK–Sub-CPMKPemetaan kurikulum OBE prodi (Dokumen 9): 10 CPL · 34 CPMK · 49 MK · 549 Sub-CPMK.
Bobot penilaianRPS tiap MK: bobot komponen penilaian dan pemetaannya ke Sub-CPMK (kisi-kisi asesmen, porsi bagi rata).
NilaiSkor 0–100 per mahasiswa per Sub-CPMK (unggah Excel). Input rata-rata kelas diperbolehkan sebagai transisi dan ditandai bukti agregat.
+ +

2. RUMUS ROLL-UP (RATA-RATA SEDERHANA / BAGI RATA)

+ + + + + + + +
Skor Sub-CPMKRata-rata skor seluruh mahasiswa pada Sub-CPMK tersebut.
Skor CPMK (dalam MK)Rata-rata skor Sub-CPMK pendukungnya dalam MK tersebut.
Skor CPMK (prodi)Rata-rata skor CPMK tersebut lintas seluruh MK yang mengukurnya{% if p.metode_antar_mk == 'sks' %} — saat ini diatur tertimbang SKS{% endif %}.
Skor CPLRata-rata skor seluruh CPMK pendukung CPL tersebut.
% KelulusanPersentase mahasiswa dengan skor ≥ ambang (hanya tersedia pada data per mahasiswa).
Kelengkapan buktiJumlah titik ukur (pasangan MK×CPMK) yang sudah menyetor nilai, dibanding seluruh titik ukur pemikul.
+ +

3. PENGATURAN PRODI

+
+ + + + + + + + + + + + + + + + + +
Tahun ajaran aktif + — semua form & rekap mengikuti tahun ini; data tahun lama tetap tersimpan
Ambang ketercapaian (skor ≥ ambang = tercapai)
Metode antar-MK + +
Skala huruf mutu +
Format per baris: HURUF|MIN|MAX|ANGKA
+
+
+ {% if boleh_ubah %} + + + {% else %} + Pengaturan prodi hanya dapat diubah oleh kaprodi. + {% endif %} +
+
+ +

4. SKALA HURUF MUTU BERLAKU

+ + + + {% for s in skala %} + + + + + + {% endfor %} + +
HurufRentangAngka Mutu
{{ s.huruf }}{{ '%g'|format(s.lo) }} – {{ '%g'|format(s.hi) }}{{ '%.2f'|format(s.angka) if s.angka is not none else '—' }}
+{% endblock %} diff --git a/app/templates/pantau.html b/app/templates/pantau.html new file mode 100644 index 0000000..1bde7df --- /dev/null +++ b/app/templates/pantau.html @@ -0,0 +1,192 @@ +{% extends "base.html" %} +{% block judul %}Dasbor Monitoring — Informatika UNTAN{% endblock %} +{% block isi %} +

Dasbor Monitoring Prodi — {{ hasil.tahun }}

+ +
+
{{ hasil.n_mk_setor }}/{{ n_mk }} MK setor nilai
+
{{ cpl_urut|selectattr('tercapai')|list|length }}/{{ cpl_urut|length }} CPL tercapai
+
{{ cpmk_urut|rejectattr('skor', 'none')|list|length }}/{{ cpmk_urut|length }} CPMK terukur
+
{% if rata_cpl is not none %}{{ '%.1f'|format(rata_cpl) }}{% else %}—{% endif %} rata-rata skor CPL
+
{{ hasil.ambang|round(0)|int }} ambang ketercapaian
+
+ + {% if hasil.n_mk_setor == 0 %} +
Belum ada MK yang menyetor nilai pada {{ hasil.tahun }} — + grafik ketercapaian akan terisi begitu nilai pertama masuk. Pantau dulu + kelengkapan proses dokumen dan daftar tindak lanjut di bawah.
+ {% endif %} + + {# ── A. Ketercapaian CPL (bar vertikal, 10 CPL) ─────────────────────── #} +

KETERCAPAIAN CPL — SEMESTER BERJALAN

+

Skor gabungan tiap CPL vs ambang {{ hasil.ambang|round(0)|int }}. + Arahkan kursor ke batang untuk rincian kelengkapan bukti & penyumbang terlemah. + Tabel rinci: Rekap.

+
+ tercapai (≥ ambang) + di bawah ambang + belum terukur + garis ambang +
+
+ + + {% for g in [0, 25, 50, 75, 100] %} + + {{ g }} + {% endfor %} + + {% set lebar_slot = 706 / cpl_urut|length %} + {% for c in cpl_urut %} + {% set x_slot = 46 + loop.index0 * lebar_slot %} + {% set x_bar = x_slot + (lebar_slot - 30) / 2 %} + + {{ c.kode }} — {% if c.skor is not none %}skor {{ '%.2f'|format(c.skor) }} ({{ 'tercapai' if c.tercapai else 'di bawah ambang' }}) +bukti: {{ c.n_mk_setor }}/{{ c.n_mk_pemikul }} titik ukur{% if c.ada_agregat %} (ada bukti agregat){% endif %} +{% if c.mk_terlemah %}terlemah: {{ c.cpmk_terlemah }} — {{ nama_mk.get(c.mk_terlemah[0], c.mk_terlemah[0]) }} ({{ '%.1f'|format(c.mk_terlemah[1]) }}){% endif %}{% else %}belum terukur — belum ada MK pemikulnya yang setor nilai{% endif %} + + {% if c.skor is not none %} + + {{ '%.1f'|format(c.skor) }} + {% else %} + + {% endif %} + {{ c.kode }} + {{ c.n_mk_setor }}/{{ c.n_mk_pemikul }} bukti + + {% endfor %} + +
+ + {# ── B. Kelengkapan proses dokumen (stacked bar per tahap) ──────────── #} +

KELENGKAPAN PROSES DOKUMEN ({{ n_mk }} MK)

+

Alur per MK: RPS → Nilai → Portofolio. Batang menunjukkan berapa MK yang sudah menuntaskan tiap tahap.

+
+ selesai + draft + belum +
+ {% for p in proses %} +
+ {{ p.label }} + + {% if p.selesai %}{{ p.selesai }}{% endif %} + {% if p.draft %}{{ p.draft }}{% endif %} + {% if p.belum %}{{ p.belum }}{% endif %} + +
+ {% endfor %} + + {# ── C. Ketercapaian CPMK (bar horizontal, tampil bila ada data) ────── #} + {% set cpmk_ternilai = cpmk_urut|rejectattr('skor', 'none')|list %} + {% if cpmk_ternilai %} +

KETERCAPAIAN CPMK — SEMESTER BERJALAN

+

Hanya CPMK yang sudah terukur ({{ cpmk_ternilai|length }} dari {{ cpmk_urut|length }}). + Garis putus-putus = ambang. Arahkan kursor untuk melihat MK pengukur terlemah.

+ {% set tinggi = cpmk_ternilai|length * 22 + 34 %} +
+ + + {% for g in [0, 25, 50, 75, 100] %} + + {{ g }} + {% endfor %} + + {% for c in cpmk_ternilai %} + {% set y = loop.index0 * 22 %} + + {{ c.kode }} (mendukung {{ c.cpl|join(', ') }}) — skor {{ '%.2f'|format(c.skor) }} +setor: {{ c.n_setor }}/{{ c.n_pemikul }} MK pemikul{% if c.ada_agregat %} (ada bukti agregat){% endif %} +{% if c.mk_terlemah %}terlemah: {{ nama_mk.get(c.mk_terlemah[0], c.mk_terlemah[0]) }} ({{ '%.1f'|format(c.mk_terlemah[1]) }}){% endif %} + + {{ c.kode }} + + {{ '%.1f'|format(c.skor) }} + + {% endfor %} + +
+ {% endif %} + + {# ── D. Tren lintas semester (sparkline per CPL, bila >1 tahun) ─────── #} + {% if tren %} +

TREN SKOR CPL LINTAS TAHUN AJARAN

+

Garis kecil per CPL; titik terakhir = {{ hasil.tahun }}. Rincian angka: Riwayat Lintas Semester.

+
+ {% for kode, titik in tren.items() %} + {% set ada = titik|selectattr(1, 'ne', none)|list %} +
+ {{ kode }} + + {% set lebar_x = 130 / (titik|length - 1) if titik|length > 1 else 0 %} + {# skala vertikal mengikuti rentang data (± 2 poin) agar tren terlihat #} + {% set nilai2 = ada|map(attribute=1)|list %} + {% set lo = [nilai2|min - 2, 0]|max %} + {% set span = [nilai2|max + 2 - lo, 4]|max %} + {% if lo <= hasil.ambang <= lo + span %} + + {% endif %} + + {% for t, s in titik %}{% if s is not none %} + + {% endif %}{% endfor %} + + {% if ada %}{{ '%.1f'|format(ada[-1][1]) }}{% else %}—{% endif %} +
+ {% endfor %} +
+ {% endif %} + + {# ── E. Perlu perhatian ─────────────────────────────────────────────── #} +

PERLU PERHATIAN

+
+
+

CPL di bawah ambang & penyumbang terlemahnya — mulai perbaikan dari sini.

+ {% set cpl_rendah = cpl_urut|rejectattr('skor', 'none')|rejectattr('tercapai')|list %} + {% if cpl_rendah %} + + + + {% for c in cpl_rendah %} + + + + + + {% endfor %} + +
CPLSkorCPMK & MK Terlemah
{{ c.kode }}{{ '%.2f'|format(c.skor) }}{% if c.mk_terlemah %}{{ c.cpmk_terlemah }} — {{ nama_mk.get(c.mk_terlemah[0], c.mk_terlemah[0]) }} ({{ '%.1f'|format(c.mk_terlemah[1]) }}){% else %}—{% endif %}
+ {% else %} +
Tidak ada CPL terukur yang berada di bawah ambang. ✓
+ {% endif %} +
+
+

MK belum setor nilai ({{ belum_nilai|length }}) — hubungi pengampunya.

+ {% if belum_nilai %} +
+ + + + {% for m in belum_nilai %} + + + + + + {% endfor %} + +
KodeMata KuliahPengampu
{{ m.kode }}{{ m.nama }}{{ m.pengampu|join(', ') or '— belum ditugaskan' }}
+
+ {% else %} +
Semua MK sudah menyetor nilai. ✓
+ {% endif %} +
+
+{% endblock %} diff --git a/app/templates/rekap.html b/app/templates/rekap.html new file mode 100644 index 0000000..4078b1e --- /dev/null +++ b/app/templates/rekap.html @@ -0,0 +1,79 @@ +{% extends "base.html" %} +{% block judul %}Rekap Ketercapaian CPL/CPMK — Informatika UNTAN{% endblock %} +{% block isi %} +

Rekap Ketercapaian CPL & CPMK — {{ hasil.tahun }}

+
+
{{ hasil.n_mk_setor }} MK sudah setor nilai
+
{{ hasil.ambang|round(0)|int }} ambang ketercapaian
+
{{ 'SKS' if hasil.metode == 'sks' else 'Bagi rata' }} metode antar-MK
+
{{ cpl_urut|selectattr('tercapai')|list|length }} CPL tercapai
+
{{ cpl_urut|rejectattr('skor', 'none')|rejectattr('tercapai')|list|length }} CPL di bawah ambang
+
+ + {% if hasil.n_mk_setor == 0 %} +
Belum ada MK yang menyetor nilai — seluruh CPL berstatus "belum terukur". Mulai dari halaman Input Nilai tiap MK di beranda.
+ {% endif %} + +

KETERCAPAIAN CPL

+

Kelengkapan bukti = berapa dari seluruh titik ukur (MK × CPMK pemikul) yang sudah menyetor nilai. Skor dari bukti yang belum lengkap harus dibaca hati-hati.

+
+ + + + + + + + + + + + {% for c in cpl_urut %} + + + + + + + + + {% endfor %} + +
CPLDeskripsiSkorStatusKelengkapan BuktiPenyumbang Terlemah
{{ c.kode }}{{ c.desk[:130] }}{% if c.desk|length > 130 %}…{% endif %}{% if c.skor is not none %}{{ '%.2f'|format(c.skor) }}{% else %}—{% endif %} + {% if c.skor is none %}BELUM TERUKUR + {% elif c.tercapai %}TERCAPAI + {% else %}DI BAWAH AMBANG{% endif %} + {{ c.n_mk_setor }}/{{ c.n_mk_pemikul }} titik ukur{% if c.ada_agregat %}
ada bukti agregat{% endif %}
+ {% if c.mk_terlemah %} + {{ c.cpmk_terlemah }} — {{ nama_mk.get(c.mk_terlemah[0], c.mk_terlemah[0]) }} + ({{ '%.1f'|format(c.mk_terlemah[1]) }}) + {% else %}—{% endif %} +
+
+ +

KETERCAPAIAN CPMK (TINGKAT PRODI)

+
+ + + + + + + + + + + + {% for c in cpmk_urut %} + + + + + + + + {% endfor %} + +
CPMKCPL DidukungSkorMK Setor / PemikulMK Terlemah
{{ c.kode }}{{ c.cpl|join(', ') }}{% if c.skor is not none %}{{ '%.2f'|format(c.skor) }}{% else %}—{% endif %}{{ c.n_setor }}/{{ c.n_pemikul }}{% if c.mk_terlemah %}{{ nama_mk.get(c.mk_terlemah[0], c.mk_terlemah[0]) }} ({{ '%.1f'|format(c.mk_terlemah[1]) }}){% else %}—{% endif %}
+
+{% endblock %} diff --git a/app/templates/riwayat.html b/app/templates/riwayat.html new file mode 100644 index 0000000..ff8c87b --- /dev/null +++ b/app/templates/riwayat.html @@ -0,0 +1,42 @@ +{% extends "base.html" %} +{% block judul %}Riwayat Ketercapaian Lintas Semester — Informatika UNTAN{% endblock %} +{% block isi %} +

Riwayat Ketercapaian CPL Lintas Semester

+

Skor CPL tingkat prodi per tahun ajaran — bahan evaluasi tren untuk rapat kurikulum dan akreditasi. Sel kosong = belum ada nilai yang masuk pada periode itu.

+ +
+ + + + + {% for t in daftar_tahun %} + + {% endfor %} + + + + {% for cp in kode_cpl %} + + + {% for t in daftar_tahun %} + {% set v = per_tahun[t].cpl.get(cp) %} + + {% endfor %} + + {% endfor %} + +
CPL{{ t }}{% if t == tahun %}
(aktif){% endif %}
{{ cp }} + {% if v and v.skor is not none %} + {{ '%.1f'|format(v.skor) }} + {% if v.skor < per_tahun[t].ambang %}{% endif %} +
{{ v.n_mk_setor }}/{{ v.n_mk_pemikul }} bukti + {% else %}—{% endif %} +
+
+ +
+ {% for t in daftar_tahun %} +
{{ per_tahun[t].n_mk_setor }} MK setor nilai · {{ t }}
+ {% endfor %} +
+{% endblock %} diff --git a/contoh_Portofolio_Kalkulus.docx b/contoh_Portofolio_Kalkulus.docx new file mode 100644 index 0000000..ebe0419 Binary files /dev/null and b/contoh_Portofolio_Kalkulus.docx differ diff --git a/contoh_RPS_Kalkulus_UNTAN.docx b/contoh_RPS_Kalkulus_UNTAN.docx new file mode 100644 index 0000000..fd0f5e2 Binary files /dev/null and b/contoh_RPS_Kalkulus_UNTAN.docx differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..fb83e8b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +fastapi>=0.115 +uvicorn[standard]>=0.30 +jinja2>=3.1 +python-docx>=1.1 +python-multipart>=0.0.9 +openpyxl>=3.1 diff --git a/sampel_Portofolio_DasarPemrograman.docx b/sampel_Portofolio_DasarPemrograman.docx new file mode 100644 index 0000000..388bd90 Binary files /dev/null and b/sampel_Portofolio_DasarPemrograman.docx differ diff --git a/sampel_RPS_DasarPemrograman.docx b/sampel_RPS_DasarPemrograman.docx new file mode 100644 index 0000000..a1de8f8 Binary files /dev/null and b/sampel_RPS_DasarPemrograman.docx differ diff --git a/seed/kurikulum.json b/seed/kurikulum.json new file mode 100644 index 0000000..6cff4e1 --- /dev/null +++ b/seed/kurikulum.json @@ -0,0 +1,6112 @@ +{ + "cpl": { + "CPL-01": { + "kode": "CPL-01", + "desk": "Mampu menunjukkan sikap profesional, mandiri, jujur, bertanggung jawab, dan beretika sesuai nilai-nilai Pancasila dalam pengambilan keputusan di bidang informatika." + }, + "CPL-02": { + "kode": "CPL-02", + "desk": "Mampu menunjukkan kepedulian terhadap isu sosial, budaya, lingkungan, serta nilai lokal dan prinsip keberlanjutan dalam solusi informatika." + }, + "CPL-03": { + "kode": "CPL-03", + "desk": "Menguasai konsep teoritis dan prinsip dasar komputasi untuk menganalisis persoalan serta mengidentifikasi kebutuhan perangkat lunak." + }, + "CPL-04": { + "kode": "CPL-04", + "desk": "Menganalisis, merancang, dan mengintegrasikan keamanan, jaringan, dan IoT untuk pengembangan solusi informatika cerdas berkelanjutan." + }, + "CPL-05": { + "kode": "CPL-05", + "desk": "Mampu berpikir logis, kritis, sistematis, dan inovatif dalam mengidentifikasi dan merancang solusi komputasi." + }, + "CPL-06": { + "kode": "CPL-06", + "desk": "Mampu berkomunikasi efektif, menyampaikan gagasan dan solusi, serta membangun kolaborasi dalam tim maupun mandiri dalam proyek perangkat lunak." + }, + "CPL-07": { + "kode": "CPL-07", + "desk": "Mampu mengambil keputusan berbasis analisis data serta informasi untuk memecahkan masalah komputasi yang kompleks secara bertanggung jawab." + }, + "CPL-08": { + "kode": "CPL-08", + "desk": "Mampu merancang solusi komputasi berbasis kebutuhan pengguna serta mendokumentasikan spesifikasi kebutuhan perangkat lunak dan mendukung aspek keberlanjutan." + }, + "CPL-09": { + "kode": "CPL-09", + "desk": "Mampu membangun, mengimplementasikan, mengevaluasi perangkat lunak berbasis komputasi secara efektif, aman, dan efisien untuk kebutuhan pengguna." + }, + "CPL-10": { + "kode": "CPL-10", + "desk": "Mampu mengimplementasikan konektivitas dan pertukaran data antara perangkat sensor dan platform IoT serta menganalisis celah keamanan dan privasi pada jaringan dan aplikasi." + } + }, + "cpmk": { + "CPMK011": { + "kode": "CPMK011", + "cpl": "CPL-01", + "desk": "Mampu menerapkan nilai etika, kejujuran, dan integritas akademik secara konsisten dalam setiap aktivitas perkuliahan dan pengerjaan tugas tanpa melakukan plagiasi.", + "mk": [ + "Pendidikan Pancasila", + "Pendidikan Agama", + "Kewarganegaraan", + "Literasi Digital dan Etika Komputasi", + "Etika, Masyarakat dan Komputer Keberlanjutan", + "PMKM" + ] + }, + "CPMK012": { + "kode": "CPMK012", + "cpl": "CPL-01", + "desk": "Mampu menunjukkan kedisiplinan dan tanggung jawab dengan menyelesaikan seluruh kewajiban tugas secara mandiri dan tepat waktu.", + "mk": [ + "Pendidikan Pancasila", + "Pendidikan Agama", + "Komunikasi dan Keterampilan Profesional", + "Literasi Digital dan Etika Komputasi", + "Etika, Masyarakat dan Komputer Keberlanjutan", + "Kerja Praktik", + "Teknopreneur", + "PMKM" + ] + }, + "CPMK013": { + "kode": "CPMK013", + "cpl": "CPL-01", + "desk": "Mampu menunjukkan sikap profesional dalam berinteraksi dan bekerjasama dengan tim melalui komunikasi yang konstruktif dan pembagian peran yang adil.", + "mk": [ + "Pendidikan Pancasila", + "Bahasa Inggris", + "Komunikasi dan Keterampilan Profesional", + "Bahasa Indonesia", + "Literasi Digital dan Etika Komputasi", + "Etika, Masyarakat dan Komputer Keberlanjutan", + "Proyek Perangkat Lunak", + "Kerja Praktik", + "Teknopreneur", + "PMKM" + ] + }, + "CPMK021": { + "kode": "CPMK021", + "cpl": "CPL-02", + "desk": "Mampu menganalisis isu sosial, budaya, dan lingkungan yang relevan dengan penerapan Teknologi Informasi melalui kajian studi kasus nyata di masyarakat.", + "mk": [ + "Pendidikan Pancasila", + "Bahasa Inggris", + "Pengantar Informatika", + "Kewarganegaraan", + "Bahasa Indonesia", + "Literasi Digital dan Etika Komputasi", + "Sistem Informasi", + "Etika, Masyarakat dan Komputer Keberlanjutan", + "PMKM" + ] + }, + "CPMK022": { + "kode": "CPMK022", + "cpl": "CPL-02", + "desk": "Mampu mengevaluasi dampak sosial dan keberlanjutan dari sebuah solusi TI menggunakan kerangka analisis dampak yang sistematis, objektif dan berbasis data.", + "mk": [ + "Pendidikan Pancasila", + "Pendidikan Agama", + "Pengantar Informatika", + "Organisasi dan Arsitektur Komputer", + "Literasi Digital dan Etika Komputasi", + "Sistem Informasi", + "Etika, Masyarakat dan Komputer Keberlanjutan", + "Teknopreneur" + ] + }, + "CPMK023": { + "kode": "CPMK023", + "cpl": "CPL-02", + "desk": "Mampu merancang dan menerapkan solusi teknologi tepat guna yang kontekstual dan berkelanjutan untuk membantu menyelesaikan permasalahan nyata di komunitas sasaran.", + "mk": [ + "Etika, Masyarakat dan Komputer Keberlanjutan", + "Kerja Praktik", + "PMKM" + ] + }, + "CPMK031": { + "kode": "CPMK031", + "cpl": "CPL-03", + "desk": "Mampu menganalisis konsep dasar komputasi dan struktur data untuk menjelaskan keterkaitannya dalam penyelesaian masalah komputasi secara tepat.", + "mk": [ + "Pengantar Informatika", + "Matematika Diskrit", + "Logika Komputasional", + "Dasar Pemrograman", + "Praktikum Dasar Pemrograman", + "Organisasi dan Arsitektur Komputer", + "Sistem Operasi", + "Struktur Data", + "Praktikum Struktur Data", + "Sistem Paralel dan Terdistribusi", + "Pemrograman Berorientasi Objek *", + "Kecerdasan Buatan" + ] + }, + "CPMK032": { + "kode": "CPMK032", + "cpl": "CPL-03", + "desk": "Mampu menganalisis teori komputasi dan model matematika untuk memformulasikan solusi masalah teknis secara sistematis.", + "mk": [ + "Kalkulus", + "Matematika Diskrit", + "Logika Komputasional", + "Aljabar Linier", + "Probabilitas dan Statistik", + "Metode Numerik", + "Kecerdasan Buatan", + "Metodologi Penelitian" + ] + }, + "CPMK033": { + "kode": "CPMK033", + "cpl": "CPL-03", + "desk": "Mampu menganalisis dan merumuskan spesifikasi kebutuhan fungsional dan non-fungsional perangkat lunak secara lengkap berdasarkan studi kasus pengguna.", + "mk": [ + "Dasar Pemrograman", + "Organisasi dan Arsitektur Komputer", + "Basis Data", + "Praktikum Basis Data", + "Rekayasa Perangkat Lunak", + "Sistem Informasi", + "Analisis dan Perancangan Sistem Informasi" + ] + }, + "CPMK034": { + "kode": "CPMK034", + "cpl": "CPL-03", + "desk": "Mampu mengevaluasi dan membandingkan berbagai alternatif solusi teknis berdasarkan efisiensi, kompleksitas, dan efektivitas komputasi.", + "mk": [ + "Dasar Pemrograman", + "Praktikum Dasar Pemrograman", + "Sistem Operasi", + "Probabilitas dan Statistik", + "Basis Data", + "Praktikum Basis Data", + "Struktur Data", + "Praktikum Struktur Data", + "Sistem Paralel dan Terdistribusi", + "Metode Numerik", + "Strategi Algoritma", + "Praktikum Strategi Algoritma", + "Kecerdasan Buatan", + "Pembelajaran Mesin*", + "Metodologi Penelitian", + "Computer Vision*", + "NLP*" + ] + }, + "CPMK041": { + "kode": "CPMK041", + "cpl": "CPL-04", + "desk": "Mampu menganalisis dan menetapkan spesifikasi kebutuhan infrastruktur jaringan dan standar keamanan sistem yang diperlukan untuk mendukung kinerja aplikasi secara optimal.", + "mk": [ + "Organisasi dan Arsitektur Komputer", + "Sistem Operasi", + "Jaringan Komputer", + "Sistem Paralel dan Terdistribusi", + "Manajemen Jaringan*", + "Keamanan Informasi dan Jaringan*" + ] + }, + "CPMK042": { + "kode": "CPMK042", + "cpl": "CPL-04", + "desk": "Mampu merancang arsitektur jaringan komputer atau sistem IoT dasar yang aman, skalabel, dan sesuai dengan topologi standar.", + "mk": [ + "Jaringan Komputer", + "Praktikum Jaringan Komputer", + "Sistem Paralel dan Terdistribusi", + "Manajemen Jaringan*", + "Internet of Things*", + "Keamanan Informasi dan Jaringan*" + ] + }, + "CPMK043": { + "kode": "CPMK043", + "cpl": "CPL-04", + "desk": "Mampu mengimplementasikan konfigurasi perangkat jaringan dan konektivitas sensor IoT agar terhubung ke gateway atau internet dengan stabil.", + "mk": [ + "Praktikum Jaringan Komputer", + "Internet of Things*" + ] + }, + "CPMK044": { + "kode": "CPMK044", + "cpl": "CPL-04", + "desk": "Mampu menganalisis potensi risiko keamanan pada arsitektur sistem jaringan atau IoT dan merumuskan langkah mitigasi dasarnya.", + "mk": [ + "Sistem Operasi", + "Jaringan Komputer", + "Praktikum Jaringan Komputer", + "Sistem Paralel dan Terdistribusi", + "Manajemen Jaringan*", + "Internet of Things*", + "Keamanan Informasi dan Jaringan*" + ] + }, + "CPMK051": { + "kode": "CPMK051", + "cpl": "CPL-05", + "desk": "Mampu menganalisis dan menerapkan teknik abstraksi dan dekomposisi untuk memecahkan masalah kompleks menjadi sub-masalah yang lebih kecil dan terkelola.", + "mk": [ + "Kalkulus", + "Matematika Diskrit", + "Logika Komputasional", + "Dasar Pemrograman", + "Praktikum Dasar Pemrograman", + "Aljabar Linier", + "Probabilitas dan Statistik", + "Struktur Data", + "Praktikum Struktur Data", + "Rekayasa Perangkat Lunak", + "Pemrograman Berorientasi Objek *", + "Strategi Algoritma", + "Praktikum Strategi Algoritma", + "Kecerdasan Buatan", + "Pembelajaran Mesin*", + "Computer Vision*" + ] + }, + "CPMK052": { + "kode": "CPMK052", + "cpl": "CPL-05", + "desk": "Mampu merancang solusi algoritmik yang logis dan terstruktur menggunakan pseudocode atau flowchart untuk menyelesaikan permasalahan komputasi.", + "mk": [ + "Matematika Diskrit", + "Logika Komputasional", + "Dasar Pemrograman", + "Praktikum Dasar Pemrograman", + "Aljabar Linier", + "Probabilitas dan Statistik", + "Struktur Data", + "Praktikum Struktur Data", + "Pemrograman Berorientasi Objek *", + "Metode Numerik", + "Strategi Algoritma", + "Praktikum Strategi Algoritma", + "Pemrograman Web*", + "Kecerdasan Buatan", + "Pembelajaran Mesin*", + "Computer Vision*", + "Pemrograman Mobile*", + "NLP*" + ] + }, + "CPMK053": { + "kode": "CPMK053", + "cpl": "CPL-05", + "desk": "Mampu mengevaluasi validitas logika dan efisiensi dari berbagai alternatif algoritma sebelum diimplementasikan ke dalam kode program.", + "mk": [ + "Logika Komputasional", + "Struktur Data", + "Praktikum Struktur Data", + "Pemrograman Berorientasi Objek *", + "Metode Numerik", + "Strategi Algoritma", + "Praktikum Strategi Algoritma", + "Kecerdasan Buatan" + ] + }, + "CPMK061": { + "kode": "CPMK061", + "cpl": "CPL-06", + "desk": "Mampu menyusun dan mengevaluasi dokumen spesifikasi teknis perangkat lunak yang lengkap dan sesuai dengan standar industri.", + "mk": [ + "Komunikasi dan Keterampilan Profesional", + "Bahasa Indonesia", + "Rekayasa Perangkat Lunak", + "Analisis dan Perancangan Sistem Informasi", + "Proyek Perangkat Lunak", + "Kerja Praktik", + "PMKM" + ] + }, + "CPMK062": { + "kode": "CPMK062", + "cpl": "CPL-06", + "desk": "Mampu menyajikan dan mempertahankan gagasan teknis secara lisan dengan argumentasi yang logis dan didukung media visual yang efektif.", + "mk": [ + "Bahasa Inggris", + "Komunikasi dan Keterampilan Profesional", + "Bahasa Indonesia", + "Literasi Digital dan Etika Komputasi", + "Sistem Informasi", + "Metodologi Penelitian", + "Proyek Perangkat Lunak", + "Kerja Praktik", + "Teknopreneur", + "PMKM" + ] + }, + "CPMK063": { + "kode": "CPMK063", + "cpl": "CPL-06", + "desk": "Mampu berkolaborasi secara produktif dalam tim proyek perangkat lunak menggunakan alat manajemen proyek untuk mencapai target bersama.", + "mk": [ + "Komunikasi dan Keterampilan Profesional", + "Rekayasa Perangkat Lunak", + "Proyek Perangkat Lunak", + "Kerja Praktik", + "Teknopreneur", + "PMKM" + ] + }, + "CPMK071": { + "kode": "CPMK071", + "cpl": "CPL-07", + "desk": "Mampu menganalisis dan memvalidasi data menggunakan teknik cleansing yang tepat hingga menghasilkan dataset yang siap dianalisis.", + "mk": [ + "Probabilitas dan Statistik", + "Basis Data", + "Praktikum Basis Data", + "Metode Numerik", + "Pembelajaran Mesin*", + "Sistem Informasi Geografis*", + "Computer Vision*", + "NLP*" + ] + }, + "CPMK072": { + "kode": "CPMK072", + "cpl": "CPL-07", + "desk": "Mampu menganalisis dan mengevaluasi pola serta tren data menggunakan metode statistik atau algoritma Machine Learning dasar untuk mendapatkan informasi yang berguna.", + "mk": [ + "Aljabar Linier", + "Probabilitas dan Statistik", + "Metode Numerik", + "Kecerdasan Buatan", + "Pembelajaran Mesin*", + "Sistem Informasi Geografis*", + "Computer Vision*", + "NLP*" + ] + }, + "CPMK073": { + "kode": "CPMK073", + "cpl": "CPL-07", + "desk": "Mampu merumuskan dan mengevaluasi rekomendasi solusi yang strategis dan logis berdasarkan hasil interpretasi data yang akurat.", + "mk": [ + "Probabilitas dan Statistik", + "Basis Data", + "Praktikum Basis Data", + "Kecerdasan Buatan", + "Pembelajaran Mesin*", + "Sistem Informasi Geografis*", + "Metodologi Penelitian", + "Computer Vision*", + "NLP*", + "Kerja Praktik", + "Teknopreneur" + ] + }, + "CPMK081": { + "kode": "CPMK081", + "cpl": "CPL-08", + "desk": "Mampu menganalisis dan merumuskan kebutuhan pengguna ke dalam bentuk User Stories atau spesifikasi kebutuhan yang komprehensif dan mudah dipahami.", + "mk": [ + "Rekayasa Perangkat Lunak", + "Sistem Informasi", + "Interaksi Manusia dan Komputer", + "Analisis dan Perancangan Sistem Informasi", + "Pemrograman Web*", + "Sistem Informasi Geografis*", + "Pemrograman Mobile*", + "Proyek Perangkat Lunak", + "PMKM" + ] + }, + "CPMK082": { + "kode": "CPMK082", + "cpl": "CPL-08", + "desk": "Mampu merancang arsitektur solusi sistem yang selaras dengan spesifikasi kebutuhan pengguna yang telah disepakati.", + "mk": [ + "Rekayasa Perangkat Lunak", + "Analisis dan Perancangan Sistem Informasi", + "Pemrograman Web*", + "Sistem Informasi Geografis*", + "Pemrograman Mobile*", + "Proyek Perangkat Lunak" + ] + }, + "CPMK083": { + "kode": "CPMK083", + "cpl": "CPL-08", + "desk": "Mampu mengintegrasikan prinsip kemudahan penggunaan (usability) dan keberlanjutan sistem ke dalam rancangan antarmuka pengguna.", + "mk": [ + "Interaksi Manusia dan Komputer", + "Analisis dan Perancangan Sistem Informasi", + "Pemrograman Web*", + "Sistem Informasi Geografis*", + "Pemrograman Mobile*", + "Proyek Perangkat Lunak" + ] + }, + "CPMK084": { + "kode": "CPMK084", + "cpl": "CPL-08", + "desk": "Mampu mengembangkan dan mengevaluasi prototipe sistem interaktif melalui pengujian pengguna (user testing) untuk memastikan kesesuaian desain.", + "mk": [ + "Interaksi Manusia dan Komputer", + "Sistem Informasi Geografis*", + "Pemrograman Mobile*", + "Uji Perangkat Lunak", + "Proyek Perangkat Lunak" + ] + }, + "CPMK091": { + "kode": "CPMK091", + "cpl": "CPL-09", + "desk": "Mampu mengimplementasikan algoritma ke dalam kode program yang efisien, bebas error, dan memenuhi standar clean code.", + "mk": [ + "Dasar Pemrograman", + "Praktikum Dasar Pemrograman", + "Basis Data", + "Praktikum Basis Data", + "Struktur Data", + "Pemrograman Berorientasi Objek *", + "Strategi Algoritma", + "Pemrograman Web*", + "Kecerdasan Buatan", + "Pembelajaran Mesin*", + "Computer Vision*", + "Pemrograman Mobile*", + "NLP*", + "Proyek Perangkat Lunak", + "Kerja Praktik" + ] + }, + "CPMK092": { + "kode": "CPMK092", + "cpl": "CPL-09", + "desk": "Mampu merancang dan melaksanakan pengujian perangkat lunak (fungsional/unit) menggunakan metode yang terstandar untuk memastikan kode berjalan dengan benar.", + "mk": [ + "Basis Data", + "Praktikum Basis Data", + "Pemrograman Berorientasi Objek *", + "Pemrograman Web*", + "Pemrograman Mobile*", + "Uji Perangkat Lunak", + "Proyek Perangkat Lunak" + ] + }, + "CPMK093": { + "kode": "CPMK093", + "cpl": "CPL-09", + "desk": "Mampu mengevaluasi kinerja dan fungsionalitas perangkat lunak melalui serangkaian skenario uji coba yang sistematis untuk mendeteksi bug.", + "mk": [ + "Basis Data", + "Praktikum Basis Data", + "Pemrograman Berorientasi Objek *", + "Pemrograman Web*", + "Computer Vision*", + "Pemrograman Mobile*", + "NLP*", + "Uji Perangkat Lunak", + "Proyek Perangkat Lunak" + ] + }, + "CPMK094": { + "kode": "CPMK094", + "cpl": "CPL-09", + "desk": "Mampu menyusun dan mengevaluasi laporan teknis hasil pengujian perangkat lunak secara terstruktur sebagai acuan perbaikan sistem.", + "mk": [ + "Basis Data", + "Praktikum Basis Data", + "Uji Perangkat Lunak", + "Proyek Perangkat Lunak", + "Kerja Praktik" + ] + }, + "CPMK101": { + "kode": "CPMK101", + "cpl": "CPL-10", + "desk": "Mampu merancang dan mengimplementasikan protokol komunikasi data untuk menghubungkan sensor fisik dengan platform IoT secara real-time.", + "mk": [ + "Jaringan Komputer", + "Internet of Things*" + ] + }, + "CPMK102": { + "kode": "CPMK102", + "cpl": "CPL-10", + "desk": "Mampu menerapkan mekanisme enkripsi dan autentikasi pada perangkat IoT untuk menjamin kerahasiaan dan integritas data.", + "mk": [ + "Sistem Operasi", + "Jaringan Komputer", + "Praktikum Jaringan Komputer", + "Manajemen Jaringan*", + "Internet of Things*", + "Keamanan Informasi dan Jaringan*" + ] + }, + "CPMK103": { + "kode": "CPMK103", + "cpl": "CPL-10", + "desk": "Mampu menganalisis dampak privasi data dan potensi celah keamanan pada sistem IoT yang dikembangkan serta merumuskan solusi perbaikan.", + "mk": [ + "Internet of Things*", + "Keamanan Informasi dan Jaringan*" + ] + } + }, + "mk": { + "MKWU-01": { + "kode": "MKWU-01", + "nama": "Pendidikan Pancasila", + "no": "1", + "cpls": { + "CPL-01": [ + "CPMK011", + "CPMK012", + "CPMK013" + ], + "CPL-02": [ + "CPMK021", + "CPMK022" + ] + } + }, + "MKWU-02": { + "kode": "MKWU-02", + "nama": "Pendidikan Agama", + "no": "2", + "cpls": { + "CPL-01": [ + "CPMK011", + "CPMK012" + ], + "CPL-02": [ + "CPMK022" + ] + } + }, + "MKWU-03": { + "kode": "MKWU-03", + "nama": "Bahasa Inggris", + "no": "3", + "cpls": { + "CPL-01": [ + "CPMK013" + ], + "CPL-02": [ + "CPMK021" + ], + "CPL-06": [ + "CPMK062" + ] + } + }, + "INF-111-101": { + "kode": "INF-111-101", + "nama": "Kalkulus", + "no": "4", + "cpls": { + "CPL-03": [ + "CPMK032" + ], + "CPL-05": [ + "CPMK051" + ] + } + }, + "INF-111-201": { + "kode": "INF-111-201", + "nama": "Pengantar Informatika", + "no": "5", + "cpls": { + "CPL-02": [ + "CPMK021", + "CPMK022" + ], + "CPL-03": [ + "CPMK031" + ] + } + }, + "INF-111-102": { + "kode": "INF-111-102", + "nama": "Matematika Diskrit", + "no": "6", + "cpls": { + "CPL-03": [ + "CPMK031", + "CPMK032" + ], + "CPL-05": [ + "CPMK051", + "CPMK052" + ] + } + }, + "INF-111-202": { + "kode": "INF-111-202", + "nama": "Logika Komputasional", + "no": "7", + "cpls": { + "CPL-03": [ + "CPMK031", + "CPMK032" + ], + "CPL-05": [ + "CPMK051", + "CPMK052", + "CPMK053" + ] + } + }, + "INF-111-501": { + "kode": "INF-111-501", + "nama": "Komunikasi dan Keterampilan Profesional", + "no": "8", + "cpls": { + "CPL-01": [ + "CPMK012", + "CPMK013" + ], + "CPL-06": [ + "CPMK061", + "CPMK062", + "CPMK063" + ] + } + }, + "MKWU-04": { + "kode": "MKWU-04", + "nama": "Kewarganegaraan", + "no": "9", + "cpls": { + "CPL-01": [ + "CPMK011" + ], + "CPL-02": [ + "CPMK021" + ] + } + }, + "MKWU-05": { + "kode": "MKWU-05", + "nama": "Bahasa Indonesia", + "no": "10", + "cpls": { + "CPL-01": [ + "CPMK013" + ], + "CPL-02": [ + "CPMK021" + ], + "CPL-06": [ + "CPMK061", + "CPMK062" + ] + } + }, + "INF-211-203": { + "kode": "INF-211-203", + "nama": "Dasar Pemrograman", + "no": "11", + "cpls": { + "CPL-03": [ + "CPMK031", + "CPMK033", + "CPMK034" + ], + "CPL-05": [ + "CPMK051", + "CPMK052" + ], + "CPL-09": [ + "CPMK091" + ] + } + }, + "INF-221-204": { + "kode": "INF-221-204", + "nama": "Praktikum Dasar Pemrograman", + "no": "12", + "cpls": { + "CPL-03": [ + "CPMK031", + "CPMK034" + ], + "CPL-05": [ + "CPMK051", + "CPMK052" + ], + "CPL-09": [ + "CPMK091" + ] + } + }, + "INF-211-205": { + "kode": "INF-211-205", + "nama": "Organisasi dan Arsitektur Komputer", + "no": "13", + "cpls": { + "CPL-02": [ + "CPMK022" + ], + "CPL-03": [ + "CPMK031", + "CPMK033" + ], + "CPL-04": [ + "CPMK041" + ] + } + }, + "INF-211-206": { + "kode": "INF-211-206", + "nama": "Sistem Operasi", + "no": "14", + "cpls": { + "CPL-03": [ + "CPMK031", + "CPMK034" + ], + "CPL-04": [ + "CPMK041", + "CPMK044" + ], + "CPL-10": [ + "CPMK102" + ] + } + }, + "INF-211-103": { + "kode": "INF-211-103", + "nama": "Aljabar Linier", + "no": "15", + "cpls": { + "CPL-03": [ + "CPMK032" + ], + "CPL-05": [ + "CPMK051", + "CPMK052" + ], + "CPL-07": [ + "CPMK072" + ] + } + }, + "INF-211-502": { + "kode": "INF-211-502", + "nama": "Literasi Digital dan Etika Komputasi", + "no": "16", + "cpls": { + "CPL-01": [ + "CPMK011", + "CPMK012", + "CPMK013" + ], + "CPL-02": [ + "CPMK021", + "CPMK022" + ], + "CPL-06": [ + "CPMK062" + ] + } + }, + "INF-311-104": { + "kode": "INF-311-104", + "nama": "Probabilitas dan Statistik", + "no": "17", + "cpls": { + "CPL-03": [ + "CPMK032", + "CPMK034" + ], + "CPL-05": [ + "CPMK051", + "CPMK052" + ], + "CPL-07": [ + "CPMK071", + "CPMK072", + "CPMK073" + ] + } + }, + "INF-311-207": { + "kode": "INF-311-207", + "nama": "Jaringan Komputer", + "no": "18", + "cpls": { + "CPL-04": [ + "CPMK041", + "CPMK042", + "CPMK044" + ], + "CPL-10": [ + "CPMK101", + "CPMK102" + ] + } + }, + "INF-321-208": { + "kode": "INF-321-208", + "nama": "Praktikum Jaringan Komputer", + "no": "19", + "cpls": { + "CPL-04": [ + "CPMK042", + "CPMK043", + "CPMK044" + ], + "CPL-10": [ + "CPMK102" + ] + } + }, + "INF-311-209": { + "kode": "INF-311-209", + "nama": "Basis Data", + "no": "20", + "cpls": { + "CPL-03": [ + "CPMK033", + "CPMK034" + ], + "CPL-07": [ + "CPMK071", + "CPMK073" + ], + "CPL-09": [ + "CPMK091", + "CPMK092", + "CPMK093", + "CPMK094" + ] + } + }, + "INF-321-210": { + "kode": "INF-321-210", + "nama": "Praktikum Basis Data", + "no": "21", + "cpls": { + "CPL-03": [ + "CPMK033", + "CPMK034" + ], + "CPL-07": [ + "CPMK071", + "CPMK073" + ], + "CPL-09": [ + "CPMK091", + "CPMK092", + "CPMK093", + "CPMK094" + ] + } + }, + "INF-311-211": { + "kode": "INF-311-211", + "nama": "Struktur Data", + "no": "22", + "cpls": { + "CPL-03": [ + "CPMK031", + "CPMK034" + ], + "CPL-05": [ + "CPMK051", + "CPMK052", + "CPMK053" + ], + "CPL-09": [ + "CPMK091" + ] + } + }, + "INF-321-212": { + "kode": "INF-321-212", + "nama": "Praktikum Struktur Data", + "no": "23", + "cpls": { + "CPL-03": [ + "CPMK031", + "CPMK034" + ], + "CPL-05": [ + "CPMK051", + "CPMK052", + "CPMK053" + ] + } + }, + "INF-311-301": { + "kode": "INF-311-301", + "nama": "Rekayasa Perangkat Lunak", + "no": "24", + "cpls": { + "CPL-03": [ + "CPMK033" + ], + "CPL-05": [ + "CPMK051" + ], + "CPL-06": [ + "CPMK061", + "CPMK063" + ], + "CPL-08": [ + "CPMK081", + "CPMK082" + ] + } + }, + "INF-311-302": { + "kode": "INF-311-302", + "nama": "Sistem Informasi", + "no": "25", + "cpls": { + "CPL-02": [ + "CPMK021", + "CPMK022" + ], + "CPL-03": [ + "CPMK033" + ], + "CPL-06": [ + "CPMK062" + ], + "CPL-08": [ + "CPMK081" + ] + } + }, + "INF-411-303": { + "kode": "INF-411-303", + "nama": "Interaksi Manusia dan Komputer", + "no": "26", + "cpls": { + "CPL-08": [ + "CPMK081", + "CPMK083", + "CPMK084" + ] + } + }, + "INF-411-304": { + "kode": "INF-411-304", + "nama": "Analisis dan Perancangan Sistem Informasi", + "no": "27", + "cpls": { + "CPL-03": [ + "CPMK033" + ], + "CPL-06": [ + "CPMK061" + ], + "CPL-08": [ + "CPMK081", + "CPMK082", + "CPMK083" + ] + } + }, + "INF-411-305": { + "kode": "INF-411-305", + "nama": "Sistem Paralel dan Terdistribusi", + "no": "28", + "cpls": { + "CPL-03": [ + "CPMK031", + "CPMK034" + ], + "CPL-04": [ + "CPMK041", + "CPMK042", + "CPMK044" + ] + } + }, + "INF-411-213": { + "kode": "INF-411-213", + "nama": "Pemrograman Berorientasi Objek *", + "no": "29", + "cpls": { + "CPL-03": [ + "CPMK031" + ], + "CPL-05": [ + "CPMK051", + "CPMK052", + "CPMK053" + ], + "CPL-09": [ + "CPMK091", + "CPMK092", + "CPMK093" + ] + } + }, + "INF-411-105": { + "kode": "INF-411-105", + "nama": "Metode Numerik", + "no": "30", + "cpls": { + "CPL-03": [ + "CPMK032", + "CPMK034" + ], + "CPL-05": [ + "CPMK052", + "CPMK053" + ], + "CPL-07": [ + "CPMK071", + "CPMK072" + ] + } + }, + "INF-411-214": { + "kode": "INF-411-214", + "nama": "Strategi Algoritma", + "no": "31", + "cpls": { + "CPL-03": [ + "CPMK034" + ], + "CPL-05": [ + "CPMK051", + "CPMK052", + "CPMK053" + ], + "CPL-09": [ + "CPMK091" + ] + } + }, + "INF-421-215": { + "kode": "INF-421-215", + "nama": "Praktikum Strategi Algoritma", + "no": "32", + "cpls": { + "CPL-03": [ + "CPMK034" + ], + "CPL-05": [ + "CPMK051", + "CPMK052", + "CPMK053" + ] + } + }, + "INF-411-306": { + "kode": "INF-411-306", + "nama": "Manajemen Jaringan*", + "no": "33", + "cpls": { + "CPL-04": [ + "CPMK041", + "CPMK042", + "CPMK044" + ], + "CPL-10": [ + "CPMK102" + ] + } + }, + "INF-411-503": { + "kode": "INF-411-503", + "nama": "Etika, Masyarakat dan Komputer Keberlanjutan", + "no": "34", + "cpls": { + "CPL-01": [ + "CPMK011", + "CPMK012", + "CPMK013" + ], + "CPL-02": [ + "CPMK021", + "CPMK022", + "CPMK023" + ] + } + }, + "INF-511-307": { + "kode": "INF-511-307", + "nama": "Pemrograman Web*", + "no": "35", + "cpls": { + "CPL-05": [ + "CPMK052" + ], + "CPL-08": [ + "CPMK081", + "CPMK082", + "CPMK083" + ], + "CPL-09": [ + "CPMK091", + "CPMK092", + "CPMK093" + ] + } + }, + "INF-511-401": { + "kode": "INF-511-401", + "nama": "Kecerdasan Buatan", + "no": "36", + "cpls": { + "CPL-03": [ + "CPMK031", + "CPMK032", + "CPMK034" + ], + "CPL-05": [ + "CPMK051", + "CPMK052", + "CPMK053" + ], + "CPL-07": [ + "CPMK072", + "CPMK073" + ], + "CPL-09": [ + "CPMK091" + ] + } + }, + "INF-511-402": { + "kode": "INF-511-402", + "nama": "Internet of Things*", + "no": "37", + "cpls": { + "CPL-04": [ + "CPMK042", + "CPMK043", + "CPMK044" + ], + "CPL-10": [ + "CPMK101", + "CPMK102", + "CPMK103" + ] + } + }, + "INF-511-403": { + "kode": "INF-511-403", + "nama": "Keamanan Informasi dan Jaringan*", + "no": "38", + "cpls": { + "CPL-04": [ + "CPMK041", + "CPMK042", + "CPMK044" + ], + "CPL-10": [ + "CPMK102", + "CPMK103" + ] + } + }, + "INF-511-404": { + "kode": "INF-511-404", + "nama": "Pembelajaran Mesin*", + "no": "39", + "cpls": { + "CPL-03": [ + "CPMK034" + ], + "CPL-05": [ + "CPMK051", + "CPMK052" + ], + "CPL-07": [ + "CPMK071", + "CPMK072", + "CPMK073" + ], + "CPL-09": [ + "CPMK091" + ] + } + }, + "INF-511-405": { + "kode": "INF-511-405", + "nama": "Sistem Informasi Geografis*", + "no": "40", + "cpls": { + "CPL-07": [ + "CPMK071", + "CPMK072", + "CPMK073" + ], + "CPL-08": [ + "CPMK081", + "CPMK082", + "CPMK083", + "CPMK084" + ] + } + }, + "INF-511-801": { + "kode": "INF-511-801", + "nama": "Metodologi Penelitian", + "no": "41", + "cpls": { + "CPL-03": [ + "CPMK032", + "CPMK034" + ], + "CPL-06": [ + "CPMK062" + ], + "CPL-07": [ + "CPMK073" + ] + } + }, + "INF-611-406": { + "kode": "INF-611-406", + "nama": "Computer Vision*", + "no": "42", + "cpls": { + "CPL-03": [ + "CPMK034" + ], + "CPL-05": [ + "CPMK051", + "CPMK052" + ], + "CPL-07": [ + "CPMK071", + "CPMK072", + "CPMK073" + ], + "CPL-09": [ + "CPMK091", + "CPMK093" + ] + } + }, + "INF-611-407": { + "kode": "INF-611-407", + "nama": "Pemrograman Mobile*", + "no": "43", + "cpls": { + "CPL-05": [ + "CPMK052" + ], + "CPL-08": [ + "CPMK081", + "CPMK082", + "CPMK083", + "CPMK084" + ], + "CPL-09": [ + "CPMK091", + "CPMK092", + "CPMK093" + ] + } + }, + "INF-611-408": { + "kode": "INF-611-408", + "nama": "NLP*", + "no": "44", + "cpls": { + "CPL-03": [ + "CPMK034" + ], + "CPL-05": [ + "CPMK052" + ], + "CPL-07": [ + "CPMK071", + "CPMK072", + "CPMK073" + ], + "CPL-09": [ + "CPMK091", + "CPMK093" + ] + } + }, + "INF-611-409": { + "kode": "INF-611-409", + "nama": "Uji Perangkat Lunak", + "no": "45", + "cpls": { + "CPL-08": [ + "CPMK084" + ], + "CPL-09": [ + "CPMK092", + "CPMK093", + "CPMK094" + ] + } + }, + "INF-611-601": { + "kode": "INF-611-601", + "nama": "Proyek Perangkat Lunak", + "no": "46", + "cpls": { + "CPL-01": [ + "CPMK013" + ], + "CPL-06": [ + "CPMK061", + "CPMK062", + "CPMK063" + ], + "CPL-08": [ + "CPMK081", + "CPMK082", + "CPMK083", + "CPMK084" + ], + "CPL-09": [ + "CPMK091", + "CPMK092", + "CPMK093", + "CPMK094" + ] + } + }, + "INF-611-802": { + "kode": "INF-611-802", + "nama": "Kerja Praktik", + "no": "47", + "cpls": { + "CPL-01": [ + "CPMK012", + "CPMK013" + ], + "CPL-02": [ + "CPMK023" + ], + "CPL-06": [ + "CPMK061", + "CPMK062", + "CPMK063" + ], + "CPL-07": [ + "CPMK073" + ], + "CPL-09": [ + "CPMK091", + "CPMK094" + ] + } + }, + "INF-711-504": { + "kode": "INF-711-504", + "nama": "Teknopreneur", + "no": "48", + "cpls": { + "CPL-01": [ + "CPMK012", + "CPMK013" + ], + "CPL-02": [ + "CPMK022" + ], + "CPL-06": [ + "CPMK062", + "CPMK063" + ], + "CPL-07": [ + "CPMK073" + ] + } + }, + "INF-711-505": { + "kode": "INF-711-505", + "nama": "PMKM", + "no": "49", + "cpls": { + "CPL-01": [ + "CPMK011", + "CPMK012", + "CPMK013" + ], + "CPL-02": [ + "CPMK021", + "CPMK023" + ], + "CPL-06": [ + "CPMK061", + "CPMK062", + "CPMK063" + ], + "CPL-08": [ + "CPMK081" + ] + } + } + }, + "matrix": [ + { + "kode": "MKWU-01", + "nama": "Pendidikan Pancasila", + "cpls": { + "CPL-01": [ + "CPMK011", + "CPMK012", + "CPMK013" + ], + "CPL-02": [ + "CPMK021", + "CPMK022" + ] + } + }, + { + "kode": "MKWU-02", + "nama": "Pendidikan Agama", + "cpls": { + "CPL-01": [ + "CPMK011", + "CPMK012" + ], + "CPL-02": [ + "CPMK022" + ] + } + }, + { + "kode": "MKWU-03", + "nama": "Bahasa Inggris", + "cpls": { + "CPL-01": [ + "CPMK013" + ], + "CPL-02": [ + "CPMK021" + ], + "CPL-06": [ + "CPMK062" + ] + } + }, + { + "kode": "INF-111-101", + "nama": "Kalkulus", + "cpls": { + "CPL-03": [ + "CPMK032" + ], + "CPL-05": [ + "CPMK051" + ] + } + }, + { + "kode": "INF-111-201", + "nama": "Pengantar Informatika", + "cpls": { + "CPL-02": [ + "CPMK021", + "CPMK022" + ], + "CPL-03": [ + "CPMK031" + ] + } + }, + { + "kode": "INF-111-102", + "nama": "Matematika Diskrit", + "cpls": { + "CPL-03": [ + "CPMK031", + "CPMK032" + ], + "CPL-05": [ + "CPMK051", + "CPMK052" + ] + } + }, + { + "kode": "INF-111-202", + "nama": "Logika Komputasional", + "cpls": { + "CPL-03": [ + "CPMK031", + "CPMK032" + ], + "CPL-05": [ + "CPMK051", + "CPMK052", + "CPMK053" + ] + } + }, + { + "kode": "INF-111-501", + "nama": "Komunikasi dan Keterampilan Profesional", + "cpls": { + "CPL-01": [ + "CPMK012", + "CPMK013" + ], + "CPL-06": [ + "CPMK061", + "CPMK062", + "CPMK063" + ] + } + }, + { + "kode": "MKWU-04", + "nama": "Kewarganegaraan", + "cpls": { + "CPL-01": [ + "CPMK011" + ], + "CPL-02": [ + "CPMK021" + ] + } + }, + { + "kode": "MKWU-05", + "nama": "Bahasa Indonesia", + "cpls": { + "CPL-01": [ + "CPMK013" + ], + "CPL-02": [ + "CPMK021" + ], + "CPL-06": [ + "CPMK061", + "CPMK062" + ] + } + }, + { + "kode": "INF-211-203", + "nama": "Dasar Pemrograman", + "cpls": { + "CPL-03": [ + "CPMK031", + "CPMK033", + "CPMK034" + ], + "CPL-05": [ + "CPMK051", + "CPMK052" + ], + "CPL-09": [ + "CPMK091" + ] + } + }, + { + "kode": "INF-221-204", + "nama": "Praktikum Dasar Pemrograman", + "cpls": { + "CPL-03": [ + "CPMK031", + "CPMK034" + ], + "CPL-05": [ + "CPMK051", + "CPMK052" + ], + "CPL-09": [ + "CPMK091" + ] + } + }, + { + "kode": "INF-211-205", + "nama": "Organisasi dan Arsitektur Komputer", + "cpls": { + "CPL-02": [ + "CPMK022" + ], + "CPL-03": [ + "CPMK031", + "CPMK033" + ], + "CPL-04": [ + "CPMK041" + ] + } + }, + { + "kode": "INF-211-206", + "nama": "Sistem Operasi", + "cpls": { + "CPL-03": [ + "CPMK031", + "CPMK034" + ], + "CPL-04": [ + "CPMK041", + "CPMK044" + ], + "CPL-10": [ + "CPMK102" + ] + } + }, + { + "kode": "INF-211-103", + "nama": "Aljabar Linier", + "cpls": { + "CPL-03": [ + "CPMK032" + ], + "CPL-05": [ + "CPMK051", + "CPMK052" + ], + "CPL-07": [ + "CPMK072" + ] + } + }, + { + "kode": "INF-211-502", + "nama": "Literasi Digital dan Etika Komputasi", + "cpls": { + "CPL-01": [ + "CPMK011", + "CPMK012", + "CPMK013" + ], + "CPL-02": [ + "CPMK021", + "CPMK022" + ], + "CPL-06": [ + "CPMK062" + ] + } + }, + { + "kode": "INF-311-104", + "nama": "Probabilitas dan Statistik", + "cpls": { + "CPL-03": [ + "CPMK032", + "CPMK034" + ], + "CPL-05": [ + "CPMK051", + "CPMK052" + ], + "CPL-07": [ + "CPMK071", + "CPMK072", + "CPMK073" + ] + } + }, + { + "kode": "INF-311-207", + "nama": "Jaringan Komputer", + "cpls": { + "CPL-04": [ + "CPMK041", + "CPMK042", + "CPMK044" + ], + "CPL-10": [ + "CPMK101", + "CPMK102" + ] + } + }, + { + "kode": "INF-321-208", + "nama": "Praktikum Jaringan Komputer", + "cpls": { + "CPL-04": [ + "CPMK042", + "CPMK043", + "CPMK044" + ], + "CPL-10": [ + "CPMK102" + ] + } + }, + { + "kode": "INF-311-209", + "nama": "Basis Data", + "cpls": { + "CPL-03": [ + "CPMK033", + "CPMK034" + ], + "CPL-07": [ + "CPMK071", + "CPMK073" + ], + "CPL-09": [ + "CPMK091", + "CPMK092", + "CPMK093", + "CPMK094" + ] + } + }, + { + "kode": "INF-321-210", + "nama": "Praktikum Basis Data", + "cpls": { + "CPL-03": [ + "CPMK033", + "CPMK034" + ], + "CPL-07": [ + "CPMK071", + "CPMK073" + ], + "CPL-09": [ + "CPMK091", + "CPMK092", + "CPMK093", + "CPMK094" + ] + } + }, + { + "kode": "INF-311-211", + "nama": "Struktur Data", + "cpls": { + "CPL-03": [ + "CPMK031", + "CPMK034" + ], + "CPL-05": [ + "CPMK051", + "CPMK052", + "CPMK053" + ], + "CPL-09": [ + "CPMK091" + ] + } + }, + { + "kode": "INF-321-212", + "nama": "Praktikum Struktur Data", + "cpls": { + "CPL-03": [ + "CPMK031", + "CPMK034" + ], + "CPL-05": [ + "CPMK051", + "CPMK052", + "CPMK053" + ] + } + }, + { + "kode": "INF-311-301", + "nama": "Rekayasa Perangkat Lunak", + "cpls": { + "CPL-03": [ + "CPMK033" + ], + "CPL-05": [ + "CPMK051" + ], + "CPL-06": [ + "CPMK061", + "CPMK063" + ], + "CPL-08": [ + "CPMK081", + "CPMK082" + ] + } + }, + { + "kode": "INF-311-302", + "nama": "Sistem Informasi", + "cpls": { + "CPL-02": [ + "CPMK021", + "CPMK022" + ], + "CPL-03": [ + "CPMK033" + ], + "CPL-06": [ + "CPMK062" + ], + "CPL-08": [ + "CPMK081" + ] + } + }, + { + "kode": "INF-411-303", + "nama": "Interaksi Manusia dan Komputer", + "cpls": { + "CPL-08": [ + "CPMK081", + "CPMK083", + "CPMK084" + ] + } + }, + { + "kode": "INF-411-304", + "nama": "Analisis dan Perancangan Sistem Informasi", + "cpls": { + "CPL-03": [ + "CPMK033" + ], + "CPL-06": [ + "CPMK061" + ], + "CPL-08": [ + "CPMK081", + "CPMK082", + "CPMK083" + ] + } + }, + { + "kode": "INF-411-305", + "nama": "Sistem Paralel dan Terdistribusi", + "cpls": { + "CPL-03": [ + "CPMK031", + "CPMK034" + ], + "CPL-04": [ + "CPMK041", + "CPMK042", + "CPMK044" + ] + } + }, + { + "kode": "INF-411-213", + "nama": "Pemrograman Berorientasi Objek *", + "cpls": { + "CPL-03": [ + "CPMK031" + ], + "CPL-05": [ + "CPMK051", + "CPMK052", + "CPMK053" + ], + "CPL-09": [ + "CPMK091", + "CPMK092", + "CPMK093" + ] + } + }, + { + "kode": "INF-411-105", + "nama": "Metode Numerik", + "cpls": { + "CPL-03": [ + "CPMK032", + "CPMK034" + ], + "CPL-05": [ + "CPMK052", + "CPMK053" + ], + "CPL-07": [ + "CPMK071", + "CPMK072" + ] + } + }, + { + "kode": "INF-411-214", + "nama": "Strategi Algoritma", + "cpls": { + "CPL-03": [ + "CPMK034" + ], + "CPL-05": [ + "CPMK051", + "CPMK052", + "CPMK053" + ], + "CPL-09": [ + "CPMK091" + ] + } + }, + { + "kode": "INF-421-215", + "nama": "Praktikum Strategi Algoritma", + "cpls": { + "CPL-03": [ + "CPMK034" + ], + "CPL-05": [ + "CPMK051", + "CPMK052", + "CPMK053" + ] + } + }, + { + "kode": "INF-411-306", + "nama": "Manajemen Jaringan*", + "cpls": { + "CPL-04": [ + "CPMK041", + "CPMK042", + "CPMK044" + ], + "CPL-10": [ + "CPMK102" + ] + } + }, + { + "kode": "INF-411-503", + "nama": "Etika, Masyarakat dan Komputer Keberlanjutan", + "cpls": { + "CPL-01": [ + "CPMK011", + "CPMK012", + "CPMK013" + ], + "CPL-02": [ + "CPMK021", + "CPMK022", + "CPMK023" + ] + } + }, + { + "kode": "INF-511-307", + "nama": "Pemrograman Web*", + "cpls": { + "CPL-05": [ + "CPMK052" + ], + "CPL-08": [ + "CPMK081", + "CPMK082", + "CPMK083" + ], + "CPL-09": [ + "CPMK091", + "CPMK092", + "CPMK093" + ] + } + }, + { + "kode": "INF-511-401", + "nama": "Kecerdasan Buatan", + "cpls": { + "CPL-03": [ + "CPMK031", + "CPMK032", + "CPMK034" + ], + "CPL-05": [ + "CPMK051", + "CPMK052", + "CPMK053" + ], + "CPL-07": [ + "CPMK072", + "CPMK073" + ], + "CPL-09": [ + "CPMK091" + ] + } + }, + { + "kode": "INF-511-402", + "nama": "Internet of Things*", + "cpls": { + "CPL-04": [ + "CPMK042", + "CPMK043", + "CPMK044" + ], + "CPL-10": [ + "CPMK101", + "CPMK102", + "CPMK103" + ] + } + }, + { + "kode": "INF-511-403", + "nama": "Keamanan Informasi dan Jaringan*", + "cpls": { + "CPL-04": [ + "CPMK041", + "CPMK042", + "CPMK044" + ], + "CPL-10": [ + "CPMK102", + "CPMK103" + ] + } + }, + { + "kode": "INF-511-404", + "nama": "Pembelajaran Mesin*", + "cpls": { + "CPL-03": [ + "CPMK034" + ], + "CPL-05": [ + "CPMK051", + "CPMK052" + ], + "CPL-07": [ + "CPMK071", + "CPMK072", + "CPMK073" + ], + "CPL-09": [ + "CPMK091" + ] + } + }, + { + "kode": "INF-511-405", + "nama": "Sistem Informasi Geografis*", + "cpls": { + "CPL-07": [ + "CPMK071", + "CPMK072", + "CPMK073" + ], + "CPL-08": [ + "CPMK081", + "CPMK082", + "CPMK083", + "CPMK084" + ] + } + }, + { + "kode": "INF-511-801", + "nama": "Metodologi Penelitian", + "cpls": { + "CPL-03": [ + "CPMK032", + "CPMK034" + ], + "CPL-06": [ + "CPMK062" + ], + "CPL-07": [ + "CPMK073" + ] + } + }, + { + "kode": "INF-611-406", + "nama": "Computer Vision*", + "cpls": { + "CPL-03": [ + "CPMK034" + ], + "CPL-05": [ + "CPMK051", + "CPMK052" + ], + "CPL-07": [ + "CPMK071", + "CPMK072", + "CPMK073" + ], + "CPL-09": [ + "CPMK091", + "CPMK093" + ] + } + }, + { + "kode": "INF-611-407", + "nama": "Pemrograman Mobile*", + "cpls": { + "CPL-05": [ + "CPMK052" + ], + "CPL-08": [ + "CPMK081", + "CPMK082", + "CPMK083", + "CPMK084" + ], + "CPL-09": [ + "CPMK091", + "CPMK092", + "CPMK093" + ] + } + }, + { + "kode": "INF-611-408", + "nama": "NLP*", + "cpls": { + "CPL-03": [ + "CPMK034" + ], + "CPL-05": [ + "CPMK052" + ], + "CPL-07": [ + "CPMK071", + "CPMK072", + "CPMK073" + ], + "CPL-09": [ + "CPMK091", + "CPMK093" + ] + } + }, + { + "kode": "INF-611-409", + "nama": "Uji Perangkat Lunak", + "cpls": { + "CPL-08": [ + "CPMK084" + ], + "CPL-09": [ + "CPMK092", + "CPMK093", + "CPMK094" + ] + } + }, + { + "kode": "INF-611-601", + "nama": "Proyek Perangkat Lunak", + "cpls": { + "CPL-01": [ + "CPMK013" + ], + "CPL-06": [ + "CPMK061", + "CPMK062", + "CPMK063" + ], + "CPL-08": [ + "CPMK081", + "CPMK082", + "CPMK083", + "CPMK084" + ], + "CPL-09": [ + "CPMK091", + "CPMK092", + "CPMK093", + "CPMK094" + ] + } + }, + { + "kode": "INF-611-802", + "nama": "Kerja Praktik", + "cpls": { + "CPL-01": [ + "CPMK012", + "CPMK013" + ], + "CPL-02": [ + "CPMK023" + ], + "CPL-06": [ + "CPMK061", + "CPMK062", + "CPMK063" + ], + "CPL-07": [ + "CPMK073" + ], + "CPL-09": [ + "CPMK091", + "CPMK094" + ] + } + }, + { + "kode": "INF-711-504", + "nama": "Teknopreneur", + "cpls": { + "CPL-01": [ + "CPMK012", + "CPMK013" + ], + "CPL-02": [ + "CPMK022" + ], + "CPL-06": [ + "CPMK062", + "CPMK063" + ], + "CPL-07": [ + "CPMK073" + ] + } + }, + { + "kode": "INF-711-505", + "nama": "PMKM", + "cpls": { + "CPL-01": [ + "CPMK011", + "CPMK012", + "CPMK013" + ], + "CPL-02": [ + "CPMK021", + "CPMK023" + ], + "CPL-06": [ + "CPMK061", + "CPMK062", + "CPMK063" + ], + "CPL-08": [ + "CPMK081" + ] + } + } + ], + "cpl_cols": [ + "CPL-01", + "CPL-02", + "CPL-03", + "CPL-04", + "CPL-05", + "CPL-06", + "CPL-07", + "CPL-08", + "CPL-09", + "CPL-10" + ], + "sub": { + "MKWU-01": { + "nama": "Pendidikan Pancasila", + "cpmks": { + "CPMK011": { + "desk": "Mampu menerapkan nilai etika, kejujuran, dan integritas akademik secara konsisten dalam setiap aktivitas perkuliahan dan pengerjaan tugas tanpa melakukan plagiasi.", + "subs": [ + { + "kode": "Sub-011-1", + "uraian": "Mampu mengidentifikasi bentuk-bentuk pelanggaran integritas akademik dan sanksinya berdasarkan peraturan perguruan tinggi." + }, + { + "kode": "Sub-011-2", + "uraian": "Mampu menerapkan prinsip kejujuran akademik dengan menyerahkan karya orisinal bebas plagiasi pada setiap penugasan." + } + ] + }, + "CPMK012": { + "desk": "Mampu menunjukkan kedisiplinan dan tanggung jawab dengan menyelesaikan seluruh kewajiban tugas secara mandiri dan tepat waktu.", + "subs": [ + { + "kode": "Sub-012-1", + "uraian": "Mampu menunjukkan kehadiran dan ketepatan waktu pengumpulan tugas sebagai indikator kedisiplinan pribadi." + }, + { + "kode": "Sub-012-2", + "uraian": "Mampu menyelesaikan seluruh komponen tugas individu secara mandiri tanpa melibatkan pihak lain secara tidak sah." + } + ] + }, + "CPMK013": { + "desk": "Mampu menunjukkan sikap profesional dalam berinteraksi dan bekerjasama dengan tim melalui komunikasi yang konstruktif dan pembagian peran yang adil.", + "subs": [ + { + "kode": "Sub-013-1", + "uraian": "Mampu berkomunikasi secara konstruktif dalam diskusi kelompok dengan menghargai pendapat anggota tim lain." + }, + { + "kode": "Sub-013-2", + "uraian": "Mampu membagi peran dan tanggung jawab secara adil dalam tugas kelompok berbasis nilai-nilai Pancasila." + } + ] + }, + "CPMK021": { + "desk": "Mampu menganalisis isu sosial, budaya, dan lingkungan yang relevan dengan penerapan TI melalui kajian studi kasus nyata di masyarakat.", + "subs": [ + { + "kode": "Sub-021-1", + "uraian": "Mampu mengidentifikasi isu-isu sosial dan teknologi yang berkaitan dengan nilai-nilai Pancasila dari sumber berita aktual." + }, + { + "kode": "Sub-021-2", + "uraian": "Mampu menganalisis dampak penerapan teknologi informasi terhadap kehidupan bermasyarakat dan bernegara." + } + ] + }, + "CPMK022": { + "desk": "Mampu mengevaluasi dampak sosial dan keberlanjutan dari sebuah solusi TI menggunakan kerangka analisis dampak yang sistematis, objektif dan berbasis data.", + "subs": [ + { + "kode": "Sub-022-1", + "uraian": "Mampu menjelaskan prinsip keberlanjutan dalam penggunaan teknologi menggunakan kerangka nilai Pancasila." + }, + { + "kode": "Sub-022-2", + "uraian": "Mampu mengevaluasi solusi teknologi dari perspektif dampak sosial dan keadilan berdasarkan sila-sila Pancasila." + } + ] + } + } + }, + "MKWU-02": { + "nama": "Pendidikan Agama", + "cpmks": { + "CPMK011": { + "desk": "Mampu menerapkan nilai etika, kejujuran, dan integritas akademik secara konsisten dalam setiap aktivitas perkuliahan dan pengerjaan tugas tanpa melakukan plagiasi.", + "subs": [ + { + "kode": "Sub-011-1", + "uraian": "Mampu menjelaskan konsep amanah dan kejujuran dalam perspektif agama sebagai landasan integritas akademik." + }, + { + "kode": "Sub-011-2", + "uraian": "Mampu menerapkan prinsip kejujuran religius dalam menyelesaikan tugas akademik tanpa kecurangan." + } + ] + }, + "CPMK012": { + "desk": "Mampu menunjukkan kedisiplinan dan tanggung jawab dengan menyelesaikan seluruh kewajiban tugas secara mandiri dan tepat waktu.", + "subs": [ + { + "kode": "Sub-012-1", + "uraian": "Mampu menunjukkan tanggung jawab sebagai wujud ibadah dengan menyelesaikan kewajiban akademik tepat waktu." + }, + { + "kode": "Sub-012-2", + "uraian": "Mampu mengelola waktu belajar secara mandiri berdasarkan prinsip disiplin yang bersumber dari nilai-nilai agama." + } + ] + }, + "CPMK022": { + "desk": "Mampu mengevaluasi dampak sosial dan keberlanjutan dari sebuah solusi TI menggunakan kerangka analisis dampak yang sistematis, objektif dan berbasis data.", + "subs": [ + { + "kode": "Sub-022-1", + "uraian": "Mampu menjelaskan pandangan agama tentang tanggung jawab manusia dalam menjaga lingkungan dan keberlanjutan." + }, + { + "kode": "Sub-022-2", + "uraian": "Mampu mengevaluasi dampak sosial penggunaan teknologi dari perspektif etika dan nilai-nilai agama." + } + ] + } + } + }, + "MKWU-03": { + "nama": "Bahasa Inggris", + "cpmks": { + "CPMK013": { + "desk": "Mampu menunjukkan sikap profesional dalam berinteraksi dan bekerjasama dengan tim melalui komunikasi yang konstruktif dan pembagian peran yang adil.", + "subs": [ + { + "kode": "Sub-013-1", + "uraian": "Mampu berkomunikasi dalam bahasa Inggris secara lisan dan tulisan menggunakan terminologi teknis informatika yang tepat." + }, + { + "kode": "Sub-013-2", + "uraian": "Mampu menyampaikan ide dan solusi teknis dalam bahasa Inggris dengan struktur yang jelas di depan kelompok." + } + ] + }, + "CPMK021": { + "desk": "Mampu menganalisis isu sosial, budaya, dan lingkungan yang relevan dengan penerapan TI melalui kajian studi kasus nyata di masyarakat.", + "subs": [ + { + "kode": "Sub-021-1", + "uraian": "Mampu membaca dan memahami artikel berbahasa Inggris tentang isu sosial dan dampak teknologi informasi." + }, + { + "kode": "Sub-021-2", + "uraian": "Mampu menganalisis teks berbahasa Inggris tentang tantangan digital society dan implikasinya bagi masyarakat." + } + ] + }, + "CPMK062": { + "desk": "Mampu menyajikan dan mempertahankan gagasan teknis secara lisan dengan argumentasi yang logis dan didukung media visual yang efektif.", + "subs": [ + { + "kode": "Sub-062-1", + "uraian": "Mampu menyusun dan menyampaikan presentasi singkat berbahasa Inggris tentang topik teknis informatika." + }, + { + "kode": "Sub-062-2", + "uraian": "Mampu menulis laporan akademik berbahasa Inggris dengan tata bahasa dan struktur argumen yang benar." + } + ] + } + } + }, + "INF-111-101": { + "nama": "Kalkulus", + "cpmks": { + "CPMK032": { + "desk": "Mampu menganalisis teori komputasi dan model matematika untuk memformulasikan solusi masalah teknis secara sistematis.", + "subs": [ + { + "kode": "Sub-032-1", + "uraian": "Mampu menghitung limit, turunan, dan integral fungsi satu variabel serta menginterpretasikan maknanya secara geometris." + }, + { + "kode": "Sub-032-2", + "uraian": "Mampu menerapkan kalkulus multivariabel untuk memodelkan masalah komputasi." + }, + { + "kode": "Sub-032-3", + "uraian": "Mampu menganalisis penggunaan turunan parsial untuk menemukan titik minimum/maksimum fungsi multivariabel sebagai dasar metode optimasi numerik." + } + ] + }, + "CPMK051": { + "desk": "Mampu menganalisis dan menerapkan teknik abstraksi dan dekomposisi untuk memecahkan masalah kompleks menjadi sub-masalah yang lebih kecil dan terkelola.", + "subs": [ + { + "kode": "Sub-051-1", + "uraian": "Mampu mendekomposisi masalah optimasi matematis menjadi sub-masalah menggunakan teknik turunan dan integral." + }, + { + "kode": "Sub-051-2", + "uraian": "Mampu mengabstraksi fenomena komputasi kontinu ke dalam model matematis yang dapat diselesaikan secara analitik." + } + ] + } + } + }, + "INF-111-201": { + "nama": "Pengantar Informatika", + "cpmks": { + "CPMK021": { + "desk": "Mampu menganalisis isu sosial, budaya, dan lingkungan yang relevan dengan penerapan TI melalui kajian studi kasus nyata di masyarakat.", + "subs": [ + { + "kode": "Sub-021-1", + "uraian": "Mampu mengidentifikasi peran teknologi informasi dalam berbagai sektor kehidupan sosial dan ekonomi masyarakat." + }, + { + "kode": "Sub-021-2", + "uraian": "Mampu menganalisis alur pemrosesan data dari input melalui sistem software hingga output, serta peran jaringan dan kecerdasan buatan dalam sistem komputasi modern." + } + ] + }, + "CPMK022": { + "desk": "Mampu mengevaluasi dampak sosial dan keberlanjutan dari sebuah solusi TI menggunakan kerangka analisis dampak yang sistematis, objektif dan berbasis data.", + "subs": [ + { + "kode": "Sub-022-1", + "uraian": "Mampu menjelaskan sub-bidang utama dalam disiplin informatika beserta keterkaitan antar bidang tersebut." + }, + { + "kode": "Sub-022-2", + "uraian": "Mampu mengidentifikasi jalur karier di bidang informatika dan memetakan mata kuliah inti yang relevan untuk setiap jalur tersebut." + } + ] + }, + "CPMK031": { + "desk": "Mampu menganalisis konsep dasar komputasi dan struktur data untuk menjelaskan keterkaitannya dalam penyelesaian masalah komputasi secara tepat.", + "subs": [ + { + "kode": "Sub-031-1", + "uraian": "Mampu menjelaskan komponen utama sistem komputer beserta fungsinya." + }, + { + "kode": "Sub-031-2", + "uraian": "Mampu menganalisis alur pemrosesan informasi dari input hingga output dalam sistem komputasi sederhana." + } + ] + } + } + }, + "INF-111-102": { + "nama": "Matematika Diskrit", + "cpmks": { + "CPMK031": { + "desk": "Mampu menganalisis konsep dasar komputasi dan struktur data untuk menjelaskan keterkaitannya dalam penyelesaian masalah komputasi secara tepat.", + "subs": [ + { + "kode": "Sub-031-1", + "uraian": "Mampu menjelaskan keterkaitan antara struktur matematika diskrit dengan struktur data pada pemrograman." + }, + { + "kode": "Sub-031-2", + "uraian": "Mampu menganalisis permasalahan komputasi menggunakan logika proposisional, predikat, dan teori himpunan." + } + ] + }, + "CPMK032": { + "desk": "Mampu menganalisis teori komputasi dan model matematika untuk memformulasikan solusi masalah teknis secara sistematis.", + "subs": [ + { + "kode": "Sub-032-1", + "uraian": "Mampu menerapkan konsep kombinatorika dan relasi untuk memodelkan serta menghitung kompleksitas masalah komputasi." + }, + { + "kode": "Sub-032-2", + "uraian": "Mampu menganalisis bukti matematis dengan teknik induksi untuk memvalidasi kebenaran algoritma." + } + ] + }, + "CPMK051": { + "desk": "Mampu menganalisis dan menerapkan teknik abstraksi dan dekomposisi untuk memecahkan masalah kompleks menjadi sub-masalah yang lebih kecil dan terkelola.", + "subs": [ + { + "kode": "Sub-051-1", + "uraian": "Mampu mendekomposisi masalah komputasi ke dalam representasi graf atau pohon untuk menemukan struktur solusinya." + }, + { + "kode": "Sub-051-2", + "uraian": "Mampu mengabstraksi pola logis dari masalah diskrit menggunakan konsep relasi dan fungsi matematis." + } + ] + }, + "CPMK052": { + "desk": "Mampu merancang solusi algoritmik yang logis dan terstruktur menggunakan pseudocode atau flowchart untuk menyelesaikan permasalahan komputasi.", + "subs": [ + { + "kode": "Sub-052-1", + "uraian": "Mampu merancang pseudocode berbasis rekursi dengan memanfaatkan prinsip induksi matematis sebagai dasar validasi." + }, + { + "kode": "Sub-052-2", + "uraian": "Mampu mengembangkan solusi algoritmik untuk masalah graf menggunakan flowchart terstruktur." + } + ] + } + } + }, + "INF-111-202": { + "nama": "Logika Komputasional", + "cpmks": { + "CPMK031": { + "desk": "Mampu menganalisis konsep dasar komputasi dan struktur data untuk menjelaskan keterkaitannya dalam penyelesaian masalah komputasi secara tepat.", + "subs": [ + { + "kode": "Sub-031-1", + "uraian": "Mampu menjelaskan empat pilar berpikir komputasional dengan contoh nyata." + }, + { + "kode": "Sub-031-2", + "uraian": "Mampu menganalisis masalah sehari-hari menggunakan kerangka berpikir komputasional secara terstruktur." + } + ] + }, + "CPMK032": { + "desk": "Mampu menganalisis teori komputasi dan model matematika untuk memformulasikan solusi masalah teknis secara sistematis.", + "subs": [ + { + "kode": "Sub-032-1", + "uraian": "Mampu mengidentifikasi pola berulang dalam sekumpulan masalah dan menggunakannya untuk menyederhanakan solusi melalui pendekatan pengenalan pola." + }, + { + "kode": "Sub-032-2", + "uraian": "Mampu menerapkan keempat pilar berpikir komputasional secara terpadu untuk menyelesaikan masalah sehari-hari." + } + ] + }, + "CPMK051": { + "desk": "Mampu menganalisis dan menerapkan teknik abstraksi dan dekomposisi untuk memecahkan masalah kompleks menjadi sub-masalah yang lebih kecil dan terkelola.", + "subs": [ + { + "kode": "Sub-051-1", + "uraian": "Mampu mendekomposisi masalah kompleks non-coding menjadi langkah-langkah logis yang terurut dan dapat dieksekusi." + }, + { + "kode": "Sub-051-2", + "uraian": "Mampu mengabstraksi esensi masalah untuk mengidentifikasi pola solusi yang dapat digunakan kembali." + } + ] + }, + "CPMK052": { + "desk": "Mampu merancang solusi algoritmik yang logis dan terstruktur menggunakan pseudocode atau flowchart untuk menyelesaikan permasalahan komputasi.", + "subs": [ + { + "kode": "Sub-052-1", + "uraian": "Mampu merancang algoritma solusi dalam bentuk pseudocode untuk masalah komputasi sehari-hari tanpa coding." + }, + { + "kode": "Sub-052-2", + "uraian": "Mampu mengembangkan flowchart yang runtut dan mudah dipahami untuk merepresentasikan alur penyelesaian masalah." + } + ] + }, + "CPMK053": { + "desk": "Mampu mengevaluasi validitas logika dan efisiensi dari berbagai alternatif algoritma sebelum diimplementasikan ke dalam kode program.", + "subs": [ + { + "kode": "Sub-053-1", + "uraian": "Mampu memvalidasi kebenaran algoritma dengan menelusuri alur eksekusi langkah demi langkah." + }, + { + "kode": "Sub-053-2", + "uraian": "Mampu mengevaluasi efisiensi relatif dua algoritma berbeda berdasarkan jumlah langkah eksekusi secara intuitif." + } + ] + } + } + }, + "INF-111-501": { + "nama": "Komunikasi dan Keterampilan Profesional", + "cpmks": { + "CPMK012": { + "desk": "Mampu menunjukkan kedisiplinan dan tanggung jawab dengan menyelesaikan seluruh kewajiban tugas secara mandiri dan tepat waktu.", + "subs": [ + { + "kode": "Sub-012-1", + "uraian": "Mampu menyelesaikan dan mengumpulkan tugas laporan serta presentasi secara mandiri sesuai tenggat waktu yang ditetapkan." + }, + { + "kode": "Sub-012-2", + "uraian": "Mampu menunjukkan tanggung jawab profesional dengan memenuhi seluruh porsi kerja tim secara tepat waktu." + } + ] + }, + "CPMK013": { + "desk": "Mampu menunjukkan sikap profesional dalam berinteraksi dan bekerjasama dengan tim melalui komunikasi yang konstruktif dan pembagian peran yang adil.", + "subs": [ + { + "kode": "Sub-013-1", + "uraian": "Mampu berinteraksi secara profesional dalam diskusi tim menggunakan komunikasi asertif dan menghormati perbedaan pendapat." + }, + { + "kode": "Sub-013-2", + "uraian": "Mampu membagi peran kerja secara adil dalam proyek tim dan mendokumentasikan kontribusi masing-masing anggota." + } + ] + }, + "CPMK061": { + "desk": "Mampu menyusun dan mengevaluasi dokumen spesifikasi teknis perangkat lunak yang lengkap dan sesuai dengan standar industri.", + "subs": [ + { + "kode": "Sub-061-1", + "uraian": "Mampu menyusun laporan teknis proyek dengan struktur standar dan tata bahasa baku." + }, + { + "kode": "Sub-061-2", + "uraian": "Mampu mengevaluasi kualitas dokumen teknis berdasarkan rubrik standar industri yang mencakup kelengkapan dan kejelasan." + } + ] + }, + "CPMK062": { + "desk": "Mampu menyajikan dan mempertahankan gagasan teknis secara lisan dengan argumentasi yang logis dan didukung media visual yang efektif.", + "subs": [ + { + "kode": "Sub-062-1", + "uraian": "Mampu menyajikan gagasan teknis secara lisan dengan slide yang efektif, terstruktur, dan selesai dalam waktu yang ditentukan." + }, + { + "kode": "Sub-062-2", + "uraian": "Mampu mempertahankan argumen teknis dalam sesi tanya-jawab dengan logis menggunakan data pendukung yang relevan." + } + ] + }, + "CPMK063": { + "desk": "Mampu berkolaborasi secara produktif dalam tim proyek perangkat lunak menggunakan alat manajemen proyek untuk mencapai target bersama.", + "subs": [ + { + "kode": "Sub-063-1", + "uraian": "Mampu berkolaborasi dalam proyek tim menggunakan platform manajemen digital secara produktif." + }, + { + "kode": "Sub-063-2", + "uraian": "Mampu menyusun rencana kerja tim berisi pembagian tugas, milestone, dan jadwal yang terukur dan realistis." + } + ] + } + } + }, + "MKWU-04": { + "nama": "Kewarganegaraan", + "cpmks": { + "CPMK011": { + "desk": "Mampu menerapkan nilai etika, kejujuran, dan integritas akademik secara konsisten dalam setiap aktivitas perkuliahan dan pengerjaan tugas tanpa melakukan plagiasi.", + "subs": [ + { + "kode": "Sub-011-1", + "uraian": "Mampu mengidentifikasi prinsip-prinsip hukum dan etika warga negara yang mendukung integritas dalam kehidupan akademik dan digital." + }, + { + "kode": "Sub-011-2", + "uraian": "Mampu menerapkan nilai-nilai kewarganegaraan yang bertanggung jawab dalam penggunaan teknologi informasi secara legal." + } + ] + }, + "CPMK021": { + "desk": "Mampu menganalisis isu sosial, budaya, dan lingkungan yang relevan dengan penerapan TI melalui kajian studi kasus nyata di masyarakat.", + "subs": [ + { + "kode": "Sub-021-1", + "uraian": "Mampu menganalisis isu demokrasi digital dan hak-hak warga negara dalam konteks transformasi teknologi informasi di Indonesia." + }, + { + "kode": "Sub-021-2", + "uraian": "Mampu mengidentifikasi regulasi TI yang berlaku dan implikasinya bagi warga negara." + } + ] + } + } + }, + "MKWU-05": { + "nama": "Bahasa Indonesia", + "cpmks": { + "CPMK013": { + "desk": "Mampu menunjukkan sikap profesional dalam berinteraksi dan bekerjasama dengan tim melalui komunikasi yang konstruktif dan pembagian peran yang adil.", + "subs": [ + { + "kode": "Sub-013-1", + "uraian": "Mampu berkomunikasi secara profesional dalam bahasa Indonesia baku dalam konteks penulisan akademik dan presentasi teknis." + }, + { + "kode": "Sub-013-2", + "uraian": "Mampu berkolaborasi dalam tim proyek menggunakan komunikasi tertulis yang efektif dan terstruktur dalam bahasa Indonesia." + } + ] + }, + "CPMK021": { + "desk": "Mampu menganalisis isu sosial, budaya, dan lingkungan yang relevan dengan penerapan TI melalui kajian studi kasus nyata di masyarakat.", + "subs": [ + { + "kode": "Sub-021-1", + "uraian": "Mampu membaca dan memahami artikel ilmiah berbahasa Indonesia tentang isu sosial dan dampak teknologi informasi." + }, + { + "kode": "Sub-021-2", + "uraian": "Mampu menyusun ringkasan analisis kritis tentang dampak TI terhadap masyarakat dalam tulisan akademik berbahasa Indonesia." + } + ] + }, + "CPMK061": { + "desk": "Mampu menyusun dan mengevaluasi dokumen spesifikasi teknis perangkat lunak yang lengkap dan sesuai dengan standar industri.", + "subs": [ + { + "kode": "Sub-061-1", + "uraian": "Mampu menyusun laporan teknis proyek perangkat lunak menggunakan format dan ejaan bahasa Indonesia yang baku." + }, + { + "kode": "Sub-061-2", + "uraian": "Mampu menulis proposal atau artikel ilmiah teknis dengan struktur argumen yang runtut dan referensi yang valid." + } + ] + }, + "CPMK062": { + "desk": "Mampu menyajikan dan mempertahankan gagasan teknis secara lisan dengan argumentasi yang logis dan didukung media visual yang efektif.", + "subs": [ + { + "kode": "Sub-062-1", + "uraian": "Mampu menyajikan hasil analisis teknis secara lisan dalam bahasa Indonesia yang formal, jelas, dan mudah dipahami." + }, + { + "kode": "Sub-062-2", + "uraian": "Mampu menyusun esai argumentatif teknis yang persuasif dengan didukung data dan referensi ilmiah berbahasa Indonesia." + } + ] + } + } + }, + "INF-211-203": { + "nama": "Dasar Pemrograman", + "cpmks": { + "CPMK031": { + "desk": "Mampu menganalisis konsep dasar komputasi dan struktur data untuk menjelaskan keterkaitannya dalam penyelesaian masalah komputasi secara tepat.", + "subs": [ + { + "kode": "Sub-031-1", + "uraian": "Mampu menjelaskan konsep variabel, tipe data, operator, dan struktur kontrol serta keterkaitannya dalam alur eksekusi program." + }, + { + "kode": "Sub-031-2", + "uraian": "Mampu menganalisis alur eksekusi program sederhana dan mengidentifikasi kesalahan logika dari kode yang diberikan." + } + ] + }, + "CPMK033": { + "desk": "Mampu menganalisis dan merumuskan spesifikasi kebutuhan fungsional dan non-fungsional perangkat lunak secara lengkap berdasarkan studi kasus pengguna.", + "subs": [ + { + "kode": "Sub-033-1", + "uraian": "Mampu mengidentifikasi kebutuhan fungsional dasar sebuah program dari deskripsi masalah yang diberikan." + }, + { + "kode": "Sub-033-2", + "uraian": "Mampu merumuskan spesifikasi input-output dan batasan program berdasarkan studi kasus pemrograman sederhana." + } + ] + }, + "CPMK034": { + "desk": "Mampu mengevaluasi dan membandingkan berbagai alternatif solusi teknis berdasarkan efisiensi, kompleksitas, dan efektivitas komputasi.", + "subs": [ + { + "kode": "Sub-034-1", + "uraian": "Mampu membandingkan berbagai pendekatan struktur kontrol iteratif dan memilih yang paling sesuai untuk kasus pemrograman tertentu." + }, + { + "kode": "Sub-034-2", + "uraian": "Mampu memilih dan membuktikan pendekatan struktur kontrol yang paling tepat untuk kasus pemrograman tertentu." + } + ] + }, + "CPMK051": { + "desk": "Mampu menganalisis dan menerapkan teknik abstraksi dan dekomposisi untuk memecahkan masalah kompleks menjadi sub-masalah yang lebih kecil dan terkelola.", + "subs": [ + { + "kode": "Sub-051-1", + "uraian": "Mampu mendekomposisi masalah pemrograman menjadi fungsi-fungsi yang lebih kecil dan memiliki tanggung jawab tunggal." + }, + { + "kode": "Sub-051-2", + "uraian": "Mampu mengabstraksi logika berulang ke dalam fungsi atau prosedur yang dapat dipanggil ulang." + } + ] + }, + "CPMK052": { + "desk": "Mampu merancang solusi algoritmik yang logis dan terstruktur menggunakan pseudocode atau flowchart untuk menyelesaikan permasalahan komputasi.", + "subs": [ + { + "kode": "Sub-052-1", + "uraian": "Mampu merancang pseudocode atau flowchart lengkap sebelum mengimplementasikan solusi dalam kode program." + }, + { + "kode": "Sub-052-2", + "uraian": "Mampu mengembangkan algoritma terstruktur untuk masalah pemrograman dasar." + } + ] + }, + "CPMK091": { + "desk": "Mampu mengimplementasikan algoritma ke dalam kode program yang efisien, bebas error, dan memenuhi standar clean code.", + "subs": [ + { + "kode": "Sub-091-1", + "uraian": "Mampu mengimplementasikan program yang benar secara sintaks dan logika menggunakan bahasa pemrograman yang ditetapkan." + }, + { + "kode": "Sub-091-2", + "uraian": "Mampu melakukan debugging sistematis untuk mengidentifikasi dan memperbaiki kesalahan pada program yang bermasalah." + } + ] + } + } + }, + "INF-221-204": { + "nama": "Praktikum Dasar Pemrograman", + "cpmks": { + "CPMK031": { + "desk": "Mampu menganalisis konsep dasar komputasi dan struktur data untuk menjelaskan keterkaitannya dalam penyelesaian masalah komputasi secara tepat.", + "subs": [ + { + "kode": "Sub-031-1", + "uraian": "Mampu menerapkan konsep dasar pemrograman dalam implementasi program pada setiap sesi praktikum." + }, + { + "kode": "Sub-031-2", + "uraian": "Mampu menganalisis kode program yang diberikan dan menjelaskan alur eksekusinya kepada instruktur dengan benar." + } + ] + }, + "CPMK034": { + "desk": "Mampu mengevaluasi dan membandingkan berbagai alternatif solusi teknis berdasarkan efisiensi, kompleksitas, dan efektivitas komputasi.", + "subs": [ + { + "kode": "Sub-034-1", + "uraian": "Mampu membandingkan solusi alternatif untuk soal praktikum dan memilih yang paling efisien berdasarkan kompleksitas kode." + }, + { + "kode": "Sub-034-2", + "uraian": "Mampu membuktikan keunggulan pendekatan yang dipilih dengan mengukur performa atau kemudahan pemahaman kode." + } + ] + }, + "CPMK051": { + "desk": "Mampu menganalisis dan menerapkan teknik abstraksi dan dekomposisi untuk memecahkan masalah kompleks menjadi sub-masalah yang lebih kecil dan terkelola.", + "subs": [ + { + "kode": "Sub-051-1", + "uraian": "Mampu mendekomposisi soal praktikum kompleks menjadi sub-fungsi sebelum mulai menulis kode program." + }, + { + "kode": "Sub-051-2", + "uraian": "Mampu mengabstraksi pola berulang dalam soal praktikum ke dalam fungsi generik yang dapat digunakan kembali." + } + ] + }, + "CPMK052": { + "desk": "Mampu merancang solusi algoritmik yang logis dan terstruktur menggunakan pseudocode atau flowchart untuk menyelesaikan permasalahan komputasi.", + "subs": [ + { + "kode": "Sub-052-1", + "uraian": "Mampu merancang solusi algoritmik setiap soal praktikum dalam pseudocode sebelum implementasi kode." + }, + { + "kode": "Sub-052-2", + "uraian": "Mampu mengembangkan kode yang terstruktur, terdokumentasi komentar, dan mengikuti konvensi penamaan yang ditetapkan." + } + ] + }, + "CPMK091": { + "desk": "Mampu mengimplementasikan algoritma ke dalam kode program yang efisien, bebas error, dan memenuhi standar clean code.", + "subs": [ + { + "kode": "Sub-091-1", + "uraian": "Mampu mengimplementasikan program praktikum yang memenuhi semua test case yang diberikan tanpa error." + }, + { + "kode": "Sub-091-2", + "uraian": "Mampu menulis kode yang rapi dengan indentasi konsisten, penamaan bermakna, dan komentar pada bagian kritis." + } + ] + } + } + }, + "INF-211-205": { + "nama": "Organisasi dan Arsitektur Komputer", + "cpmks": { + "CPMK022": { + "desk": "Mampu mengevaluasi dampak sosial dan keberlanjutan dari sebuah solusi TI menggunakan kerangka analisis dampak yang sistematis, objektif dan berbasis data.", + "subs": [ + { + "kode": "Sub-022-1", + "uraian": "Mampu mengidentifikasi komponen utama CPU dan menjelaskan peran masing-masing dalam siklus instruksi." + }, + { + "kode": "Sub-022-2", + "uraian": "Mampu menganalisis struktur hierarki memori dan dampaknya terhadap kinerja sistem komputasi." + } + ] + }, + "CPMK031": { + "desk": "Mampu menganalisis konsep dasar komputasi dan struktur data untuk menjelaskan keterkaitannya dalam penyelesaian masalah komputasi secara tepat.", + "subs": [ + { + "kode": "Sub-031-1", + "uraian": "Mampu menjelaskan siklus fetch-decode-execute pada CPU dan kaitannya dengan kinerja sistem komputasi." + }, + { + "kode": "Sub-031-2", + "uraian": "Mampu menganalisis hierarki memori dan implikasinya terhadap performa program." + } + ] + }, + "CPMK033": { + "desk": "Mampu menganalisis dan merumuskan spesifikasi kebutuhan fungsional dan non-fungsional perangkat lunak secara lengkap berdasarkan studi kasus pengguna.", + "subs": [ + { + "kode": "Sub-033-1", + "uraian": "Mampu mengidentifikasi kebutuhan spesifikasi perangkat keras berdasarkan analisis beban komputasi suatu aplikasi." + }, + { + "kode": "Sub-033-2", + "uraian": "Mampu merumuskan kebutuhan kapasitas memori dan bandwidth I/O untuk mendukung sistem tertentu." + } + ] + }, + "CPMK041": { + "desk": "Mampu menganalisis dan menetapkan spesifikasi kebutuhan infrastruktur jaringan dan standar keamanan sistem yang diperlukan untuk mendukung kinerja aplikasi secara optimal.", + "subs": [ + { + "kode": "Sub-041-1", + "uraian": "Mampu menjelaskan konsep instruction set architecture termasuk format instruksi, mode pengalamatan, dan kategori instruksi." + }, + { + "kode": "Sub-041-2", + "uraian": "Mampu menganalisis mekanisme pipelining CPU, mengidentifikasi jenis hazard, dan menjelaskan teknik penanganannya." + } + ] + } + } + }, + "INF-211-206": { + "nama": "Sistem Operasi", + "cpmks": { + "CPMK031": { + "desk": "Mampu menganalisis konsep dasar komputasi dan struktur data untuk menjelaskan keterkaitannya dalam penyelesaian masalah komputasi secara tepat.", + "subs": [ + { + "kode": "Sub-031-1", + "uraian": "Mampu menjelaskan konsep proses, thread, dan mekanisme context-switching pada sistem operasi modern." + }, + { + "kode": "Sub-031-2", + "uraian": "Mampu menganalisis cara kerja manajemen memori virtual dan dampaknya terhadap performa aplikasi." + } + ] + }, + "CPMK034": { + "desk": "Mampu mengevaluasi dan membandingkan berbagai alternatif solusi teknis berdasarkan efisiensi, kompleksitas, dan efektivitas komputasi.", + "subs": [ + { + "kode": "Sub-034-1", + "uraian": "Mampu membandingkan algoritma penjadwalan CPU berdasarkan waktu tunggu rata-rata." + }, + { + "kode": "Sub-034-2", + "uraian": "Mampu mengevaluasi strategi penggantian halaman berdasarkan tingkat page fault pada skenario tertentu." + } + ] + }, + "CPMK041": { + "desk": "Mampu menganalisis dan menetapkan spesifikasi kebutuhan infrastruktur jaringan dan standar keamanan sistem yang diperlukan untuk mendukung kinerja aplikasi secara optimal.", + "subs": [ + { + "kode": "Sub-041-1", + "uraian": "Mampu menganalisis mekanisme akses kontrol dan permission sistem operasi sebagai fondasi keamanan aplikasi server." + }, + { + "kode": "Sub-041-2", + "uraian": "Mampu mengidentifikasi kebutuhan konfigurasi sistem operasi untuk mendukung infrastruktur layanan jaringan yang aman." + } + ] + }, + "CPMK044": { + "desk": "Mampu menganalisis potensi risiko keamanan pada arsitektur sistem jaringan atau IoT dan merumuskan langkah mitigasi dasarnya.", + "subs": [ + { + "kode": "Sub-044-1", + "uraian": "Mampu mengidentifikasi potensi kerentanan sistem operasi dan mekanisme proteksinya." + }, + { + "kode": "Sub-044-2", + "uraian": "Mampu menganalisis kebijakan akses dan permission file system untuk memitigasi risiko keamanan pada sistem operasi." + } + ] + }, + "CPMK102": { + "desk": "Mampu menerapkan mekanisme enkripsi dan autentikasi pada perangkat IoT untuk menjamin kerahasiaan dan integritas data.", + "subs": [ + { + "kode": "Sub-102-1", + "uraian": "Mampu menerapkan konfigurasi manajemen pengguna, grup, dan permission pada sistem operasi Unix/Linux." + }, + { + "kode": "Sub-102-2", + "uraian": "Mampu menerapkan mekanisme sinkronisasi proses pada sistem operasi untuk mencegah kondisi balapan dan menjamin konsistensi akses sumber daya bersama." + } + ] + } + } + }, + "INF-211-103": { + "nama": "Aljabar Linier", + "cpmks": { + "CPMK032": { + "desk": "Mampu menganalisis teori komputasi dan model matematika untuk memformulasikan solusi masalah teknis secara sistematis.", + "subs": [ + { + "kode": "Sub-032-1", + "uraian": "Mampu menerapkan operasi matriks untuk menyelesaikan sistem persamaan linier dalam komputasi." + }, + { + "kode": "Sub-032-2", + "uraian": "Mampu menganalisis konsep eigenvalue/eigenvector dan menjelaskan kegunaannya dalam PCA dan grafika komputer." + } + ] + }, + "CPMK051": { + "desk": "Mampu menganalisis dan menerapkan teknik abstraksi dan dekomposisi untuk memecahkan masalah kompleks menjadi sub-masalah yang lebih kecil dan terkelola.", + "subs": [ + { + "kode": "Sub-051-1", + "uraian": "Mampu mendekomposisi masalah representasi data multidimensi menggunakan transformasi linier yang sesuai." + }, + { + "kode": "Sub-051-2", + "uraian": "Mampu mengabstraksi masalah komputasi ke dalam formulasi aljabar linier yang terstruktur." + } + ] + }, + "CPMK052": { + "desk": "Mampu merancang solusi algoritmik yang logis dan terstruktur menggunakan pseudocode atau flowchart untuk menyelesaikan permasalahan komputasi.", + "subs": [ + { + "kode": "Sub-052-1", + "uraian": "Mampu merancang pseudocode untuk algoritma operasi matriks yang efisien." + }, + { + "kode": "Sub-052-2", + "uraian": "Mampu mengembangkan solusi algoritmik untuk transformasi ruang vektor dalam konteks grafika atau pengolahan sinyal." + } + ] + }, + "CPMK072": { + "desk": "Mampu menganalisis dan mengevaluasi pola serta tren data menggunakan metode statistik atau algoritma Machine Learning dasar untuk mendapatkan informasi yang berguna.", + "subs": [ + { + "kode": "Sub-072-1", + "uraian": "Mampu menerapkan dekomposisi nilai singular untuk menyelesaikan sistem persamaan linier yang tidak tentu dan menginterpretasikan hasil secara geometris." + }, + { + "kode": "Sub-072-2", + "uraian": "Mampu menganalisis rank, nullity, dan ruang baris/kolom matriks untuk menentukan konsistensi dan solusi sistem persamaan linier." + } + ] + } + } + }, + "INF-211-502": { + "nama": "Literasi Digital dan Etika Komputasi", + "cpmks": { + "CPMK011": { + "desk": "Mampu menerapkan nilai etika, kejujuran, dan integritas akademik secara konsisten dalam setiap aktivitas perkuliahan dan pengerjaan tugas tanpa melakukan plagiasi.", + "subs": [ + { + "kode": "Sub-011-1", + "uraian": "Mampu mengidentifikasi pelanggaran etika digital beserta konsekuensi hukumnya." + }, + { + "kode": "Sub-011-2", + "uraian": "Mampu menerapkan etika digital dalam aktivitas akademik online termasuk penggunaan AI generatif secara bertanggung jawab." + } + ] + }, + "CPMK012": { + "desk": "Mampu menunjukkan kedisiplinan dan tanggung jawab dengan menyelesaikan seluruh kewajiban tugas secara mandiri dan tepat waktu.", + "subs": [ + { + "kode": "Sub-012-1", + "uraian": "Mampu mengelola identitas dan jejak digital secara positif dan bertanggung jawab di platform online." + }, + { + "kode": "Sub-012-2", + "uraian": "Mampu menyelesaikan penugasan digital secara mandiri dan tepat waktu dengan menghindari penggunaan bantuan yang tidak sah." + } + ] + }, + "CPMK013": { + "desk": "Mampu menunjukkan sikap profesional dalam berinteraksi dan bekerjasama dengan tim melalui komunikasi yang konstruktif dan pembagian peran yang adil.", + "subs": [ + { + "kode": "Sub-013-1", + "uraian": "Mampu berkolaborasi secara profesional di platform digital sambil menjaga etika komunikasi dan keamanan akun." + }, + { + "kode": "Sub-013-2", + "uraian": "Mampu berkontribusi secara adil dalam proyek digital tim dengan mendokumentasikan kontribusi pada alat kolaborasi." + } + ] + }, + "CPMK021": { + "desk": "Mampu menganalisis isu sosial, budaya, dan lingkungan yang relevan dengan penerapan TI melalui kajian studi kasus nyata di masyarakat.", + "subs": [ + { + "kode": "Sub-021-1", + "uraian": "Mampu menganalisis dampak kesenjangan digital terhadap kesetaraan akses informasi di masyarakat." + }, + { + "kode": "Sub-021-2", + "uraian": "Mampu mengidentifikasi isu privasi dan keamanan data dalam aktivitas digital sehari-hari menggunakan kerangka analisis yang tepat." + } + ] + }, + "CPMK022": { + "desk": "Mampu mengevaluasi dampak sosial dan keberlanjutan dari sebuah solusi TI menggunakan kerangka analisis dampak yang sistematis, objektif dan berbasis data.", + "subs": [ + { + "kode": "Sub-022-1", + "uraian": "Mampu mengevaluasi dampak penggunaan media sosial terhadap kesehatan mental dan kohesi sosial berdasarkan studi kasus." + }, + { + "kode": "Sub-022-2", + "uraian": "Mampu menganalisis implikasi lingkungan dari perilaku digital dan merumuskan rekomendasi." + } + ] + }, + "CPMK062": { + "desk": "Mampu menyajikan dan mempertahankan gagasan teknis secara lisan dengan argumentasi yang logis dan didukung media visual yang efektif.", + "subs": [ + { + "kode": "Sub-062-1", + "uraian": "Mampu mempresentasikan analisis kasus etika digital secara argumentatif dengan media visual yang efektif." + }, + { + "kode": "Sub-062-2", + "uraian": "Mampu mempertahankan posisi argumen dalam debat tentang isu privasi, kebebasan berekspresi, dan etika teknologi." + } + ] + } + } + }, + "INF-311-104": { + "nama": "Probabilitas dan Statistik", + "cpmks": { + "CPMK032": { + "desk": "Mampu menganalisis teori komputasi dan model matematika untuk memformulasikan solusi masalah teknis secara sistematis.", + "subs": [ + { + "kode": "Sub-032-1", + "uraian": "Mampu menerapkan distribusi probabilitas dan inferensi statistik untuk memodelkan ketidakpastian dalam sistem komputasi." + }, + { + "kode": "Sub-032-2", + "uraian": "Mampu menganalisis masalah teknis menggunakan teknik pengujian hipotesis dan estimasi interval kepercayaan." + } + ] + }, + "CPMK034": { + "desk": "Mampu mengevaluasi dan membandingkan berbagai alternatif solusi teknis berdasarkan efisiensi, kompleksitas, dan efektivitas komputasi.", + "subs": [ + { + "kode": "Sub-034-1", + "uraian": "Mampu membandingkan metode statistik berdasarkan asumsi dan karakteristik data." + }, + { + "kode": "Sub-034-2", + "uraian": "Mampu mengevaluasi validitas model regresi menggunakan metrik dan uji asumsi yang sesuai." + } + ] + }, + "CPMK051": { + "desk": "Mampu menganalisis dan menerapkan teknik abstraksi dan dekomposisi untuk memecahkan masalah kompleks menjadi sub-masalah yang lebih kecil dan terkelola.", + "subs": [ + { + "kode": "Sub-051-1", + "uraian": "Mampu mendekomposisi masalah analisis data menjadi langkah eksplorasi, pemodelan, dan validasi yang terstruktur." + }, + { + "kode": "Sub-051-2", + "uraian": "Mampu mengabstraksi fenomena nyata ke dalam model probabilistik yang dapat dioperasionalisasikan secara komputasi." + } + ] + }, + "CPMK052": { + "desk": "Mampu merancang solusi algoritmik yang logis dan terstruktur menggunakan pseudocode atau flowchart untuk menyelesaikan permasalahan komputasi.", + "subs": [ + { + "kode": "Sub-052-1", + "uraian": "Mampu merancang prosedur pengujian hipotesis statistik yang logis untuk menjawab pertanyaan riset yang diberikan." + }, + { + "kode": "Sub-052-2", + "uraian": "Mampu mengembangkan alur analisis data statistik secara sistematis dari eksplorasi hingga penarikan kesimpulan." + } + ] + }, + "CPMK071": { + "desk": "Mampu menganalisis dan memvalidasi data menggunakan teknik cleansing yang tepat hingga menghasilkan dataset yang siap dianalisis.", + "subs": [ + { + "kode": "Sub-071-1", + "uraian": "Mampu menerapkan teknik deteksi outlier dan penanganan missing value untuk memvalidasi kualitas dataset." + }, + { + "kode": "Sub-071-2", + "uraian": "Mampu menganalisis distribusi data dan mengidentifikasi anomali menggunakan statistik deskriptif dan visualisasi." + } + ] + }, + "CPMK072": { + "desk": "Mampu menganalisis dan mengevaluasi pola serta tren data menggunakan metode statistik atau algoritma Machine Learning dasar untuk mendapatkan informasi yang berguna.", + "subs": [ + { + "kode": "Sub-072-1", + "uraian": "Mampu menganalisis pola dan tren data menggunakan teknik regresi linier dan analisis korelasi dengan interpretasi tepat." + }, + { + "kode": "Sub-072-2", + "uraian": "Mampu mengevaluasi signifikansi statistik dari hubungan antar variabel menggunakan uji yang sesuai." + } + ] + }, + "CPMK073": { + "desk": "Mampu merumuskan dan mengevaluasi rekomendasi solusi yang strategis dan logis berdasarkan hasil interpretasi data yang akurat.", + "subs": [ + { + "kode": "Sub-073-1", + "uraian": "Mampu merumuskan rekomendasi berbasis hasil uji statistik yang valid dan dapat diinterpretasikan oleh pemangku kepentingan." + }, + { + "kode": "Sub-073-2", + "uraian": "Mampu mengevaluasi kekuatan dan keterbatasan kesimpulan statistik serta menyajikannya dalam laporan yang terstruktur." + } + ] + } + } + }, + "INF-311-207": { + "nama": "Jaringan Komputer", + "cpmks": { + "CPMK041": { + "desk": "Mampu menganalisis dan menetapkan spesifikasi kebutuhan infrastruktur jaringan dan standar keamanan sistem yang diperlukan untuk mendukung kinerja aplikasi secara optimal.", + "subs": [ + { + "kode": "Sub-041-1", + "uraian": "Mampu menganalisis kebutuhan spesifikasi jaringan berdasarkan profil beban aplikasi." + }, + { + "kode": "Sub-041-2", + "uraian": "Mampu menetapkan standar keamanan jaringan yang diperlukan berdasarkan analisis ancaman dan kebutuhan layanan." + } + ] + }, + "CPMK042": { + "desk": "Mampu merancang arsitektur jaringan komputer atau sistem IoT dasar yang aman, skalabel, dan sesuai dengan topologi standar.", + "subs": [ + { + "kode": "Sub-042-1", + "uraian": "Mampu merancang topologi jaringan LAN/WAN beserta skema pengalamatan IP yang optimal." + }, + { + "kode": "Sub-042-2", + "uraian": "Mampu merancang konfigurasi routing untuk menghubungkan beberapa segmen jaringan secara efisien." + } + ] + }, + "CPMK044": { + "desk": "Mampu menganalisis potensi risiko keamanan pada arsitektur sistem jaringan atau IoT dan merumuskan langkah mitigasi dasarnya.", + "subs": [ + { + "kode": "Sub-044-1", + "uraian": "Mampu menganalisis kerentanan umum protokol jaringan dan merumuskan langkah mitigasi dasar yang tepat." + }, + { + "kode": "Sub-044-2", + "uraian": "Mampu mengidentifikasi anomali trafik jaringan melalui analisis paket menggunakan Wireshark." + } + ] + }, + "CPMK101": { + "desk": "Mampu merancang dan mengimplementasikan protokol komunikasi data untuk menghubungkan sensor fisik dengan platform IoT secara real-time.", + "subs": [ + { + "kode": "Sub-101-1", + "uraian": "Mampu menjelaskan arsitektur model OSI tujuh lapisan dan model TCP/IP beserta fungsi, protokol representatif, dan PDU pada setiap lapisan." + }, + { + "kode": "Sub-101-2", + "uraian": "Mampu menganalisis proses switching layer-2 dan membedakannya dengan proses routing layer-3." + } + ] + }, + "CPMK102": { + "desk": "Mampu menerapkan mekanisme enkripsi dan autentikasi pada perangkat IoT untuk menjamin kerahasiaan dan integritas data.", + "subs": [ + { + "kode": "Sub-102-1", + "uraian": "Mampu menerapkan konfigurasi keamanan dasar pada perangkat jaringan untuk membatasi akses tidak sah." + }, + { + "kode": "Sub-102-2", + "uraian": "Mampu mengimplementasikan protokol enkripsi komunikasi pada lapisan transport jaringan." + } + ] + } + } + }, + "INF-321-208": { + "nama": "Praktikum Jaringan Komputer", + "cpmks": { + "CPMK042": { + "desk": "Mampu merancang arsitektur jaringan komputer atau sistem IoT dasar yang aman, skalabel, dan sesuai dengan topologi standar.", + "subs": [ + { + "kode": "Sub-042-1", + "uraian": "Mampu merancang dan mengimplementasikan simulasi jaringan sesuai skenario praktikum." + }, + { + "kode": "Sub-042-2", + "uraian": "Mampu mengkonfigurasi routing statis dan dinamis pada topologi multi-router yang diberikan." + } + ] + }, + "CPMK043": { + "desk": "Mampu mengimplementasikan konfigurasi perangkat jaringan dan konektivitas sensor IoT agar terhubung ke gateway atau internet dengan stabil.", + "subs": [ + { + "kode": "Sub-043-1", + "uraian": "Mampu mengkonfigurasi perangkat jaringan secara lengkap sesuai spesifikasi praktikum." + }, + { + "kode": "Sub-043-2", + "uraian": "Mampu memverifikasi konektivitas jaringan menggunakan perintah diagnostik." + } + ] + }, + "CPMK044": { + "desk": "Mampu menganalisis potensi risiko keamanan pada arsitektur sistem jaringan atau IoT dan merumuskan langkah mitigasi dasarnya.", + "subs": [ + { + "kode": "Sub-044-1", + "uraian": "Mampu menganalisis kelemahan konfigurasi jaringan pada skenario praktikum dan memberikan rekomendasi perbaikan." + }, + { + "kode": "Sub-044-2", + "uraian": "Mampu mengimplementasikan port security dan ACL dasar pada switch/router untuk mengamankan jaringan praktikum." + } + ] + }, + "CPMK102": { + "desk": "Mampu menerapkan mekanisme enkripsi dan autentikasi pada perangkat IoT untuk menjamin kerahasiaan dan integritas data.", + "subs": [ + { + "kode": "Sub-102-1", + "uraian": "Mampu mengkonfigurasi autentikasi SSH dan enkripsi pada perangkat jaringan untuk menggantikan akses Telnet yang tidak aman." + }, + { + "kode": "Sub-102-2", + "uraian": "Mampu menguji efektivitas konfigurasi keamanan jaringan melalui skenario simulasi penetrasi dasar pada lingkungan lab terkontrol." + } + ] + } + } + }, + "INF-311-209": { + "nama": "Basis Data", + "cpmks": { + "CPMK033": { + "desk": "Mampu menganalisis dan merumuskan spesifikasi kebutuhan fungsional dan non-fungsional perangkat lunak secara lengkap berdasarkan studi kasus pengguna.", + "subs": [ + { + "kode": "Sub-033-1", + "uraian": "Mampu mengidentifikasi entitas, atribut, dan relasi dari narasi kebutuhan pengguna dan menuangkannya dalam ERD." + }, + { + "kode": "Sub-033-2", + "uraian": "Mampu memetakan ERD ke skema relasional dan menormalisasi hingga 3NF untuk menghilangkan redundansi data." + }, + { + "kode": "Sub-033-3", + "uraian": "Mampu merumuskan constraint integritas yang diperlukan berdasarkan aturan bisnis sistem." + } + ] + }, + "CPMK034": { + "desk": "Mampu mengevaluasi dan membandingkan berbagai alternatif solusi teknis berdasarkan efisiensi, kompleksitas, dan efektivitas komputasi.", + "subs": [ + { + "kode": "Sub-034-1", + "uraian": "Mampu mengimplementasikan query SQL kompleks sesuai kebutuhan." + }, + { + "kode": "Sub-034-2", + "uraian": "Mampu menganalisis execution plan query dan merancang strategi indeksasi untuk mengoptimalkan performa." + }, + { + "kode": "Sub-034-3", + "uraian": "Mampu mengevaluasi dan memilih solusi penyimpanan antara basis data relasional dan NoSQL berdasarkan karakteristik data, pola akses, dan kebutuhan skalabilitas." + } + ] + }, + "CPMK071": { + "desk": "Mampu menganalisis dan memvalidasi data menggunakan teknik cleansing yang tepat hingga menghasilkan dataset yang siap dianalisis.", + "subs": [ + { + "kode": "Sub-071-1", + "uraian": "Mampu mengidentifikasi dan menangani masalah kualitas data menggunakan operasi SQL." + }, + { + "kode": "Sub-071-2", + "uraian": "Mampu menerapkan constraint dan trigger untuk menjaga integritas dan konsistensi data secara otomatis." + } + ] + }, + "CPMK073": { + "desk": "Mampu merumuskan dan mengevaluasi rekomendasi solusi yang strategis dan logis berdasarkan hasil interpretasi data yang akurat.", + "subs": [ + { + "kode": "Sub-073-1", + "uraian": "Mampu mengimplementasikan transaksi ACID dan mekanisme rollback untuk memastikan konsistensi data dalam skenario konkurensi." + }, + { + "kode": "Sub-073-2", + "uraian": "Mampu merumuskan rekomendasi strategi optimasi basis data berdasarkan analisis performa dan kebutuhan sistem." + } + ] + }, + "CPMK091": { + "desk": "Mampu mengimplementasikan algoritma ke dalam kode program yang efisien, bebas error, dan memenuhi standar clean code.", + "subs": [ + { + "kode": "Sub-091-1", + "uraian": "Mampu mengimplementasikan DDL dan DML lengkap sesuai standar SQL." + }, + { + "kode": "Sub-091-2", + "uraian": "Mampu menulis stored procedure, fungsi, dan trigger yang bersih, terdokumentasi, dan bebas dari SQL injection." + } + ] + }, + "CPMK092": { + "desk": "Mampu merancang dan melaksanakan pengujian perangkat lunak (fungsional/unit) menggunakan metode yang terstandar untuk memastikan kode berjalan dengan benar.", + "subs": [ + { + "kode": "Sub-092-1", + "uraian": "Mampu merancang test case komprehensif untuk memverifikasi kebenaran query, stored procedure, dan trigger basis data." + }, + { + "kode": "Sub-092-2", + "uraian": "Mampu melaksanakan pengujian integritas transaksional dan constraint untuk memvalidasi implementasi basis data." + } + ] + }, + "CPMK093": { + "desk": "Mampu mengevaluasi kinerja dan fungsionalitas perangkat lunak melalui serangkaian skenario uji coba yang sistematis untuk mendeteksi bug.", + "subs": [ + { + "kode": "Sub-093-1", + "uraian": "Mampu menggunakan EXPLAIN PLAN untuk mengidentifikasi bottleneck dan menganalisis performa query secara sistematis." + }, + { + "kode": "Sub-093-2", + "uraian": "Mampu mengevaluasi efisiensi skema basis data melalui pengujian beban dan mengidentifikasi area yang perlu dioptimalkan." + } + ] + }, + "CPMK094": { + "desk": "Mampu menyusun dan mengevaluasi laporan teknis hasil pengujian perangkat lunak secara terstruktur sebagai acuan perbaikan sistem.", + "subs": [ + { + "kode": "Sub-094-1", + "uraian": "Mampu menyusun dokumentasi teknis basis data beserta laporan hasil pengujian yang terstruktur." + }, + { + "kode": "Sub-094-2", + "uraian": "Mampu merumuskan rekomendasi perbaikan terukur berdasarkan temuan pengujian dan analisis performa basis data." + } + ] + } + } + }, + "INF-321-210": { + "nama": "Praktikum Basis Data", + "cpmks": { + "CPMK033": { + "desk": "Mampu menganalisis dan merumuskan spesifikasi kebutuhan fungsional dan non-fungsional perangkat lunak secara lengkap berdasarkan studi kasus pengguna.", + "subs": [ + { + "kode": "Sub-033-1", + "uraian": "Mampu mengimplementasikan skema basis data dari ERD yang dirancang menggunakan DBMS secara lengkap." + }, + { + "kode": "Sub-033-2", + "uraian": "Mampu memverifikasi kesesuaian implementasi fisik dengan spesifikasi kebutuhan melalui pengujian constraint." + } + ] + }, + "CPMK034": { + "desk": "Mampu mengevaluasi dan membandingkan berbagai alternatif solusi teknis berdasarkan efisiensi, kompleksitas, dan efektivitas komputasi.", + "subs": [ + { + "kode": "Sub-034-1", + "uraian": "Mampu menjalankan dan membandingkan query SQL dengan strategi indeksasi berbeda pada dataset praktikum berukuran nyata." + }, + { + "kode": "Sub-034-2", + "uraian": "Mampu mengevaluasi performa query menggunakan tools EXPLAIN dan memilih solusi optimasi yang paling efektif." + } + ] + }, + "CPMK071": { + "desk": "Mampu menganalisis dan memvalidasi data menggunakan teknik cleansing yang tepat hingga menghasilkan dataset yang siap dianalisis.", + "subs": [ + { + "kode": "Sub-071-1", + "uraian": "Mampu mengeksekusi skrip cleansing data pada dataset praktikum dan memverifikasi hasilnya menggunakan query validasi." + }, + { + "kode": "Sub-071-2", + "uraian": "Mampu menerapkan constraint integritas dan membuktikan efektivitasnya melalui pengujian dengan data uji yang dirancang." + } + ] + }, + "CPMK073": { + "desk": "Mampu merumuskan dan mengevaluasi rekomendasi solusi yang strategis dan logis berdasarkan hasil interpretasi data yang akurat.", + "subs": [ + { + "kode": "Sub-073-1", + "uraian": "Mampu mengimplementasikan dan menguji transaksi multi-step dengan skenario commit dan rollback pada praktikum." + }, + { + "kode": "Sub-073-2", + "uraian": "Mampu merumuskan kesimpulan teknis dan rekomendasi perbaikan berdasarkan hasil pengujian basis data praktikum." + } + ] + }, + "CPMK091": { + "desk": "Mampu mengimplementasikan algoritma ke dalam kode program yang efisien, bebas error, dan memenuhi standar clean code.", + "subs": [ + { + "kode": "Sub-091-1", + "uraian": "Mampu mengimplementasikan seluruh komponen basis data sesuai spesifikasi praktikum." + }, + { + "kode": "Sub-091-2", + "uraian": "Mampu menghasilkan script SQL yang dapat dieksekusi ulang, terdokumentasi, dan mengikuti konvensi penamaan standar." + } + ] + }, + "CPMK092": { + "desk": "Mampu merancang dan melaksanakan pengujian perangkat lunak (fungsional/unit) menggunakan metode yang terstandar untuk memastikan kode berjalan dengan benar.", + "subs": [ + { + "kode": "Sub-092-1", + "uraian": "Mampu merancang dan menjalankan test suite untuk memverifikasi seluruh fungsionalitas basis data pada skenario praktikum." + }, + { + "kode": "Sub-092-2", + "uraian": "Mampu mendokumentasikan hasil pengujian dalam format laporan pengujian yang mencakup input, ekspektasi, dan aktual." + } + ] + }, + "CPMK093": { + "desk": "Mampu mengevaluasi kinerja dan fungsionalitas perangkat lunak melalui serangkaian skenario uji coba yang sistematis untuk mendeteksi bug.", + "subs": [ + { + "kode": "Sub-093-1", + "uraian": "Mampu menganalisis kinerja query pada dataset berskala menggunakan tools profiling DBMS dan menginterpretasikan hasilnya." + }, + { + "kode": "Sub-093-2", + "uraian": "Mampu mengidentifikasi dan memperbaiki bottleneck performa dalam implementasi basis data praktikum secara terukur." + } + ] + }, + "CPMK094": { + "desk": "Mampu menyusun dan mengevaluasi laporan teknis hasil pengujian perangkat lunak secara terstruktur sebagai acuan perbaikan sistem.", + "subs": [ + { + "kode": "Sub-094-1", + "uraian": "Mampu menyusun laporan teknis praktikum basis data yang mencakup perancangan, implementasi, pengujian, dan analisis." + }, + { + "kode": "Sub-094-2", + "uraian": "Mampu mempresentasikan temuan dan rekomendasi implementasi basis data kepada instruktur secara terstruktur dan percaya diri." + } + ] + } + } + }, + "INF-311-211": { + "nama": "Struktur Data", + "cpmks": { + "CPMK031": { + "desk": "Mampu menganalisis konsep dasar komputasi dan struktur data untuk menjelaskan keterkaitannya dalam penyelesaian masalah komputasi secara tepat.", + "subs": [ + { + "kode": "Sub-031-1", + "uraian": "Mampu menjelaskan karakteristik, kompleksitas waktu, dan ruang dari struktur data." + }, + { + "kode": "Sub-031-2", + "uraian": "Mampu menganalisis kesesuaian pemilihan struktur data untuk menyelesaikan masalah komputasi tertentu secara efisien." + } + ] + }, + "CPMK034": { + "desk": "Mampu mengevaluasi dan membandingkan berbagai alternatif solusi teknis berdasarkan efisiensi, kompleksitas, dan efektivitas komputasi.", + "subs": [ + { + "kode": "Sub-034-1", + "uraian": "Mampu membandingkan kompleksitas operasi antara berbagai struktur data untuk skenario penggunaan tertentu." + }, + { + "kode": "Sub-034-2", + "uraian": "Mampu mengevaluasi trade-off antara penggunaan memori dan kecepatan akses dalam pemilihan struktur data." + } + ] + }, + "CPMK051": { + "desk": "Mampu menganalisis dan menerapkan teknik abstraksi dan dekomposisi untuk memecahkan masalah kompleks menjadi sub-masalah yang lebih kecil dan terkelola.", + "subs": [ + { + "kode": "Sub-051-1", + "uraian": "Mampu mendekomposisi masalah komputasi kompleks menjadi sub-masalah yang masing-masing dapat diselesaikan dengan struktur data tertentu." + }, + { + "kode": "Sub-051-2", + "uraian": "Mampu mengabstraksi kebutuhan penyimpanan dan akses data ke dalam pemilihan struktur data yang tepat." + } + ] + }, + "CPMK052": { + "desk": "Mampu merancang solusi algoritmik yang logis dan terstruktur menggunakan pseudocode atau flowchart untuk menyelesaikan permasalahan komputasi.", + "subs": [ + { + "kode": "Sub-052-1", + "uraian": "Mampu merancang pseudocode untuk operasi kunci pada struktur data." + }, + { + "kode": "Sub-052-2", + "uraian": "Mampu mengembangkan algoritma efisien berbasis struktur data untuk menyelesaikan soal pemrograman yang diberikan." + } + ] + }, + "CPMK053": { + "desk": "Mampu mengevaluasi validitas logika dan efisiensi dari berbagai alternatif algoritma sebelum diimplementasikan ke dalam kode program.", + "subs": [ + { + "kode": "Sub-053-1", + "uraian": "Mampu memvalidasi kebenaran algoritma traversal dan manipulasi struktur data melalui hand tracing pada data uji." + }, + { + "kode": "Sub-053-2", + "uraian": "Mampu mengevaluasi dan memilih implementasi struktur data yang paling efisien dari beberapa alternatif yang tersedia." + } + ] + }, + "CPMK091": { + "desk": "Mampu mengimplementasikan algoritma ke dalam kode program yang efisien, bebas error, dan memenuhi standar clean code.", + "subs": [ + { + "kode": "Sub-091-1", + "uraian": "Mampu mengimplementasikan struktur data dari awal menggunakan bahasa pemrograman yang ditetapkan." + }, + { + "kode": "Sub-091-2", + "uraian": "Mampu menulis kode implementasi struktur data yang bersih, efisien, dan dilengkapi komentar dokumentasi yang memadai." + } + ] + } + } + }, + "INF-321-212": { + "nama": "Praktikum Struktur Data", + "cpmks": { + "CPMK031": { + "desk": "Mampu menganalisis konsep dasar komputasi dan struktur data untuk menjelaskan keterkaitannya dalam penyelesaian masalah komputasi secara tepat.", + "subs": [ + { + "kode": "Sub-031-1", + "uraian": "Mampu mengimplementasikan dan menguji operasi dasar setiap struktur data pada sesi praktikum sesuai spesifikasi yang diberikan." + }, + { + "kode": "Sub-031-2", + "uraian": "Mampu menganalisis dan menjelaskan alur eksekusi kode struktur data yang diberikan saat sesi review praktikum." + } + ] + }, + "CPMK034": { + "desk": "Mampu mengevaluasi dan membandingkan berbagai alternatif solusi teknis berdasarkan efisiensi, kompleksitas, dan efektivitas komputasi.", + "subs": [ + { + "kode": "Sub-034-1", + "uraian": "Mampu mengukur dan membandingkan waktu eksekusi operasi pada struktur data berbeda menggunakan dataset praktikum." + }, + { + "kode": "Sub-034-2", + "uraian": "Mampu membuktikan pilihan struktur data yang optimal melalui eksperimen performa pada praktikum." + } + ] + }, + "CPMK051": { + "desk": "Mampu menganalisis dan menerapkan teknik abstraksi dan dekomposisi untuk memecahkan masalah kompleks menjadi sub-masalah yang lebih kecil dan terkelola.", + "subs": [ + { + "kode": "Sub-051-1", + "uraian": "Mampu mendekomposisi soal praktikum kompleks yang membutuhkan kombinasi beberapa struktur data menjadi bagian yang terkelola." + }, + { + "kode": "Sub-051-2", + "uraian": "Mampu mengabstraksi kebutuhan soal praktikum ke dalam pemilihan dan perancangan struktur data yang tepat." + } + ] + }, + "CPMK052": { + "desk": "Mampu merancang solusi algoritmik yang logis dan terstruktur menggunakan pseudocode atau flowchart untuk menyelesaikan permasalahan komputasi.", + "subs": [ + { + "kode": "Sub-052-1", + "uraian": "Mampu merancang pseudocode solusi setiap soal praktikum sebelum implementasi dan mendiskusikannya dengan instruktur." + }, + { + "kode": "Sub-052-2", + "uraian": "Mampu mengembangkan kode praktikum yang modular dan menggunakan struktur data yang sesuai dengan kebutuhan." + } + ] + }, + "CPMK053": { + "desk": "Mampu mengevaluasi validitas logika dan efisiensi dari berbagai alternatif algoritma sebelum diimplementasikan ke dalam kode program.", + "subs": [ + { + "kode": "Sub-053-1", + "uraian": "Mampu memvalidasi implementasi struktur data dengan menjalankan serangkaian test case edge case yang dirancang sendiri." + }, + { + "kode": "Sub-053-2", + "uraian": "Mampu mengevaluasi kode temannya dan mengidentifikasi inefficiency dalam penggunaan struktur data." + } + ] + } + } + }, + "INF-311-301": { + "nama": "Rekayasa Perangkat Lunak", + "cpmks": { + "CPMK033": { + "desk": "Mampu menganalisis dan merumuskan spesifikasi kebutuhan fungsional dan non-fungsional perangkat lunak secara lengkap berdasarkan studi kasus pengguna.", + "subs": [ + { + "kode": "Sub-033-1", + "uraian": "Mampu mengidentifikasi kebutuhan fungsional dan non-fungsional sistem perangkat lunak melalui teknik elisitasi." + }, + { + "kode": "Sub-033-2", + "uraian": "Mampu merumuskan spesifikasi kebutuhan dalam format SRS yang lengkap dan terstruktur." + } + ] + }, + "CPMK051": { + "desk": "Mampu menganalisis dan menerapkan teknik abstraksi dan dekomposisi untuk memecahkan masalah kompleks menjadi sub-masalah yang lebih kecil dan terkelola.", + "subs": [ + { + "kode": "Sub-051-1", + "uraian": "Mampu mendekomposisi sistem perangkat lunak kompleks menjadi modul-modul dengan tanggung jawab yang jelas menggunakan UML." + }, + { + "kode": "Sub-051-2", + "uraian": "Mampu mengabstraksi kebutuhan pengguna ke dalam arsitektur solusi yang dapat dikerjakan secara bertahap." + } + ] + }, + "CPMK061": { + "desk": "Mampu menyusun dan mengevaluasi dokumen spesifikasi teknis perangkat lunak yang lengkap dan sesuai dengan standar industri.", + "subs": [ + { + "kode": "Sub-061-1", + "uraian": "Mampu menyusun dokumen SRS, SDD, dan test plan sesuai standar IEEE yang lengkap dan dapat digunakan oleh tim pengembang." + }, + { + "kode": "Sub-061-2", + "uraian": "Mampu mengevaluasi kualitas dokumen spesifikasi menggunakan checklist standar industri dan memberikan umpan balik konstruktif." + } + ] + }, + "CPMK063": { + "desk": "Mampu berkolaborasi secara produktif dalam tim proyek perangkat lunak menggunakan alat manajemen proyek untuk mencapai target bersama.", + "subs": [ + { + "kode": "Sub-063-1", + "uraian": "Mampu mengelola backlog proyek menggunakan metodologi Agile/Scrum dengan sprint planning dan daily standup yang efektif." + }, + { + "kode": "Sub-063-2", + "uraian": "Mampu membandingkan model proses pengembangan perangkat lunak dan memilih model yang tepat berdasarkan karakteristik proyek." + } + ] + }, + "CPMK081": { + "desk": "Mampu menganalisis dan merumuskan kebutuhan pengguna ke dalam bentuk User Stories atau spesifikasi kebutuhan yang komprehensif dan mudah dipahami.", + "subs": [ + { + "kode": "Sub-081-1", + "uraian": "Mampu mengidentifikasi dan mendokumentasikan kebutuhan pengguna dalam format User Stories dengan kriteria penerimaan yang jelas." + }, + { + "kode": "Sub-081-2", + "uraian": "Mampu merancang strategi pengujian perangkat lunak yang mencakup unit test, integration test, system test, dan acceptance test dalam konteks SDLC." + } + ] + }, + "CPMK082": { + "desk": "Mampu merancang arsitektur solusi sistem yang selaras dengan spesifikasi kebutuhan pengguna yang telah disepakati.", + "subs": [ + { + "kode": "Sub-082-1", + "uraian": "Mampu merancang arsitektur perangkat lunak yang sesuai dengan kebutuhan dan constraint sistem." + }, + { + "kode": "Sub-082-2", + "uraian": "Mampu mendokumentasikan arsitektur solusi menggunakan diagram komponen, deployment, dan sequence yang tepat." + } + ] + } + } + }, + "INF-311-302": { + "nama": "Sistem Informasi", + "cpmks": { + "CPMK021": { + "desk": "Mampu menganalisis isu sosial, budaya, dan lingkungan yang relevan dengan penerapan TI melalui kajian studi kasus nyata di masyarakat.", + "subs": [ + { + "kode": "Sub-021-1", + "uraian": "Mampu mengidentifikasi permasalahan organisasi yang dapat diselesaikan dengan penerapan sistem informasi yang tepat." + }, + { + "kode": "Sub-021-2", + "uraian": "Mampu menganalisis dampak sosial dan organisasional dari implementasi sistem informasi pada studi kasus nyata." + } + ] + }, + "CPMK022": { + "desk": "Mampu mengevaluasi dampak sosial dan keberlanjutan dari sebuah solusi TI menggunakan kerangka analisis dampak yang sistematis, objektif dan berbasis data.", + "subs": [ + { + "kode": "Sub-022-1", + "uraian": "Mampu membuat DFD level 0 dan level 1 untuk memodelkan aliran data dalam sistem informasi organisasi." + }, + { + "kode": "Sub-022-2", + "uraian": "Mampu mengidentifikasi dan menjelaskan kategori teknologi pendukung SI beserta perannya dalam mendukung proses bisnis organisasi." + } + ] + }, + "CPMK033": { + "desk": "Mampu menganalisis dan merumuskan spesifikasi kebutuhan fungsional dan non-fungsional perangkat lunak secara lengkap berdasarkan studi kasus pengguna.", + "subs": [ + { + "kode": "Sub-033-1", + "uraian": "Mampu mengidentifikasi kebutuhan informasi organisasi dan merumuskannya ke dalam spesifikasi fungsional sistem informasi." + }, + { + "kode": "Sub-033-2", + "uraian": "Mampu menganalisis proses bisnis existing dan merumuskan kebutuhan sistem informasi yang mendukung efisiensinya." + } + ] + }, + "CPMK062": { + "desk": "Mampu menyajikan dan mempertahankan gagasan teknis secara lisan dengan argumentasi yang logis dan didukung media visual yang efektif.", + "subs": [ + { + "kode": "Sub-062-1", + "uraian": "Mampu mempresentasikan analisis sistem informasi dengan visualisasi yang efektif dan mudah dipahami." + }, + { + "kode": "Sub-062-2", + "uraian": "Mampu mempertahankan rekomendasi desain sistem informasi kepada pemangku kepentingan dengan argumentasi berbasis data." + } + ] + }, + "CPMK081": { + "desk": "Mampu menganalisis dan merumuskan kebutuhan pengguna ke dalam bentuk User Stories atau spesifikasi kebutuhan yang komprehensif dan mudah dipahami.", + "subs": [ + { + "kode": "Sub-081-1", + "uraian": "Mampu menganalisis dan mendokumentasikan kebutuhan pengguna sistem informasi melalui wawancara dan observasi proses bisnis." + }, + { + "kode": "Sub-081-2", + "uraian": "Mampu menyusun spesifikasi kebutuhan sistem informasi yang mudah dipahami oleh pengguna non-teknis." + } + ] + } + } + }, + "INF-411-303": { + "nama": "Interaksi Manusia dan Komputer", + "cpmks": { + "CPMK081": { + "desk": "Mampu menganalisis dan merumuskan kebutuhan pengguna ke dalam bentuk User Stories atau spesifikasi kebutuhan yang komprehensif dan mudah dipahami.", + "subs": [ + { + "kode": "Sub-081-1", + "uraian": "Mampu mengidentifikasi kebutuhan dan karakteristik pengguna melalui teknik user research." + }, + { + "kode": "Sub-081-2", + "uraian": "Mampu menjelaskan proses User-Centered Design sebagai kerangka kerja pengembangan antarmuka dan menerapkannya dalam tahapan penelitian, desain, prototyping, dan evaluasi." + } + ] + }, + "CPMK083": { + "desk": "Mampu mengintegrasikan prinsip kemudahan penggunaan (usability) dan keberlanjutan sistem ke dalam rancangan antarmuka pengguna.", + "subs": [ + { + "kode": "Sub-083-1", + "uraian": "Mampu merancang wireframe dan mockup antarmuka yang mempertimbangkan prinsip usability secara konsisten." + }, + { + "kode": "Sub-083-2", + "uraian": "Mampu mengintegrasikan prinsip aksesibilitas ke dalam rancangan antarmuka pengguna yang inklusif." + } + ] + }, + "CPMK084": { + "desk": "Mampu mengembangkan dan mengevaluasi prototipe sistem interaktif melalui pengujian pengguna (user testing) untuk memastikan kesesuaian desain.", + "subs": [ + { + "kode": "Sub-084-1", + "uraian": "Mampu melakukan heuristic evaluation menggunakan prinsip usability terhadap antarmuka yang ada dan mendokumentasikan temuan berdasarkan tingkat keparahan." + }, + { + "kode": "Sub-084-2", + "uraian": "Mampu mengevaluasi prototipe melalui sesi user testing terstruktur dan merevisi desain berdasarkan temuan." + } + ] + } + } + }, + "INF-411-304": { + "nama": "Analisis dan Perancangan Sistem Informasi", + "cpmks": { + "CPMK033": { + "desk": "Mampu menganalisis dan merumuskan spesifikasi kebutuhan fungsional dan non-fungsional perangkat lunak secara lengkap berdasarkan studi kasus pengguna.", + "subs": [ + { + "kode": "Sub-033-1", + "uraian": "Mampu menganalisis kebutuhan sistem informasi yang kompleks menggunakan teknik elisitasi terstruktur." + }, + { + "kode": "Sub-033-2", + "uraian": "Mampu merumuskan spesifikasi kebutuhan fungsional dan non-fungsional lengkap untuk sistem informasi organisasi." + } + ] + }, + "CPMK061": { + "desk": "Mampu menyusun dan mengevaluasi dokumen spesifikasi teknis perangkat lunak yang lengkap dan sesuai dengan standar industri.", + "subs": [ + { + "kode": "Sub-061-1", + "uraian": "Mampu menyusun dokumen analisis sistem yang komprehensif dan sesuai standar dokumentasi yang ditetapkan." + }, + { + "kode": "Sub-061-2", + "uraian": "Mampu mengevaluasi kelengkapan dan konsistensi dokumentasi analisis sistem menggunakan teknik walkthrough." + } + ] + }, + "CPMK081": { + "desk": "Mampu menganalisis dan merumuskan kebutuhan pengguna ke dalam bentuk User Stories atau spesifikasi kebutuhan yang komprehensif dan mudah dipahami.", + "subs": [ + { + "kode": "Sub-081-1", + "uraian": "Mampu menganalisis dan mendokumentasikan kebutuhan pengguna sistem informasi dalam format yang terstruktur dan dapat divalidasi." + }, + { + "kode": "Sub-081-2", + "uraian": "Mampu memprioritaskan dan menegosiasikan kebutuhan dengan pemangku kepentingan berdasarkan urgensi dan dampak bisnis." + } + ] + }, + "CPMK082": { + "desk": "Mampu merancang arsitektur solusi sistem yang selaras dengan spesifikasi kebutuhan pengguna yang telah disepakati.", + "subs": [ + { + "kode": "Sub-082-1", + "uraian": "Mampu merancang arsitektur sistem informasi yang sesuai dengan kebutuhan, constraint anggaran, dan kapasitas infrastruktur." + }, + { + "kode": "Sub-082-2", + "uraian": "Mampu mendokumentasikan rancangan arsitektur sistem menggunakan diagram yang standar dan dapat dikomunikasikan kepada tim." + } + ] + }, + "CPMK083": { + "desk": "Mampu mengintegrasikan prinsip kemudahan penggunaan (usability) dan keberlanjutan sistem ke dalam rancangan antarmuka pengguna.", + "subs": [ + { + "kode": "Sub-083-1", + "uraian": "Mampu membuat Use Case Diagram dan skenario Use Case yang lengkap berdasarkan kebutuhan pengguna yang telah diidentifikasi." + }, + { + "kode": "Sub-083-2", + "uraian": "Mampu membuat DFD level 0, level 1, dan level 2 untuk memodelkan aliran data dan proses dalam sistem informasi yang dianalisis." + } + ] + } + } + }, + "INF-411-305": { + "nama": "Sistem Paralel dan Terdistribusi", + "cpmks": { + "CPMK031": { + "desk": "Mampu menganalisis konsep dasar komputasi dan struktur data untuk menjelaskan keterkaitannya dalam penyelesaian masalah komputasi secara tepat.", + "subs": [ + { + "kode": "Sub-031-1", + "uraian": "Mampu menjelaskan model komputasi paralel dan implikasinya terhadap desain algoritma." + }, + { + "kode": "Sub-031-2", + "uraian": "Mampu menganalisis masalah race condition, deadlock, dan starvation dalam sistem konkurensi dan cara penanganannya." + } + ] + }, + "CPMK034": { + "desk": "Mampu mengevaluasi dan membandingkan berbagai alternatif solusi teknis berdasarkan efisiensi, kompleksitas, dan efektivitas komputasi.", + "subs": [ + { + "kode": "Sub-034-1", + "uraian": "Mampu membandingkan arsitektur sistem terdistribusi berdasarkan kebutuhan skalabilitas." + }, + { + "kode": "Sub-034-2", + "uraian": "Mampu mengevaluasi trade-off konsistensi-availabilitas-partisi dalam desain sistem terdistribusi." + } + ] + }, + "CPMK041": { + "desk": "Mampu menganalisis dan menetapkan spesifikasi kebutuhan infrastruktur jaringan dan standar keamanan sistem yang diperlukan untuk mendukung kinerja aplikasi secara optimal.", + "subs": [ + { + "kode": "Sub-041-1", + "uraian": "Mampu menjelaskan model layanan cloud dan deployment model serta relevansinya sebagai infrastruktur sistem terdistribusi modern." + }, + { + "kode": "Sub-041-2", + "uraian": "Mampu menganalisis mekanisme komunikasi antar proses dan menentukan model yang sesuai untuk skenario sistem terdistribusi tertentu." + } + ] + }, + "CPMK042": { + "desk": "Mampu merancang arsitektur jaringan komputer atau sistem IoT dasar yang aman, skalabel, dan sesuai dengan topologi standar.", + "subs": [ + { + "kode": "Sub-042-1", + "uraian": "Mampu merancang arsitektur sistem terdistribusi sederhana yang mempertimbangkan skalabilitas dan fault tolerance." + }, + { + "kode": "Sub-042-2", + "uraian": "Mampu merancang mekanisme komunikasi antar proses yang efisien untuk sistem paralel." + } + ] + }, + "CPMK044": { + "desk": "Mampu menganalisis potensi risiko keamanan pada arsitektur sistem jaringan atau IoT dan merumuskan langkah mitigasi dasarnya.", + "subs": [ + { + "kode": "Sub-044-1", + "uraian": "Mampu menganalisis potensi ancaman keamanan dalam komunikasi antar node sistem terdistribusi dan merumuskan mitigasinya." + }, + { + "kode": "Sub-044-2", + "uraian": "Mampu merancang strategi autentikasi dan otorisasi untuk mengamankan akses dalam sistem terdistribusi." + } + ] + } + } + }, + "INF-411-213": { + "nama": "Pemrograman Berorientasi Objek", + "cpmks": { + "CPMK031": { + "desk": "Mampu menganalisis konsep dasar komputasi dan struktur data untuk menjelaskan keterkaitannya dalam penyelesaian masalah komputasi secara tepat.", + "subs": [ + { + "kode": "Sub-031-1", + "uraian": "Mampu menjelaskan konsep OOP beserta contoh implementasinya." + }, + { + "kode": "Sub-031-2", + "uraian": "Mampu menganalisis kode OOP yang diberikan dan mengidentifikasi prinsip OOP mana yang diterapkan atau dilanggar." + } + ] + }, + "CPMK051": { + "desk": "Mampu menganalisis dan menerapkan teknik abstraksi dan dekomposisi untuk memecahkan masalah kompleks menjadi sub-masalah yang lebih kecil dan terkelola.", + "subs": [ + { + "kode": "Sub-051-1", + "uraian": "Mampu mendekomposisi masalah ke dalam hierarki kelas yang bersih menggunakan prinsip Single Responsibility." + }, + { + "kode": "Sub-051-2", + "uraian": "Mampu mengabstraksi entitas dunia nyata menjadi kelas dengan atribut dan metode yang kohesif dan terenkapsulasi." + } + ] + }, + "CPMK052": { + "desk": "Mampu merancang solusi algoritmik yang logis dan terstruktur menggunakan pseudocode atau flowchart untuk menyelesaikan permasalahan komputasi.", + "subs": [ + { + "kode": "Sub-052-1", + "uraian": "Mampu merancang diagram kelas UML yang menggambarkan hubungan antar kelas." + }, + { + "kode": "Sub-052-2", + "uraian": "Mampu mengembangkan solusi OOP menggunakan design pattern dasar yang sesuai konteks." + } + ] + }, + "CPMK053": { + "desk": "Mampu mengevaluasi validitas logika dan efisiensi dari berbagai alternatif algoritma sebelum diimplementasikan ke dalam kode program.", + "subs": [ + { + "kode": "Sub-053-1", + "uraian": "Mampu mengevaluasi desain kelas menggunakan prinsip SOLID dan mengidentifikasi pelanggaran yang perlu diperbaiki." + }, + { + "kode": "Sub-053-2", + "uraian": "Mampu membandingkan solusi OOP berbeda dan memilih yang paling maintainable berdasarkan kriteria yang jelas." + } + ] + }, + "CPMK091": { + "desk": "Mampu mengimplementasikan algoritma ke dalam kode program yang efisien, bebas error, dan memenuhi standar clean code.", + "subs": [ + { + "kode": "Sub-091-1", + "uraian": "Mampu mengimplementasikan hierarki kelas yang menggunakan inheritance, polymorphism, dan interface atau abstract class secara tepat sesuai konteks masalah." + }, + { + "kode": "Sub-091-2", + "uraian": "Mampu menulis kode OOP yang mengikuti prinsip clean code dan dilengkapi dokumentasi." + } + ] + }, + "CPMK092": { + "desk": "Mampu merancang dan melaksanakan pengujian perangkat lunak (fungsional/unit) menggunakan metode yang terstandar untuk memastikan kode berjalan dengan benar.", + "subs": [ + { + "kode": "Sub-092-1", + "uraian": "Mampu merancang dan mengimplementasikan unit test untuk setiap kelas menggunakan framework testing yang ditetapkan." + }, + { + "kode": "Sub-092-2", + "uraian": "Mampu melaksanakan pengujian integrasi antar kelas untuk memverifikasi interaksi dan perilaku polimorfik." + } + ] + }, + "CPMK093": { + "desk": "Mampu mengevaluasi kinerja dan fungsionalitas perangkat lunak melalui serangkaian skenario uji coba yang sistematis untuk mendeteksi bug.", + "subs": [ + { + "kode": "Sub-093-1", + "uraian": "Mampu mengevaluasi kualitas kode OOP menggunakan metrik dan mengidentifikasi code smell." + }, + { + "kode": "Sub-093-2", + "uraian": "Mampu melakukan refactoring kode OOP yang bermasalah dan memverifikasi bahwa fungsionalitas tetap terjaga." + } + ] + } + } + }, + "INF-411-105": { + "nama": "Metode Numerik", + "cpmks": { + "CPMK032": { + "desk": "Mampu menganalisis teori komputasi dan model matematika untuk memformulasikan solusi masalah teknis secara sistematis.", + "subs": [ + { + "kode": "Sub-032-1", + "uraian": "Mampu menerapkan metode numerik untuk menyelesaikan persamaan non-linier." + }, + { + "kode": "Sub-032-2", + "uraian": "Mampu menganalisis galat truncation dan rounding dalam metode numerik serta strategi pengendaliannya." + } + ] + }, + "CPMK034": { + "desk": "Mampu mengevaluasi dan membandingkan berbagai alternatif solusi teknis berdasarkan efisiensi, kompleksitas, dan efektivitas komputasi.", + "subs": [ + { + "kode": "Sub-034-1", + "uraian": "Mampu membandingkan metode interpolasi berdasarkan akurasi dan kompleksitas komputasinya." + }, + { + "kode": "Sub-034-2", + "uraian": "Mampu mengevaluasi dan memilih metode integrasi numerik yang optimal untuk kasus tertentu." + } + ] + }, + "CPMK052": { + "desk": "Mampu merancang solusi algoritmik yang logis dan terstruktur menggunakan pseudocode atau flowchart untuk menyelesaikan permasalahan komputasi.", + "subs": [ + { + "kode": "Sub-052-1", + "uraian": "Mampu merancang pseudocode untuk algoritma metode numerik iteratif dengan kriteria konvergensi yang tepat." + }, + { + "kode": "Sub-052-2", + "uraian": "Mampu mengembangkan alur komputasi numerik yang terstruktur dengan penanganan kasus divergensi." + } + ] + }, + "CPMK053": { + "desk": "Mampu mengevaluasi validitas logika dan efisiensi dari berbagai alternatif algoritma sebelum diimplementasikan ke dalam kode program.", + "subs": [ + { + "kode": "Sub-053-1", + "uraian": "Mampu memvalidasi solusi numerik dengan membandingkannya terhadap solusi analitik atau referensi yang diketahui." + }, + { + "kode": "Sub-053-2", + "uraian": "Mampu mengevaluasi tingkat akurasi dan efisiensi komputasi dari beberapa alternatif metode numerik." + } + ] + }, + "CPMK071": { + "desk": "Mampu menganalisis dan memvalidasi data menggunakan teknik cleansing yang tepat hingga menghasilkan dataset yang siap dianalisis.", + "subs": [ + { + "kode": "Sub-071-1", + "uraian": "Mampu menerapkan metode interpolasi untuk mengaproksimasi nilai fungsi di titik yang tidak diketahui dan menganalisis galat interpolasi." + }, + { + "kode": "Sub-071-2", + "uraian": "Mampu menerapkan metode diferensiasi numerik dan menganalisis galat truncation yang dihasilkan." + } + ] + }, + "CPMK072": { + "desk": "Mampu menganalisis dan mengevaluasi pola serta tren data menggunakan metode statistik atau algoritma Machine Learning dasar untuk mendapatkan informasi yang berguna.", + "subs": [ + { + "kode": "Sub-072-1", + "uraian": "Mampu menerapkan metode integrasi numerik dan membandingkan akurasi antar metode." + }, + { + "kode": "Sub-072-2", + "uraian": "Mampu menganalisis konvergensi dan kestabilan metode numerik iteratif serta menentukan kriteria berhenti yang tepat berdasarkan galat relatif." + } + ] + } + } + }, + "INF-411-214": { + "nama": "Strategi Algoritma", + "cpmks": { + "CPMK034": { + "desk": "Mampu mengevaluasi dan membandingkan berbagai alternatif solusi teknis berdasarkan efisiensi, kompleksitas, dan efektivitas komputasi.", + "subs": [ + { + "kode": "Sub-034-1", + "uraian": "Mampu membandingkan solusi brute force dengan solusi optimal menggunakan paradigma divide-and-conquer atau dynamic programming." + }, + { + "kode": "Sub-034-2", + "uraian": "Mampu mengevaluasi kompleksitas waktu dan ruang solusi algoritmik dengan notasi Big-O dan mengidentifikasi bottleneck." + } + ] + }, + "CPMK051": { + "desk": "Mampu menganalisis dan menerapkan teknik abstraksi dan dekomposisi untuk memecahkan masalah kompleks menjadi sub-masalah yang lebih kecil dan terkelola.", + "subs": [ + { + "kode": "Sub-051-1", + "uraian": "Mampu mendekomposisi masalah komputasi kompleks menjadi sub-masalah yang dapat diselesaikan secara rekursif atau iteratif." + }, + { + "kode": "Sub-051-2", + "uraian": "Mampu mengabstraksi pola masalah untuk menentukan strategi algoritmik yang tepat." + } + ] + }, + "CPMK052": { + "desk": "Mampu merancang solusi algoritmik yang logis dan terstruktur menggunakan pseudocode atau flowchart untuk menyelesaikan permasalahan komputasi.", + "subs": [ + { + "kode": "Sub-052-1", + "uraian": "Mampu merancang pseudocode algoritma menggunakan strategi greedy, dynamic programming, atau backtracking untuk masalah yang diberikan." + }, + { + "kode": "Sub-052-2", + "uraian": "Mampu mengembangkan algoritma efisien dengan mempertimbangkan trade-off waktu dan memori yang disepakati." + } + ] + }, + "CPMK053": { + "desk": "Mampu mengevaluasi validitas logika dan efisiensi dari berbagai alternatif algoritma sebelum diimplementasikan ke dalam kode program.", + "subs": [ + { + "kode": "Sub-053-1", + "uraian": "Mampu membuktikan keoptimalan solusi greedy atau DP melalui argumen matematis atau simulasi pada kasus uji representatif." + }, + { + "kode": "Sub-053-2", + "uraian": "Mampu mengevaluasi dan membandingkan beberapa alternatif algoritma untuk masalah yang sama berdasarkan kompleksitas asimptotik." + } + ] + }, + "CPMK091": { + "desk": "Mampu mengimplementasikan algoritma ke dalam kode program yang efisien, bebas error, dan memenuhi standar clean code.", + "subs": [ + { + "kode": "Sub-091-1", + "uraian": "Mampu mengimplementasikan algoritma lanjutan dalam kode yang efisien dan benar." + }, + { + "kode": "Sub-091-2", + "uraian": "Mampu mengoptimalkan kode algoritmik menggunakan teknik memoization atau pruning untuk meningkatkan performa." + } + ] + } + } + }, + "INF-421-215": { + "nama": "Praktikum Strategi Algoritma", + "cpmks": { + "CPMK034": { + "desk": "Mampu mengevaluasi dan membandingkan berbagai alternatif solusi teknis berdasarkan efisiensi, kompleksitas, dan efektivitas komputasi.", + "subs": [ + { + "kode": "Sub-034-1", + "uraian": "Mampu mengukur dan membandingkan waktu eksekusi algoritma berbeda untuk masalah yang sama pada dataset praktikum." + }, + { + "kode": "Sub-034-2", + "uraian": "Mampu membuktikan secara empiris superioritas algoritma optimal atas brute force pada ukuran input yang meningkat." + } + ] + }, + "CPMK051": { + "desk": "Mampu menganalisis dan menerapkan teknik abstraksi dan dekomposisi untuk memecahkan masalah kompleks menjadi sub-masalah yang lebih kecil dan terkelola.", + "subs": [ + { + "kode": "Sub-051-1", + "uraian": "Mampu mendekomposisi soal praktikum algoritma kompleks menjadi sub-masalah dan mengidentifikasi strategi penyelesaian yang tepat." + }, + { + "kode": "Sub-051-2", + "uraian": "Mampu mengabstraksi pola algoritma dari soal-soal serupa untuk membangun pendekatan solusi yang generik." + } + ] + }, + "CPMK052": { + "desk": "Mampu merancang solusi algoritmik yang logis dan terstruktur menggunakan pseudocode atau flowchart untuk menyelesaikan permasalahan komputasi.", + "subs": [ + { + "kode": "Sub-052-1", + "uraian": "Mampu merancang pseudocode lengkap untuk setiap soal praktikum sebelum implementasi dan mendokumentasikannya." + }, + { + "kode": "Sub-052-2", + "uraian": "Mampu mengimplementasikan solusi algoritmik yang terstruktur dan memenuhi seluruh test case pada platform penilaian." + } + ] + }, + "CPMK053": { + "desk": "Mampu mengevaluasi validitas logika dan efisiensi dari berbagai alternatif algoritma sebelum diimplementasikan ke dalam kode program.", + "subs": [ + { + "kode": "Sub-053-1", + "uraian": "Mampu menganalisis dan memperbaiki solusi yang tidak efisien menjadi solusi dengan kompleksitas yang lebih baik." + }, + { + "kode": "Sub-053-2", + "uraian": "Mampu mengevaluasi kode teman dan mengidentifikasi peluang optimasi algoritma yang konkret." + } + ] + } + } + }, + "INF-411-306": { + "nama": "Manajemen Jaringan", + "cpmks": { + "CPMK041": { + "desk": "Mampu menganalisis dan menetapkan spesifikasi kebutuhan infrastruktur jaringan dan standar keamanan sistem yang diperlukan untuk mendukung kinerja aplikasi secara optimal.", + "subs": [ + { + "kode": "Sub-041-1", + "uraian": "Mampu menganalisis kebutuhan infrastruktur jaringan enterprise berdasarkan trafik, keandalan, dan SLA yang ditetapkan." + }, + { + "kode": "Sub-041-2", + "uraian": "Mampu menetapkan spesifikasi teknis keamanan jaringan berdasarkan profil ancaman organisasi." + } + ] + }, + "CPMK042": { + "desk": "Mampu merancang arsitektur jaringan komputer atau sistem IoT dasar yang aman, skalabel, dan sesuai dengan topologi standar.", + "subs": [ + { + "kode": "Sub-042-1", + "uraian": "Mampu merancang topologi jaringan enterprise berskala besar dengan redundansi dan load balancing yang sesuai." + }, + { + "kode": "Sub-042-2", + "uraian": "Mampu merancang dan mengkonfigurasi segmentasi VLAN beserta inter-VLAN routing dan trunking pada topologi jaringan enterprise yang diberikan." + } + ] + }, + "CPMK044": { + "desk": "Mampu menganalisis potensi risiko keamanan pada arsitektur sistem jaringan atau IoT dan merumuskan langkah mitigasi dasarnya.", + "subs": [ + { + "kode": "Sub-044-1", + "uraian": "Mampu menerapkan protokol dan tools network monitoring untuk memantau kondisi dan kinerja jaringan secara proaktif." + }, + { + "kode": "Sub-044-2", + "uraian": "Mampu mengotomatisasi konfigurasi perangkat jaringan menggunakan skrip dan menjelaskan manfaat network automation untuk operasi jaringan berskala besar." + } + ] + }, + "CPMK102": { + "desk": "Mampu menerapkan mekanisme enkripsi dan autentikasi pada perangkat IoT untuk menjamin kerahasiaan dan integritas data.", + "subs": [ + { + "kode": "Sub-102-1", + "uraian": "Mampu mengkonfigurasi dan memverifikasi VPN site-to-site menggunakan protokol IPSec untuk komunikasi aman antar cabang." + }, + { + "kode": "Sub-102-2", + "uraian": "Mampu menerapkan 802.1X dan RADIUS untuk autentikasi berbasis sertifikat pada jaringan enterprise." + } + ] + } + } + }, + "INF-411-503": { + "nama": "Etika, Masyarakat dan Komputer Keberlanjutan", + "cpmks": { + "CPMK011": { + "desk": "Mampu menerapkan nilai etika, kejujuran, dan integritas akademik secara konsisten dalam setiap aktivitas perkuliahan dan pengerjaan tugas tanpa melakukan plagiasi.", + "subs": [ + { + "kode": "Sub-011-1", + "uraian": "Mampu mengidentifikasi standar etika profesi informatika dan menerapkannya dalam analisis kasus nyata." + }, + { + "kode": "Sub-011-2", + "uraian": "Mampu mengevaluasi dilema etika dalam pengembangan perangkat lunak dan merumuskan keputusan yang bertanggung jawab." + } + ] + }, + "CPMK012": { + "desk": "Mampu menunjukkan kedisiplinan dan tanggung jawab dengan menyelesaikan seluruh kewajiban tugas secara mandiri dan tepat waktu.", + "subs": [ + { + "kode": "Sub-012-1", + "uraian": "Mampu menunjukkan tanggung jawab profesional dalam menyelesaikan tugas analisis etika secara mandiri dan tepat waktu." + }, + { + "kode": "Sub-012-2", + "uraian": "Mampu mendokumentasikan proses pengambilan keputusan etis dalam laporan yang terstruktur dan dapat dipertanggungjawabkan." + } + ] + }, + "CPMK013": { + "desk": "Mampu menunjukkan sikap profesional dalam berinteraksi dan bekerjasama dengan tim melalui komunikasi yang konstruktif dan pembagian peran yang adil.", + "subs": [ + { + "kode": "Sub-013-1", + "uraian": "Mampu berdiskusi secara konstruktif tentang isu etika teknologi dalam forum yang menghormati perbedaan perspektif." + }, + { + "kode": "Sub-013-2", + "uraian": "Mampu berkolaborasi dalam tim untuk menganalisis kasus etika dan menyusun rekomendasi kebijakan yang seimbang." + } + ] + }, + "CPMK021": { + "desk": "Mampu menganalisis isu sosial, budaya, dan lingkungan yang relevan dengan penerapan TI melalui kajian studi kasus nyata di masyarakat.", + "subs": [ + { + "kode": "Sub-021-1", + "uraian": "Mampu menganalisis isu privasi data, bias algoritma, dan kesenjangan digital dari perspektif sosial-teknis yang komprehensif." + }, + { + "kode": "Sub-021-2", + "uraian": "Mampu mengidentifikasi dampak sosial penggunaan AI dan otomasi terhadap tenaga kerja dan komunitas yang rentan." + } + ] + }, + "CPMK022": { + "desk": "Mampu mengevaluasi dampak sosial dan keberlanjutan dari sebuah solusi TI menggunakan kerangka analisis dampak yang sistematis, objektif dan berbasis data.", + "subs": [ + { + "kode": "Sub-022-1", + "uraian": "Mampu mengevaluasi dampak lingkungan infrastruktur komputasi menggunakan kerangka LCA." + }, + { + "kode": "Sub-022-2", + "uraian": "Mampu menganalisis implementasi green computing dan mengusulkan rekomendasi keberlanjutan yang terukur." + } + ] + }, + "CPMK023": { + "desk": "Mampu merancang dan menerapkan solusi teknologi tepat guna yang kontekstual dan berkelanjutan untuk membantu menyelesaikan permasalahan nyata di komunitas sasaran.", + "subs": [ + { + "kode": "Sub-023-1", + "uraian": "Mampu merancang solusi teknologi yang mempertimbangkan inklusivitas, keadilan, dan keberlanjutan untuk komunitas yang kurang terlayani." + }, + { + "kode": "Sub-023-2", + "uraian": "Mampu menerapkan prinsip responsible innovation dalam perancangan proyek teknologi berbasis dampak sosial positif." + } + ] + } + } + }, + "INF-511-307": { + "nama": "Pemrograman Web", + "cpmks": { + "CPMK052": { + "desk": "Mampu merancang solusi algoritmik yang logis dan terstruktur menggunakan pseudocode atau flowchart untuk menyelesaikan permasalahan komputasi.", + "subs": [ + { + "kode": "Sub-052-1", + "uraian": "Mampu membangun halaman web statis dengan HTML semantik yang benar dan CSS yang responsif." + }, + { + "kode": "Sub-052-2", + "uraian": "Mampu mengimplementasikan interaktivitas halaman web menggunakan JavaScript modern termasuk DOM manipulation, event handling, dan fetch API." + } + ] + }, + "CPMK081": { + "desk": "Mampu menganalisis dan merumuskan kebutuhan pengguna ke dalam bentuk User Stories atau spesifikasi kebutuhan yang komprehensif dan mudah dipahami.", + "subs": [ + { + "kode": "Sub-081-1", + "uraian": "Mampu menganalisis dan merumuskan User Stories untuk fitur aplikasi web dari hasil wawancara pengguna atau brief proyek." + }, + { + "kode": "Sub-081-2", + "uraian": "Mampu mendefinisikan kriteria penerimaan yang terukur untuk setiap User Story aplikasi web yang dikembangkan." + } + ] + }, + "CPMK082": { + "desk": "Mampu merancang arsitektur solusi sistem yang selaras dengan spesifikasi kebutuhan pengguna yang telah disepakati.", + "subs": [ + { + "kode": "Sub-082-1", + "uraian": "Mampu merancang arsitektur RESTful API dengan endpoint yang konsisten dan dokumentasi OpenAPI/Swagger yang lengkap." + }, + { + "kode": "Sub-082-2", + "uraian": "Mampu memilih dan membuktikan arsitektur front-end yang sesuai dengan kebutuhan pengguna." + } + ] + }, + "CPMK083": { + "desk": "Mampu mengintegrasikan prinsip kemudahan penggunaan (usability) dan keberlanjutan sistem ke dalam rancangan antarmuka pengguna.", + "subs": [ + { + "kode": "Sub-083-1", + "uraian": "Mampu merancang antarmuka web yang responsif dan mengikuti prinsip mobile-first dengan mempertimbangkan aksesibilitas WCAG." + }, + { + "kode": "Sub-083-2", + "uraian": "Mampu mengimplementasikan proses deployment aplikasi web ke platform cloud menggunakan prosedur yang benar." + } + ] + }, + "CPMK091": { + "desk": "Mampu mengimplementasikan algoritma ke dalam kode program yang efisien, bebas error, dan memenuhi standar clean code.", + "subs": [ + { + "kode": "Sub-091-1", + "uraian": "Mampu mengimplementasikan aplikasi web full-stack dengan front-end dan back-end yang terintegrasi menggunakan framework modern." + }, + { + "kode": "Sub-091-2", + "uraian": "Mampu menulis kode web yang mengikuti standar keamanan OWASP." + } + ] + }, + "CPMK092": { + "desk": "Mampu merancang dan melaksanakan pengujian perangkat lunak (fungsional/unit) menggunakan metode yang terstandar untuk memastikan kode berjalan dengan benar.", + "subs": [ + { + "kode": "Sub-092-1", + "uraian": "Mampu merancang dan melaksanakan pengujian API menggunakan Postman/Thunder Client untuk memverifikasi seluruh endpoint." + }, + { + "kode": "Sub-092-2", + "uraian": "Mampu mengimplementasikan unit test untuk komponen front-end dan fungsi back-end menggunakan framework testing." + } + ] + }, + "CPMK093": { + "desk": "Mampu mengevaluasi kinerja dan fungsionalitas perangkat lunak melalui serangkaian skenario uji coba yang sistematis untuk mendeteksi bug.", + "subs": [ + { + "kode": "Sub-093-1", + "uraian": "Mampu mengevaluasi performa aplikasi web menggunakan Lighthouse dan mengidentifikasi bottleneck yang perlu dioptimalkan." + }, + { + "kode": "Sub-093-2", + "uraian": "Mampu mendeteksi dan memperbaiki bug aplikasi web melalui pengujian lintas browser dan skenario edge case." + } + ] + } + } + }, + "INF-511-401": { + "nama": "Kecerdasan Buatan", + "cpmks": { + "CPMK031": { + "desk": "Mampu menganalisis konsep dasar komputasi dan struktur data untuk menjelaskan keterkaitannya dalam penyelesaian masalah komputasi secara tepat.", + "subs": [ + { + "kode": "Sub-031-1", + "uraian": "Mampu menjelaskan paradigma utama AI dan hubungannya dengan komputasi." + }, + { + "kode": "Sub-031-2", + "uraian": "Mampu menganalisis karakteristik masalah untuk menentukan pendekatan AI yang paling sesuai." + } + ] + }, + "CPMK032": { + "desk": "Mampu menganalisis teori komputasi dan model matematika untuk memformulasikan solusi masalah teknis secara sistematis.", + "subs": [ + { + "kode": "Sub-032-1", + "uraian": "Mampu menerapkan model probabilistik untuk merepresentasikan ketidakpastian dalam penalaran berbasis AI." + }, + { + "kode": "Sub-032-2", + "uraian": "Mampu menganalisis masalah inferensi logis menggunakan propositional logic dan predicate logic dalam sistem berbasis aturan." + } + ] + }, + "CPMK034": { + "desk": "Mampu mengevaluasi dan membandingkan berbagai alternatif solusi teknis berdasarkan efisiensi, kompleksitas, dan efektivitas komputasi.", + "subs": [ + { + "kode": "Sub-034-1", + "uraian": "Mampu membandingkan algoritma pencarian berdasarkan kelengkapan, optimalitas, dan kompleksitasnya." + }, + { + "kode": "Sub-034-2", + "uraian": "Mampu mengevaluasi representasi pengetahuan untuk domain masalah tertentu." + } + ] + }, + "CPMK051": { + "desk": "Mampu menganalisis dan menerapkan teknik abstraksi dan dekomposisi untuk memecahkan masalah kompleks menjadi sub-masalah yang lebih kecil dan terkelola.", + "subs": [ + { + "kode": "Sub-051-1", + "uraian": "Mampu mendekomposisi masalah AI kompleks ke dalam sub-masalah yang terstruktur." + }, + { + "kode": "Sub-051-2", + "uraian": "Mampu mengabstraksi masalah nyata ke dalam representasi formal yang dapat diproses oleh algoritma AI." + } + ] + }, + "CPMK052": { + "desk": "Mampu merancang solusi algoritmik yang logis dan terstruktur menggunakan pseudocode atau flowchart untuk menyelesaikan permasalahan komputasi.", + "subs": [ + { + "kode": "Sub-052-1", + "uraian": "Mampu merancang pseudocode untuk algoritma pencarian heuristik dengan fungsi heuristik yang admissible." + }, + { + "kode": "Sub-052-2", + "uraian": "Mampu mengembangkan agen cerdas sederhana yang beroperasi berdasarkan aturan persepsi-aksi yang terstruktur." + } + ] + }, + "CPMK053": { + "desk": "Mampu mengevaluasi validitas logika dan efisiensi dari berbagai alternatif algoritma sebelum diimplementasikan ke dalam kode program.", + "subs": [ + { + "kode": "Sub-053-1", + "uraian": "Mampu memvalidasi keoptimalan algoritma pencarian melalui analisis kelengkapan dan admissibility heuristik yang digunakan." + }, + { + "kode": "Sub-053-2", + "uraian": "Mampu mengevaluasi efektivitas sistem berbasis aturan menggunakan test case yang mencakup kasus tepi dan kasus umum." + } + ] + }, + "CPMK072": { + "desk": "Mampu menganalisis dan mengevaluasi pola serta tren data menggunakan metode statistik atau algoritma Machine Learning dasar untuk mendapatkan informasi yang berguna.", + "subs": [ + { + "kode": "Sub-072-1", + "uraian": "Mampu menganalisis pola dan tren data menggunakan pendekatan probabilistik AI untuk pengambilan keputusan." + }, + { + "kode": "Sub-072-2", + "uraian": "Mampu mengevaluasi kinerja model AI dengan metrik yang sesuai pada dataset yang diberikan." + } + ] + }, + "CPMK073": { + "desk": "Mampu merumuskan dan mengevaluasi rekomendasi solusi yang strategis dan logis berdasarkan hasil interpretasi data yang akurat.", + "subs": [ + { + "kode": "Sub-073-1", + "uraian": "Mampu merumuskan rekomendasi solusi berbasis hasil inferensi sistem AI dengan disertai penjelasan alur penalaran yang logis." + }, + { + "kode": "Sub-073-2", + "uraian": "Mampu menganalisis isu etika dan bias dalam sistem AI dan mengusulkan pendekatan pengembangan AI yang bertanggung jawab." + } + ] + }, + "CPMK091": { + "desk": "Mampu mengimplementasikan algoritma ke dalam kode program yang efisien, bebas error, dan memenuhi standar clean code.", + "subs": [ + { + "kode": "Sub-091-1", + "uraian": "Mampu mengimplementasikan algoritma AI dalam kode yang efisien menggunakan library yang sesuai." + }, + { + "kode": "Sub-091-2", + "uraian": "Mampu mengintegrasikan komponen AI ke dalam aplikasi yang dapat menunjukkan perilaku cerdas pada kasus nyata." + } + ] + } + } + }, + "INF-511-402": { + "nama": "Internet of Things", + "cpmks": { + "CPMK042": { + "desk": "Mampu merancang arsitektur jaringan komputer atau sistem IoT dasar yang aman, skalabel, dan sesuai dengan topologi standar.", + "subs": [ + { + "kode": "Sub-042-1", + "uraian": "Mampu merancang arsitektur sistem IoT end-to-end yang aman dan dapat diskalakan." + }, + { + "kode": "Sub-042-2", + "uraian": "Mampu merancang protokol komunikasi IoT yang sesuai dengan karakteristik perangkat dan jaringan." + } + ] + }, + "CPMK043": { + "desk": "Mampu mengimplementasikan konfigurasi perangkat jaringan dan konektivitas sensor IoT agar terhubung ke gateway atau internet dengan stabil.", + "subs": [ + { + "kode": "Sub-043-1", + "uraian": "Mampu memprogram embedded system untuk mengkonfigurasi dan menghubungkan sensor atau aktuator ke platform IoT menggunakan protokol komunikasi yang ditetapkan." + }, + { + "kode": "Sub-043-2", + "uraian": "Mampu memverifikasi konektivitas perangkat IoT ke gateway dan cloud melalui pengujian end-to-end yang terstruktur." + } + ] + }, + "CPMK044": { + "desk": "Mampu menganalisis potensi risiko keamanan pada arsitektur sistem jaringan atau IoT dan merumuskan langkah mitigasi dasarnya.", + "subs": [ + { + "kode": "Sub-044-1", + "uraian": "Mampu menganalisis vektor ancaman keamanan sistem IoT dan mitigasinya." + }, + { + "kode": "Sub-044-2", + "uraian": "Mampu merancang kebijakan keamanan IoT yang mencakup autentikasi perangkat, enkripsi data, dan pembaruan firmware aman." + } + ] + }, + "CPMK101": { + "desk": "Mampu merancang dan mengimplementasikan protokol komunikasi data untuk menghubungkan sensor fisik dengan platform IoT secara real-time.", + "subs": [ + { + "kode": "Sub-101-1", + "uraian": "Mampu merancang dan mengimplementasikan protokol komunikasi MQTT untuk mengirim data sensor secara real-time ke broker." + }, + { + "kode": "Sub-101-2", + "uraian": "Mampu memprogram microcontroller untuk membaca data sensor dan mengirimkannya melalui protokol IoT ke broker atau server." + } + ] + }, + "CPMK102": { + "desk": "Mampu menerapkan mekanisme enkripsi dan autentikasi pada perangkat IoT untuk menjamin kerahasiaan dan integritas data.", + "subs": [ + { + "kode": "Sub-102-1", + "uraian": "Mampu menerapkan TLS/SSL pada komunikasi MQTT dan HTTPS untuk mengenkripsi transmisi data sensor IoT." + }, + { + "kode": "Sub-102-2", + "uraian": "Mampu mengimplementasikan mekanisme autentikasi perangkat menggunakan certificate-based atau token-based authentication." + } + ] + }, + "CPMK103": { + "desk": "Mampu menganalisis dampak privasi data dan potensi celah keamanan pada sistem IoT yang dikembangkan serta merumuskan solusi perbaikan.", + "subs": [ + { + "kode": "Sub-103-1", + "uraian": "Mampu menganalisis dampak privasi pengguna dari sistem IoT yang dikembangkan dan mengidentifikasi data sensitif yang dikumpulkan." + }, + { + "kode": "Sub-103-2", + "uraian": "Mampu merumuskan kebijakan perlindungan privasi dan mitigasi keamanan untuk celah yang ditemukan dalam sistem IoT." + } + ] + } + } + }, + "INF-511-403": { + "nama": "Keamanan Informasi dan Jaringan", + "cpmks": { + "CPMK041": { + "desk": "Mampu menganalisis dan menetapkan spesifikasi kebutuhan infrastruktur jaringan dan standar keamanan sistem yang diperlukan untuk mendukung kinerja aplikasi secara optimal.", + "subs": [ + { + "kode": "Sub-041-1", + "uraian": "Mampu menganalisis kebutuhan keamanan infrastruktur jaringan menggunakan framework risk assessment." + }, + { + "kode": "Sub-041-2", + "uraian": "Mampu melakukan threat modeling menggunakan metodologi tertentu untuk mengidentifikasi ancaman pada sistem dan merumuskan kontrol keamanan yang sesuai." + } + ] + }, + "CPMK042": { + "desk": "Mampu merancang arsitektur jaringan komputer atau sistem IoT dasar yang aman, skalabel, dan sesuai dengan topologi standar.", + "subs": [ + { + "kode": "Sub-042-1", + "uraian": "Mampu merancang segmentasi jaringan menggunakan DMZ, VLAN, dan firewall untuk meminimalkan attack surface." + }, + { + "kode": "Sub-042-2", + "uraian": "Mampu merancang kebijakan akses jaringan yang sesuai dengan kebutuhan keamanan organisasi." + } + ] + }, + "CPMK044": { + "desk": "Mampu menganalisis potensi risiko keamanan pada arsitektur sistem jaringan atau IoT dan merumuskan langkah mitigasi dasarnya.", + "subs": [ + { + "kode": "Sub-044-1", + "uraian": "Mampu menganalisis kerentanan sistem menggunakan tools scanning dan mengklasifikasikan temuan berdasarkan CVSS." + }, + { + "kode": "Sub-044-2", + "uraian": "Mampu merumuskan rencana remediasi yang diprioritaskan berdasarkan tingkat risiko kerentanan yang ditemukan." + } + ] + }, + "CPMK102": { + "desk": "Mampu menerapkan mekanisme enkripsi dan autentikasi pada perangkat IoT untuk menjamin kerahasiaan dan integritas data.", + "subs": [ + { + "kode": "Sub-102-1", + "uraian": "Mampu menerapkan kriptografi untuk mengamankan data at-rest dan data in-transit pada sistem nyata." + }, + { + "kode": "Sub-102-2", + "uraian": "Mampu mengkonfigurasi PKI dan manajemen sertifikat untuk autentikasi entitas dalam jaringan." + } + ] + }, + "CPMK103": { + "desk": "Mampu menganalisis dampak privasi data dan potensi celah keamanan pada sistem IoT yang dikembangkan serta merumuskan solusi perbaikan.", + "subs": [ + { + "kode": "Sub-103-1", + "uraian": "Mampu mengkonfigurasi dan mengelola sistem IDS atau IPS untuk mendeteksi pola serangan dan menghasilkan alert yang dapat ditindaklanjuti." + }, + { + "kode": "Sub-103-2", + "uraian": "Mampu merumuskan prosedur incident response yang terstruktur dan mendokumentasikan temuan investigasi keamanan." + } + ] + } + } + }, + "INF-511-404": { + "nama": "Pembelajaran Mesin", + "cpmks": { + "CPMK034": { + "desk": "Mampu mengevaluasi dan membandingkan berbagai alternatif solusi teknis berdasarkan efisiensi, kompleksitas, dan efektivitas komputasi.", + "subs": [ + { + "kode": "Sub-034-1", + "uraian": "Mampu membandingkan algoritma supervised learning berdasarkan akurasi dan kompleksitas." + }, + { + "kode": "Sub-034-2", + "uraian": "Mampu mengevaluasi dan memilih model machine learning terbaik menggunakan cross-validation, hyperparameter tuning, dan metrik yang tepat." + } + ] + }, + "CPMK051": { + "desk": "Mampu menganalisis dan menerapkan teknik abstraksi dan dekomposisi untuk memecahkan masalah kompleks menjadi sub-masalah yang lebih kecil dan terkelola.", + "subs": [ + { + "kode": "Sub-051-1", + "uraian": "Mampu mendekomposisi pipeline machine learning menjadi tahapan yang terkelola." + }, + { + "kode": "Sub-051-2", + "uraian": "Mampu mengabstraksi masalah bisnis/teknis ke dalam formulasi masalah ML yang tepat." + } + ] + }, + "CPMK052": { + "desk": "Mampu merancang solusi algoritmik yang logis dan terstruktur menggunakan pseudocode atau flowchart untuk menyelesaikan permasalahan komputasi.", + "subs": [ + { + "kode": "Sub-052-1", + "uraian": "Mampu merancang pipeline ML end-to-end menggunakan pseudocode yang mencakup preprocessing hingga deployment model." + }, + { + "kode": "Sub-052-2", + "uraian": "Mampu menerapkan teknik reduksi dimensi untuk data berdimensi tinggi dan menginterpretasikan hasil proyeksi." + } + ] + }, + "CPMK071": { + "desk": "Mampu menganalisis dan memvalidasi data menggunakan teknik cleansing yang tepat hingga menghasilkan dataset yang siap dianalisis.", + "subs": [ + { + "kode": "Sub-071-1", + "uraian": "Mampu menerapkan teknik preprocessing data ML secara tepat." + }, + { + "kode": "Sub-071-2", + "uraian": "Mampu menganalisis dan memvalidasi kualitas dataset ML menggunakan teknik visualisasi dan statistik deskriptif." + } + ] + }, + "CPMK072": { + "desk": "Mampu menganalisis dan mengevaluasi pola serta tren data menggunakan metode statistik atau algoritma Machine Learning dasar untuk mendapatkan informasi yang berguna.", + "subs": [ + { + "kode": "Sub-072-1", + "uraian": "Mampu menjelaskan konsep dasar Reinforcement Learning beserta komponen utamanya dan mengidentifikasi kasus penggunaan yang sesuai." + }, + { + "kode": "Sub-072-2", + "uraian": "Mampu mengevaluasi performa model ML menggunakan confusion matrix, ROC-AUC, RMSE, dan metrik evaluasi lain yang relevan." + } + ] + }, + "CPMK073": { + "desk": "Mampu merumuskan dan mengevaluasi rekomendasi solusi yang strategis dan logis berdasarkan hasil interpretasi data yang akurat.", + "subs": [ + { + "kode": "Sub-073-1", + "uraian": "Mampu merumuskan rekomendasi model ML terbaik berdasarkan hasil eksperimen yang sistematis dan terdokumentasi dengan baik." + }, + { + "kode": "Sub-073-2", + "uraian": "Mampu mengevaluasi dan mengkomunikasikan limitasi model ML kepada pemangku kepentingan secara jujur dan berbasis data." + } + ] + }, + "CPMK091": { + "desk": "Mampu mengimplementasikan algoritma ke dalam kode program yang efisien, bebas error, dan memenuhi standar clean code.", + "subs": [ + { + "kode": "Sub-091-1", + "uraian": "Mampu mengimplementasikan pipeline ML lengkap menggunakan Python dari preprocessing hingga evaluasi." + }, + { + "kode": "Sub-091-2", + "uraian": "Mampu menulis kode ML yang termodulasi, reproducible, dan didokumentasikan dalam format notebook atau script yang rapi." + } + ] + } + } + }, + "INF-511-405": { + "nama": "Sistem Informasi Geografis", + "cpmks": { + "CPMK071": { + "desk": "Mampu menganalisis dan memvalidasi data menggunakan teknik cleansing yang tepat hingga menghasilkan dataset yang siap dianalisis.", + "subs": [ + { + "kode": "Sub-071-1", + "uraian": "Mampu mengimpor, membersihkan, dan memvalidasi data geospasial dari berbagai format menggunakan tools SIG." + }, + { + "kode": "Sub-071-2", + "uraian": "Mampu menganalisis kualitas data geografis dan melakukan koreksi yang diperlukan." + } + ] + }, + "CPMK072": { + "desk": "Mampu menganalisis dan mengevaluasi pola serta tren data menggunakan metode statistik atau algoritma Machine Learning dasar untuk mendapatkan informasi yang berguna.", + "subs": [ + { + "kode": "Sub-072-1", + "uraian": "Mampu menganalisis pola spasial menggunakan teknik geostatistik dan menginterpretasikan hasilnya." + }, + { + "kode": "Sub-072-2", + "uraian": "Mampu mengevaluasi tren spasio-temporal menggunakan analisis overlay dan perubahan tutupan lahan pada data multi-temporal." + } + ] + }, + "CPMK073": { + "desk": "Mampu merumuskan dan mengevaluasi rekomendasi solusi yang strategis dan logis berdasarkan hasil interpretasi data yang akurat.", + "subs": [ + { + "kode": "Sub-073-1", + "uraian": "Mampu merumuskan rekomendasi berbasis analisis spasial yang akurat untuk mendukung pengambilan keputusan berbasis lokasi." + }, + { + "kode": "Sub-073-2", + "uraian": "Mampu mengevaluasi validitas model SIG dan mempresentasikan temuan geospasial dalam format peta dan laporan yang komunikatif." + } + ] + }, + "CPMK081": { + "desk": "Mampu menganalisis dan merumuskan kebutuhan pengguna ke dalam bentuk User Stories atau spesifikasi kebutuhan yang komprehensif dan mudah dipahami.", + "subs": [ + { + "kode": "Sub-081-1", + "uraian": "Mampu menganalisis kebutuhan pengguna aplikasi berbasis peta dan merumuskan spesifikasi fitur geospasial yang diperlukan." + }, + { + "kode": "Sub-081-2", + "uraian": "Mampu menjelaskan konsep dasar GIS meliputi sistem koordinat, proyeksi peta, dan tipe data spasial serta perbedaan penggunaannya." + } + ] + }, + "CPMK082": { + "desk": "Mampu merancang arsitektur solusi sistem yang selaras dengan spesifikasi kebutuhan pengguna yang telah disepakati.", + "subs": [ + { + "kode": "Sub-082-1", + "uraian": "Mampu merancang arsitektur sistem informasi geografis yang mencakup server geospasial, database, dan antarmuka peta." + }, + { + "kode": "Sub-082-2", + "uraian": "Mampu menerapkan operasi geoprocessing dasar untuk menjawab pertanyaan analisis spasial yang diberikan." + } + ] + }, + "CPMK083": { + "desk": "Mampu mengintegrasikan prinsip kemudahan penggunaan (usability) dan keberlanjutan sistem ke dalam rancangan antarmuka pengguna.", + "subs": [ + { + "kode": "Sub-083-1", + "uraian": "Mampu merancang antarmuka WebGIS yang intuitif dengan kontrol peta, layer management, dan legenda yang mudah dipahami pengguna." + }, + { + "kode": "Sub-083-2", + "uraian": "Mampu mengintegrasikan prinsip kartografi yang baik ke dalam rancangan visualisasi data geospasial yang efektif." + } + ] + }, + "CPMK084": { + "desk": "Mampu mengembangkan dan mengevaluasi prototipe sistem interaktif melalui pengujian pengguna (user testing) untuk memastikan kesesuaian desain.", + "subs": [ + { + "kode": "Sub-084-1", + "uraian": "Mampu mengembangkan prototipe aplikasi WebGIS yang fungsional dan melakukan user testing dengan pengguna target." + }, + { + "kode": "Sub-084-2", + "uraian": "Mampu mengevaluasi dan merevisi desain antarmuka WebGIS berdasarkan temuan user testing secara iteratif." + } + ] + } + } + }, + "INF-511-801": { + "nama": "Metodologi Penelitian", + "cpmks": { + "CPMK032": { + "desk": "Mampu menganalisis teori komputasi dan model matematika untuk memformulasikan solusi masalah teknis secara sistematis.", + "subs": [ + { + "kode": "Sub-032-1", + "uraian": "Mampu melakukan systematic literature review menggunakan protokol pencarian terstruktur untuk mengidentifikasi state of the art dan research gap yang relevan dengan topik penelitian." + }, + { + "kode": "Sub-032-2", + "uraian": "Mampu memformulasikan pertanyaan penelitian yang SMART dan rumusan masalah yang dapat diuji secara empiris." + } + ] + }, + "CPMK034": { + "desk": "Mampu mengevaluasi dan membandingkan berbagai alternatif solusi teknis berdasarkan efisiensi, kompleksitas, dan efektivitas komputasi.", + "subs": [ + { + "kode": "Sub-034-1", + "uraian": "Mampu membandingkan metodologi penelitian dan memilih yang paling sesuai." + }, + { + "kode": "Sub-034-2", + "uraian": "Mampu mengevaluasi kekuatan dan kelemahan rancangan penelitian yang diusulkan berdasarkan kriteria validitas dan reliabilitas." + } + ] + }, + "CPMK062": { + "desk": "Mampu menyajikan dan mempertahankan gagasan teknis secara lisan dengan argumentasi yang logis dan didukung media visual yang efektif.", + "subs": [ + { + "kode": "Sub-062-1", + "uraian": "Mampu menyajikan proposal penelitian secara lisan dengan struktur yang jelas dan argumentasi yang didukung literatur kuat." + }, + { + "kode": "Sub-062-2", + "uraian": "Mampu mempertahankan justifikasi metodologi penelitian dari pertanyaan kritis penguji dengan argumen ilmiah yang kuat." + } + ] + }, + "CPMK073": { + "desk": "Mampu merumuskan dan mengevaluasi rekomendasi solusi yang strategis dan logis berdasarkan hasil interpretasi data yang akurat.", + "subs": [ + { + "kode": "Sub-073-1", + "uraian": "Mampu merumuskan hipotesis atau pertanyaan penelitian yang dapat dijawab melalui prosedur metodologi yang direncanakan." + }, + { + "kode": "Sub-073-2", + "uraian": "Mampu mengevaluasi temuan penelitian yang ada dan mengidentifikasi kontribusi baru yang dapat diberikan oleh penelitian." + } + ] + } + } + }, + "INF-611-406": { + "nama": "Computer Vision", + "cpmks": { + "CPMK034": { + "desk": "Mampu mengevaluasi dan membandingkan berbagai alternatif solusi teknis berdasarkan efisiensi, kompleksitas, dan efektivitas komputasi.", + "subs": [ + { + "kode": "Sub-034-1", + "uraian": "Mampu membandingkan arsitektur CNN berdasarkan akurasi dan biaya komputasi untuk tugas vision." + }, + { + "kode": "Sub-034-2", + "uraian": "Mampu mengevaluasi dan memilih teknik augmentasi data yang optimal untuk meningkatkan generalisasi model vision." + } + ] + }, + "CPMK051": { + "desk": "Mampu menganalisis dan menerapkan teknik abstraksi dan dekomposisi untuk memecahkan masalah kompleks menjadi sub-masalah yang lebih kecil dan terkelola.", + "subs": [ + { + "kode": "Sub-051-1", + "uraian": "Mampu mendekomposisi pipeline computer vision secara terstruktur." + }, + { + "kode": "Sub-051-2", + "uraian": "Mampu mengabstraksi masalah visual ke dalam formulasi yang dapat diselesaikan dengan CV." + } + ] + }, + "CPMK052": { + "desk": "Mampu merancang solusi algoritmik yang logis dan terstruktur menggunakan pseudocode atau flowchart untuk menyelesaikan permasalahan komputasi.", + "subs": [ + { + "kode": "Sub-052-1", + "uraian": "Mampu merancang arsitektur model CNN beserta strategi training menggunakan pseudocode." + }, + { + "kode": "Sub-052-2", + "uraian": "Mampu mengembangkan pipeline preprocessing citra yang terstruktur untuk tugas computer vision tertentu." + } + ] + }, + "CPMK071": { + "desk": "Mampu menganalisis dan memvalidasi data menggunakan teknik cleansing yang tepat hingga menghasilkan dataset yang siap dianalisis.", + "subs": [ + { + "kode": "Sub-071-1", + "uraian": "Mampu membersihkan dan memvalidasi dataset citra sebelum proses training." + }, + { + "kode": "Sub-071-2", + "uraian": "Mampu menganalisis distribusi dataset dan mengidentifikasi class imbalance serta strategi penanganannya." + } + ] + }, + "CPMK072": { + "desk": "Mampu menganalisis dan mengevaluasi pola serta tren data menggunakan metode statistik atau algoritma Machine Learning dasar untuk mendapatkan informasi yang berguna.", + "subs": [ + { + "kode": "Sub-072-1", + "uraian": "Mampu menganalisis performa model CV menggunakan metrik dan confusion matrix secara tepat." + }, + { + "kode": "Sub-072-2", + "uraian": "Mampu mengevaluasi trade-off antara akurasi dan kecepatan inferensi model untuk deployment pada device tertentu." + } + ] + }, + "CPMK073": { + "desk": "Mampu merumuskan dan mengevaluasi rekomendasi solusi yang strategis dan logis berdasarkan hasil interpretasi data yang akurat.", + "subs": [ + { + "kode": "Sub-073-1", + "uraian": "Mampu merumuskan rekomendasi model dan konfigurasi training terbaik berdasarkan hasil eksperimen yang terdokumentasi." + }, + { + "kode": "Sub-073-2", + "uraian": "Mampu mengimplementasikan model segmentasi citra dan menginterpretasikan hasil segmentasi secara kuantitatif dan visual." + } + ] + }, + "CPMK091": { + "desk": "Mampu mengimplementasikan algoritma ke dalam kode program yang efisien, bebas error, dan memenuhi standar clean code.", + "subs": [ + { + "kode": "Sub-091-1", + "uraian": "Mampu mengimplementasikan pipeline CV lengkap menggunakan PyTorch atau TensorFlow." + }, + { + "kode": "Sub-091-2", + "uraian": "Mampu mengimplementasikan model deteksi objek dan mengevaluasi performanya menggunakan metrik deteksi yang sesuai." + } + ] + }, + "CPMK093": { + "desk": "Mampu mengevaluasi kinerja dan fungsionalitas perangkat lunak melalui serangkaian skenario uji coba yang sistematis untuk mendeteksi bug.", + "subs": [ + { + "kode": "Sub-093-1", + "uraian": "Mampu mengevaluasi model CV secara sistematis menggunakan test set yang tidak terlihat dan menginterpretasikan error cases." + }, + { + "kode": "Sub-093-2", + "uraian": "Mampu mendeteksi dan mendiagnosis masalah overfitting/underfitting dari kurva training dan merancang solusi perbaikan." + } + ] + } + } + }, + "INF-611-407": { + "nama": "Pemrograman Mobile", + "cpmks": { + "CPMK052": { + "desk": "Mampu merancang solusi algoritmik yang logis dan terstruktur menggunakan pseudocode atau flowchart untuk menyelesaikan permasalahan komputasi.", + "subs": [ + { + "kode": "Sub-052-1", + "uraian": "Mampu merancang arsitektur aplikasi mobile dan alur navigasi antar layar menggunakan wireframe yang terstruktur." + }, + { + "kode": "Sub-052-2", + "uraian": "Mampu mengembangkan pseudocode untuk logika bisnis utama aplikasi mobile sebelum implementasi kode." + } + ] + }, + "CPMK081": { + "desk": "Mampu menganalisis dan merumuskan kebutuhan pengguna ke dalam bentuk User Stories atau spesifikasi kebutuhan yang komprehensif dan mudah dipahami.", + "subs": [ + { + "kode": "Sub-081-1", + "uraian": "Mampu menganalisis kebutuhan pengguna aplikasi mobile melalui analisis kompetitor dan user research untuk menyusun spesifikasi." + }, + { + "kode": "Sub-081-2", + "uraian": "Mampu mendokumentasikan User Stories aplikasi mobile dengan kriteria penerimaan yang spesifik dan dapat diuji." + } + ] + }, + "CPMK082": { + "desk": "Mampu merancang arsitektur solusi sistem yang selaras dengan spesifikasi kebutuhan pengguna yang telah disepakati.", + "subs": [ + { + "kode": "Sub-082-1", + "uraian": "Mampu merancang arsitektur aplikasi mobile yang mempertimbangkan offline-first, sinkronisasi data, dan manajemen state." + }, + { + "kode": "Sub-082-2", + "uraian": "Mampu mendokumentasikan desain arsitektur komponen mobile beserta alur data antar layer yang jelas." + } + ] + }, + "CPMK083": { + "desk": "Mampu mengintegrasikan prinsip kemudahan penggunaan (usability) dan keberlanjutan sistem ke dalam rancangan antarmuka pengguna.", + "subs": [ + { + "kode": "Sub-083-1", + "uraian": "Mampu merancang antarmuka mobile yang mengikuti panduan desain platform dengan UX yang intuitif." + }, + { + "kode": "Sub-083-2", + "uraian": "Mampu mengintegrasikan prinsip aksesibilitas mobile ke dalam desain UI." + } + ] + }, + "CPMK084": { + "desk": "Mampu mengembangkan dan mengevaluasi prototipe sistem interaktif melalui pengujian pengguna (user testing) untuk memastikan kesesuaian desain.", + "subs": [ + { + "kode": "Sub-084-1", + "uraian": "Mampu mengembangkan prototipe aplikasi mobile interaktif menggunakan Figma dan melakukan usability testing dengan 5 pengguna." + }, + { + "kode": "Sub-084-2", + "uraian": "Mampu merevisi desain antarmuka mobile berdasarkan temuan usability testing secara iteratif dan terdokumentasi." + } + ] + }, + "CPMK091": { + "desk": "Mampu mengimplementasikan algoritma ke dalam kode program yang efisien, bebas error, dan memenuhi standar clean code.", + "subs": [ + { + "kode": "Sub-091-1", + "uraian": "Mampu mengimplementasikan aplikasi mobile fungsional menggunakan Flutter/React Native yang dapat berjalan di perangkat nyata." + }, + { + "kode": "Sub-091-2", + "uraian": "Mampu mengintegrasikan REST API ke aplikasi mobile dengan penanganan state loading, error, dan data yang baik." + } + ] + }, + "CPMK092": { + "desk": "Mampu merancang dan melaksanakan pengujian perangkat lunak (fungsional/unit) menggunakan metode yang terstandar untuk memastikan kode berjalan dengan benar.", + "subs": [ + { + "kode": "Sub-092-1", + "uraian": "Mampu merancang dan melaksanakan widget/component test dan integration test untuk fitur utama aplikasi mobile." + }, + { + "kode": "Sub-092-2", + "uraian": "Mampu melakukan pengujian performa aplikasi mobile menggunakan DevTools yang tersedia." + } + ] + }, + "CPMK093": { + "desk": "Mampu mengevaluasi kinerja dan fungsionalitas perangkat lunak melalui serangkaian skenario uji coba yang sistematis untuk mendeteksi bug.", + "subs": [ + { + "kode": "Sub-093-1", + "uraian": "Mampu mengevaluasi kinerja aplikasi mobile menggunakan profiler dan menyusun perbaikan." + }, + { + "kode": "Sub-093-2", + "uraian": "Mampu mendeteksi memory leak dan mendiagnosis crash menggunakan tools debugging mobile yang sesuai." + } + ] + } + } + }, + "INF-611-408": { + "nama": "NLP", + "cpmks": { + "CPMK034": { + "desk": "Mampu mengevaluasi dan membandingkan berbagai alternatif solusi teknis berdasarkan efisiensi, kompleksitas, dan efektivitas komputasi.", + "subs": [ + { + "kode": "Sub-034-1", + "uraian": "Mampu membandingkan arsitektur model NLP berdasarkan performa dan kebutuhan komputasi." + }, + { + "kode": "Sub-034-2", + "uraian": "Mampu mengevaluasi dan memilih teknik representasi teks yang sesuai untuk tugas NLP tertentu." + } + ] + }, + "CPMK052": { + "desk": "Mampu merancang solusi algoritmik yang logis dan terstruktur menggunakan pseudocode atau flowchart untuk menyelesaikan permasalahan komputasi.", + "subs": [ + { + "kode": "Sub-052-1", + "uraian": "Mampu merancang pipeline NLP end-to-end menggunakan pseudocode yang terstruktur." + }, + { + "kode": "Sub-052-2", + "uraian": "Mampu mengembangkan strategi preprocessing teks berbahasa Indonesia yang mempertimbangkan karakteristik linguistik khusus." + } + ] + }, + "CPMK071": { + "desk": "Mampu menganalisis dan memvalidasi data menggunakan teknik cleansing yang tepat hingga menghasilkan dataset yang siap dianalisis.", + "subs": [ + { + "kode": "Sub-071-1", + "uraian": "Mampu membersihkan dan memvalidasi dataset teks untuk keperluan training model NLP." + }, + { + "kode": "Sub-071-2", + "uraian": "Mampu menerapkan teknik preprocessing teks secara tepat." + } + ] + }, + "CPMK072": { + "desk": "Mampu menganalisis dan mengevaluasi pola serta tren data menggunakan metode statistik atau algoritma Machine Learning dasar untuk mendapatkan informasi yang berguna.", + "subs": [ + { + "kode": "Sub-072-1", + "uraian": "Mampu menganalisis pola distribusi kata dan topik dalam corpus menggunakan teknik NLP eksploratif." + }, + { + "kode": "Sub-072-2", + "uraian": "Mampu mengevaluasi model NLP menggunakan metrik yang sesuai dan menginterpretasikan hasilnya." + } + ] + }, + "CPMK073": { + "desk": "Mampu merumuskan dan mengevaluasi rekomendasi solusi yang strategis dan logis berdasarkan hasil interpretasi data yang akurat.", + "subs": [ + { + "kode": "Sub-073-1", + "uraian": "Mampu merumuskan rekomendasi solusi NLP terbaik berdasarkan hasil benchmark yang sistematis dan terdokumentasi." + }, + { + "kode": "Sub-073-2", + "uraian": "Mampu mengevaluasi keterbatasan model NLP pada data Bahasa Indonesia dan mengusulkan perbaikan yang konkret." + } + ] + }, + "CPMK091": { + "desk": "Mampu mengimplementasikan algoritma ke dalam kode program yang efisien, bebas error, dan memenuhi standar clean code.", + "subs": [ + { + "kode": "Sub-091-1", + "uraian": "Mampu mengimplementasikan model NLP menggunakan library HuggingFace atau NLTK/spaCy." + }, + { + "kode": "Sub-091-2", + "uraian": "Mampu melakukan fine-tuning model Transformer pretrained pada dataset Bahasa Indonesia yang dikumpulkan sendiri." + } + ] + }, + "CPMK093": { + "desk": "Mampu mengevaluasi kinerja dan fungsionalitas perangkat lunak melalui serangkaian skenario uji coba yang sistematis untuk mendeteksi bug.", + "subs": [ + { + "kode": "Sub-093-1", + "uraian": "Mampu mengevaluasi model NLP secara menyeluruh menggunakan test set dan menganalisis error case secara kualitatif." + }, + { + "kode": "Sub-093-2", + "uraian": "Mampu mendeteksi bias dalam model NLP dan merumuskan strategi mitigasi yang terukur." + } + ] + } + } + }, + "INF-611-409": { + "nama": "Uji Perangkat Lunak", + "cpmks": { + "CPMK084": { + "desk": "Mampu mengembangkan dan mengevaluasi prototipe sistem interaktif melalui pengujian pengguna (user testing) untuk memastikan kesesuaian desain.", + "subs": [ + { + "kode": "Sub-084-1", + "uraian": "Mampu merancang dan melaksanakan white-box testing menggunakan teknik basis path testing dengan mengukur statement dan branch coverage." + }, + { + "kode": "Sub-084-2", + "uraian": "Mampu mengimplementasikan automated test suite menggunakan framework testing yang dapat diintegrasikan dalam pipeline CI/CD." + } + ] + }, + "CPMK092": { + "desk": "Mampu merancang dan melaksanakan pengujian perangkat lunak (fungsional/unit) menggunakan metode yang terstandar untuk memastikan kode berjalan dengan benar.", + "subs": [ + { + "kode": "Sub-092-1", + "uraian": "Mampu merancang test plan komprehensif yang mencakup unit test, integration test, dan system test untuk sistem yang diberikan." + }, + { + "kode": "Sub-092-2", + "uraian": "Mampu melaksanakan pengujian fungsional black-box menggunakan teknik equivalence partitioning dan boundary value analysis." + } + ] + }, + "CPMK093": { + "desk": "Mampu mengevaluasi kinerja dan fungsionalitas perangkat lunak melalui serangkaian skenario uji coba yang sistematis untuk mendeteksi bug.", + "subs": [ + { + "kode": "Sub-093-1", + "uraian": "Mampu mengevaluasi kinerja sistem perangkat lunak menggunakan tools performance testing dengan skenario beban nyata." + }, + { + "kode": "Sub-093-2", + "uraian": "Mampu menganalisis hasil pengujian regresi dan mengidentifikasi defect yang perlu diperbaiki sebelum rilis." + } + ] + }, + "CPMK094": { + "desk": "Mampu menyusun dan mengevaluasi laporan teknis hasil pengujian perangkat lunak secara terstruktur sebagai acuan perbaikan sistem.", + "subs": [ + { + "kode": "Sub-094-1", + "uraian": "Mampu menyusun laporan bug report yang lengkap menggunakan issue tracker." + }, + { + "kode": "Sub-094-2", + "uraian": "Mampu mengevaluasi dan menyusun laporan akhir pengujian yang mencakup ringkasan eksekusi, defect, dan rekomendasi rilis." + } + ] + } + } + }, + "INF-611-601": { + "nama": "Proyek Perangkat Lunak", + "cpmks": { + "CPMK013": { + "desk": "Mampu menunjukkan sikap profesional dalam berinteraksi dan bekerjasama dengan tim melalui komunikasi yang konstruktif dan pembagian peran yang adil.", + "subs": [ + { + "kode": "Sub-013-1", + "uraian": "Mampu menunjukkan kepemimpinan tim yang profesional dengan membagi peran, memfasilitasi meeting, dan menyelesaikan konflik." + }, + { + "kode": "Sub-013-2", + "uraian": "Mampu berkontribusi secara adil dan kolaboratif dalam tim proyek melalui komunikasi yang terbuka dan konstruktif." + } + ] + }, + "CPMK061": { + "desk": "Mampu menyusun dan mengevaluasi dokumen spesifikasi teknis perangkat lunak yang lengkap dan sesuai dengan standar industri.", + "subs": [ + { + "kode": "Sub-061-1", + "uraian": "Mampu menyusun dokumentasi proyek lengkap yang memenuhi standar industri." + }, + { + "kode": "Sub-061-2", + "uraian": "Mampu mengevaluasi dan melakukan review dokumentasi teknis tim menggunakan walkthrough yang terstruktur." + } + ] + }, + "CPMK062": { + "desk": "Mampu menyajikan dan mempertahankan gagasan teknis secara lisan dengan argumentasi yang logis dan didukung media visual yang efektif.", + "subs": [ + { + "kode": "Sub-062-1", + "uraian": "Mampu mempresentasikan progres sprint kepada pemangku kepentingan dengan visualisasi burndown chart dan demo fitur." + }, + { + "kode": "Sub-062-2", + "uraian": "Mampu mempertahankan keputusan desain teknis dalam sesi review dengan argumen yang berbasis data dan praktis." + } + ] + }, + "CPMK063": { + "desk": "Mampu berkolaborasi secara produktif dalam tim proyek perangkat lunak menggunakan alat manajemen proyek untuk mencapai target bersama.", + "subs": [ + { + "kode": "Sub-063-1", + "uraian": "Mampu mengelola proyek perangkat lunak menggunakan metodologi Agile dengan sprint planning, daily standup, dan retrospektif." + }, + { + "kode": "Sub-063-2", + "uraian": "Mampu menggunakan version control dan CI/CD pipeline untuk kolaborasi tim yang efektif." + } + ] + }, + "CPMK081": { + "desk": "Mampu menganalisis dan merumuskan kebutuhan pengguna ke dalam bentuk User Stories atau spesifikasi kebutuhan yang komprehensif dan mudah dipahami.", + "subs": [ + { + "kode": "Sub-081-1", + "uraian": "Mampu melakukan elisitasi kebutuhan proyek dari client/stakeholder dan mendokumentasikannya dalam SRS yang tervalidasi." + }, + { + "kode": "Sub-081-2", + "uraian": "Mampu mengelola perubahan kebutuhan selama proyek menggunakan change management yang terstruktur." + } + ] + }, + "CPMK082": { + "desk": "Mampu merancang arsitektur solusi sistem yang selaras dengan spesifikasi kebutuhan pengguna yang telah disepakati.", + "subs": [ + { + "kode": "Sub-082-1", + "uraian": "Mampu merancang arsitektur sistem proyek yang scalable dan didokumentasikan dalam Architecture Decision Record." + }, + { + "kode": "Sub-082-2", + "uraian": "Mampu melakukan code review arsitektur tim dan memastikan kesesuaian implementasi dengan desain yang disepakati." + } + ] + }, + "CPMK083": { + "desk": "Mampu mengintegrasikan prinsip kemudahan penggunaan (usability) dan keberlanjutan sistem ke dalam rancangan antarmuka pengguna.", + "subs": [ + { + "kode": "Sub-083-1", + "uraian": "Mampu memimpin desain antarmuka pengguna proyek menggunakan pendekatan iteratif dengan umpan balik pengguna nyata." + }, + { + "kode": "Sub-083-2", + "uraian": "Mampu mengintegrasikan aspek keberlanjutan dan aksesibilitas ke dalam desain produk perangkat lunak secara sistematis." + } + ] + }, + "CPMK084": { + "desk": "Mampu mengembangkan dan mengevaluasi prototipe sistem interaktif melalui pengujian pengguna (user testing) untuk memastikan kesesuaian desain.", + "subs": [ + { + "kode": "Sub-084-1", + "uraian": "Mampu mengkoordinasikan user acceptance testing dengan pengguna nyata dan mendokumentasikan hasilnya secara formal." + }, + { + "kode": "Sub-084-2", + "uraian": "Mampu mengevaluasi kesiapan rilis produk berdasarkan hasil UAT dan test coverage yang dicapai tim." + } + ] + }, + "CPMK091": { + "desk": "Mampu mengimplementasikan algoritma ke dalam kode program yang efisien, bebas error, dan memenuhi standar clean code.", + "subs": [ + { + "kode": "Sub-091-1", + "uraian": "Mampu mengimplementasikan fitur-fitur proyek sesuai acceptance criteria menggunakan standar coding tim yang disepakati." + }, + { + "kode": "Sub-091-2", + "uraian": "Mampu melakukan code review yang konstruktif dan memastikan kualitas kode tim memenuhi standar clean code." + } + ] + }, + "CPMK092": { + "desk": "Mampu merancang dan melaksanakan pengujian perangkat lunak (fungsional/unit) menggunakan metode yang terstandar untuk memastikan kode berjalan dengan benar.", + "subs": [ + { + "kode": "Sub-092-1", + "uraian": "Mampu merancang dan mengimplementasikan automated test suite dengan coverage minimal yang ditetapkan." + }, + { + "kode": "Sub-092-2", + "uraian": "Mampu mengintegrasikan CI/CD pipeline yang menjalankan automated test setiap kali ada commit ke branch utama." + } + ] + }, + "CPMK093": { + "desk": "Mampu mengevaluasi kinerja dan fungsionalitas perangkat lunak melalui serangkaian skenario uji coba yang sistematis untuk mendeteksi bug.", + "subs": [ + { + "kode": "Sub-093-1", + "uraian": "Mampu mengevaluasi kualitas proyek secara menyeluruh menggunakan tools analisis statis dan profiler performa." + }, + { + "kode": "Sub-093-2", + "uraian": "Mampu mengidentifikasi dan memprioritaskan technical debt yang perlu diselesaikan sebelum rilis produk." + } + ] + }, + "CPMK094": { + "desk": "Mampu menyusun dan mengevaluasi laporan teknis hasil pengujian perangkat lunak secara terstruktur sebagai acuan perbaikan sistem.", + "subs": [ + { + "kode": "Sub-094-1", + "uraian": "Mampu menyusun laporan akhir proyek yang mencakup arsitektur, implementasi, pengujian, retrospektif, dan rencana ke depan." + }, + { + "kode": "Sub-094-2", + "uraian": "Mampu mendokumentasikan lessons learned proyek secara jujur sebagai masukan untuk proyek berikutnya." + } + ] + } + } + }, + "INF-611-802": { + "nama": "Kerja Praktik", + "cpmks": { + "CPMK012": { + "desk": "Mampu menunjukkan kedisiplinan dan tanggung jawab dengan menyelesaikan seluruh kewajiban tugas secara mandiri dan tepat waktu.", + "subs": [ + { + "kode": "Sub-012-1", + "uraian": "Mampu menunjukkan kedisiplinan kerja industri dengan memenuhi jam kerja, deadline tugas, dan standar profesional institusi." + }, + { + "kode": "Sub-012-2", + "uraian": "Mampu menyelesaikan tugas-tugas KP yang diberikan pembimbing lapangan secara mandiri dan tepat waktu." + } + ] + }, + "CPMK013": { + "desk": "Mampu menunjukkan sikap profesional dalam berinteraksi dan bekerjasama dengan tim melalui komunikasi yang konstruktif dan pembagian peran yang adil.", + "subs": [ + { + "kode": "Sub-013-1", + "uraian": "Mampu beradaptasi dan berinteraksi secara profesional dengan rekan kerja dan atasan di lingkungan industri nyata." + }, + { + "kode": "Sub-013-2", + "uraian": "Mampu berkontribusi secara kolaboratif dalam tim proyek industri dengan mengambil inisiatif yang konstruktif." + } + ] + }, + "CPMK023": { + "desk": "Mampu merancang dan menerapkan solusi teknologi tepat guna yang kontekstual dan berkelanjutan untuk membantu menyelesaikan permasalahan nyata di komunitas sasaran.", + "subs": [ + { + "kode": "Sub-023-1", + "uraian": "Mampu mengidentifikasi permasalahan nyata di institusi KP dan merancang solusi teknologi yang kontekstual dan feasible." + }, + { + "kode": "Sub-023-2", + "uraian": "Mampu menerapkan solusi teknologi yang dirancang dalam konteks organisasi tempat KP dengan dampak yang terukur." + } + ] + }, + "CPMK061": { + "desk": "Mampu menyusun dan mengevaluasi dokumen spesifikasi teknis perangkat lunak yang lengkap dan sesuai dengan standar industri.", + "subs": [ + { + "kode": "Sub-061-1", + "uraian": "Mampu menyusun laporan KP yang komprehensif mencakup profil institusi, pelaksanaan, hasil, dan analisis pengalaman." + }, + { + "kode": "Sub-061-2", + "uraian": "Mampu mengevaluasi dan merefleksikan gap antara teori yang dipelajari di kampus dengan praktik di industri secara kritis." + } + ] + }, + "CPMK062": { + "desk": "Mampu menyajikan dan mempertahankan gagasan teknis secara lisan dengan argumentasi yang logis dan didukung media visual yang efektif.", + "subs": [ + { + "kode": "Sub-062-1", + "uraian": "Mampu mempresentasikan pengalaman dan hasil KP kepada penguji dengan strukturasi yang logis dan komunikasi yang percaya diri." + }, + { + "kode": "Sub-062-2", + "uraian": "Mampu mempertahankan temuan dan rekomendasi KP dari pertanyaan penguji dengan argumen yang berdasarkan pengalaman nyata." + } + ] + }, + "CPMK063": { + "desk": "Mampu berkolaborasi secara produktif dalam tim proyek perangkat lunak menggunakan alat manajemen proyek untuk mencapai target bersama.", + "subs": [ + { + "kode": "Sub-063-1", + "uraian": "Mampu bekerja dalam tim proyek industri menggunakan metodologi dan alat yang berlaku di tempat KP." + }, + { + "kode": "Sub-063-2", + "uraian": "Mampu mendokumentasikan proses kolaborasi tim selama KP sebagai refleksi pembelajaran soft skill industri." + } + ] + }, + "CPMK073": { + "desk": "Mampu merumuskan dan mengevaluasi rekomendasi solusi yang strategis dan logis berdasarkan hasil interpretasi data yang akurat.", + "subs": [ + { + "kode": "Sub-073-1", + "uraian": "Mampu menganalisis permasalahan teknis di tempat KP dan merumuskan rekomendasi perbaikan berbasis data yang ditemukan." + }, + { + "kode": "Sub-073-2", + "uraian": "Mampu mengevaluasi efektivitas solusi yang diimplementasikan selama KP dan mendokumentasikan hasilnya." + } + ] + }, + "CPMK091": { + "desk": "Mampu mengimplementasikan algoritma ke dalam kode program yang efisien, bebas error, dan memenuhi standar clean code.", + "subs": [ + { + "kode": "Sub-091-1", + "uraian": "Mampu mengimplementasikan solusi teknis yang ditugaskan pembimbing lapangan menggunakan teknologi yang berlaku di industri." + }, + { + "kode": "Sub-091-2", + "uraian": "Mampu menghasilkan artefak teknis yang memenuhi standar kualitas industri tempat KP." + } + ] + }, + "CPMK094": { + "desk": "Mampu menyusun dan mengevaluasi laporan teknis hasil pengujian perangkat lunak secara terstruktur sebagai acuan perbaikan sistem.", + "subs": [ + { + "kode": "Sub-094-1", + "uraian": "Mampu menyusun laporan teknis hasil pekerjaan KP yang mencakup metodologi, implementasi, pengujian, dan evaluasi outcome." + }, + { + "kode": "Sub-094-2", + "uraian": "Mampu mendokumentasikan best practice industri yang dipelajari selama KP sebagai referensi pengembangan profesional." + } + ] + } + } + }, + "INF-711-504": { + "nama": "Teknopreneur", + "cpmks": { + "CPMK012": { + "desk": "Mampu menunjukkan kedisiplinan dan tanggung jawab dengan menyelesaikan seluruh kewajiban tugas secara mandiri dan tepat waktu.", + "subs": [ + { + "kode": "Sub-012-1", + "uraian": "Mampu menyelesaikan seluruh komponen rencana bisnis secara mandiri dan tepat waktu sesuai tahapan yang ditentukan." + }, + { + "kode": "Sub-012-2", + "uraian": "Mampu menunjukkan tanggung jawab dalam memimpin atau berkontribusi pada tim startup sesuai peran yang disepakati." + } + ] + }, + "CPMK013": { + "desk": "Mampu menunjukkan sikap profesional dalam berinteraksi dan bekerjasama dengan tim melalui komunikasi yang konstruktif dan pembagian peran yang adil.", + "subs": [ + { + "kode": "Sub-013-1", + "uraian": "Mampu berkolaborasi efektif dalam tim startup dengan komunikasi yang terbuka dan pembagian kerja yang adil." + }, + { + "kode": "Sub-013-2", + "uraian": "Mampu mempresentasikan ide bisnis teknologi kepada calon investor/mitra dengan percaya diri dan profesional." + } + ] + }, + "CPMK022": { + "desk": "Mampu mengevaluasi dampak sosial dan keberlanjutan dari sebuah solusi TI menggunakan kerangka analisis dampak yang sistematis, objektif dan berbasis data.", + "subs": [ + { + "kode": "Sub-022-1", + "uraian": "Mampu mengevaluasi dampak sosial dan keberlanjutan dari ide bisnis teknologi menggunakan kerangka SDG yang relevan." + }, + { + "kode": "Sub-022-2", + "uraian": "Mampu menganalisis implikasi etika dan lingkungan dari model bisnis teknologi yang dikembangkan." + } + ] + }, + "CPMK062": { + "desk": "Mampu menyajikan dan mempertahankan gagasan teknis secara lisan dengan argumentasi yang logis dan didukung media visual yang efektif.", + "subs": [ + { + "kode": "Sub-062-1", + "uraian": "Mampu menyusun dan menyampaikan pitch deck startup yang menarik, terstruktur, dan didukung data pasar yang valid." + }, + { + "kode": "Sub-062-2", + "uraian": "Mampu mempertahankan kelayakan bisnis teknologi dari pertanyaan kritis investor dalam sesi simulasi pitch." + } + ] + }, + "CPMK063": { + "desk": "Mampu berkolaborasi secara produktif dalam tim proyek perangkat lunak menggunakan alat manajemen proyek untuk mencapai target bersama.", + "subs": [ + { + "kode": "Sub-063-1", + "uraian": "Mampu mengelola tim startup menggunakan framework Lean Startup dengan build-measure-learn cycle yang efektif." + }, + { + "kode": "Sub-063-2", + "uraian": "Mampu menggunakan alat manajemen proyek bisnis untuk memantau progres validasi hipotesis bisnis." + } + ] + }, + "CPMK073": { + "desk": "Mampu merumuskan dan mengevaluasi rekomendasi solusi yang strategis dan logis berdasarkan hasil interpretasi data yang akurat.", + "subs": [ + { + "kode": "Sub-073-1", + "uraian": "Mampu merumuskan value proposition yang jelas dan terdiferensiasi berdasarkan hasil analisis kebutuhan pengguna target." + }, + { + "kode": "Sub-073-2", + "uraian": "Mampu mengevaluasi kelayakan bisnis teknologi menggunakan analisis pasar, kompetitor, dan proyeksi keuangan dasar." + } + ] + } + } + }, + "INF-711-505": { + "nama": "PMKM", + "cpmks": { + "CPMK011": { + "desk": "Mampu menerapkan nilai etika, kejujuran, dan integritas akademik secara konsisten dalam setiap aktivitas perkuliahan dan pengerjaan tugas tanpa melakukan plagiasi.", + "subs": [ + { + "kode": "Sub-011-1", + "uraian": "Mampu menunjukkan integritas dan etika profesional dalam setiap interaksi dengan komunitas dan mitra PMKM." + }, + { + "kode": "Sub-011-2", + "uraian": "Mampu mendokumentasikan seluruh aktivitas PMKM secara jujur dan transparan dalam laporan yang dapat diverifikasi." + } + ] + }, + "CPMK012": { + "desk": "Mampu menunjukkan kedisiplinan dan tanggung jawab dengan menyelesaikan seluruh kewajiban tugas secara mandiri dan tepat waktu.", + "subs": [ + { + "kode": "Sub-012-1", + "uraian": "Mampu memenuhi komitmen waktu dan target capaian PMKM yang telah disepakati dengan supervisor dan komunitas." + }, + { + "kode": "Sub-012-2", + "uraian": "Mampu menyelesaikan deliverable PMKM secara mandiri sesuai jadwal yang telah direncanakan." + } + ] + }, + "CPMK013": { + "desk": "Mampu menunjukkan sikap profesional dalam berinteraksi dan bekerjasama dengan tim melalui komunikasi yang konstruktif dan pembagian peran yang adil.", + "subs": [ + { + "kode": "Sub-013-1", + "uraian": "Mampu berinteraksi secara empati dan profesional dengan komunitas sasaran PMKM sebagai mitra yang setara." + }, + { + "kode": "Sub-013-2", + "uraian": "Mampu berkolaborasi secara produktif dalam tim PMKM lintas disiplin untuk mencapai target program." + } + ] + }, + "CPMK021": { + "desk": "Mampu menganalisis isu sosial, budaya, dan lingkungan yang relevan dengan penerapan TI melalui kajian studi kasus nyata di masyarakat.", + "subs": [ + { + "kode": "Sub-021-1", + "uraian": "Mampu mengidentifikasi dan menganalisis permasalahan sosial-teknologi yang dialami komunitas sasaran melalui need assessment." + }, + { + "kode": "Sub-021-2", + "uraian": "Mampu menganalisis konteks sosial-budaya komunitas dan implikasinya terhadap desain solusi teknologi yang tepat guna." + } + ] + }, + "CPMK023": { + "desk": "Mampu merancang dan menerapkan solusi teknologi tepat guna yang kontekstual dan berkelanjutan untuk membantu menyelesaikan permasalahan nyata di komunitas sasaran.", + "subs": [ + { + "kode": "Sub-023-1", + "uraian": "Mampu merancang solusi teknologi tepat guna yang sesuai dengan kapasitas dan kebutuhan komunitas sasaran PMKM." + }, + { + "kode": "Sub-023-2", + "uraian": "Mampu menerapkan dan mendampingi komunitas dalam menggunakan solusi teknologi yang dikembangkan secara berkelanjutan." + } + ] + }, + "CPMK061": { + "desk": "Mampu menyusun dan mengevaluasi dokumen spesifikasi teknis perangkat lunak yang lengkap dan sesuai dengan standar industri.", + "subs": [ + { + "kode": "Sub-061-1", + "uraian": "Mampu menyusun laporan akhir PMKM yang komprehensif mencakup analisis kebutuhan, implementasi, dampak, dan keberlanjutan." + }, + { + "kode": "Sub-061-2", + "uraian": "Mampu mendokumentasikan best practice dan lesson learned PMKM sebagai referensi program sejenis di masa depan." + } + ] + }, + "CPMK062": { + "desk": "Mampu menyajikan dan mempertahankan gagasan teknis secara lisan dengan argumentasi yang logis dan didukung media visual yang efektif.", + "subs": [ + { + "kode": "Sub-062-1", + "uraian": "Mampu mempresentasikan hasil PMKM kepada pemangku kepentingan dengan data dampak yang terukur dan komunikatif." + }, + { + "kode": "Sub-062-2", + "uraian": "Mampu mempertahankan keputusan desain solusi PMKM dari pertanyaan kritis dengan argumentasi berbasis kondisi lapangan." + } + ] + }, + "CPMK063": { + "desk": "Mampu berkolaborasi secara produktif dalam tim proyek perangkat lunak menggunakan alat manajemen proyek untuk mencapai target bersama.", + "subs": [ + { + "kode": "Sub-063-1", + "uraian": "Mampu mengelola program PMKM secara kolaboratif dengan stakeholder menggunakan alat manajemen program yang tepat." + }, + { + "kode": "Sub-063-2", + "uraian": "Mampu memfasilitasi transfer pengetahuan kepada komunitas agar solusi teknologi dapat dioperasikan secara mandiri." + } + ] + }, + "CPMK081": { + "desk": "Mampu menganalisis dan merumuskan kebutuhan pengguna ke dalam bentuk User Stories atau spesifikasi kebutuhan yang komprehensif dan mudah dipahami.", + "subs": [ + { + "kode": "Sub-081-1", + "uraian": "Mampu menganalisis dan mendokumentasikan kebutuhan komunitas sasaran secara sistematis sebagai dasar perancangan solusi." + }, + { + "kode": "Sub-081-2", + "uraian": "Mampu memvalidasi kebutuhan komunitas melalui iterasi co-design yang melibatkan perwakilan pengguna secara aktif." + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/seed/sks.json b/seed/sks.json new file mode 100644 index 0000000..18e2c22 --- /dev/null +++ b/seed/sks.json @@ -0,0 +1,198 @@ +{ + "MKWU-01": { + "sks": 2.0, + "semester": 1 + }, + "MKWU-02": { + "sks": 3.0, + "semester": 1 + }, + "MKWU-03": { + "sks": 2.0, + "semester": 1 + }, + "INF-111-101": { + "sks": 3.0, + "semester": 1 + }, + "INF-111-201": { + "sks": 2.0, + "semester": 1 + }, + "INF-111-102": { + "sks": 3.0, + "semester": 1 + }, + "INF-111-202": { + "sks": 2.0, + "semester": 1 + }, + "INF-111-501": { + "sks": 2.0, + "semester": 1 + }, + "MKWU-04": { + "sks": 2.0, + "semester": 2 + }, + "MKWU-05": { + "sks": 2.0, + "semester": 2 + }, + "INF-211-203": { + "sks": 2.0, + "semester": 2 + }, + "INF-221-204": { + "sks": 2.0, + "semester": 2 + }, + "INF-211-205": { + "sks": 3.0, + "semester": 2 + }, + "INF-211-206": { + "sks": 3.0, + "semester": 2 + }, + "INF-211-103": { + "sks": 3.0, + "semester": 2 + }, + "INF-211-502": { + "sks": 2.0, + "semester": 2 + }, + "INF-311-104": { + "sks": 3.0, + "semester": 3 + }, + "INF-311-207": { + "sks": 2.0, + "semester": 3 + }, + "INF-321-208": { + "sks": 2.0, + "semester": 3 + }, + "INF-311-209": { + "sks": 3.0, + "semester": 3 + }, + "INF-321-210": { + "sks": 1.0, + "semester": 3 + }, + "INF-311-211": { + "sks": 2.0, + "semester": 3 + }, + "INF-321-212": { + "sks": 2.0, + "semester": 3 + }, + "INF-311-301": { + "sks": 3.0, + "semester": 4 + }, + "INF-311-302": { + "sks": 2.0, + "semester": 4 + }, + "INF-411-303": { + "sks": 2.0, + "semester": 4 + }, + "INF-411-304": { + "sks": 3.0, + "semester": 4 + }, + "INF-411-305": { + "sks": 2.0, + "semester": 4 + }, + "INF-411-213": { + "sks": 3.0, + "semester": 4 + }, + "INF-411-105": { + "sks": 3.0, + "semester": 4 + }, + "INF-411-214": { + "sks": 2.0, + "semester": 5 + }, + "INF-421-215": { + "sks": 2.0, + "semester": 5 + }, + "INF-411-306": { + "sks": 3.0, + "semester": 5 + }, + "INF-411-503": { + "sks": 2.0, + "semester": 5 + }, + "INF-511-307": { + "sks": 3.0, + "semester": 5 + }, + "INF-511-401": { + "sks": 3.0, + "semester": 5 + }, + "INF-511-402": { + "sks": 3.0, + "semester": 5 + }, + "INF-511-403": { + "sks": 3.0, + "semester": 6 + }, + "INF-511-404": { + "sks": 3.0, + "semester": 6 + }, + "INF-511-405": { + "sks": 3.0, + "semester": 6 + }, + "INF-511-801": { + "sks": 2.0, + "semester": 6 + }, + "INF-611-406": { + "sks": 3.0, + "semester": 6 + }, + "INF-611-407": { + "sks": 3.0, + "semester": 6 + }, + "INF-611-408": { + "sks": 3.0, + "semester": 6 + }, + "INF-611-409": { + "sks": 3.0, + "semester": 7 + }, + "INF-611-601": { + "sks": 4.0, + "semester": 7 + }, + "INF-611-802": { + "sks": 2.0, + "semester": 7 + }, + "INF-711-504": { + "sks": 3.0, + "semester": 7 + }, + "INF-711-505": { + "sks": 2.0, + "semester": 8 + } +} \ No newline at end of file diff --git a/tools/extract_seed.py b/tools/extract_seed.py new file mode 100644 index 0000000..3cac9da --- /dev/null +++ b/tools/extract_seed.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +""" +Ekstrak data kurikulum (CPL, CPMK, MK, Sub-CPMK, matriks) yang ter-embed di +dashboard pemetaan, lalu simpan sebagai seed/kurikulum.json. + +Jalankan ulang bila data dashboard diperbarui: + python tools/extract_seed.py [path_index_html] +""" +import json +import pathlib +import sys + +BASE = pathlib.Path(__file__).resolve().parent.parent +DEFAULT_SRC = BASE.parent / "dashboard-deploy" / "index.html" +OUT = BASE / "seed" / "kurikulum.json" + + +def main() -> None: + src = pathlib.Path(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT_SRC + html = src.read_text(encoding="utf-8") + i = html.find('{"cpl"') + if i < 0: + raise SystemExit(f"Blok JSON kurikulum tidak ditemukan di {src}") + data, _ = json.JSONDecoder().raw_decode(html[i:]) + + OUT.parent.mkdir(parents=True, exist_ok=True) + OUT.write_text(json.dumps(data, ensure_ascii=False, indent=1), encoding="utf-8") + + n_sub = sum( + len(c["subs"]) for mk in data["sub"].values() for c in mk["cpmks"].values() + ) + print( + f"[OK] {OUT.name}: {len(data['cpl'])} CPL, {len(data['cpmk'])} CPMK, " + f"{len(data['mk'])} MK, {n_sub} Sub-CPMK (sumber: {src})" + ) + + +if __name__ == "__main__": + main() diff --git a/tools/extract_sks.py b/tools/extract_sks.py new file mode 100644 index 0000000..0584565 --- /dev/null +++ b/tools/extract_sks.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +""" +Ekstrak SKS + semester per MK dari sheet '14. Susunan MK' spreadsheet kurikulum +→ seed/sks.json ({kode: {"sks": x, "semester": n}}). +SKS dipakai untuk opsi roll-up tertimbang SKS; semester untuk prefill form RPS. + +Jalankan: python tools/extract_sks.py [path_xlsx] +""" +import json +import pathlib +import sys + +import openpyxl + +BASE = pathlib.Path(__file__).resolve().parent.parent +DEFAULT_SRC = BASE.parent / "Draft Kurikulum Terbaru 2025.xlsx" +OUT = BASE / "seed" / "sks.json" +SHEET = "14. Susunan MK" + + +def main() -> None: + src = pathlib.Path(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT_SRC + kur = json.loads((BASE / "seed" / "kurikulum.json").read_text(encoding="utf-8")) + kode_valid = set(kur["mk"].keys()) + + wb = openpyxl.load_workbook(src, read_only=True, data_only=True) + ws = wb[SHEET] + rows = [list(r) for r in ws.iter_rows(values_only=True)] + + # baris header: memuat 'Kode MK' dan 'SKS' + header_i = next(i for i, r in enumerate(rows) + if any(str(v).strip() == "Kode MK" for v in r if v)) + header = [str(v).strip() if v is not None else "" for v in rows[header_i]] + i_kode = header.index("Kode MK") + i_sks = header.index("SKS") + kolom_smt = {ci: int(h) for ci, h in enumerate(header) if h.isdigit()} + + data = {} + for r in rows[header_i + 1:]: + if i_kode >= len(r) or not isinstance(r[i_kode], str): + continue + kode = r[i_kode].strip() + if kode not in kode_valid: + continue + try: + sks = float(r[i_sks]) + except (TypeError, ValueError, IndexError): + continue + semester = next((smt for ci, smt in kolom_smt.items() + if ci < len(r) and r[ci] not in (None, "")), None) + data[kode] = {"sks": sks, "semester": semester} + + OUT.write_text(json.dumps(data, ensure_ascii=False, indent=1), encoding="utf-8") + hilang = sorted(kode_valid - set(data)) + print(f"[OK] {OUT.name}: SKS untuk {len(data)}/{len(kode_valid)} MK " + f"(total SKS: {sum(v['sks'] for v in data.values()):g})") + if hilang: + print(" tanpa SKS:", ", ".join(hilang)) + + +if __name__ == "__main__": + main()