# -*- 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()