TIF_E41253310/Backend/app/parsers/converters.py

567 lines
25 KiB
Python

"""
Functions untuk parsing dan konversi nilai dari berbagai format CSV tracer study.
Modul ini bertugas menerjemahkan kode-kode numerik kuesioner baku DIKTI (seperti F8, F504, F505, F1101, F14, F15, dll)
menjadi teks deskriptif yang manusiawi untuk disimpan ke database dan ditampilkan di frontend.
"""
# Mengimpor modul ekspresi reguler (re) untuk pencocokan pola string (misal: "1 - Teks")
import re
# Mengimpor pustaka pandas untuk operasi analisis data dan pengecekan nilai NaN (pd.isna)
import pandas as pd
# Mengimpor datetime untuk manipulasi tanggal dan timedelta untuk perhitungan selisih hari tanggal serial Excel
from datetime import datetime, timedelta
# Mengimpor tipe hint dari modul typing: Optional (bisa None), Tuple (pasangan data), Any (tipe fleksibel)
from typing import Optional, Tuple, Any
# Mengimpor objek Session dari SQLAlchemy ORM untuk interaksi sesi database
from sqlalchemy.orm import Session
# =========================================================================
# MAPPING KODE KE TEKS JAWABAN (Standar Kuesioner Tracer Study DIKTI)
# =========================================================================
# Kamus terpusat yang memetakan kode pertanyaan kuesioner DIKTI ke teks pilihan jawabannya
JAWABAN_MAPPING = {
# F8: Pertanyaan status aktivitas alumni saat ini
'F8': {
1: 'Bekerja (full time/part time)', # Kode 1: Alumni sudah bekerja penuh/paruh waktu
2: 'Belum memungkinkan bekerja', # Kode 2: Belum dapat bekerja karena alasan tertentu
3: 'Wiraswasta', # Kode 3: Membuka usaha sendiri / wirausaha
4: 'Melanjutkan Pendidikan', # Kode 4: Sedang menempuh studi lanjut S2/Spesialis
5: 'Tidak kerja tetapi sedang mencari kerja', # Kode 5: Pengangguran aktif mencari lowongan
},
# F504: Apakah mendapatkan pekerjaan dalam kurun waktu <= 6 bulan setelah lulus?
'F504': {
1: 'Ya', # Kode 1: Ya, dapat kerja <= 6 bulan
2: 'Tidak', # Kode 2: Tidak, dapat kerja > 6 bulan
},
# F505: Kisaran rata-rata pendapatan/gaji alumni per bulan
'F505': {
1000000: 'Rp 1.000.000', # Range nominal 1 juta
1500000: 'Rp 1.500.000', # Range nominal 1.5 juta
2000000: 'Rp 2.000.000', # Range nominal 2 juta
2500000: 'Rp 2.500.000', # Range nominal 2.5 juta
3000000: 'Rp 3.000.000', # Range nominal 3 juta
3500000: 'Rp 3.500.000', # Range nominal 3.5 juta
4000000: 'Rp 4.000.000', # Range nominal 4 juta
4500000: 'Rp 4.500.000', # Range nominal 4.5 juta
5000000: 'Rp 5.000.000', # Range nominal 5 juta
6000000: 'Rp 6.000.000', # Range nominal 6 juta
7000000: 'Rp 7.000.000', # Range nominal 7 juta
8000000: '>Rp 8.000.000', # Range nominal di atas 8 juta
},
# F1101: Jenis atau bentuk institusi / perusahaan tempat alumni bekerja
'F1101': {
1: 'Instansi pemerintah', # Kode 1: Instansi pemerintah / PNS
2: 'Organisasi non-profit/Lembaga Swadaya Masyarakat', # Kode 2: LSM / Non-profit / Yayasan
3: 'Perusahaan swasta', # Kode 3: Korporasi swasta
4: 'Wiraswasta/perusahaan sendiri', # Kode 4: Usaha mandiri
5: 'Lainnya', # Kode 5: Bentuk instansi lainnya
6: 'BUMN/BUMD', # Kode 6: Badan Usaha Milik Negara / Daerah
7: 'Institusi/Organisasi Multilateral', # Kode 7: Lembaga internasional / PBB dsb.
},
# F5d / F5D: Skala tingkatan tempat kerja (wilayah operasional perusahaan)
'F5D': {
1: 'Lokal/wilayah/wiraswasta tidak berbadan hukum', # Kode 1: Skala lokal / UKM non-badan hukum
2: 'Nasional/wiraswasta berbadan hukum', # Kode 2: Skala nasional (PT, CV)
3: 'Multinasional/Internasional', # Kode 3: Skala internasional lintas negara
},
'F5d': {
1: 'Lokal/wilayah/wiraswasta tidak berbadan hukum', # Versi huruf kecil F5d
2: 'Nasional/wiraswasta berbadan hukum', # Versi huruf kecil F5d
3: 'Multinasional/Internasional', # Versi huruf kecil F5d
},
# F5c / F5C: Posisi tingkatan jabatan alumni jika berwirausaha
'F5C': {
1: 'Founder', # Pendiri usaha
2: 'Co-Founder', # Rekan pendiri
3: 'Staff', # Staf pelaksana
4: 'Freelance/Kerja Lepas', # Pekerja lepas
},
'F5c': {
1: 'Founder',
2: 'Co-Founder',
3: 'Staff',
4: 'Freelance/Kerja Lepas',
},
# F18a / F18A: Sumber pembiayaan untuk studi lanjut
'F18A': {
1: 'Biaya Sendiri', # Biaya mandiri/keluarga
2: 'Beasiswa', # Beasiswa pendidikan
},
'F18a': {
1: 'Biaya Sendiri',
2: 'Beasiswa',
},
# F1201: Sumber utama biaya kuliah selama menempuh diploma/sarjana
'F1201': {
1: 'Biaya Sendiri/Keluarga', # Biaya orang tua / pribadi
2: 'Beasiswa ADIK', # Beasiswa Afirmasi Dikti
3: 'Beasiswa BIDIKMISI', # Beasiswa Bidikmisi / KIP-Kuliah
4: 'Beasiswa PPA', # Beasiswa Peningkatan Prestasi Akademik
5: 'Beasiswa AFIRMASI', # Beasiswa Afirmasi daerah 3T
6: 'Beasiswa Perusahaan/Swasta', # Beasiswa industri (Djarum, Tanoto, dll)
7: 'Lainnya', # Sumber bantuan biaya lain
},
# F14: Tingkat keeratan hubungan antara program studi dengan bidang pekerjaan
'F14': {
1: 'Sangat Erat', # Relevansi sangat tinggi
2: 'Erat', # Relevansi tinggi
3: 'Cukup Erat', # Relevansi sedang
4: 'Kurang Erat', # Relevansi rendah
5: 'Tidak sama sekali', # Sama sekali tidak relevan
},
# F15: Kesesuaian tingkat pendidikan yang dituntut oleh pekerjaan
'F15': {
1: 'Setingkat lebih tinggi', # Butuh strata lebih tinggi (misal butuh S2)
2: 'Tingkat yang sama', # Tepat sesuai jenjang (D4/S1)
3: 'Setingkat lebih rendah', # Cukup jenjang D3/SMA
4: 'Tidak perlu pendidikan tinggi', # Tidak mensyaratkan gelar kuliah
},
# F301: Waktu mulai aktif mencari pekerjaan
'F301': {
1: 'Saya mencari kerja sebelum lulus', # Sudah berburu kerja sebelum wisuda
2: 'Saya mencari kerja sesudah wisuda', # Baru mencari setelah resmi diwisuda
3: 'Saya tidak mencari kerja', # Langsung usaha / belum ingin bekerja
},
# F1001: Keaktifan mencari pekerjaan dalam kurun 4 minggu terakhir
'F1001': {
1: 'Tidak', # Tidak mencari kerja
2: 'Tidak, tapi saya sedang menunggu hasil lamaran kerja', # Menunggu pengumuman
3: 'Ya, saya akan mulai bekerja dalam 2 minggu ke depan', # Sudah diterima dan siap masuk
4: 'Ya, tapi saya belum pasti akan bekerja dalam 2 minggu ke depan', # Masih proses tes
5: 'Lainnya', # Alasan keaktifan lainnya
},
# F1601: Kesesuaian pekerjaan dengan latar belakang pendidikan
'F1601': {
0: 'Tidak sesuai',
1: 'Pekerjaan saya sekarang sudah sesuai dengan pendidikan saya',
},
# F1602: Alasan mengapa pekerjaan belum sesuai
'F1602': {
0: 'Pekerjaan saya sudah sesuai',
1: 'Saya belum mendapatkan pekerjaan yang lebih sesuai',
},
# F1603 - F1613: Berbagai pertimbangan alasan memilih pekerjaan yang tidak sesuai
'F1603': {
0: 'Pekerjaan saya sudah sesuai',
1: 'Di pekerjaan ini saya memeroleh prospek karir yang baik',
},
'F1604': {
0: 'Pekerjaan saya sudah sesuai',
1: 'Saya lebih suka bekerja di area pekerjaan yang tidak ada hubungannya dengan pendidikan saya',
},
'F1605': {
0: 'Pekerjaan saya sudah sesuai',
1: 'Saya dipromosikan ke posisi yang kurang berhubungan dengan pendidikan saya',
},
'F1606': {
0: 'Pekerjaan saya sudah sesuai',
1: 'Saya dapat memeroleh pendapatan yang lebih tinggi di pekerjaan ini',
},
'F1607': {
0: 'Pekerjaan saya sudah sesuai',
1: 'Pekerjaan saya saat ini lebih aman/terjamin/secure',
},
'F1608': {
0: 'Pekerjaan saya sudah sesuai',
1: 'Pekerjaan saya saat ini lebih menarik',
},
'F1609': {
0: 'Pekerjaan saya sudah sesuai',
1: 'Pekerjaan saya saat ini lebih memungkinkan saya mengambil pekerjaan',
},
'F1610': {
0: 'Pekerjaan saya sudah sesuai',
1: 'Pekerjaan saya saat ini lokasinya lebih dekat dari rumah saya',
},
'F1611': {
0: 'Pekerjaan saya sudah sesuai',
1: 'Pekerjaan saya saat ini dapat lebih menjamin kebutuhan keluarga saya',
},
'F1612': {
0: 'Pekerjaan saya sudah sesuai',
1: 'Pada awal meniti karir ini, saya harus menerima pekerjaan yang tidak berhubungan dengan pendidikan saya',
},
'F1613': {
0: 'Pekerjaan saya sudah sesuai',
1: 'Lainnya',
},
}
# Mapping resmi kode numerik wilayah provinsi Indonesia (F5a1)
PROVINSI_MAPPING = {
10000: 'D.K.I. Jakarta',
20000: 'Jawa Barat',
30000: 'Jawa Tengah',
40000: 'D.I. Yogyakarta',
50000: 'Jawa Timur',
60000: 'Aceh',
70000: 'Sumatera Utara',
80000: 'Sumatera Barat',
90000: 'Riau',
100000: 'Jambi',
110000: 'Sumatera Selatan',
120000: 'Lampung',
130000: 'Kalimantan Barat',
140000: 'Kalimantan Tengah',
150000: 'Kalimantan Selatan',
160000: 'Kalimantan Timur',
170000: 'Sulawesi Utara',
180000: 'Sulawesi Tengah',
190000: 'Sulawesi Selatan',
200000: 'Sulawesi Tenggara',
210000: 'Maluku',
220000: 'Bali',
230000: 'Nusa Tenggara Barat',
240000: 'Nusa Tenggara Timur',
250000: 'Papua',
260000: 'Bengkulu',
270000: 'Maluku Utara',
280000: 'Banten',
290000: 'Kepulauan Bangka Belitung',
300000: 'Gorontalo',
310000: 'Kepulauan Riau',
320000: 'Papua Barat',
330000: 'Sulawesi Barat',
340000: 'Kalimantan Utara',
350000: 'Luar Negeri',
}
# Mapping beberapa kode utama kabupaten / kota wilayah Jawa Timur (F5a2)
KABUPATEN_MAPPING = {
52000: 'Kota Surabaya',
52100: 'Kota Malang',
52200: 'Kota Probolinggo',
52300: 'Kota Pasuruan',
52400: 'Kab. Jember',
52500: 'Kab. Banyuwangi',
}
# Mapping skala likert 1-5 untuk tingkat kompetensi Dikti (F1761 - F1774)
KOMPETENSI_LEVEL = {
1: '1 - Sangat Rendah',
2: '2 - Rendah',
3: '3 - Netral',
4: '4 - Tinggi',
5: '5 - Sangat Tinggi',
}
# Mapping skala likert 1-5 untuk efektivitas metode pembelajaran di kampus (F21 - F27)
METODE_LEVEL = {
1: '1 - Sangat Besar',
2: '2 - Besar',
3: '3 - Cukup Besar',
4: '4 - Kurang',
5: '5 - Tidak Sama Sekali',
}
# Mapping nilai biner 0 dan 1 untuk saluran pencarian kerja Ya/Tidak (F401 - F415)
YA_TIDAK = {
0: '0 - Tidak',
1: '1 - Ya',
}
# =========================================================================
# FUNGSI-FUNGSI PARSING DATA MENTAH
# =========================================================================
# Fungsi untuk memisahkan kode angka dan teks deskripsi dari nilai kolom CSV (khususnya format kuesioner tahun 2024)
def parse_2024_value(val) -> Tuple[Optional[int], Optional[str]]:
"""
Mengurai (parse) nilai kolom dari format ekspor CSV 2024 yang memadukan kode angka dan teks.
Contoh pola yang ditangani:
- "3 - Cukup Erat" -> menghasilkan tuple: (3, "Cukup Erat")
- "1 - Ya" -> menghasilkan tuple: (1, "Ya")
- "Rp 3.000.000,- s/d Rp 4.000.000,-" -> menghasilkan tuple: (3000000, teks_asli)
- "Bekerja (full time/part time)" -> menghasilkan tuple: (None, teks_asli)
Returns:
Tuple (kode_as_int, teks_bersih):
- kode_as_int: integer angka kode jika berhasil diekstrak, None jika tidak ada
- teks_bersih: string teks deskripsi bersih tanpa kode awalan
"""
# Memeriksa apakah nilai input kosong atau bernilai NaN (Not a Number dari Pandas)
if pd.isna(val) or val == '':
# Kembalikan None untuk kedua elemen jika tidak ada data
return None, None
# Jika nilai sudah bertipe numerik langsung (int atau float murni)
if isinstance(val, (int, float)):
# Konversi ke int jika nilai bulat, jika desimal return None untuk kode
return int(val) if val == int(val) else None, None
# Mengubah nilai menjadi tipe string dan membuang spasi kosong di awal/akhir
val_str = str(val).strip()
# Memeriksa jika string hasil strip adalah string kosong atau representasi literal null ('nan', 'None')
if val_str in ['', 'nan', 'None']:
return None, None
# Mencocokkan dengan pola Regex: Angka di awal diikuti tanda strip/dash lalu teks (misal: "1 - Teks" atau "12 - Teks")
match = re.match(r'^\s*(\d+)\s*[-–—]\s*(.+)$', val_str)
# Jika pola angka strip teks cocok
if match:
# Mengambil grup angka pertama dan mengonversinya ke integer
kode = int(match.group(1))
# Mengambil grup teks kedua dan membersihkan spasi
teks = match.group(2).strip()
# Mengembalikan pasangan (kode, teks)
return kode, teks
# Pola khusus untuk teks rentang nominal gaji (contoh: "Rp 3.000.000,- s/d Rp 4.000.000,-")
if 's/d' in val_str or 'sd' in val_str.lower():
# Mengekstrak seluruh digit angka dan titik sebelum pemisah 's/d'
numbers = re.findall(r'[\d.]+', val_str.split('s/d')[0] if 's/d' in val_str else val_str.split('sd')[0])
# Jika digit angka berhasil ditemukan
if numbers:
try:
# Mengambil angka batas bawah, menghapus tanda pemisah titik, dan mengonversinya ke integer
num = int(numbers[-1].replace('.', ''))
# Mengembalikan angka nominal batas bawah bersama string teks aslinya
return num, val_str
except:
# Jika konversi gagal, abaikan dan lanjut ke fallback
pass
# Jika sudah berupa string teks bebas tanpa format kode di depannya, kembalikan teks apa adanya
return None, val_str
# Fungsi untuk memvalidasi dan mengonversi format tanggal string atau serial Excel ke standar format tanggal MySQL (YYYY-MM-DD)
def parse_date(val) -> Optional[str]:
"""
Mengonversi berbagai variasi format penanggalan dari file CSV atau format angka serial Excel menjadi string tanggal baku ISO (YYYY-MM-DD).
"""
# Memeriksa jika nilai tanggal bernilai kosong atau None
if pd.isna(val) or val is None or val == '':
return None
# Jika nilai input sudah merupakan instance objek datetime atau pandas Timestamp
if isinstance(val, (datetime, pd.Timestamp)):
# Format langsung menjadi 'YYYY-MM-DD'
return val.strftime('%Y-%m-%d')
# Mengubah nilai menjadi string bersih
val_str = str(val).strip()
# Mengabaikan string kosong atau karakter placeholder umum seperti tanda strip
if val_str in ['', 'nan', 'None', '-', '_', '0']:
return None
# Memeriksa kemungkinan format angka serial tanggal bawaan Microsoft Excel (misal: angka 44794 merepresentasikan hari sejak 30-12-1899)
try:
float_val = float(val_str)
# Rentang serial number tanggal Excel valid (antara rentang tahun 1980 - 2064)
if 30000 <= float_val <= 60000:
# Menghitung tanggal kalender dengan menambahkan selisih hari ke basis kalender Excel (30 Desember 1899)
excel_date = datetime(1899, 12, 30) + timedelta(days=float_val)
# Mengembalikan tanggal dalam format string 'YYYY-MM-DD'
return excel_date.strftime('%Y-%m-%d')
except (ValueError, TypeError):
# Jika bukan angka serial Excel, lanjutkan ke parsing format tanggal teks
pass
# Daftar format pola string penanggalan umum yang sering muncul pada spreadsheet kuesioner
formats = [
'%Y-%m-%d', '%Y/%m/%d',
'%d-%m-%Y', '%d/%m/%Y',
'%m-%d-%Y', '%m/%d/%Y',
'%d-%m-%y', '%d/%m/%y',
'%m-%d-%y', '%m/%d/%y',
'%Y-%m-%d %H:%M:%S', '%Y/%m/%d %H:%M:%S',
'%d/%m/%Y %H:%M:%S', '%d-%m-%Y %H:%M:%S'
]
# Mencoba mencocokkan nilai string dengan setiap format tanggal yang tersedia
for fmt in formats:
try:
# Jika format cocok, kembalikan tanggal terformat ISO
return datetime.strptime(val_str, fmt).strftime('%Y-%m-%d')
except ValueError:
# Jika tidak cocok, coba format berikutnya di list
continue
# Kembalikan None jika tidak ada satupun pola tanggal yang cocok
return None
# =========================================================================
# FUNGSI-FUNGSI KONVERSI NILAI KUESIONER
# =========================================================================
# Fungsi utama untuk menerjemahkan nilai kode jawaban mentah menjadi teks deskriptif
def konversi_jawaban(field_code: Optional[str], nilai, db: Optional[Session] = None) -> Any:
"""
Mengonversi kode angka kuesioner ke teks label jawaban yang sesuai.
PENTING: Nilai 0 ADALAH jawaban valid untuk beberapa field kuesioner:
- F502: 0 bulan (artinya langsung bekerja saat lulus kuliah)
- F302/F303: 0 bulan (mulai mencari saat lulus)
- F401-F415: 0 bernilai "Tidak", 1 bernilai "Ya"
Args:
field_code: Kode butir pertanyaan (contoh: 'F14', 'F504')
nilai: Nilai mentah dari sel CSV
db: Sesi koneksi database opsional
Returns:
Nilai teks yang telah dikonversi, atau None jika sel kosong
"""
# Memeriksa sel kosong atau NaN
if pd.isna(nilai) or nilai == '':
return None
# Memeriksa string kosong setelah pembersihan spasi
if isinstance(nilai, str) and nilai.strip() in ['', 'nan', 'None']:
return None
# Penanganan data kuesioner format 2024 yang sudah memuat "KODE - TEKS"
if isinstance(nilai, str):
# Mencocokkan regex pola angka strip teks
match = re.match(r'^\s*(\d+)\s*[-–—]\s*(.+)$', nilai)
if match:
kode = int(match.group(1))
teks = match.group(2).strip()
# Jika teks memiliki deskripsi yang cukup panjang (>5 karakter), gunakan langsung teks tersebut
if len(teks) > 5:
return teks
# Jika nilai diawali huruf (sudah berbentuk teks naratif penuh tanpa angka kode)
if not nilai[0].isdigit() if nilai else True:
return nilai.strip()
# Menyimpan nilai asli sebelum dikonversi
original_nilai = nilai
# Konversi string angka menjadi integer
if isinstance(nilai, str):
try:
nilai = int(nilai)
except ValueError:
pass
# Konversi float murni (misal 1.0) menjadi integer (1)
if isinstance(nilai, float):
if nilai != int(nilai):
return None # Mengabaikan bilangan desimal yang tidak valid untuk kode kuesioner
nilai = int(nilai)
# Menentukan nilai kunci pencarian (key)
nilai_key: Any = int(nilai) if isinstance(nilai, (int, float)) else nilai
# Mengubah kode field menjadi huruf kapital bersih
code_upper = field_code.upper().strip() if field_code else ''
# ================= Konversi Berdasarkan Kode Pertanyaan Spesifik =================
# F1761-F1774: Penilaian 7 kelompok kompetensi saat lulus & saat ini (skala 1-5, nilai 0 diabaikan menjadi NULL)
if code_upper in ['F1761', 'F1762', 'F1763', 'F1764', 'F1765', 'F1766', 'F1767', 'F1768', 'F1769', 'F1770', 'F1771', 'F1772', 'F1773', 'F1774']:
if nilai_key == 0:
return None
return KOMPETENSI_LEVEL.get(nilai_key)
# F21-F27: Evaluasi metode pembelajaran di kampus (skala 1-5, nilai 0 diabaikan menjadi NULL)
if code_upper in ['F21', 'F22', 'F23', 'F24', 'F25', 'F26', 'F27']:
if nilai_key == 0:
return None
return METODE_LEVEL.get(nilai_key)
# F401-F415: Saluran cara mencari pekerjaan (jawaban biner: 0 = "Tidak", 1 = "Ya")
if code_upper in ['F401', 'F402', 'F403', 'F404', 'F405', 'F406', 'F407',
'F408', 'F409', 'F410', 'F411', 'F412', 'F413', 'F414', 'F415']:
return YA_TIDAK.get(nilai_key)
# F504: Status mendapatkan pekerjaan dalam <= 6 bulan (1 = Ya, 2 = Tidak)
if code_upper == 'F504':
return JAWABAN_MAPPING.get('F504', {}).get(nilai_key)
# F505: Rata-rata pendapatan gaji per bulan (pencocokan ke range atau format mata uang Rupiah)
if code_upper == 'F505':
if nilai_key in JAWABAN_MAPPING.get('F505', {}):
return JAWABAN_MAPPING['F505'][nilai_key]
if isinstance(nilai_key, (int, float)) and nilai_key > 0:
return f"Rp {int(nilai_key):,}".replace(',', '.')
if isinstance(nilai, str) and nilai.strip() and nilai.strip() not in ['0', '0.0']:
return nilai.strip()
return None
# F1601-F1613: Pertimbangan alasan memilih pekerjaan yang tidak sesuai bidang
if code_upper in ['F1601', 'F1602', 'F1603', 'F1604', 'F1605', 'F1606',
'F1607', 'F1608', 'F1609', 'F1610', 'F1611', 'F1612', 'F1613']:
if code_upper in JAWABAN_MAPPING:
return JAWABAN_MAPPING[code_upper].get(nilai_key)
return YA_TIDAK.get(nilai_key)
# F502, F302, F303: Jumlah durasi bulan masa tunggu mencari kerja (nilai 0 s.d. 12 valid berupa angka murni)
if code_upper in ['F502', 'F302', 'F303']:
return nilai_key # Dikembalikan sebagai angka numerik murni
# F5A1: Wilayah provinsi tempat bekerja (konversi kode provinsi ke nama provinsi)
if code_upper == 'F5A1':
nama_provinsi = PROVINSI_MAPPING.get(nilai_key)
if nama_provinsi:
return f"{nilai_key} - {nama_provinsi}"
return nilai_key
# F5A2: Wilayah kabupaten atau kota tempat bekerja
if code_upper == 'F5A2':
nama_kabupaten = KABUPATEN_MAPPING.get(nilai_key)
if nama_kabupaten:
return f"{nilai_key} - {nama_kabupaten}"
return nilai_key
# Pengecekan pada kamus umum JAWABAN_MAPPING untuk kode lainnya
if code_upper in JAWABAN_MAPPING:
return JAWABAN_MAPPING[code_upper].get(nilai_key)
if field_code in JAWABAN_MAPPING:
return JAWABAN_MAPPING[field_code].get(nilai_key)
# Nilai bawaan jika tidak ada kamus pencocokan: kembalikan nilai asli
return original_nilai
# Fungsi pembantu untuk memilih apakah menyimpan kode numerik atau teks deskripsi sesuai tipe kolom di database
def convert_value_for_field(field_code: str, kode: int, teks: str, is_numeric_field: bool) -> Any:
"""
Menentukan representasi data akhir yang akan disimpan ke database berdasarkan tipe kolom tabel target.
Args:
field_code: Kode butir pertanyaan (contoh: 'F14', 'F504')
kode: Angka kode jawaban hasil parsing
teks: Teks label jawaban hasil parsing
is_numeric_field: Bernilai True jika kolom database bertipe integer (misal: bulan tunggu)
Returns:
Nilai yang telah disesuaikan dengan tipe kolom database
"""
# Jika kolom database adalah tipe numerik integer, prioritaskan mengembalikan kode angka
if is_numeric_field:
return kode
# Jika teks deskripsi tersedia dan panjangnya memadai, gunakan teks deskriptif
if teks and len(teks) > 5:
return teks
# Jika kode angka ada, coba konversikan kode tersebut ke teks melalui fungsi konversi_jawaban
if kode is not None:
return konversi_jawaban(field_code, kode)
# Opsi fallback terakhir: kembalikan teks
return teks