540 lines
20 KiB
Python
540 lines
20 KiB
Python
"""
|
|
Router untuk Dashboard API - PRD Section 22
|
|
"""
|
|
|
|
from fastapi import APIRouter, Depends, Query
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy import func, or_
|
|
from typing import Optional, List, Dict, Any
|
|
|
|
from app.config.database import get_db
|
|
from app.models.alumni import Alumni
|
|
from app.models.models import Pekerjaan, Kompetensi, ClusteringResult, ModelEvaluation, TracerResponse
|
|
|
|
router = APIRouter(tags=["Dashboard"])
|
|
|
|
|
|
@router.get("/stats")
|
|
def get_dashboard_stats(
|
|
kode_prodi: Optional[str] = Query(None),
|
|
tahun_lulus: Optional[int] = Query(None),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
Dashboard Utama - PRD Section 22
|
|
Menampilkan: total alumni, total bekerja, total belum bekerja, distribusi C0-C3
|
|
Mendukung filter prodi (khusus role Kaprodi atau Admin) dan filter angkatan/tahun_lulus
|
|
"""
|
|
prodi_filter = kode_prodi if (kode_prodi and kode_prodi != "all") else None
|
|
|
|
# Total alumni
|
|
q_alumni = db.query(Alumni)
|
|
if prodi_filter:
|
|
q_alumni = q_alumni.filter(Alumni.kode_prodi == prodi_filter)
|
|
if tahun_lulus:
|
|
q_alumni = q_alumni.filter(Alumni.tahun_lulus == int(tahun_lulus))
|
|
total_alumni = q_alumni.count()
|
|
|
|
# Total hasil clustering
|
|
q_cluster = db.query(ClusteringResult).join(Alumni, Alumni.id == ClusteringResult.alumni_id)
|
|
if prodi_filter:
|
|
q_cluster = q_cluster.filter(Alumni.kode_prodi == prodi_filter)
|
|
if tahun_lulus:
|
|
q_cluster = q_cluster.filter(Alumni.tahun_lulus == int(tahun_lulus))
|
|
total_clustered = q_cluster.count()
|
|
|
|
# Distribusi kategori C0-C3
|
|
q_kategori = db.query(
|
|
ClusteringResult.kategori,
|
|
func.count(ClusteringResult.id).label('count')
|
|
).join(Alumni, Alumni.id == ClusteringResult.alumni_id)
|
|
if prodi_filter:
|
|
q_kategori = q_kategori.filter(Alumni.kode_prodi == prodi_filter)
|
|
if tahun_lulus:
|
|
q_kategori = q_kategori.filter(Alumni.tahun_lulus == int(tahun_lulus))
|
|
kategori_dist = q_kategori.group_by(ClusteringResult.kategori).all()
|
|
|
|
kategori_dict = {k: c for k, c in kategori_dist}
|
|
|
|
# Ambil model evaluation terbaru
|
|
latest_eval = db.query(ModelEvaluation).order_by(
|
|
ModelEvaluation.created_at.desc()
|
|
).first()
|
|
|
|
# Get unique tahun lulus
|
|
q_tahun = db.query(Alumni.tahun_lulus)
|
|
if prodi_filter:
|
|
q_tahun = q_tahun.filter(Alumni.kode_prodi == prodi_filter)
|
|
tahun_list = q_tahun.distinct().order_by(Alumni.tahun_lulus.desc()).all()
|
|
tahun_lulus = [t[0] for t in tahun_list if t[0]]
|
|
|
|
return {
|
|
"total_alumni": total_alumni,
|
|
"total_clustered": total_clustered,
|
|
"kategori": {
|
|
"C0": kategori_dict.get("C0", 0),
|
|
"C1": kategori_dict.get("C1", 0),
|
|
"C2": kategori_dict.get("C2", 0),
|
|
"C3": kategori_dict.get("C3", 0),
|
|
},
|
|
"silhouette_score": latest_eval.silhouette_score if latest_eval else None,
|
|
"model_version": latest_eval.model_version if latest_eval else None,
|
|
"tahun_lulus": tahun_lulus # Include tahun for dropdown
|
|
}
|
|
|
|
|
|
@router.get("/by-prodi")
|
|
def get_stats_by_prodi(
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Distribusi alumni dan kategori per prodi"""
|
|
# Get alumni count by prodi
|
|
prodi_stats = db.query(
|
|
Alumni.kode_prodi,
|
|
func.count(Alumni.id).label('total')
|
|
).group_by(Alumni.kode_prodi).all()
|
|
|
|
# Get clustering results by prodi and kategori
|
|
results = db.query(
|
|
Alumni.kode_prodi,
|
|
ClusteringResult.kategori,
|
|
func.count(ClusteringResult.id).label('count')
|
|
).join(
|
|
ClusteringResult, Alumni.id == ClusteringResult.alumni_id
|
|
).group_by(
|
|
Alumni.kode_prodi, ClusteringResult.kategori
|
|
).all()
|
|
|
|
# Format results
|
|
prodi_data = {}
|
|
for prodi, total in prodi_stats:
|
|
prodi_data[prodi] = {"total": total, "C0": 0, "C1": 0, "C2": 0, "C3": 0}
|
|
|
|
for prodi, kategori, count in results:
|
|
if prodi in prodi_data:
|
|
prodi_data[prodi][kategori] = count
|
|
|
|
return prodi_data
|
|
|
|
|
|
@router.get("/by-year")
|
|
def get_stats_by_year(
|
|
kode_prodi: Optional[str] = Query(None),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Distribusi alumni dan tren kategori C0-C3 per tahun kelulusan secara kronologis (Ascending), mendukung filter prodi"""
|
|
prodi_filter = kode_prodi if (kode_prodi and kode_prodi != "all") else None
|
|
|
|
# 1. Total alumni per tahun lulus urut dari tahun terlama ke terbaru
|
|
q_year = db.query(
|
|
Alumni.tahun_lulus,
|
|
func.count(Alumni.id).label('total')
|
|
).filter(Alumni.tahun_lulus.isnot(None))
|
|
if prodi_filter:
|
|
q_year = q_year.filter(Alumni.kode_prodi == prodi_filter)
|
|
year_totals = q_year.group_by(Alumni.tahun_lulus).order_by(Alumni.tahun_lulus.asc()).all()
|
|
|
|
# 2. Distribusi per kategori C0, C1, C2, C3 per tahun
|
|
q_cat = db.query(
|
|
Alumni.tahun_lulus,
|
|
ClusteringResult.kategori,
|
|
func.count(ClusteringResult.id).label('count')
|
|
).join(ClusteringResult, Alumni.id == ClusteringResult.alumni_id)\
|
|
.filter(Alumni.tahun_lulus.isnot(None))
|
|
if prodi_filter:
|
|
q_cat = q_cat.filter(Alumni.kode_prodi == prodi_filter)
|
|
cat_results = q_cat.group_by(Alumni.tahun_lulus, ClusteringResult.kategori)\
|
|
.order_by(Alumni.tahun_lulus.asc()).all()
|
|
|
|
# Susun struktur data kronologis
|
|
years_list = [str(y[0]) for y in year_totals if y[0]]
|
|
breakdown_map = {str(y[0]): {"total": y[1], "C0": 0, "C1": 0, "C2": 0, "C3": 0} for y in year_totals if y[0]}
|
|
|
|
for thn, kat, cnt in cat_results:
|
|
thn_str = str(thn)
|
|
if thn_str in breakdown_map and kat in breakdown_map[thn_str]:
|
|
breakdown_map[thn_str][kat] = cnt
|
|
|
|
# Format series untuk area / line chart
|
|
c0_series = [breakdown_map[y]["C0"] for y in years_list]
|
|
c1_series = [breakdown_map[y]["C1"] for y in years_list]
|
|
c2_series = [breakdown_map[y]["C2"] for y in years_list]
|
|
c3_series = [breakdown_map[y]["C3"] for y in years_list]
|
|
|
|
return {
|
|
"years": years_list,
|
|
"breakdown": breakdown_map,
|
|
"series": [
|
|
{"name": "C0 - Sesuai Profil", "data": c0_series, "color": "#059669"},
|
|
{"name": "C1 - Bidang Diketahui", "data": c1_series, "color": "#2563eb"},
|
|
{"name": "C2 - Kurang Teridentifikasi", "data": c2_series, "color": "#d97706"},
|
|
{"name": "C3 - Belum Bekerja", "data": c3_series, "color": "#64748b"},
|
|
],
|
|
"totals_by_year": {str(year): total for year, total in year_totals}
|
|
}
|
|
|
|
|
|
@router.get("/by-cluster")
|
|
def get_stats_by_cluster(
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Distribusi alumni per cluster"""
|
|
cluster_stats = db.query(
|
|
ClusteringResult.cluster,
|
|
ClusteringResult.kategori,
|
|
func.count(ClusteringResult.id).label('count')
|
|
).group_by(
|
|
ClusteringResult.cluster,
|
|
ClusteringResult.kategori
|
|
).all()
|
|
|
|
# Format results
|
|
cluster_data = {}
|
|
for cluster, kategori, count in cluster_stats:
|
|
key = f"Cluster_{cluster}"
|
|
if key not in cluster_data:
|
|
cluster_data[key] = {"total": 0, "kategori": {}}
|
|
cluster_data[key]["total"] += count
|
|
cluster_data[key]["kategori"][kategori] = count
|
|
|
|
return cluster_data
|
|
|
|
|
|
@router.get("/competency-profile")
|
|
def get_competency_profile(
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Profil kompetensi rata-rata per kategori"""
|
|
# Get average competency scores by kategori
|
|
results = db.query(
|
|
ClusteringResult.kategori,
|
|
func.avg(Kompetensi.etika_lulus).label('etika'),
|
|
func.avg(Kompetensi.keahlian_lulus).label('keahlian'),
|
|
func.avg(Kompetensi.bahasa_inggris_lulus).label('bahasa_inggris'),
|
|
func.avg(Kompetensi.teknologi_informasi_lulus).label('teknologi_informasi'),
|
|
func.avg(Kompetensi.komunikasi_lulus).label('komunikasi'),
|
|
func.avg(Kompetensi.kerja_sama_lulus).label('kerja_sama'),
|
|
func.avg(Kompetensi.pengembangan_diri_lulus).label('pengembangan_diri'),
|
|
func.count(Kompetensi.id).label('count')
|
|
).join(
|
|
Kompetensi, Alumni.id == Kompetensi.alumni_id
|
|
).join(
|
|
ClusteringResult, Alumni.id == ClusteringResult.alumni_id
|
|
).group_by(
|
|
ClusteringResult.kategori
|
|
).all()
|
|
|
|
profile = {}
|
|
for r in results:
|
|
profile[r.kategori] = {
|
|
"etika": round(float(r.etika), 2) if r.etika else None,
|
|
"keahlian": round(float(r.keahlian), 2) if r.keahlian else None,
|
|
"bahasa_inggris": round(float(r.bahasa_inggris), 2) if r.bahasa_inggris else None,
|
|
"teknologi_informasi": round(float(r.teknologi_informasi), 2) if r.teknologi_informasi else None,
|
|
"komunikasi": round(float(r.komunikasi), 2) if r.komunikasi else None,
|
|
"kerja_sama": round(float(r.kerja_sama), 2) if r.kerja_sama else None,
|
|
"pengembangan_diri": round(float(r.pengembangan_diri), 2) if r.pengembangan_diri else None,
|
|
"count": r.count
|
|
}
|
|
|
|
return profile
|
|
|
|
|
|
@router.get("/company-types")
|
|
def get_top_company_types(
|
|
limit: int = Query(10, ge=1, le=50),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Top jenis perusahaan berdasarkan hasil clustering"""
|
|
results = db.query(
|
|
Pekerjaan.jenis_perusahaan,
|
|
func.count(Pekerjaan.id).label('count')
|
|
).join(
|
|
Alumni, Pekerjaan.alumni_id == Alumni.id
|
|
).join(
|
|
ClusteringResult, Alumni.id == ClusteringResult.alumni_id
|
|
).group_by(
|
|
Pekerjaan.jenis_perusahaan
|
|
).order_by(
|
|
func.count(Pekerjaan.id).desc()
|
|
).limit(limit).all()
|
|
|
|
return {
|
|
"jenis_perusahaan": [
|
|
{"jenis": j, "count": c} for j, c in results
|
|
]
|
|
}
|
|
|
|
|
|
@router.get("/top-keywords")
|
|
def get_top_matched_keywords(
|
|
limit: int = Query(10, ge=1, le=50),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Top keywords yang match dengan kamus prodi"""
|
|
# Get all matched keywords
|
|
results = db.query(ClusteringResult.matched_keyword).filter(
|
|
ClusteringResult.matched_keyword.isnot(None),
|
|
ClusteringResult.matched_keyword != ""
|
|
).all()
|
|
|
|
# Count keyword frequency
|
|
keyword_count = {}
|
|
for r in results:
|
|
if r.matched_keyword:
|
|
# Split by comma for multiple keywords
|
|
keywords = [k.strip() for k in r.matched_keyword.split(",")]
|
|
for kw in keywords:
|
|
keyword_count[kw] = keyword_count.get(kw, 0) + 1
|
|
|
|
# Sort and limit
|
|
sorted_keywords = sorted(keyword_count.items(), key=lambda x: x[1], reverse=True)[:limit]
|
|
|
|
return {
|
|
"keywords": [
|
|
{"keyword": kw, "count": c} for kw, c in sorted_keywords
|
|
]
|
|
}
|
|
|
|
|
|
@router.get("/akreditasi-tables")
|
|
def get_akreditasi_tables(
|
|
ts_year: Optional[int] = Query(None, description="Tahun Laporan TS (misal 2024 atau 2025)"),
|
|
kode_prodi: Optional[str] = Query(None, description="Kode program studi atau 'all'"),
|
|
mode: Optional[str] = Query("ts_range", description="'ts_range' (TS-2 s/d TS) atau 'all_years'"),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
Tabel Akreditasi LAM INFOKOM (Kriteria 9 / IKU-1):
|
|
- Tabel 2.B.4: Rata-rata Masa Tunggu Lulusan untuk Bekerja Pertama Kali
|
|
- Tabel 2.B.5: Kesesuaian Bidang Kerja Lulusan (Infokom vs Non-Infokom & Lingkup Kerja)
|
|
Mendukung filter TS-2, TS-1, TS dan pemisahan per Program Studi (TIF, MIF, TKK).
|
|
"""
|
|
# 1. Dapatkan daftar tahun kelulusan yang valid (< 2026)
|
|
year_rows = db.query(Alumni.tahun_lulus)\
|
|
.filter(Alumni.tahun_lulus.isnot(None), Alumni.tahun_lulus < 2026)\
|
|
.distinct().order_by(Alumni.tahun_lulus.desc()).all()
|
|
available_years = [y[0] for y in year_rows if y[0]]
|
|
|
|
# Tentukan TS Tahun (default 2025 jika tidak disediakan, atau tahun terbaru yang valid)
|
|
if not ts_year:
|
|
ts_year = 2025 if 2025 in available_years else (available_years[0] if available_years else 2025)
|
|
|
|
# Tentukan deret tahun yang akan dihitung
|
|
if mode == "all_years":
|
|
target_years = sorted(available_years)
|
|
else:
|
|
# Standar LAM INFOKOM: 3 Tahun Lulus Terakhir (TS-2, TS-1, TS)
|
|
target_years = [ts_year - 2, ts_year - 1, ts_year]
|
|
|
|
# Kamus Nama Prodi Resmi JTI Polije
|
|
PRODI_INFO = {
|
|
"all": "Semua Program Studi (Jurusan TI)",
|
|
"55301": "D4 Teknik Informatika (TIF)",
|
|
"57401": "D3 Manajemen Informatika (MIF)",
|
|
"56401": "D3 Teknik Komputer (TKK)",
|
|
"55302": "D4 Teknik Rekayasa Komputer",
|
|
"55304": "D3 Manajemen Informatika (Kampus Lain)"
|
|
}
|
|
prodi_filter = kode_prodi if (kode_prodi and kode_prodi != "all") else None
|
|
prodi_label = PRODI_INFO.get(kode_prodi, "Semua Program Studi (Jurusan TI)") if kode_prodi else "Semua Program Studi (Jurusan TI)"
|
|
|
|
rows_2b4 = []
|
|
rows_2b5 = []
|
|
|
|
sum_lulusan = 0
|
|
sum_terlacak = 0
|
|
weighted_waktu_sum = 0.0
|
|
weighted_waktu_count = 0
|
|
|
|
sum_infokom = 0
|
|
sum_non_infokom = 0
|
|
sum_multinasional = 0
|
|
sum_nasional = 0
|
|
sum_wirausaha = 0
|
|
|
|
for year in target_years:
|
|
# Tentukan label TS (misal TS-2, TS-1, TS)
|
|
diff = ts_year - year
|
|
if diff == 0:
|
|
ts_label = "TS"
|
|
elif diff > 0:
|
|
ts_label = f"TS-{diff}"
|
|
else:
|
|
ts_label = f"TS+{abs(diff)}"
|
|
|
|
display_label = f"{ts_label} ({year})" if mode == "ts_range" else f"{year} ({ts_label})"
|
|
|
|
# A. Jumlah Lulusan
|
|
q_lulus = db.query(Alumni).filter(Alumni.tahun_lulus == year)
|
|
if prodi_filter:
|
|
q_lulus = q_lulus.filter(Alumni.kode_prodi == prodi_filter)
|
|
n_lulus = q_lulus.count()
|
|
|
|
# B. Jumlah Lulusan yang Terlacak (isi TracerResponse atau memiliki riwayat kerja)
|
|
q_terlacak = db.query(Alumni).join(TracerResponse, Alumni.id == TracerResponse.alumni_id)\
|
|
.filter(Alumni.tahun_lulus == year)
|
|
if prodi_filter:
|
|
q_terlacak = q_terlacak.filter(Alumni.kode_prodi == prodi_filter)
|
|
n_terlacak = q_terlacak.count()
|
|
|
|
# C. Rata-rata Waktu Tunggu (Bulan) — dihitung dari alumni yang bekerja dengan bulan_dapat_kerja > 0
|
|
q_waktu = db.query(
|
|
func.avg(TracerResponse.bulan_dapat_kerja),
|
|
func.count(TracerResponse.id)
|
|
).join(Alumni, Alumni.id == TracerResponse.alumni_id)\
|
|
.filter(
|
|
Alumni.tahun_lulus == year,
|
|
TracerResponse.bulan_dapat_kerja.isnot(None),
|
|
TracerResponse.bulan_dapat_kerja > 0
|
|
)
|
|
if prodi_filter:
|
|
q_waktu = q_waktu.filter(Alumni.kode_prodi == prodi_filter)
|
|
avg_waktu_res, count_waktu = q_waktu.first()
|
|
avg_waktu = round(float(avg_waktu_res), 2) if avg_waktu_res is not None else 0.0
|
|
|
|
# D. Profesi Bidang Infokom (C0)
|
|
q_c0 = db.query(ClusteringResult).join(Alumni, Alumni.id == ClusteringResult.alumni_id)\
|
|
.filter(Alumni.tahun_lulus == year, ClusteringResult.kategori == "C0")
|
|
if prodi_filter:
|
|
q_c0 = q_c0.filter(Alumni.kode_prodi == prodi_filter)
|
|
n_infokom = q_c0.count()
|
|
|
|
# E. Profesi Bidang NON Infokom (C1 + C2)
|
|
q_non_c0 = db.query(ClusteringResult).join(Alumni, Alumni.id == ClusteringResult.alumni_id)\
|
|
.filter(Alumni.tahun_lulus == year, ClusteringResult.kategori.in_(["C1", "C2"]))
|
|
if prodi_filter:
|
|
q_non_c0 = q_non_c0.filter(Alumni.kode_prodi == prodi_filter)
|
|
n_non_infokom = q_non_c0.count()
|
|
|
|
# F. Lingkup Tempat Kerja: Multinasional / Internasional
|
|
q_multi = db.query(TracerResponse).join(Alumni, Alumni.id == TracerResponse.alumni_id)\
|
|
.filter(
|
|
Alumni.tahun_lulus == year,
|
|
or_(
|
|
TracerResponse.tingkatan_tempat_kerja.ilike("%multinasional%"),
|
|
TracerResponse.tingkatan_tempat_kerja.ilike("%internasional%"),
|
|
TracerResponse.jenis_perusahaan_full.ilike("%multilateral%")
|
|
)
|
|
)
|
|
if prodi_filter:
|
|
q_multi = q_multi.filter(Alumni.kode_prodi == prodi_filter)
|
|
n_multi = q_multi.count()
|
|
|
|
# G. Lingkup Tempat Kerja: Nasional (BUMN, Instansi Pusat, Swasta Nasional)
|
|
q_nas = db.query(TracerResponse).join(Alumni, Alumni.id == TracerResponse.alumni_id)\
|
|
.filter(
|
|
Alumni.tahun_lulus == year,
|
|
TracerResponse.tingkatan_tempat_kerja.ilike("%nasional%")
|
|
)
|
|
if prodi_filter:
|
|
q_nas = q_nas.filter(Alumni.kode_prodi == prodi_filter)
|
|
n_nas = q_nas.count()
|
|
|
|
# H. Lingkup Tempat Kerja: Wirausaha
|
|
q_wira = db.query(Pekerjaan).join(Alumni, Alumni.id == Pekerjaan.alumni_id)\
|
|
.filter(
|
|
Alumni.tahun_lulus == year,
|
|
or_(
|
|
Pekerjaan.status_pekerjaan.ilike("%wiraswasta%"),
|
|
Pekerjaan.jenis_perusahaan.ilike("%wiraswasta%")
|
|
)
|
|
)
|
|
if prodi_filter:
|
|
q_wira = q_wira.filter(Alumni.kode_prodi == prodi_filter)
|
|
n_wira = q_wira.count()
|
|
|
|
# Masukkan ke baris tabel 2.B.4
|
|
rows_2b4.append({
|
|
"ts_code": ts_label,
|
|
"tahun_lulus": year,
|
|
"tahun_label": display_label,
|
|
"jumlah_lulusan": n_lulus,
|
|
"jumlah_terlacak": n_terlacak,
|
|
"persen_terlacak": round((n_terlacak / n_lulus * 100), 1) if n_lulus > 0 else 0,
|
|
"rata_waktu_tunggu": avg_waktu
|
|
})
|
|
|
|
# Masukkan ke baris tabel 2.B.5
|
|
rows_2b5.append({
|
|
"ts_code": ts_label,
|
|
"tahun_lulus": year,
|
|
"tahun_label": display_label,
|
|
"jumlah_lulusan": n_lulus,
|
|
"jumlah_terlacak": n_terlacak,
|
|
"profesi_infokom": n_infokom,
|
|
"profesi_non_infokom": n_non_infokom,
|
|
"persen_infokom": round((n_infokom / n_terlacak * 100), 1) if n_terlacak > 0 else 0,
|
|
"lingkup_multinasional": n_multi,
|
|
"lingkup_nasional": n_nas,
|
|
"lingkup_wirausaha": n_wira
|
|
})
|
|
|
|
sum_lulusan += n_lulus
|
|
sum_terlacak += n_terlacak
|
|
if avg_waktu > 0 and count_waktu:
|
|
weighted_waktu_sum += (avg_waktu * count_waktu)
|
|
weighted_waktu_count += count_waktu
|
|
|
|
sum_infokom += n_infokom
|
|
sum_non_infokom += n_non_infokom
|
|
sum_multinasional += n_multi
|
|
sum_nasional += n_nas
|
|
sum_wirausaha += n_wira
|
|
|
|
overall_avg_waktu = round(weighted_waktu_sum / weighted_waktu_count, 2) if weighted_waktu_count > 0 else 0.0
|
|
|
|
# Summary Totals
|
|
total_2b4 = {
|
|
"ts_code": "Jumlah",
|
|
"tahun_label": "Jumlah / Rata-rata",
|
|
"jumlah_lulusan": sum_lulusan,
|
|
"jumlah_terlacak": sum_terlacak,
|
|
"persen_terlacak": round((sum_terlacak / sum_lulusan * 100), 1) if sum_lulusan > 0 else 0,
|
|
"rata_waktu_tunggu": overall_avg_waktu
|
|
}
|
|
|
|
total_2b5 = {
|
|
"ts_code": "Jumlah",
|
|
"tahun_label": "Jumlah",
|
|
"jumlah_lulusan": sum_lulusan,
|
|
"jumlah_terlacak": sum_terlacak,
|
|
"profesi_infokom": sum_infokom,
|
|
"profesi_non_infokom": sum_non_infokom,
|
|
"persen_infokom": round((sum_infokom / sum_terlacak * 100), 1) if sum_terlacak > 0 else 0,
|
|
"lingkup_multinasional": sum_multinasional,
|
|
"lingkup_nasional": sum_nasional,
|
|
"lingkup_wirausaha": sum_wirausaha
|
|
}
|
|
|
|
available_prodis = [
|
|
{"kode": "all", "nama": "Semua Program Studi (Jurusan TI)"},
|
|
{"kode": "55301", "nama": "D4 Teknik Informatika (TIF)"},
|
|
{"kode": "57401", "nama": "D3 Manajemen Informatika (MIF)"},
|
|
{"kode": "56401", "nama": "D3 Teknik Komputer (TKK)"}
|
|
]
|
|
|
|
return {
|
|
"status": "success",
|
|
"meta": {
|
|
"ts_year": ts_year,
|
|
"kode_prodi": kode_prodi or "all",
|
|
"prodi_label": prodi_label,
|
|
"mode": mode,
|
|
"standard": "Instrumen Akreditasi Program Studi (IAPS) LAM INFOKOM Kriteria 9"
|
|
},
|
|
"available_years": available_years,
|
|
"available_prodis": available_prodis,
|
|
"tabel_2b4": {
|
|
"title": "Tabel 2.B.4 Rata-rata Masa Tunggu Lulusan untuk Bekerja Pertama Kali",
|
|
"rows": rows_2b4,
|
|
"total": total_2b4
|
|
},
|
|
"tabel_2b5": {
|
|
"title": "Tabel 2.B.5. Kesesuaian Bidang Kerja Lulusan",
|
|
"rows": rows_2b5,
|
|
"total": total_2b5
|
|
}
|
|
}
|
|
|