5d7ab16733
- Portofolio: section "Analisis Proses Asesmen" (dropdown tertutup default) — distribusi huruf mutu, kelompok capaian (mean ± SD), Sub-CPMK terlemah/terkuat, kesenjangan rencana vs realisasi, mahasiswa berisiko, tren lintas tahun ajaran - Portofolio: Nomor Dokumen & Tanggal Terbit terisi otomatis; placeholder contoh di Realisasi Pembelajaran dan Kendala & Tindak Lanjut; tombol "Isi Otomatis" merangkai draf Simpulan dari analisis + realisasi + kendala - Nilai: tabel Kelompok Capaian (Atas/Tengah/Bawah, shading warna) dipindah ke dropdown "Detail Tambahan" bersama Skala Poin/Rincian Bobot/Hasil Ketercapaian; tabel utama tak lagi ada scroll vertikal internal - Nilai: riwayat pengisian dicatat terstruktur (waktu, dosen, aksi, komponen), tanpa batas 8 entri, dengan ringkasan per komponen + dropdown Log Lengkap — mendukung akuntabilitas team teaching - Fix bug data: seed kurikulum menyimpan 'cpl' CPMK sebagai string tunggal, bukan list, sehingga pecah per huruf saat di-join() di tabel Hasil Ketercapaian/Rekap (mis. "CPL-03" -> "C, P, L, -, 0, 3"); dinormalisasi di db.py (runtime) dan tools/extract_seed.py (agar re-seed berikutnya benar) - Hover tooltip deskripsi lengkap di kode Sub-CPMK/CPMK/CPL (halaman Nilai dan Rekap CPL/CPMK) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
422 lines
18 KiB
Python
422 lines
18 KiB
Python
# -*- 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": [{"nim","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 skor_dari_sesi(komponen_daftar: list[dict], sesi: dict) -> dict[str, dict[str, float]]:
|
|
"""nim -> {sub: skor}, dihitung dari log sesi input per-komponen (tombol
|
|
'isi per komponen' di halaman nilai): rata sederhana antar sesi dalam
|
|
komponen yang sama (mis. beberapa kali Kuis), lalu rata tertimbang bobot
|
|
antar-komponen bila satu Sub-CPMK diukur oleh >1 komponen (mis. Kuis dan
|
|
Presentasi sama-sama menilai satu Sub-CPMK) — bobot mengikuti alokasi
|
|
yang sama dengan bobot_sub_dari_rps (bobot komponen dibagi rata ke subnya).
|
|
|
|
Tiap sesi boleh punya "subs" sendiri (subset Sub-CPMK komponen itu — dipakai
|
|
komponen Observasi/Tugas untuk rincian ala RTM per tugas bernama); sesi
|
|
tanpa "subs" (atau kosong) dianggap menyasar semua Sub-CPMK komponen itu
|
|
(perilaku lama). Dengan begitu satu komponen boleh diisi lewat beberapa
|
|
sesi yang masing-masing hanya menyasar sebagian Sub-CPMK, tanpa
|
|
menyamaratakan nilainya ke Sub-CPMK lain yang tak disasar sesi itu.
|
|
"""
|
|
komponen_by_i = {k["i"]: k for k in komponen_daftar}
|
|
kontribusi_per_sub: dict[str, list[tuple[float, dict]]] = {}
|
|
for i, k in komponen_by_i.items():
|
|
daftar_sesi = (sesi or {}).get(i) or []
|
|
if not daftar_sesi:
|
|
continue
|
|
try:
|
|
bobot = float(k.get("bobot") or 0)
|
|
except (TypeError, ValueError):
|
|
bobot = 0
|
|
n_subs = len(k["subs"]) or 1
|
|
w = bobot / n_subs if bobot else 1.0
|
|
for sub in k["subs"]:
|
|
relevan = [s for s in daftar_sesi if sub in (s.get("subs") or k["subs"])]
|
|
if not relevan:
|
|
continue
|
|
per_nim: dict[str, list[float]] = {}
|
|
for s in relevan:
|
|
for nim, v in (s.get("nilai") or {}).items():
|
|
if v is not None:
|
|
per_nim.setdefault(nim, []).append(float(v))
|
|
avg_map = {nim: _rata(xs) for nim, xs in per_nim.items()}
|
|
kontribusi_per_sub.setdefault(sub, []).append((w, avg_map))
|
|
|
|
out: dict[str, dict[str, float]] = {}
|
|
for sub, kontribusi in kontribusi_per_sub.items():
|
|
nims: set[str] = set()
|
|
for _, avg_map in kontribusi:
|
|
nims |= avg_map.keys()
|
|
for nim in nims:
|
|
pasangan = [(w, avg_map[nim]) for w, avg_map in kontribusi if nim in avg_map]
|
|
total_w = sum(w for w, _ in pasangan)
|
|
nilai = (sum(w * v for w, v in pasangan) / total_w) if total_w \
|
|
else _rata([v for _, v in pasangan])
|
|
out.setdefault(nim, {})[sub] = nilai
|
|
return out
|
|
|
|
|
|
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({"nim": m.get("nim", ""), "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"]:
|
|
pendukung = [v for v in cpmk.values() if cp["kode"] in v["cpl"]]
|
|
ternilai = [v for v in pendukung if v["skor"] is not None]
|
|
skor = _rata([v["skor"] for v in ternilai])
|
|
cpl[cp["kode"]] = {"skor": skor, "n_cpmk": len(pendukung),
|
|
"n_cpmk_ternilai": len(ternilai)}
|
|
|
|
return {"sub": sub, "cpmk": cpmk, "cpl": cpl,
|
|
"mode": nilai_data.get("mode", "agregat")}
|
|
|
|
|
|
def sub_terurut(peta: dict, rollup_sub: dict) -> list[dict]:
|
|
"""Sub-CPMK ternilai, terurut naik dari yang terlemah — bahan analisis
|
|
proses asesmen di Portofolio (mana yang paling perlu perhatian)."""
|
|
out = []
|
|
for s in peta["subs"]:
|
|
info = rollup_sub.get(s["kode"])
|
|
if info and info["rata"] is not None:
|
|
out.append({"kode": s["kode"], "uraian": s["uraian"],
|
|
"rata": info["rata"], "lulus_pct": info["lulus_pct"]})
|
|
return sorted(out, key=lambda s: s["rata"])
|
|
|
|
|
|
def kesenjangan_rencana_realisasi(bobot_sub: dict[str, float],
|
|
rollup_sub: dict) -> list[str]:
|
|
"""Sub-CPMK yang diberi bobot penilaian di RPS tapi belum ada nilai
|
|
masuk sama sekali — kesenjangan antara rencana dan realisasi asesmen."""
|
|
return sorted(
|
|
s for s in bobot_sub
|
|
if bobot_sub[s] and (s not in rollup_sub or rollup_sub[s]["rata"] is None)
|
|
)
|
|
|
|
|
|
def mahasiswa_berisiko(nilai_data: dict, akhir: list[dict], ambang: float,
|
|
min_sub_dibawah: int = 2) -> list[dict]:
|
|
"""Mahasiswa dengan >= min_sub_dibawah Sub-CPMK di bawah ambang, atau
|
|
nilai akhir tertimbangnya sendiri di bawah ambang (early-warning list)."""
|
|
if nilai_data.get("mode") != "per_mahasiswa":
|
|
return []
|
|
akhir_by_nim = {a["nim"]: a["akhir"] for a in akhir}
|
|
out = []
|
|
for m in nilai_data.get("mahasiswa", []):
|
|
skor = {s: v for s, v in (m.get("skor") or {}).items() if v is not None}
|
|
if not skor:
|
|
continue
|
|
n_dibawah = sum(1 for v in skor.values() if v < ambang)
|
|
nim = m.get("nim", "")
|
|
nilai_akhir = akhir_by_nim.get(nim)
|
|
if n_dibawah >= min_sub_dibawah or (nilai_akhir is not None and nilai_akhir < ambang):
|
|
out.append({"nim": nim, "nama": m.get("nama", ""),
|
|
"n_dibawah": n_dibawah, "total_sub": len(skor),
|
|
"akhir": nilai_akhir})
|
|
return sorted(out, key=lambda m: (-m["n_dibawah"], m["akhir"] if m["akhir"] is not None else 999))
|
|
|
|
|
|
def tren_mk(kode: str, daftar_tahun: list[str], ambang: float) -> list[dict]:
|
|
"""Rata-rata nilai akhir & % kelulusan MK ini per tahun ajaran yang
|
|
berdata — untuk melihat apakah capaian membaik atau tidak antar tahun."""
|
|
out = []
|
|
for t in daftar_tahun:
|
|
nilai_t = db.ambil_dok("nilai", kode, t)
|
|
if not nilai_t:
|
|
continue
|
|
nilai_data_t = nilai_t["data"]
|
|
rps_t = db.ambil_rps(kode, t)
|
|
bobot_t = bobot_sub_dari_rps(rps_t["data"] if rps_t else None)
|
|
if nilai_data_t.get("mode") == "per_mahasiswa":
|
|
akhir_t = nilai_akhir_mahasiswa(nilai_data_t, bobot_t)
|
|
if akhir_t:
|
|
xs = [a["akhir"] for a in akhir_t]
|
|
out.append({"tahun": t, "mean": _rata(xs),
|
|
"lulus_pct": 100.0 * sum(1 for x in xs if x >= ambang) / len(xs),
|
|
"n": len(xs)})
|
|
continue
|
|
sub_t = skor_sub(nilai_data_t, ambang)
|
|
nilai_ada = [v["rata"] for v in sub_t.values() if v["rata"] is not None]
|
|
if nilai_ada:
|
|
out.append({"tahun": t, "mean": _rata(nilai_ada), "lulus_pct": None, "n": None})
|
|
return out
|
|
|
|
|
|
def kelompok_capaian(akhir: list[dict], statistik: dict | None) -> dict | None:
|
|
"""Kelompokkan mahasiswa jadi Atas/Tengah/Bawah berdasarkan mean ± 1 SD
|
|
(acuan norma, lazim dipakai dalam evaluasi pendidikan) — pelengkap
|
|
'mahasiswa berisiko' yang acuannya ambang kelulusan tetap, bukan sebaran
|
|
kelas ini."""
|
|
if not statistik or not akhir:
|
|
return None
|
|
batas_bawah = statistik["mean"] - statistik["stdev"]
|
|
batas_atas = statistik["mean"] + statistik["stdev"]
|
|
atas, tengah, bawah = [], [], []
|
|
for a in akhir:
|
|
if a["akhir"] > batas_atas:
|
|
atas.append(a)
|
|
elif a["akhir"] < batas_bawah:
|
|
bawah.append(a)
|
|
else:
|
|
tengah.append(a)
|
|
return {
|
|
"batas_bawah": batas_bawah, "batas_atas": batas_atas,
|
|
"atas": sorted(atas, key=lambda a: -a["akhir"]),
|
|
"tengah": sorted(tengah, key=lambda a: -a["akhir"]),
|
|
"bawah": sorted(bawah, key=lambda a: -a["akhir"]),
|
|
}
|
|
|
|
|
|
def saran_simpulan(akhir: list[dict], statistik: dict | None, sub_urut: list[dict],
|
|
kesenjangan: list[str], berisiko: list[dict],
|
|
tren: list[dict], ambang: float) -> str:
|
|
"""Rangkai draf narasi Simpulan Hasil Evaluasi dari hasil Analisis Proses
|
|
Asesmen — dipakai sebagai placeholder awal, dosen tetap yang menulis versi
|
|
finalnya (termasuk simpulan ketercapaian CPL/CPMK dan tindak lanjut)."""
|
|
bagian = []
|
|
if statistik:
|
|
lulus_pct = 100.0 * sum(1 for a in akhir if a["akhir"] >= ambang) / len(akhir)
|
|
bagian.append(
|
|
f"Rata-rata nilai akhir {statistik['mean']:.1f} (median {statistik['median']:.1f}) "
|
|
f"dari {statistik['n']} mahasiswa; {lulus_pct:.0f}% mencapai ambang {ambang:.0f}."
|
|
)
|
|
if sub_urut:
|
|
terlemah, terkuat = sub_urut[0], sub_urut[-1]
|
|
if terlemah["kode"] != terkuat["kode"]:
|
|
bagian.append(
|
|
f"Capaian terendah pada {terlemah['kode']} (skor {terlemah['rata']:.1f}), "
|
|
f"tertinggi pada {terkuat['kode']} (skor {terkuat['rata']:.1f})."
|
|
)
|
|
else:
|
|
bagian.append(f"Satu-satunya Sub-CPMK ternilai: {terlemah['kode']} (skor {terlemah['rata']:.1f}).")
|
|
if kesenjangan:
|
|
bagian.append(
|
|
f"{len(kesenjangan)} Sub-CPMK direncanakan di RPS namun belum ternilai: "
|
|
f"{', '.join(kesenjangan)}."
|
|
)
|
|
if berisiko:
|
|
bagian.append(f"{len(berisiko)} mahasiswa teridentifikasi berisiko (nilai rendah di beberapa Sub-CPMK).")
|
|
if len(tren) > 1:
|
|
sekarang, sebelum = tren[-1], tren[-2]
|
|
if sekarang["mean"] is not None and sebelum["mean"] is not None:
|
|
selisih = sekarang["mean"] - sebelum["mean"]
|
|
arah = "naik" if selisih > 0 else ("turun" if selisih < 0 else "stabil")
|
|
bagian.append(
|
|
f"Dibanding {sebelum['tahun']} ({sebelum['mean']:.1f}), rata-rata {arah} "
|
|
f"{abs(selisih):.1f} poin di {sekarang['tahun']}."
|
|
)
|
|
bagian.append(
|
|
"Lengkapi dengan simpulan ketercapaian CPL/CPMK terkait dan rencana tindak lanjut "
|
|
"untuk siklus berikutnya."
|
|
)
|
|
return " ".join(bagian)
|
|
|
|
|
|
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"}
|