""" 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())