TIF_E41253310/Backend/app/models/models.py

458 lines
25 KiB
Python

"""
Model Database Baru - sesuai PRD Section 24
Hanya tabel baru (pekerjaan, kompetensi, clustering_result, dll)
"""
# Mengimpor modul Any untuk type hint fleksibel di Python
from typing import Any
# Mengimpor modul kolom dan tipe data dari SQLAlchemy: Column, Integer, String, DateTime, ForeignKey, Float, Boolean, Text
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Float, Boolean, Text
# Mengimpor relationship untuk relasi antar tabel ORM
from sqlalchemy.orm import relationship
# Mengimpor func untuk fungsi SQL bawaan seperti pengambilan waktu server
from sqlalchemy.sql import func
# Mengimpor kelas basis Declarative Base
from app.models.base import Base
# =========================================================================
# MODEL ENTITAS: PEKERJAAN (Tabel Pekerjaan Alumni)
# =========================================================================
class Pekerjaan(Base):
"""Tabel Pekerjaan - PRD Section 24: Menyimpan informasi spesifik profesi & tempat kerja alumni"""
# Nama tabel fisik di database MySQL
__tablename__ = "pekerjaan"
# Primary key identitas record pekerjaan bertipe integer
id = Column(Integer, primary_key=True, index=True)
# Foreign key yang berelasi ke kolom id di tabel alumni (unique=True karena One-to-One dengan alumni)
alumni_id = Column(Integer, ForeignKey("alumni.id"), nullable=False, unique=True)
# Status pekerjaan saat ini: misal bekerja, belum_kerja, wiraswasta, melanjutkan_pendidikan
status_pekerjaan = Column(String(50)) # bekerja, belum_kerja, wiraswasta, dll
# Jenis institusi / perusahaan: BUMN, Swasta, Lembaga Pemerintah, Yayasan/Nirlaba
jenis_perusahaan = Column(String(100))
# Nama resmi perusahaan / kantor tempat alumni bekerja
nama_perusahaan = Column(String(255))
# Posisi / nama jabatan alumni di perusahaan (misal: Software Engineer, Data Analyst)
jabatan = Column(String(150))
# Skala tingkatan tempat kerja: Lokal / Tidak Berbadan Hukum, Nasional Berbadan Hukum, atau Multinasional/Internasional
tingkatan_tempat_kerja = Column(String(50))
# Lokasi provinsi tempat bekerja
provinsi = Column(String(100))
# Lokasi kabupaten / kota tempat bekerja
kabupaten_kota = Column(String(100))
# Teks gabungan (concatenation) dari posisi, perusahaan, dan deskripsi kerja yang digunakan sebagai korpus input TF-IDF dan Klastering
teks_pekerjaan = Column(Text) # Hasil concatenation untuk clustering
# Relasi ORM balik ke tabel Alumni (bidirectional relationship)
alumni = relationship("Alumni", back_populates="pekerjaan_detail")
# Waktu pembuatan record data pekerjaan
created_at = Column(DateTime(timezone=True), server_default=func.now())
# Waktu update terakhir record data pekerjaan
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# =========================================================================
# MODEL ENTITAS: KOMPETENSI (Tabel Nilai 7 Kelompok Kompetensi)
# =========================================================================
class Kompetensi(Base):
"""Tabel Kompetensi - PRD Section 24: Menyimpan penilaian 7 kompetensi saat lulus kuliah"""
# Nama tabel fisik di database MySQL
__tablename__ = "kompetensi"
# Primary key identitas record kompetensi bertipe integer
id = Column(Integer, primary_key=True, index=True)
# Foreign key yang mengaitkan data kompetensi dengan alumni terkait (One-to-One)
alumni_id = Column(Integer, ForeignKey("alumni.id"), nullable=False, unique=True)
# 7 Kelompok Kompetensi (sesuai standar instrumen Tracer Study Dikti / PRD Section 9.4)
# 1. Penilaian etika, moral, dan integritas saat lulus
etika_lulus = Column(String(255))
# 2. Penilaian keahlian berdasarkan bidang ilmu spesifik (keahlian utama)
keahlian_lulus = Column(String(255))
# 3. Penilaian kecakapan penguasaan bahasa Inggris saat lulus
bahasa_inggris_lulus = Column(String(255))
# 4. Penilaian kemahiran penggunaan teknologi informasi & komputer
teknologi_informasi_lulus = Column(String(255))
# 5. Penilaian kemampuan komunikasi interpersonal dan presentasi
komunikasi_lulus = Column(String(255))
# 6. Penilaian kemampuan kerja sama dalam tim (teamwork)
kerja_sama_lulus = Column(String(255))
# 7. Penilaian komitmen pengembangan diri dan kemampuan belajar mandiri (lifelong learning)
pengembangan_diri_lulus = Column(String(255))
# Relasi ORM balik ke model Alumni
alumni = relationship("Alumni", back_populates="kompetensi")
# Waktu pembuatan record kompetensi
created_at = Column(DateTime(timezone=True), server_default=func.now())
# Waktu pembaruan record kompetensi
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# =========================================================================
# MODEL ENTITAS: CLUSTERING RESULT (Tabel Hasil Prediksi Klaster KMeans)
# =========================================================================
class ClusteringResult(Base):
"""Tabel Hasil Clustering - PRD Section 24: Menyimpan label klaster per alumni dari algoritma KMeans"""
# Nama tabel fisik hasil klastering
__tablename__ = "clustering_result"
# Primary key identitas hasil klastering
id = Column(Integer, primary_key=True, index=True)
# Foreign key mengacu pada ID alumni yang diklasifikasikan
alumni_id = Column(Integer, ForeignKey("alumni.id"), nullable=False)
# Identifikasi versi model Machine Learning yang menghasilkan prediksi (misal: "kmeans_v1_2024")
model_version = Column(String(50))
# Jumlah klaster (k) yang digunakan saat pemodelan (misal: k=3 atau k=4)
k = Column(Integer, nullable=False) # Jumlah cluster
# Indeks cluster numerik hasil komputasi KMeans (0, 1, 2, dst.)
cluster = Column(Integer, nullable=False) # Cluster 0,1,2,3
# Label kategori klaster yang diformat (misal: 'C0', 'C1', 'C2', 'C3')
kategori = Column(String(5), nullable=False) # C0, C1, C2, C3
# Indikator kesesuaian antara bidang kerja alumni dengan profil lulusan prodi (True/False)
profile_match = Column(Boolean, default=False) # Apakah sesuai profil
# Kata kunci (keyword) yang cocok antara profil kompetensi alumni dan kamus kata kunci prodi
matched_keyword = Column(Text) # Keyword yang match
# Relasi ORM balik ke entitas Alumni
alumni = relationship("Alumni", back_populates="clustering_results")
# Waktu eksekusi dan pencatatan hasil klastering ke database
created_at = Column(DateTime(timezone=True), server_default=func.now())
# =========================================================================
# MODEL ENTITAS: MODEL EVALUATION (Tabel Metrik Evaluasi Model ML)
# =========================================================================
class ModelEvaluation(Base):
"""Tabel Evaluasi Model - PRD Section 24: Menyimpan metrik evaluasi algoritma KMeans (Silhouette Score & Inertia/Elbow)"""
# Nama tabel riwayat evaluasi model
__tablename__ = "model_evaluation"
# Primary key identitas catatan evaluasi
id = Column(Integer, primary_key=True, index=True)
# Kode versi model yang diuji
model_version = Column(String(50), nullable=False)
# Nilai hiperparameter k (jumlah cluster) yang diuji
k = Column(Integer, nullable=False)
# Nilai Inertia (Within-Cluster Sum of Squares) untuk analisis Elbow Method
inertia = Column(Float)
# Nilai Silhouette Coefficient (-1 hingga +1) untuk mengukur kerapatan dan pemisahan antar klaster
silhouette_score = Column(Float)
# Waktu pengujian model dijalankan dan dicatat
created_at = Column(DateTime(timezone=True), server_default=func.now())
# =========================================================================
# MODEL ENTITAS: KEYWORD DICTIONARY (Tabel Kamus Kata Kunci Prodi)
# =========================================================================
class KeywordDictionary(Base):
"""Tabel Kamus Kata Kunci - PRD Section 24: Kosakata kompetensi/profesi acuan untuk pencocokan profil lulusan prodi"""
# Nama tabel kamus kata kunci
__tablename__ = "keyword_dictionary"
# Primary key identitas kata kunci
id = Column(Integer, primary_key=True, index=True)
# Kode program studi target (contoh: 'TIF', 'MIF', 'TKK')
kode_prodi = Column(String(10), nullable=False) # TIF, MIF, TKK
# Kata kunci keahlian atau pekerjaan (contoh: "programmer", "network engineer", "database")
keyword = Column(String(100), nullable=False)
# Klasifikasi kata kunci: 'technical', 'softskill', 'role', dll.
kategori_keyword = Column(String(50)) # technical, softskill, dll
# Status apakah kata kunci ini aktif diikutsertakan dalam pencocokan profil lulusan
aktif = Column(Boolean, default=True)
# Waktu penambahan kata kunci ke dalam kamus
created_at = Column(DateTime(timezone=True), server_default=func.now())
# =========================================================================
# MODEL ENTITAS: TRACER RESPONSE (Tabel Respon Lengkap Kuesioner Alumni)
# =========================================================================
class TracerResponse(Base):
"""
Tabel Respon Tracer Study - Menyimpan SEMUA kolom dari kuesioner tracer study
Sesuai dengan dokumen Pertanyaan-dan-jawaban.md dan format baku Tracer Study DIKTI
"""
# Nama tabel fisik di database MySQL
__tablename__ = "tracer_responses"
# Kolom ID utama entitas respon tracer study (Primary Key)
id = Column(Integer, primary_key=True, index=True)
# Foreign key yang menghubungkan respon ke ID alumni (One-to-One, unik)
alumni_id = Column(Integer, ForeignKey("alumni.id"), nullable=False, unique=True)
# Kolom Nomor Induk Mahasiswa (NIM) alumni yang diindeks untuk pencarian cepat
nim: Any = Column(String(50), index=True)
# ===== SECTION 1: Identitas Diri (Extended) =====
# Kode resmi perguruan tinggi asal alumni (contoh: 005016 untuk Politeknik Negeri Jember)
kode_pt = Column(String(100)) # Kode Perguruan Tinggi
# Nomor Induk Kependudukan (NIK/KTP) alumni
nik = Column(String(50)) # NIK/No KTP
# Nomor Pokok Wajib Pajak (NPWP) alumni jika sudah memiliki
npwp = Column(String(50)) # NPWP
# Nomor telepon seluler / WhatsApp aktif alumni
telepon = Column(String(50)) # No HP/WhatsApp
# Alamat surat elektronik (email) aktif alumni
email = Column(String(255)) # Email
# ===== SECTION 2: Kuesioner Utama =====
# Status aktivitas alumni saat ini (F8: Bekerja, Wiraswasta, Belum Kerja, atau Studi Lanjut)
status_saat_ini = Column(String(255)) # F8: Bekerja/Wiraswasta/Belum/Melanjutkan
# Masa tunggu mendapatkan pekerjaan pertama dalam hitungan bulan (F502)
bulan_dapat_kerja = Column(Integer) # F502: 0-12 bulan sebelum/sesudah lulus
# Status apakah alumni memperoleh pekerjaan dalam kurun waktu <= 6 bulan setelah lulus (F504)
dapet_kerja_6_bulan = Column(String(255)) # F504: <=6 bulan (Ya/Tidak/kode)
# Rata-rata pendapatan atau take-home pay per bulan (F505)
rata_pendapatan = Column(Text) # F505: Gaji per bulan
# Jumlah bulan yang dibutuhkan untuk mendapatkan pekerjaan setelah resmi diwisuda (F506)
bulan_dapat_kerja_setelah = Column(Integer) # F506
# Lokasi kerja
# Provinsi wilayah tempat instansi / kantor alumni bekerja (F5a1)
provinsi_kerja = Column(String(100)) # F5a1
# Kabupaten atau kota wilayah tempat kantor alumni bekerja (F5a2)
kab_kota_kerja = Column(String(100)) # F5a2
# Info perusahaan
# Kategori status badan usaha instansi (F1101: Instansi pemerintah, BUMN, swasta, dll)
jenis_perusahaan_full = Column(String(255)) # F1101: Instansi pemerintah/swasta/BUMN/dll
# Jumlah total perusahaan yang dilamar atau rincian tambahan jika memilih opsi lainnya (F1102)
jumlah_perusahaan = Column(Text) # F1102 (dapat berupa teks panjang jika memilih lainnya)
# Nama resmi lembaga / instansi tempat alumni saat ini berkarir (F5b)
nama_perusahaan = Column(Text) # F5b
# Jabatan atau role posisi pekerjaan alumni (F5c: misal Backend Developer, Staff IT)
jabatan = Column(String(255)) # F5c
# Skala cakupan operasional tempat kerja (F5d: Lokal, Nasional, Multinasional/Internasional)
tingkatan_tempat_kerja = Column(String(255)) # F5d
# ===== SECTION 3: Studi Lanjut =====
# Sumber dana pembiayaan studi lanjut (F18a: Biaya Mandiri, Beasiswa LPDP, dll)
sumber_biaya_studi = Column(String(255)) # F18a: Biaya sendiri/Beasiswa
# Nama perguruan tinggi tujuan tempat melanjutkan jenjang studi berikutnya (F18b)
pt_studi_lanjut = Column(Text) # F18b: Nama PT
# Program studi yang diambil pada jenjang pendidikan lanjutan (F18c)
prodi_studi_lanjut = Column(Text) # F18c: Nama Prodi
# Tanggal atau semester resmi mulai perkuliahan studi lanjut (F18d)
tanggal_masuk_studi = Column(DateTime) # F18d: Tanggal masuk
# ===== SECTION 4: Pembiayaan Kuliah =====
# Sumber dana utama selama menempuh kuliah diploma/sarjana (F1201: Orang Tua, Bidikmisi, Beasiswa ADik)
sumber_biaya_kuliah = Column(String(255)) # F1201: Biaya sendiri/Beasiswa ADIK/BIDIKMISI/dll
# ===== SECTION 5: Kesesuaian Kerja =====
# Tingkat keeratan hubungan antara bidang keilmuan kuliah dengan pekerjaan (F14: Sangat Erat s.d. Tidak Sama Sekali)
keeratan_bidang_studi = Column(String(255)) # F14: Sangat Erat - Tidak sama sekali
# Tingkat kesesuaian strata pendidikan yang dipersyaratkan oleh posisi kerja (F15)
tingkat_pendidikan_tepat = Column(String(255)) # F15: Setingkat lebih tinggi - Tidak perlu
# ===== SECTION 6: Kompetensi SAAT LULUS (F1761-F1773) =====
# Penilaian penguasaan etika saat baru dinyatakan lulus (F1761)
etika_lulus_full = Column(String(255)) # F1761
# Penilaian keahlian bidang ilmu kompetensi inti saat lulus (F1763)
keahlian_lulus_full = Column(String(255)) # F1763
# Penilaian kemampuan bahasa Inggris saat lulus (F1765)
bahasa_inggris_lulus_full = Column(String(255)) # F1765
# Penilaian penguasaan teknologi informasi saat lulus (F1767)
teknologi_informasi_lulus_full = Column(String(255)) # F1767
# Penilaian kemampuan komunikasi verbal/tulisan saat lulus (F1769)
komunikasi_lulus_full = Column(String(255)) # F1769
# Penilaian kemampuan kerja sama tim saat lulus (F1771)
kerja_sama_lulus_full = Column(String(255)) # F1771
# Penilaian kesiapan pengembangan diri berkelanjutan saat lulus (F1773)
pengembangan_diri_lulus_full = Column(String(255)) # F1773
# ===== SECTION 7: Kompetensi SAAT INI (F1762-F1774) =====
# Tingkat kebutuhan kompetensi etika dalam pekerjaan saat ini (F1762)
etika_saat_ini = Column(String(255)) # F1762
# Tingkat kebutuhan keahlian bidang ilmu dalam pekerjaan saat ini (F1764)
keahlian_saat_ini = Column(String(255)) # F1764
# Tingkat kebutuhan bahasa Inggris dalam pekerjaan saat ini (F1766)
bahasa_inggris_saat_ini = Column(String(255)) # F1766
# Tingkat kebutuhan teknologi informasi dalam pekerjaan saat ini (F1768)
teknologi_informasi_saat_ini = Column(String(255)) # F1768
# Tingkat kebutuhan kemampuan komunikasi dalam pekerjaan saat ini (F1770)
komunikasi_saat_ini = Column(String(255)) # F1770
# Tingkat kebutuhan kerja sama tim dalam pekerjaan saat ini (F1772)
kerja_sama_saat_ini = Column(String(255)) # F1772
# Tingkat kebutuhan pengembangan diri dalam pekerjaan saat ini (F1774)
pengembangan_diri_saat_ini = Column(String(255)) # F1774
# ===== SECTION 8: Metode Pembelajaran (F21-F27) =====
# Efektivitas metode perkuliahan teori di kelas (F21)
metode_perkuliahan = Column(String(255)) # F21
# Efektivitas metode demonstrasi / peragaan materi kuliah (F22)
metode_demonstrasi = Column(String(255)) # F22
# Efektivitas metode pengerjaan proyek riset / tugas akhir (F23)
metode_proyek_riset = Column(String(255)) # F23
# Efektivitas pengalaman program magang / kerja praktik (F24)
metode_magang = Column(String(255)) # F24
# Efektivitas sesi praktikum di laboratorium komputer (F25)
metode_praktikum = Column(String(255)) # F25
# Efektivitas kegiatan kerja lapangan / PKL (F26)
metode_kerja_lapangan = Column(String(255)) # F26
# Efektivitas metode diskusi interaktif kelompok (F27)
metode_diskusi = Column(String(255)) # F27
# ===== SECTION 9: Waktu Mencari Kerja (F301-F303) =====
# Saat mulai mencari kerja (F301: Sebelum wisuda, Setelah wisuda, atau Tidak mencari kerja)
mulai_cari_kerja = Column(String(255)) # F301: Sebelum/Sesudah wisuda/Tidak
# Jumlah bulan mulai berburu lowongan kerja sebelum resmi diwisuda (F302)
bulan_cari_sebelum_lulus = Column(Integer) # F302: 0-12
# Jumlah bulan mulai berburu lowongan kerja setelah resmi diwisuda (F303)
bulan_cari_setelah_wisuda = Column(Integer) # F303: 0-12
# ===== SECTION 10: Cara Mencari Kerja (F401-F415) =====
# Mencari kerja melalui iklan lowongan koran/majalah (F401)
cari_iklan = Column(String(255)) # F401
# Melamar langsung ke perusahaan tanpa menunggu pengumuman lowongan (F402)
cari_melamar_tanpa_lowongan = Column(String(255)) # F402
# Mencari lowongan melalui job fair / bursa pameran kerja (F403)
cari_bursa_pameran = Column(String(255)) # F403
# Melamar kerja secara daring via portal internet / LinkedIn / Jobstreet (F404)
cari_internet = Column(String(255)) # F404
# Dihubungi langsung oleh perusahaan / headhunter (F405)
cari_dihubungi_perusahaan = Column(String(255)) # F405
# Menghubungi dinas tenaga kerja / Kemenakertrans (F406)
cari_kemenakertrans = Column(String(255)) # F406
# Menggunakan jasa agen penyalur tenaga kerja resmi (F407)
cari_agen_tenaga_kerja = Column(String(255)) # F407
# Memanfaatkan pusat pengembangan karir kampus (CDC Polije) (F408)
cari_pusat_karir = Column(String(255)) # F408
# Menghubungi unit kantor kemahasiswaan kampus (F409)
cari_kantor_mahasiswa = Column(String(255)) # F409
# Membangun jejaring profesional semasa masih berstatus mahasiswa (F410)
cari_jejaring = Column(String(255)) # F410
# Memperoleh informasi lowongan lewat relasi keluarga atau rekan (F411)
cari_relasi = Column(String(255)) # F411
# Merintis dan membangun badan usaha bisnis mandiri (F412)
cari_bangun_usaha = Column(String(255)) # F412
# Melanjutkan bekerja di institusi tempat pernah magang (F413)
cari_magang = Column(String(255)) # F413
# Bekerja di lingkungan institusi kampus tempat kuliah (F414)
cari_tempat_kuliah = Column(String(255)) # F414
# Saluran cara pencarian kerja lainnya (F415)
cari_lainnya = Column(Text) # F415
# ===== SECTION 11: Lamaran & Keaktifan (F6, F7, F7a, F1001) =====
# Jumlah total instansi/perusahaan yang pernah dikirimi surat lamaran kerja (F6)
jumlah_perusahaan_dilamar = Column(String(100)) # F6
# Jumlah perusahaan yang memberikan balasan respon atas surat lamaran (F7)
jumlah_perusahaan_merespons = Column(String(100)) # F7
# Jumlah perusahaan yang mengundang untuk tahapan wawancara/interview (F7a)
jumlah_perusahaan_wawancara = Column(String(100)) # F7a
# Status apakah aktif mencari pekerjaan dalam 4 minggu terakhir (F1001)
aktif_cari_kerja_4_minggu = Column(String(255)) # F1001: Ya/Tidak/Lainnya
# ===== SECTION 12: Kesesuaian Pekerjaan (F1601-F1614) =====
# Keterangan pekerjaan yang dijalani saat ini tidak sesuai dengan bidang studi (F1601)
kerja_tidak_sesuai_1 = Column(Text) # F1601
# Keterangan pekerjaan tidak memerlukan kualifikasi perguruan tinggi (F1602)
kerja_tidak_sesuai_2 = Column(Text) # F1602
# Alasan ketidaksesuaian 1: Pertimbangan faktor gaji atau pendapatan (F1603)
alasan_tidak_sesuai_1 = Column(Text) # F1603
# Alasan ketidaksesuaian 2: Faktor prospek karir dan jenjang promosi (F1604)
alasan_tidak_sesuai_2 = Column(Text) # F1604
# Alasan ketidaksesuaian 3: Pertimbangan keamanan kerja atau stabilitas (F1605)
alasan_tidak_sesuai_3 = Column(Text) # F1605
# Alasan ketidaksesuaian 4: Kesesuaian dengan minat dan bakat pribadi (F1606)
alasan_tidak_sesuai_4 = Column(Text) # F1606
# Alasan ketidaksesuaian 5: Pertimbangan kedekatan lokasi tempat tinggal (F1607)
alasan_tidak_sesuai_5 = Column(Text) # F1607
# Alasan ketidaksesuaian 6: Lingkungan suasana kerja yang nyaman (F1608)
alasan_tidak_sesuai_6 = Column(Text) # F1608
# Alasan ketidaksesuaian 7: Belum berhasil mendapat pekerjaan yang sesuai prodi (F1609)
alasan_tidak_sesuai_7 = Column(Text) # F1609
# Alasan ketidaksesuaian 8: Belum berencana mencari pekerjaan yang sesuai (F1610)
alasan_tidak_sesuai_8 = Column(Text) # F1610
# Alasan ketidaksesuaian 9: Pekerjaan saat ini dirasa cukup menarik (F1611)
alasan_tidak_sesuai_9 = Column(Text) # F1611
# Alasan ketidaksesuaian 10: Pengaruh pertimbangan keluarga (F1612)
alasan_tidak_sesuai_10 = Column(Text) # F1612
# Alasan ketidaksesuaian 11: Faktor lain yang tidak disebutkan (F1613)
alasan_tidak_sesuai_11 = Column(Text) # F1613
# Alasan ketidaksesuaian lainnya dalam uraian narasi bebas (F1614)
alasan_tidak_sesuai_lainnya = Column(Text) # F1614
# ===== Data Extra (untuk kolom dinamis yang tidak terduga) =====
# Kolom fleksibel berformat JSON text untuk menampung field tambahan dari kuesioner sewaktu-waktu
data_extra = Column(Text) # JSON untuk kolom tambahan
# Relasi ORM balik ke model Alumni (One-to-One)
alumni = relationship("Alumni", back_populates="tracer_response")
# Waktu saat alumni mengirimkan/menyimpan respon kuesioner tracer study
created_at = Column(DateTime(timezone=True), server_default=func.now())
# Waktu saat alumni memperbarui data respon kuesioner tracer study
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# =========================================================================
# MODEL ENTITAS: QUESTIONS (Tabel Master Butir Pertanyaan Kuesioner)
# =========================================================================
class Question(Base):
"""Tabel Questions - Simpan master pertanyaan instrumen tracer study secara dinamis"""
# Nama tabel master pertanyaan di database
__tablename__ = "questions"
# Primary key identitas pertanyaan bertipe integer
id: Any = Column(Integer, primary_key=True, index=True)
# Kode unik butir pertanyaan (contoh: "F8", "F502", "F1761")
kode: Any = Column(String(20), unique=True, nullable=False, index=True)
# Bunyi kalimat teks lengkap pertanyaan kuesioner
pertanyaan: Any = Column(Text, nullable=False)
# Tipe komponen input formulir: dropdown, pilihan, skala, teks, date, ya_tidak
tipe: Any = Column(String(20), default="pilihan") # dropdown, pilihan, skala, teks, date, ya_tidak
# Kategori bagian form instrumen kuesioner (misal: "Identitas", "Pekerjaan", "Kompetensi")
section: Any = Column(String(100), index=True)
# Urutan nomor urut tampilan form di antarmuka frontend
nomor_form: Any = Column(Integer, default=0)
# Tahun berlakunya instrumen pertanyaan (default: "all" untuk berlaku di semua tahun angkatan)
tahun: Any = Column(String(20), default="all")
# Status keaktifan butir pertanyaan (True = ditampilkan di frontend, False = diarsipkan)
is_active: Any = Column(Boolean, default=True)
# Relasi One-to-Many ke tabel AnswerOption (jika tipe pertanyaan memiliki opsi pilihan berganda)
# cascade="all, delete-orphan": Jika pertanyaan dihapus, seluruh opsi jawabannya otomatis ikut terhapus
options = relationship("AnswerOption", back_populates="question", cascade="all, delete-orphan")
# Waktu penambahan pertanyaan ke sistem
created_at = Column(DateTime(timezone=True), server_default=func.now())
# Waktu pembaruan teks atau konfigurasi pertanyaan
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# =========================================================================
# MODEL ENTITAS: ANSWER OPTIONS (Tabel Master Pilihan Jawaban Kuesioner)
# =========================================================================
class AnswerOption(Base):
"""Tabel Answer Options - Simpan pilihan jawaban opsi berganda untuk setiap butir pertanyaan"""
# Nama tabel master opsi jawaban di database
__tablename__ = "answer_options"
# Primary key identitas opsi jawaban bertipe integer
id: Any = Column(Integer, primary_key=True, index=True)
# Foreign key yang mengaitkan opsi ini ke ID pertanyaan di tabel questions
question_id: Any = Column(Integer, ForeignKey("questions.id", ondelete="CASCADE"), nullable=False, index=True)
# Nilai value atau kode jawaban yang disimpan ke database saat dipilih (contoh: "1", "2", "Ya", "Tidak")
kode: Any = Column(String(50), nullable=False, index=True) # Kode jawaban: 1, 2, 3, atau "1", "Ya", dll
# Label teks deskripsi jawaban yang ditampilkan kepada pengguna di antarmuka web
teks: Any = Column(Text, nullable=False) # Teks jawaban
# Nomor urut posisi urutan opsi jawaban dalam pilihan radio button atau dropdown
urutan: Any = Column(Integer, default=0)
# Status keaktifan opsi jawaban (True = aktif dapat dipilih)
is_active: Any = Column(Boolean, default=True)
# Relasi ORM balik ke tabel Question
question = relationship("Question", back_populates="options")
# Waktu pembuatan opsi jawaban
created_at = Column(DateTime(timezone=True), server_default=func.now())