Files
OBE-Mapping/app/generator/rps_docx.py
Power BI Dev 9be6bd03e3 Gabungkan aplikasi RPS & Portofolio OBE (dashboard jadi bagian aplikasi)
Dashboard pemetaan statis kini dilayani aplikasi di /dashboard
(app/static/dashboard.html). Fitur lengkap Tahap 1-4 + dasbor monitoring,
menu global, filter semester, input nilai langsung, penggabungan nilai
team teaching, tabel bisa urut. Dockerfile berubah dari nginx statis ke
uvicorn/FastAPI: healthcheck, proxy-headers, volume /data (SQLite).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 12:05:28 +07:00

336 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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()