add a condition for pillar
This commit is contained in:
@@ -20,6 +20,32 @@ Narrative style:
|
||||
- Plain text, tanpa markdown bold (**)
|
||||
- Interpretatif: membaca tren, gap, anomali, konsistensi dari data nyata
|
||||
- Bilingual: narrative_en (Inggris) + narrative_id (Indonesia)
|
||||
|
||||
KONDISI PILAR (pillar_condition_en / pillar_condition_id):
|
||||
Kolom tambahan di agg_pillar_by_country untuk mendeskripsikan kondisi
|
||||
tiap pilar per negara per tahun secara kontekstual dan kuantitatif.
|
||||
|
||||
Landasan teori:
|
||||
1. FAO & CFS (1996 World Food Summit; CFS Reform Document 2009):
|
||||
Definisi 4 pilar ketahanan pangan dan makna substantif masing-masing.
|
||||
Referensi: FAO (2009). "Declaration of the World Summit on Food Security."
|
||||
CFS (2012). "Global Strategic Framework for Food Security & Nutrition."
|
||||
2. GFSI — Economist Impact (2022):
|
||||
Threshold klasifikasi skor 0-100:
|
||||
>= 75 : "Good" environment -> label "Secure / Aman"
|
||||
>= 60 : above threshold -> label "Adequate / Memadai"
|
||||
>= 40 : "Moderate" env -> label "Moderate / Sedang"
|
||||
>= 20 : below moderate -> label "At Risk / Berisiko"
|
||||
< 20 : severe -> label "Critical / Kritis"
|
||||
Referensi: Economist Impact (2022). "Global Food Security Index 2022."
|
||||
3. IPC — Integrated Food Security Phase Classification (2019):
|
||||
Klasifikasi bertingkat per pilar: dari "Moderate Risk" hingga "Critical".
|
||||
Referensi: IPC (2019). "IPC Technical Manual Version 3.0."
|
||||
4. FAO SOFI (2023/2024):
|
||||
Konteks kondisi per pilar: availability (supply/stok), access (keterjangkauan),
|
||||
utilization (nutrisi/sanitasi), stability (kerentanan terhadap guncangan).
|
||||
Referensi: FAO et al. (2024). "The State of Food Security and Nutrition
|
||||
in the World 2024."
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
@@ -157,6 +183,173 @@ def translate_pillar(name: str) -> str:
|
||||
return PILLAR_TRANSLATION_ID.get(name, name)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# PILLAR CONDITION CLASSIFIER
|
||||
# =============================================================================
|
||||
#
|
||||
# Landasan teori (lihat docstring modul di atas untuk referensi lengkap):
|
||||
#
|
||||
# Tier skor (skala 1-100, mengacu GFSI 2022 + IPC Phase Classification):
|
||||
# >= 75 : Secure / Aman — performa tinggi, kondisi baik
|
||||
# >= 60 : Adequate / Memadai — di atas threshold, masih ada ruang
|
||||
# >= 40 : Moderate / Sedang — tantangan nyata, perlu perhatian
|
||||
# >= 20 : At Risk / Berisiko — kondisi lemah, butuh intervensi
|
||||
# < 20 : Critical / Kritis — sangat buruk, tindakan segera
|
||||
#
|
||||
# Label kontekstual per pilar mengacu definisi FAO/CFS empat pilar:
|
||||
# Food Availability : ketersediaan pasokan (produksi, stok, impor)
|
||||
# Food Access : keterjangkauan ekonomi & fisik terhadap pangan
|
||||
# Food Utilization : pemanfaatan biologis (gizi, sanitasi, kesehatan)
|
||||
# Food Stability : konsistensi tiga pilar di atas dari waktu ke waktu
|
||||
# Food Other : indikator multidimensi / suplemen
|
||||
#
|
||||
# =============================================================================
|
||||
|
||||
# Tier thresholds (urut dari tertinggi)
|
||||
_CONDITION_TIERS = [
|
||||
# (min_score, base_label_en, base_label_id)
|
||||
(75, "Secure", "Aman"),
|
||||
(60, "Adequate", "Memadai"),
|
||||
(40, "Moderate", "Sedang"),
|
||||
(20, "At Risk", "Berisiko"),
|
||||
( 0, "Critical", "Kritis"),
|
||||
]
|
||||
|
||||
# Konteks kondisi per pilar per tier (EN, ID)
|
||||
# Mengacu makna substantif pilar (FAO SOFI 2024; FSC Handbook 2020;
|
||||
# IPC Technical Manual 2019).
|
||||
_PILLAR_CONTEXT: dict = {
|
||||
# ---- Food Availability ----
|
||||
"Food Availability": {
|
||||
"Secure" : ("Food supply is abundant and well-distributed",
|
||||
"Pasokan pangan berlimpah dan terdistribusi merata"),
|
||||
"Adequate" : ("Food supply is sufficient with minor gaps",
|
||||
"Pasokan pangan cukup dengan kesenjangan minor"),
|
||||
"Moderate" : ("Food supply shows signs of strain",
|
||||
"Pasokan pangan menunjukkan tanda-tanda tekanan"),
|
||||
"At Risk" : ("Food supply is insufficient; stocks are dwindling",
|
||||
"Pasokan pangan tidak mencukupi; stok mulai menipis"),
|
||||
"Critical" : ("Severe food supply deficit; stocks critically low",
|
||||
"Defisit pasokan pangan parah; stok dalam kondisi kritis"),
|
||||
},
|
||||
# ---- Food Access ----
|
||||
"Food Access": {
|
||||
"Secure" : ("Food is economically and physically accessible to all",
|
||||
"Pangan terjangkau secara ekonomi dan fisik bagi semua"),
|
||||
"Adequate" : ("Food access is generally good with limited barriers",
|
||||
"Akses pangan umumnya baik dengan hambatan terbatas"),
|
||||
"Moderate" : ("Portions of the population face access constraints",
|
||||
"Sebagian penduduk menghadapi kendala akses pangan"),
|
||||
"At Risk" : ("Significant affordability or physical access barriers",
|
||||
"Hambatan keterjangkauan atau akses fisik yang signifikan"),
|
||||
"Critical" : ("Widespread inability to access sufficient food",
|
||||
"Ketidakmampuan meluas dalam mengakses pangan yang cukup"),
|
||||
},
|
||||
# ---- Food Utilization ----
|
||||
"Food Utilization": {
|
||||
"Secure" : ("Dietary quality, nutrition, and sanitation are strong",
|
||||
"Kualitas gizi, nutrisi, dan sanitasi dalam kondisi baik"),
|
||||
"Adequate" : ("Nutrition and sanitation are adequate; minor deficiencies",
|
||||
"Gizi dan sanitasi memadai; kekurangan minor masih ada"),
|
||||
"Moderate" : ("Nutritional gaps or sanitation issues are evident",
|
||||
"Kesenjangan gizi atau masalah sanitasi mulai terlihat"),
|
||||
"At Risk" : ("Significant nutritional deficiencies or poor sanitation",
|
||||
"Kekurangan gizi atau sanitasi buruk yang signifikan"),
|
||||
"Critical" : ("Severe malnutrition and/or critical sanitation deficits",
|
||||
"Malnutrisi parah dan/atau defisit sanitasi yang kritis"),
|
||||
},
|
||||
# ---- Food Stability ----
|
||||
"Food Stability": {
|
||||
"Secure" : ("Food security is consistently maintained over time",
|
||||
"Ketahanan pangan terjaga konsisten dari waktu ke waktu"),
|
||||
"Adequate" : ("Stability is generally good with manageable risks",
|
||||
"Stabilitas umumnya baik dengan risiko yang masih terkelola"),
|
||||
"Moderate" : ("Periodic shocks or vulnerabilities affect stability",
|
||||
"Guncangan periodik atau kerentanan memengaruhi stabilitas"),
|
||||
"At Risk" : ("Frequent disruptions threaten food security continuity",
|
||||
"Gangguan berulang mengancam kesinambungan ketahanan pangan"),
|
||||
"Critical" : ("Sustained instability; food security is highly fragile",
|
||||
"Ketidakstabilan berkelanjutan; ketahanan pangan sangat rapuh"),
|
||||
},
|
||||
# ---- Food Other / Indikator Tambahan ----
|
||||
"Food Other": {
|
||||
"Secure" : ("Supplementary indicators reflect strong food system",
|
||||
"Indikator tambahan mencerminkan sistem pangan yang kuat"),
|
||||
"Adequate" : ("Supplementary indicators are at acceptable levels",
|
||||
"Indikator tambahan berada pada level yang dapat diterima"),
|
||||
"Moderate" : ("Supplementary indicators signal emerging challenges",
|
||||
"Indikator tambahan memberi sinyal tantangan yang muncul"),
|
||||
"At Risk" : ("Supplementary indicators show concerning levels",
|
||||
"Indikator tambahan menunjukkan level yang mengkhawatirkan"),
|
||||
"Critical" : ("Supplementary indicators reflect systemic food system failure",
|
||||
"Indikator tambahan mencerminkan kegagalan sistemik pangan"),
|
||||
},
|
||||
}
|
||||
|
||||
# Fallback jika pillar_name tidak dikenali
|
||||
_PILLAR_CONTEXT_FALLBACK: dict = {
|
||||
"Secure" : ("Performance is high across food security indicators",
|
||||
"Performa tinggi pada indikator ketahanan pangan"),
|
||||
"Adequate" : ("Performance is adequate across food security indicators",
|
||||
"Performa memadai pada indikator ketahanan pangan"),
|
||||
"Moderate" : ("Performance shows moderate challenges",
|
||||
"Performa menunjukkan tantangan yang moderat"),
|
||||
"At Risk" : ("Performance indicates vulnerability in food security",
|
||||
"Performa mengindikasikan kerentanan ketahanan pangan"),
|
||||
"Critical" : ("Performance is critically low; urgent action needed",
|
||||
"Performa sangat rendah; tindakan segera diperlukan"),
|
||||
}
|
||||
|
||||
|
||||
def get_pillar_condition(pillar_name: str, score: float) -> tuple:
|
||||
"""
|
||||
Mengembalikan (condition_en, condition_id) berdasarkan skor dan nama pilar.
|
||||
|
||||
Tier mengacu GFSI 2022 (Economist Impact) + IPC Phase Classification (2019):
|
||||
>= 75 -> Secure / Aman
|
||||
>= 60 -> Adequate / Memadai
|
||||
>= 40 -> Moderate / Sedang
|
||||
>= 20 -> At Risk / Berisiko
|
||||
< 20 -> Critical / Kritis
|
||||
|
||||
Deskripsi kontekstual mengacu FAO/CFS definisi 4 pilar (World Food Summit
|
||||
1996; CFS 2009) dan FAO SOFI 2024.
|
||||
|
||||
Args:
|
||||
pillar_name : Nama pilar dalam bahasa Inggris (e.g. "Food Availability").
|
||||
score : Skor ternormalisasi skala 1-100.
|
||||
|
||||
Returns:
|
||||
Tuple (condition_en: str, condition_id: str)
|
||||
"""
|
||||
if score is None or (isinstance(score, float) and np.isnan(score)):
|
||||
return ("N/A", "N/A")
|
||||
|
||||
# Tentukan tier
|
||||
tier_label_en = _CONDITION_TIERS[-1][1] # default: Critical
|
||||
tier_label_id = _CONDITION_TIERS[-1][2]
|
||||
for min_score, lbl_en, lbl_id in _CONDITION_TIERS:
|
||||
if score >= min_score:
|
||||
tier_label_en = lbl_en
|
||||
tier_label_id = lbl_id
|
||||
break
|
||||
|
||||
# Ambil konteks per pilar
|
||||
ctx = _PILLAR_CONTEXT.get(pillar_name, None)
|
||||
if ctx:
|
||||
ctx_en, ctx_id = ctx.get(
|
||||
tier_label_en,
|
||||
_PILLAR_CONTEXT_FALLBACK.get(tier_label_en, ("", ""))
|
||||
)
|
||||
else:
|
||||
ctx_en, ctx_id = _PILLAR_CONTEXT_FALLBACK.get(tier_label_en, ("", ""))
|
||||
|
||||
# Format akhir: "TIER — Context"
|
||||
condition_en = f"{tier_label_en} — {ctx_en}"
|
||||
condition_id = f"{tier_label_id} — {ctx_id}"
|
||||
return condition_en, condition_id
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# WINDOWS CP1252 SAFE LOGGING
|
||||
# =============================================================================
|
||||
@@ -334,8 +527,6 @@ def _find_anomaly_year(values_by_year: dict) -> tuple:
|
||||
|
||||
# =============================================================================
|
||||
# NARRATIVE BUILDER — PILLAR
|
||||
# Digunakan untuk SEMUA baris: per negara dan ASEAN aggregate.
|
||||
# Jika is_asean=True, narasi tidak menyebut "country" melainkan "ASEAN region".
|
||||
# =============================================================================
|
||||
|
||||
def _build_pillar_narrative(
|
||||
@@ -418,7 +609,6 @@ def _build_pillar_narrative(
|
||||
sentences_en.append(s3_en)
|
||||
sentences_id.append(s3_id)
|
||||
|
||||
# Gap antar negara hanya relevan untuk ASEAN narrative
|
||||
if is_asean and not country_pillar_all.empty:
|
||||
gap_trend = _detect_country_gap(
|
||||
country_pillar_all[country_pillar_all["year"] <= year],
|
||||
@@ -437,7 +627,6 @@ def _build_pillar_narrative(
|
||||
sentences_en.append(s4_en)
|
||||
sentences_id.append(s4_id)
|
||||
|
||||
# Top/bottom hanya ditampilkan untuk baris ASEAN
|
||||
if is_asean and top_country and bot_country and top_country != bot_country:
|
||||
top_country_id = translate_country(top_country)
|
||||
bot_country_id = translate_country(bot_country)
|
||||
@@ -453,7 +642,6 @@ def _build_pillar_narrative(
|
||||
sentences_en.append(s5_en)
|
||||
sentences_id.append(s5_id)
|
||||
|
||||
# Perbandingan antar pilar
|
||||
if not all_pillar_scores_year.empty and len(all_pillar_scores_year) > 1:
|
||||
sorted_pillars = all_pillar_scores_year.sort_values("pillar_country_score_1_100", ascending=False)
|
||||
strongest = sorted_pillars.iloc[0]
|
||||
@@ -530,7 +718,6 @@ class FoodSecurityAggregator:
|
||||
self.logger.warning(f" [DIRECTION] {n_null_dir} rows NULL -> diisi 'positive'")
|
||||
self.df["direction"] = self.df["direction"].fillna("positive")
|
||||
|
||||
# Rename pillar_name: add 'Food ' prefix, remove Sustainability
|
||||
PILLAR_RENAME_MAP = {
|
||||
'Availability' : 'Food Availability',
|
||||
'Access' : 'Food Access',
|
||||
@@ -542,7 +729,6 @@ class FoodSecurityAggregator:
|
||||
}
|
||||
self.df["pillar_name"] = self.df["pillar_name"].replace(PILLAR_RENAME_MAP)
|
||||
|
||||
# Kolom terjemahan Indonesia
|
||||
if "country_name_id" not in self.df.columns:
|
||||
self.df["country_name_id"] = self.df["country_name"].apply(translate_country)
|
||||
if "pillar_name_id" not in self.df.columns:
|
||||
@@ -686,6 +872,12 @@ class FoodSecurityAggregator:
|
||||
"asean_country_id" : ASEAN_COUNTRY_ID,
|
||||
"pillar_change" : "Sustainability renamed to Food Other, all pillars prefixed with Food",
|
||||
"architecture" : "ASEAN merged into country tables (country_id=0)",
|
||||
"condition_column" : "pillar_condition_en/id added to agg_pillar_by_country",
|
||||
"condition_reference" : (
|
||||
"GFSI 2022 (Economist Impact) score tiers >= 75/60/40/20; "
|
||||
"IPC Technical Manual 2019; FAO/CFS 4-pillar framework 1996/2009; "
|
||||
"FAO SOFI 2024"
|
||||
),
|
||||
}),
|
||||
"validation_metrics" : json.dumps({
|
||||
"status" : status,
|
||||
@@ -698,11 +890,6 @@ class FoodSecurityAggregator:
|
||||
# =========================================================================
|
||||
|
||||
def _build_asean_pillar_rows(self, df_normed: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Hitung rata-rata ASEAN per pillar per year dari norm_value semua negara,
|
||||
kemudian scale ulang ke 1-100 dalam konteks SELURUH tabel (negara + ASEAN).
|
||||
Return DataFrame dengan format sama seperti baris per-negara.
|
||||
"""
|
||||
asean_agg = (
|
||||
df_normed
|
||||
.groupby(["pillar_id", "pillar_name", "year"])
|
||||
@@ -716,7 +903,7 @@ class FoodSecurityAggregator:
|
||||
return asean_agg
|
||||
|
||||
# =========================================================================
|
||||
# STEP 2: agg_pillar_by_country (termasuk ASEAN)
|
||||
# STEP 2: agg_pillar_by_country (termasuk ASEAN + kolom kondisi)
|
||||
# =========================================================================
|
||||
|
||||
def calc_pillar_by_country(self) -> pd.DataFrame:
|
||||
@@ -725,6 +912,7 @@ class FoodSecurityAggregator:
|
||||
self.logger.info("\n" + "=" * 70)
|
||||
self.logger.info(f"STEP 2: {table_name} -> [Gold] fs_asean_gold")
|
||||
self.logger.info(" Termasuk baris ASEAN (country_id=0) untuk filter Looker Studio")
|
||||
self.logger.info(" Kolom baru: pillar_condition_en, pillar_condition_id")
|
||||
self.logger.info("=" * 70)
|
||||
|
||||
try:
|
||||
@@ -746,10 +934,27 @@ class FoodSecurityAggregator:
|
||||
# Gabung
|
||||
df = pd.concat([df_countries, df_asean], ignore_index=True)
|
||||
|
||||
# Scale 1-100 secara BERSAMA (negara + ASEAN dalam satu ruang skala)
|
||||
# Scale 1-100 secara BERSAMA
|
||||
df["pillar_country_score_1_100"] = global_minmax(df["pillar_country_norm"])
|
||||
|
||||
# Rank hanya di antara negara asli (ASEAN tidak di-rank melawan dirinya sendiri)
|
||||
# ---------------------------------------------------------------
|
||||
# TAMBAHAN: kolom kondisi pilar
|
||||
# Dibangkitkan SETELAH score_1_100 tersedia, sehingga tier
|
||||
# langsung mencerminkan skor dalam skala akhir 1-100.
|
||||
# Referensi tier: GFSI 2022 (Economist Impact); IPC 2019;
|
||||
# FAO/CFS 1996/2009; FAO SOFI 2024.
|
||||
# ---------------------------------------------------------------
|
||||
conditions = df.apply(
|
||||
lambda row: get_pillar_condition(
|
||||
row["pillar_name"],
|
||||
row["pillar_country_score_1_100"]
|
||||
),
|
||||
axis=1
|
||||
)
|
||||
df["pillar_condition_en"] = conditions.apply(lambda x: x[0])
|
||||
df["pillar_condition_id"] = conditions.apply(lambda x: x[1])
|
||||
|
||||
# Rank hanya di antara negara asli
|
||||
country_only = df[df["country_id"] != ASEAN_COUNTRY_ID].copy()
|
||||
country_only["rank_in_pillar_year"] = (
|
||||
country_only.groupby(["pillar_id", "year"])["pillar_country_score_1_100"]
|
||||
@@ -771,12 +976,20 @@ class FoodSecurityAggregator:
|
||||
df["pillar_country_score_1_100"] = df["pillar_country_score_1_100"].astype(float)
|
||||
df["pillar_name_id"] = df["pillar_name_id"].astype(str)
|
||||
df["country_name_id"] = df["country_name_id"].astype(str)
|
||||
df["pillar_condition_en"] = df["pillar_condition_en"].astype(str)
|
||||
df["pillar_condition_id"] = df["pillar_condition_id"].astype(str)
|
||||
|
||||
self.logger.info(
|
||||
f" Total rows: {len(df):,} "
|
||||
f"({len(df_countries):,} country + {len(asean_only):,} ASEAN)"
|
||||
)
|
||||
|
||||
# Log distribusi kondisi untuk QA
|
||||
self.logger.info("\n Distribusi pillar_condition_en (sample):")
|
||||
cond_dist = df["pillar_condition_en"].value_counts().head(10)
|
||||
for cond, cnt in cond_dist.items():
|
||||
self.logger.info(f" {cnt:>6,} {cond}")
|
||||
|
||||
schema = [
|
||||
bigquery.SchemaField("country_id", "INTEGER", mode="REQUIRED"),
|
||||
bigquery.SchemaField("country_name", "STRING", mode="REQUIRED"),
|
||||
@@ -789,6 +1002,10 @@ class FoodSecurityAggregator:
|
||||
bigquery.SchemaField("pillar_country_score_1_100", "FLOAT", mode="REQUIRED"),
|
||||
bigquery.SchemaField("rank_in_pillar_year", "INTEGER", mode="REQUIRED"),
|
||||
bigquery.SchemaField("year_over_year_change", "FLOAT", mode="NULLABLE"),
|
||||
# --- KOLOM KONDISI BARU ---
|
||||
# Tier skor (GFSI 2022) + konteks substantif per pilar (FAO/CFS; IPC 2019)
|
||||
bigquery.SchemaField("pillar_condition_en", "STRING", mode="REQUIRED"),
|
||||
bigquery.SchemaField("pillar_condition_id", "STRING", mode="REQUIRED"),
|
||||
]
|
||||
rows = load_to_bigquery(
|
||||
self.client, df, table_name, layer='gold',
|
||||
@@ -927,7 +1144,7 @@ class FoodSecurityAggregator:
|
||||
|
||||
df_countries = pd.concat(parts, ignore_index=True)
|
||||
|
||||
# ---- ASEAN aggregate (rata-rata dari semua negara per framework per year) ----
|
||||
# ---- ASEAN aggregate ----
|
||||
asean_parts = []
|
||||
for fw in df_countries["framework"].unique():
|
||||
fw_df = df_countries[
|
||||
@@ -958,7 +1175,6 @@ class FoodSecurityAggregator:
|
||||
|
||||
df = check_and_dedup(df, ["country_id", "framework", "year"], context=table_name, logger=self.logger)
|
||||
|
||||
# Rank hanya di antara negara asli
|
||||
country_mask = df["country_id"] != ASEAN_COUNTRY_ID
|
||||
df.loc[country_mask, "rank_in_framework_year"] = (
|
||||
df[country_mask]
|
||||
@@ -1014,7 +1230,6 @@ class FoodSecurityAggregator:
|
||||
self.logger.info("\n" + "=" * 70)
|
||||
self.logger.info(f"STEP 4: {table_name} -> [Gold] fs_asean_gold")
|
||||
self.logger.info(" Termasuk baris ASEAN (country_id=0)")
|
||||
self.logger.info(" Filter country_name='ASEAN' untuk overview regional")
|
||||
self.logger.info("=" * 70)
|
||||
|
||||
try:
|
||||
@@ -1022,7 +1237,6 @@ class FoodSecurityAggregator:
|
||||
years = sorted(df_pillar_by_country["year"].unique())
|
||||
pillars = df_pillar_by_country["pillar_id"].unique()
|
||||
|
||||
# Precompute history per country x pillar
|
||||
history = {}
|
||||
for (c_id, p_id), grp in df_pillar_by_country.groupby(["country_id", "pillar_id"]):
|
||||
history[(c_id, p_id)] = dict(
|
||||
@@ -1031,8 +1245,6 @@ class FoodSecurityAggregator:
|
||||
|
||||
for yr in years:
|
||||
yr_df = df_pillar_by_country[df_pillar_by_country["year"] == yr]
|
||||
|
||||
# Semua negara asli untuk referensi top/bottom dalam narasi ASEAN
|
||||
country_only_yr = yr_df[yr_df["country_id"] != ASEAN_COUNTRY_ID]
|
||||
|
||||
for p_id in pillars:
|
||||
@@ -1044,11 +1256,9 @@ class FoodSecurityAggregator:
|
||||
p_name = str(p_name_row["pillar_name"])
|
||||
n_pillars = len(pillars)
|
||||
|
||||
# Ranking di antara semua pillar (gunakan skor ASEAN untuk rank antar pillar)
|
||||
asean_yr_all_pillars = yr_df[yr_df["country_id"] == ASEAN_COUNTRY_ID]
|
||||
asean_sorted = asean_yr_all_pillars.sort_values("pillar_country_score_1_100", ascending=False).reset_index(drop=True)
|
||||
|
||||
# Top/bottom di antara negara asli (untuk narasi ASEAN)
|
||||
country_pillar_yr = country_only_yr[country_only_yr["pillar_id"] == p_id]
|
||||
if not country_pillar_yr.empty:
|
||||
top_row = country_pillar_yr.loc[country_pillar_yr["pillar_country_score_1_100"].idxmax()]
|
||||
@@ -1061,7 +1271,6 @@ class FoodSecurityAggregator:
|
||||
top_country = bot_country = None
|
||||
top_score = bot_score = None
|
||||
|
||||
# Iterasi setiap baris (negara + ASEAN) pada pillar ini
|
||||
for _, row in yr_pillar_all.iterrows():
|
||||
c_id = int(row["country_id"])
|
||||
c_name = str(row["country_name"])
|
||||
@@ -1072,22 +1281,15 @@ class FoodSecurityAggregator:
|
||||
p_name_id = translate_pillar(p_name)
|
||||
is_asean = (c_id == ASEAN_COUNTRY_ID)
|
||||
|
||||
# Rank pilar ini dalam konteks yang sesuai
|
||||
if is_asean:
|
||||
# ASEAN: rank pilar ini di antara semua pilar ASEAN tahun ini
|
||||
rank_sorted = asean_sorted.reset_index(drop=True)
|
||||
p_rank = int(rank_sorted[rank_sorted["pillar_id"] == p_id].index[0]) + 1 if p_id in rank_sorted["pillar_id"].values else 0
|
||||
else:
|
||||
# Negara: rank pillar ini di antara semua pillar negara ini
|
||||
country_all_pillars = yr_df[yr_df["country_id"] == c_id].sort_values("pillar_country_score_1_100", ascending=False).reset_index(drop=True)
|
||||
p_rank = int(country_all_pillars[country_all_pillars["pillar_id"] == p_id].index[0]) + 1 if p_id in country_all_pillars["pillar_id"].values else 0
|
||||
|
||||
hist_up = {y: s for y, s in history.get((c_id, p_id), {}).items() if y <= yr}
|
||||
|
||||
# all_pillar_scores_year untuk perbandingan lintas pilar
|
||||
all_pillar_yr = yr_df[yr_df["country_id"] == c_id][["pillar_name", "pillar_country_score_1_100"]].copy()
|
||||
|
||||
# country_pillar_all untuk gap trend (hanya relevan untuk ASEAN)
|
||||
cpa = df_pillar_by_country[
|
||||
(df_pillar_by_country["pillar_id"] == p_id) &
|
||||
(df_pillar_by_country["country_id"] != ASEAN_COUNTRY_ID)
|
||||
@@ -1110,6 +1312,10 @@ class FoodSecurityAggregator:
|
||||
is_asean = is_asean,
|
||||
)
|
||||
|
||||
# Ambil kondisi dari kolom yang sudah dihitung di df_pillar_by_country
|
||||
cond_en = str(row.get("pillar_condition_en", "N/A"))
|
||||
cond_id = str(row.get("pillar_condition_id", "N/A"))
|
||||
|
||||
records.append({
|
||||
"year": yr,
|
||||
"country_id": c_id,
|
||||
@@ -1128,6 +1334,8 @@ class FoodSecurityAggregator:
|
||||
"bottom_country_id": translate_country(bot_country) if (is_asean and bot_country) else None,
|
||||
"bottom_country_score": bot_score if is_asean else None,
|
||||
"is_asean_aggregate": is_asean,
|
||||
"pillar_condition_en": cond_en,
|
||||
"pillar_condition_id": cond_id,
|
||||
"narrative_en": narrative_en,
|
||||
"narrative_id": narrative_id,
|
||||
})
|
||||
@@ -1140,6 +1348,8 @@ class FoodSecurityAggregator:
|
||||
df["is_asean_aggregate"] = df["is_asean_aggregate"].astype(bool)
|
||||
df["pillar_name_id"] = df["pillar_name_id"].astype(str)
|
||||
df["country_name_id"] = df["country_name_id"].astype(str)
|
||||
df["pillar_condition_en"] = df["pillar_condition_en"].astype(str)
|
||||
df["pillar_condition_id"] = df["pillar_condition_id"].astype(str)
|
||||
df["narrative_en"] = df["narrative_en"].astype(str)
|
||||
df["narrative_id"] = df["narrative_id"].astype(str)
|
||||
for col in ["pillar_score", "yoy_change", "top_country_score", "bottom_country_score"]:
|
||||
@@ -1148,10 +1358,6 @@ class FoodSecurityAggregator:
|
||||
self.logger.info(f"\n Total rows: {len(df):,}")
|
||||
self.logger.info(f" ASEAN rows: {df['is_asean_aggregate'].sum():,}")
|
||||
self.logger.info(f" Country rows: {(~df['is_asean_aggregate']).sum():,}")
|
||||
self.logger.info("\n Sample ASEAN narrative_en (first):")
|
||||
asean_sample = df[df["is_asean_aggregate"]].head(1)
|
||||
if not asean_sample.empty:
|
||||
self.logger.info(f" {asean_sample.iloc[0]['narrative_en'][:300]}")
|
||||
|
||||
schema = [
|
||||
bigquery.SchemaField("year", "INTEGER", mode="REQUIRED"),
|
||||
@@ -1171,6 +1377,8 @@ class FoodSecurityAggregator:
|
||||
bigquery.SchemaField("bottom_country_id", "STRING", mode="NULLABLE"),
|
||||
bigquery.SchemaField("bottom_country_score", "FLOAT", mode="NULLABLE"),
|
||||
bigquery.SchemaField("is_asean_aggregate", "BOOL", mode="REQUIRED"),
|
||||
bigquery.SchemaField("pillar_condition_en", "STRING", mode="REQUIRED"),
|
||||
bigquery.SchemaField("pillar_condition_id", "STRING", mode="REQUIRED"),
|
||||
bigquery.SchemaField("narrative_en", "STRING", mode="REQUIRED"),
|
||||
bigquery.SchemaField("narrative_id", "STRING", mode="REQUIRED"),
|
||||
]
|
||||
@@ -1244,11 +1452,12 @@ class FoodSecurityAggregator:
|
||||
self.logger.info("\n" + "=" * 70)
|
||||
self.logger.info("FOOD SECURITY AGGREGATION — 3 TABLES -> fs_asean_gold")
|
||||
self.logger.info(" ASEAN aggregate DIGABUNG ke tabel yang sama (country_id=0)")
|
||||
self.logger.info(" Tabel dihapus: agg_pillar_composite, agg_framework_asean,")
|
||||
self.logger.info(" agg_narrative_overview")
|
||||
self.logger.info(" Kolom baru : pillar_condition_en, pillar_condition_id")
|
||||
self.logger.info(f" Performance threshold: {PERFORMANCE_THRESHOLD}")
|
||||
self.logger.info(f" Condition tiers (GFSI 2022): >=75 Secure | >=60 Adequate |")
|
||||
self.logger.info(f" >=40 Moderate | >=20 At Risk | <20 Critical")
|
||||
self.logger.info(f" Narrative style : interpretive, plain text, bilingual EN/ID")
|
||||
self.logger.info(f" Sustainability : renamed to 'Food Other' (EN) / 'Indikator Tambahan' (ID)")
|
||||
self.logger.info(f" Sustainability : renamed to 'Food Other' / 'Indikator Tambahan'")
|
||||
self.logger.info("=" * 70)
|
||||
|
||||
self.load_data()
|
||||
@@ -1302,6 +1511,7 @@ if __name__ == "__main__":
|
||||
print(f" NORMALIZE_FRAMEWORKS_JOINTLY : {NORMALIZE_FRAMEWORKS_JOINTLY}")
|
||||
print(f" PERFORMANCE_THRESHOLD : {PERFORMANCE_THRESHOLD}")
|
||||
print(f" ASEAN_COUNTRY_ID : {ASEAN_COUNTRY_ID}")
|
||||
print(f" Condition tiers (GFSI 2022) : >=75 Secure | >=60 Adequate | >=40 Moderate | >=20 At Risk | <20 Critical")
|
||||
print("=" * 70)
|
||||
|
||||
logger = setup_logging()
|
||||
|
||||
Reference in New Issue
Block a user