add a condition for pillar

This commit is contained in:
Debby
2026-06-29 10:29:44 +07:00
parent ebc189f2a0
commit d379b2c729
+248 -38
View File
@@ -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()