indonesian version column
This commit is contained in:
@@ -9,6 +9,8 @@ Filtering Order:
|
||||
4. Filter countries with ALL pillars (FIXED SET)
|
||||
5. Filter indicators with consistent presence across FIXED countries
|
||||
6. Save analytical table (dengan nama/label lengkap untuk Looker Studio)
|
||||
|
||||
ADDED: Kolom indicator_name_id dan pillar_name_id (terjemahan Bahasa Indonesia)
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
@@ -34,6 +36,176 @@ from scripts.bigquery_helpers import (
|
||||
from google.cloud import bigquery
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# TRANSLATION DICTIONARIES
|
||||
# =============================================================================
|
||||
|
||||
PILLAR_TRANSLATION_ID: dict = {
|
||||
# 4 pilar utama Food Security
|
||||
"Availability" : "Ketersediaan",
|
||||
"Access" : "Keterjangkauan",
|
||||
"Utilization" : "Pemanfaatan",
|
||||
"Stability" : "Stabilitas",
|
||||
# Variasi penulisan yang mungkin muncul
|
||||
"availability" : "Ketersediaan",
|
||||
"access" : "Keterjangkauan",
|
||||
"utilization" : "Pemanfaatan",
|
||||
"stability" : "Stabilitas",
|
||||
"Food Availability" : "Ketersediaan Pangan",
|
||||
"Food Access" : "Keterjangkauan Pangan",
|
||||
"Food Utilization" : "Pemanfaatan Pangan",
|
||||
"Food Stability" : "Stabilitas Pangan",
|
||||
}
|
||||
|
||||
INDICATOR_TRANSLATION_ID: dict = {
|
||||
# -------------------------------------------------------------------------
|
||||
# AVAILABILITY
|
||||
# -------------------------------------------------------------------------
|
||||
"Average dietary energy supply adequacy (percent) (3-year average)":
|
||||
"Kecukupan rata-rata pasokan energi makanan (persen) (rata-rata 3 tahun)",
|
||||
"Average value of food production (constant 2014-2016 thousand US$) (3-year average)":
|
||||
"Nilai rata-rata produksi pangan (ribu US$ konstan 2014-2016) (rata-rata 3 tahun)",
|
||||
"Share of dietary energy supply derived from cereals, roots and tubers (percent) (3-year average)":
|
||||
"Proporsi pasokan energi makanan dari serealia, akar, dan umbi-umbian (persen) (rata-rata 3 tahun)",
|
||||
"Average protein supply (g/cap/day) (3-year average)":
|
||||
"Rata-rata pasokan protein (g/kapita/hari) (rata-rata 3 tahun)",
|
||||
"Average supply of protein of animal origin (g/cap/day) (3-year average)":
|
||||
"Rata-rata pasokan protein hewani (g/kapita/hari) (rata-rata 3 tahun)",
|
||||
"Cereal import dependency ratio (percent) (3-year average)":
|
||||
"Rasio ketergantungan impor sereal (persen) (rata-rata 3 tahun)",
|
||||
"Percent of arable land equipped for irrigation (percent) (3-year average)":
|
||||
"Persentase lahan pertanian yang dilengkapi irigasi (persen) (rata-rata 3 tahun)",
|
||||
"Crop production index (2014-2016 = 100)":
|
||||
"Indeks produksi tanaman pangan (2014-2016 = 100)",
|
||||
"Livestock production index (2014-2016 = 100)":
|
||||
"Indeks produksi peternakan (2014-2016 = 100)",
|
||||
"Value of food imports over total merchandise exports (percent) (3-year average)":
|
||||
"Nilai impor pangan terhadap total ekspor barang (persen) (rata-rata 3 tahun)",
|
||||
"Food production variability (constant 2014-2016 thousand US$ per capita)":
|
||||
"Variabilitas produksi pangan (ribu US$ konstan 2014-2016 per kapita)",
|
||||
"Food supply variability (kcal/cap/day)":
|
||||
"Variabilitas pasokan pangan (kkal/kapita/hari)",
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# ACCESS
|
||||
# -------------------------------------------------------------------------
|
||||
"Gross domestic product per capita, PPP (constant 2017 international $)":
|
||||
"Produk domestik bruto per kapita, PPP (internasional konstan 2017 US$)",
|
||||
"Domestic food price level index (2015 = 1.00)":
|
||||
"Indeks tingkat harga pangan domestik (2015 = 1,00)",
|
||||
"Domestic food price volatility index":
|
||||
"Indeks volatilitas harga pangan domestik",
|
||||
"Prevalence of undernourishment (percent) (3-year average)":
|
||||
"Prevalensi kekurangan gizi (persen) (rata-rata 3 tahun)",
|
||||
"Number of people undernourished (million) (3-year average)":
|
||||
"Jumlah penduduk kekurangan gizi (juta jiwa) (rata-rata 3 tahun)",
|
||||
"Depth of the food deficit (kcal/capita/day) (3-year average)":
|
||||
"Kedalaman defisit pangan (kkal/kapita/hari) (rata-rata 3 tahun)",
|
||||
"Percentage of population using at least basic drinking water services (percent)":
|
||||
"Persentase penduduk yang menggunakan layanan air minum dasar (persen)",
|
||||
"Percentage of population using safely managed drinking water services (percent)":
|
||||
"Persentase penduduk yang menggunakan layanan air minum yang dikelola dengan aman (persen)",
|
||||
"Percentage of population using at least basic sanitation services (percent)":
|
||||
"Persentase penduduk yang menggunakan layanan sanitasi dasar (persen)",
|
||||
"Percentage of population using safely managed sanitation services (percent)":
|
||||
"Persentase penduduk yang menggunakan layanan sanitasi yang dikelola dengan aman (persen)",
|
||||
"Access to electricity (percent of rural population)":
|
||||
"Akses listrik (persen penduduk pedesaan)",
|
||||
"Proportion of population with access to electricity (percent)":
|
||||
"Proporsi penduduk dengan akses listrik (persen)",
|
||||
"Road infrastructure index":
|
||||
"Indeks infrastruktur jalan",
|
||||
"Rail lines density (total route-km per 100 square km of land area)":
|
||||
"Kepadatan jalur kereta api (total rute-km per 100 km2 lahan)",
|
||||
"Gross national income per capita (Atlas method, current US$)":
|
||||
"Pendapatan nasional bruto per kapita (metode Atlas, US$ terkini)",
|
||||
"Food Insecurity Experience Scale (FIES)":
|
||||
"Skala Pengalaman Ketidakamanan Pangan (FIES)",
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# UTILIZATION
|
||||
# -------------------------------------------------------------------------
|
||||
"Prevalence of severe food insecurity in the total population (percent) (3-year average)":
|
||||
"Prevalensi kerawanan pangan berat pada total penduduk (persen) (rata-rata 3 tahun)",
|
||||
"Prevalence of severe food insecurity in the male adult population (percent) (3-year average)":
|
||||
"Prevalensi kerawanan pangan berat pada penduduk laki-laki dewasa (persen) (rata-rata 3 tahun)",
|
||||
"Prevalence of severe food insecurity in the female adult population (percent) (3-year average)":
|
||||
"Prevalensi kerawanan pangan berat pada penduduk perempuan dewasa (persen) (rata-rata 3 tahun)",
|
||||
"Prevalence of moderate or severe food insecurity in the total population (percent) (3-year average)":
|
||||
"Prevalensi kerawanan pangan sedang atau berat pada total penduduk (persen) (rata-rata 3 tahun)",
|
||||
"Prevalence of moderate or severe food insecurity in the male adult population (percent) (3-year average)":
|
||||
"Prevalensi kerawanan pangan sedang atau berat pada penduduk laki-laki dewasa (persen) (rata-rata 3 tahun)",
|
||||
"Prevalence of moderate or severe food insecurity in the female adult population (percent) (3-year average)":
|
||||
"Prevalensi kerawanan pangan sedang atau berat pada penduduk perempuan dewasa (persen) (rata-rata 3 tahun)",
|
||||
"Number of severely food insecure people (million) (3-year average)":
|
||||
"Jumlah penduduk yang mengalami kerawanan pangan berat (juta jiwa) (rata-rata 3 tahun)",
|
||||
"Number of severely food insecure male adults (million) (3-year average)":
|
||||
"Jumlah laki-laki dewasa yang mengalami kerawanan pangan berat (juta jiwa) (rata-rata 3 tahun)",
|
||||
"Number of severely food insecure female adults (million) (3-year average)":
|
||||
"Jumlah perempuan dewasa yang mengalami kerawanan pangan berat (juta jiwa) (rata-rata 3 tahun)",
|
||||
"Number of moderately or severely food insecure people (million) (3-year average)":
|
||||
"Jumlah penduduk yang mengalami kerawanan pangan sedang atau berat (juta jiwa) (rata-rata 3 tahun)",
|
||||
"Number of moderately or severely food insecure male adults (million) (3-year average)":
|
||||
"Jumlah laki-laki dewasa yang mengalami kerawanan pangan sedang atau berat (juta jiwa) (rata-rata 3 tahun)",
|
||||
"Number of moderately or severely food insecure female adults (million) (3-year average)":
|
||||
"Jumlah perempuan dewasa yang mengalami kerawanan pangan sedang atau berat (juta jiwa) (rata-rata 3 tahun)",
|
||||
"Percentage of children under 5 years of age who are stunted (modelled estimates) (percent)":
|
||||
"Persentase anak di bawah 5 tahun yang mengalami stunting (estimasi model) (persen)",
|
||||
"Number of children under 5 years of age who are stunted (modeled estimates) (million)":
|
||||
"Jumlah anak di bawah 5 tahun yang mengalami stunting (estimasi model) (juta jiwa)",
|
||||
"Percentage of children under 5 years affected by wasting (percent)":
|
||||
"Persentase anak di bawah 5 tahun yang mengalami wasting (persen)",
|
||||
"Number of children under 5 years affected by wasting (million)":
|
||||
"Jumlah anak di bawah 5 tahun yang mengalami wasting (juta jiwa)",
|
||||
"Percentage of children under 5 years of age who are overweight (modelled estimates) (percent)":
|
||||
"Persentase anak di bawah 5 tahun yang mengalami kelebihan berat badan (estimasi model) (persen)",
|
||||
"Number of children under 5 years of age who are overweight (modeled estimates) (million)":
|
||||
"Jumlah anak di bawah 5 tahun yang mengalami kelebihan berat badan (estimasi model) (juta jiwa)",
|
||||
"Prevalence of anemia among women of reproductive age (15-49 years) (percent)":
|
||||
"Prevalensi anemia pada perempuan usia reproduksi (15-49 tahun) (persen)",
|
||||
"Number of women of reproductive age (15-49 years) affected by anemia (million)":
|
||||
"Jumlah perempuan usia reproduksi (15-49 tahun) yang menderita anemia (juta jiwa)",
|
||||
"Prevalence of obesity in the adult population (18 years and older) (percent)":
|
||||
"Prevalensi obesitas pada penduduk dewasa (18 tahun ke atas) (persen)",
|
||||
"Prevalence of exclusive breastfeeding among infants 0-5 months of age (percent)":
|
||||
"Prevalensi pemberian ASI eksklusif pada bayi usia 0-5 bulan (persen)",
|
||||
"Minimum dietary diversity for women (MDD-W) (percent)":
|
||||
"Keragaman pola makan minimum untuk perempuan (MDD-W) (persen)",
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# STABILITY
|
||||
# -------------------------------------------------------------------------
|
||||
"Cereal import dependency ratio (percent)":
|
||||
"Rasio ketergantungan impor sereal (persen)",
|
||||
"Political stability and absence of violence/terrorism (index)":
|
||||
"Stabilitas politik dan tidak adanya kekerasan/terorisme (indeks)",
|
||||
"Domestic food price volatility":
|
||||
"Volatilitas harga pangan domestik",
|
||||
"Per capita food supply variability (kcal/cap/day)":
|
||||
"Variabilitas pasokan pangan per kapita (kkal/kapita/hari)",
|
||||
"Percentage of arable land equipped for irrigation (percent)":
|
||||
"Persentase lahan pertanian yang dilengkapi irigasi (persen)",
|
||||
"GDP per capita growth (annual %)":
|
||||
"Pertumbuhan PDB per kapita (% tahunan)",
|
||||
"GDP growth (annual %)":
|
||||
"Pertumbuhan PDB (% tahunan)",
|
||||
}
|
||||
|
||||
|
||||
def translate_indicator(name: str) -> str:
|
||||
"""Terjemahkan nama indikator ke Bahasa Indonesia. Fallback ke nama asli."""
|
||||
if not name:
|
||||
return name
|
||||
return INDICATOR_TRANSLATION_ID.get(name, name)
|
||||
|
||||
|
||||
def translate_pillar(name: str) -> str:
|
||||
"""Terjemahkan nama pillar ke Bahasa Indonesia. Fallback ke nama asli."""
|
||||
if not name:
|
||||
return name
|
||||
return PILLAR_TRANSLATION_ID.get(name, name)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# ANALYTICAL LAYER CLASS
|
||||
# =============================================================================
|
||||
@@ -46,9 +218,13 @@ class AnalyticalLayerLoader:
|
||||
1. Complete per country (no gaps from start_year to end_year)
|
||||
2. Filter countries with all pillars
|
||||
3. Ensure indicators have consistent country count across all years
|
||||
4. Save dengan kolom lengkap (nama + ID) untuk kemudahan Looker Studio
|
||||
4. Save dengan kolom lengkap (nama + ID + nama Indonesia) untuk Looker Studio
|
||||
|
||||
Output: fact_asean_food_security_selected -> DW layer (Gold) -> fs_asean_gold
|
||||
|
||||
Kolom tambahan:
|
||||
- indicator_name_id : terjemahan Bahasa Indonesia dari indicator_name
|
||||
- pillar_name_id : terjemahan Bahasa Indonesia dari pillar_name
|
||||
"""
|
||||
|
||||
def __init__(self, client: bigquery.Client):
|
||||
@@ -424,9 +600,6 @@ class AnalyticalLayerLoader:
|
||||
return year_stats
|
||||
|
||||
def save_analytical_table(self):
|
||||
# ---------------------------------------------------------------
|
||||
# CHANGED: nama tabel baru + kolom lengkap untuk Looker Studio
|
||||
# ---------------------------------------------------------------
|
||||
table_name = 'fact_asean_food_security_selected'
|
||||
|
||||
self.logger.info("\n" + "=" * 80)
|
||||
@@ -434,11 +607,6 @@ class AnalyticalLayerLoader:
|
||||
self.logger.info("=" * 80)
|
||||
|
||||
try:
|
||||
# ------------------------------------------------------------------
|
||||
# Pilih kolom: ID + Nama lengkap + value
|
||||
# Kolom nama memudahkan filtering/slicing langsung di Looker Studio
|
||||
# tanpa perlu join ulang ke tabel dimensi.
|
||||
# ------------------------------------------------------------------
|
||||
analytical_df = self.df_clean[[
|
||||
'country_id',
|
||||
'country_name',
|
||||
@@ -452,37 +620,68 @@ class AnalyticalLayerLoader:
|
||||
'value',
|
||||
]].copy()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# TAMBAHAN: kolom terjemahan Bahasa Indonesia
|
||||
# indicator_name_id : terjemahan Bahasa Indonesia dari indicator_name
|
||||
# pillar_name_id : terjemahan Bahasa Indonesia dari pillar_name
|
||||
# ------------------------------------------------------------------
|
||||
analytical_df['indicator_name_id'] = analytical_df['indicator_name'].apply(translate_indicator)
|
||||
analytical_df['pillar_name_id'] = analytical_df['pillar_name'].apply(translate_pillar)
|
||||
|
||||
# Log indikator yang belum punya terjemahan (fallback ke nama asli)
|
||||
no_trans_ind = analytical_df[
|
||||
analytical_df['indicator_name_id'] == analytical_df['indicator_name']
|
||||
]['indicator_name'].unique()
|
||||
if len(no_trans_ind) > 0:
|
||||
self.logger.warning(
|
||||
f" [TRANSLATION] {len(no_trans_ind)} indicator(s) tidak ada di kamus "
|
||||
f"(menggunakan nama asli): {list(no_trans_ind)[:5]}"
|
||||
)
|
||||
|
||||
no_trans_pil = analytical_df[
|
||||
analytical_df['pillar_name_id'] == analytical_df['pillar_name']
|
||||
]['pillar_name'].unique()
|
||||
if len(no_trans_pil) > 0:
|
||||
self.logger.warning(
|
||||
f" [TRANSLATION] {len(no_trans_pil)} pillar(s) tidak ada di kamus "
|
||||
f"(menggunakan nama asli): {list(no_trans_pil)}"
|
||||
)
|
||||
|
||||
analytical_df = analytical_df.sort_values(
|
||||
['year', 'country_name', 'pillar_name', 'indicator_name']
|
||||
).reset_index(drop=True)
|
||||
|
||||
# Pastikan tipe data konsisten
|
||||
analytical_df['country_id'] = analytical_df['country_id'].astype(int)
|
||||
analytical_df['country_name'] = analytical_df['country_name'].astype(str)
|
||||
analytical_df['indicator_id'] = analytical_df['indicator_id'].astype(int)
|
||||
analytical_df['indicator_name']= analytical_df['indicator_name'].astype(str)
|
||||
analytical_df['direction'] = analytical_df['direction'].astype(str)
|
||||
analytical_df['pillar_id'] = analytical_df['pillar_id'].astype(int)
|
||||
analytical_df['pillar_name'] = analytical_df['pillar_name'].astype(str)
|
||||
analytical_df['time_id'] = analytical_df['time_id'].astype(int)
|
||||
analytical_df['year'] = analytical_df['year'].astype(int)
|
||||
analytical_df['value'] = analytical_df['value'].astype(float)
|
||||
analytical_df['country_id'] = analytical_df['country_id'].astype(int)
|
||||
analytical_df['country_name'] = analytical_df['country_name'].astype(str)
|
||||
analytical_df['indicator_id'] = analytical_df['indicator_id'].astype(int)
|
||||
analytical_df['indicator_name'] = analytical_df['indicator_name'].astype(str)
|
||||
analytical_df['indicator_name_id'] = analytical_df['indicator_name_id'].astype(str)
|
||||
analytical_df['direction'] = analytical_df['direction'].astype(str)
|
||||
analytical_df['pillar_id'] = analytical_df['pillar_id'].astype(int)
|
||||
analytical_df['pillar_name'] = analytical_df['pillar_name'].astype(str)
|
||||
analytical_df['pillar_name_id'] = analytical_df['pillar_name_id'].astype(str)
|
||||
analytical_df['time_id'] = analytical_df['time_id'].astype(int)
|
||||
analytical_df['year'] = analytical_df['year'].astype(int)
|
||||
analytical_df['value'] = analytical_df['value'].astype(float)
|
||||
|
||||
self.logger.info(f" Kolom yang disimpan: {list(analytical_df.columns)}")
|
||||
self.logger.info(f" Total rows: {len(analytical_df):,}")
|
||||
|
||||
# Schema BigQuery
|
||||
schema = [
|
||||
bigquery.SchemaField("country_id", "INTEGER", mode="REQUIRED"),
|
||||
bigquery.SchemaField("country_name", "STRING", mode="REQUIRED"),
|
||||
bigquery.SchemaField("indicator_id", "INTEGER", mode="REQUIRED"),
|
||||
bigquery.SchemaField("indicator_name", "STRING", mode="REQUIRED"),
|
||||
bigquery.SchemaField("direction", "STRING", mode="REQUIRED"),
|
||||
bigquery.SchemaField("pillar_id", "INTEGER", mode="REQUIRED"),
|
||||
bigquery.SchemaField("pillar_name", "STRING", mode="REQUIRED"),
|
||||
bigquery.SchemaField("time_id", "INTEGER", mode="REQUIRED"),
|
||||
bigquery.SchemaField("year", "INTEGER", mode="REQUIRED"),
|
||||
bigquery.SchemaField("value", "FLOAT", mode="REQUIRED"),
|
||||
bigquery.SchemaField("country_id", "INTEGER", mode="REQUIRED"),
|
||||
bigquery.SchemaField("country_name", "STRING", mode="REQUIRED"),
|
||||
bigquery.SchemaField("indicator_id", "INTEGER", mode="REQUIRED"),
|
||||
bigquery.SchemaField("indicator_name", "STRING", mode="REQUIRED"),
|
||||
bigquery.SchemaField("indicator_name_id", "STRING", mode="REQUIRED"),
|
||||
bigquery.SchemaField("direction", "STRING", mode="REQUIRED"),
|
||||
bigquery.SchemaField("pillar_id", "INTEGER", mode="REQUIRED"),
|
||||
bigquery.SchemaField("pillar_name", "STRING", mode="REQUIRED"),
|
||||
bigquery.SchemaField("pillar_name_id", "STRING", mode="REQUIRED"),
|
||||
bigquery.SchemaField("time_id", "INTEGER", mode="REQUIRED"),
|
||||
bigquery.SchemaField("year", "INTEGER", mode="REQUIRED"),
|
||||
bigquery.SchemaField("value", "FLOAT", mode="REQUIRED"),
|
||||
]
|
||||
|
||||
rows_loaded = load_to_bigquery(
|
||||
@@ -508,7 +707,7 @@ class AnalyticalLayerLoader:
|
||||
'fixed_countries': len(self.selected_country_ids),
|
||||
'no_gaps' : True,
|
||||
'layer' : 'gold',
|
||||
'columns' : 'id + name + value (Looker Studio ready)'
|
||||
'columns' : 'id + name + name_id (Looker Studio ready)'
|
||||
}),
|
||||
'validation_metrics' : json.dumps({
|
||||
'fixed_countries' : len(self.selected_country_ids),
|
||||
@@ -517,8 +716,8 @@ class AnalyticalLayerLoader:
|
||||
}
|
||||
save_etl_metadata(self.client, metadata)
|
||||
|
||||
self.logger.info(f" ✓ {table_name}: {rows_loaded:,} rows → [DW/Gold] fs_asean_gold")
|
||||
self.logger.info(f" Metadata → [AUDIT] etl_metadata")
|
||||
self.logger.info(f" [OK] {table_name}: {rows_loaded:,} rows -> [DW/Gold] fs_asean_gold")
|
||||
self.logger.info(f" Metadata -> [AUDIT] etl_metadata")
|
||||
return rows_loaded
|
||||
|
||||
except Exception as e:
|
||||
@@ -530,7 +729,7 @@ class AnalyticalLayerLoader:
|
||||
self.pipeline_metadata['start_time'] = self.pipeline_start
|
||||
|
||||
self.logger.info("\n" + "=" * 80)
|
||||
self.logger.info("Output: fact_asean_food_security_selected → fs_asean_gold")
|
||||
self.logger.info("Output: fact_asean_food_security_selected -> fs_asean_gold")
|
||||
self.logger.info("=" * 80)
|
||||
|
||||
self.load_source_data()
|
||||
@@ -577,7 +776,7 @@ def run_analytical_layer():
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=" * 80)
|
||||
print("Output: fact_asean_food_security_selected → fs_asean_gold")
|
||||
print("Output: fact_asean_food_security_selected -> fs_asean_gold")
|
||||
print("=" * 80)
|
||||
|
||||
logger = setup_logging()
|
||||
|
||||
Reference in New Issue
Block a user