From f738eacbfbe216f56618422ff01e875a0fda8d35 Mon Sep 17 00:00:00 2001 From: Debby Date: Mon, 29 Jun 2026 18:16:08 +0700 Subject: [PATCH] add condition desk --- scripts/bigquery_aggregate_layer.py | 147 ++++++++++++++++------------ 1 file changed, 86 insertions(+), 61 deletions(-) diff --git a/scripts/bigquery_aggregate_layer.py b/scripts/bigquery_aggregate_layer.py index 775a339..66d4653 100644 --- a/scripts/bigquery_aggregate_layer.py +++ b/scripts/bigquery_aggregate_layer.py @@ -303,7 +303,7 @@ _PILLAR_CONTEXT_FALLBACK: dict = { def get_pillar_condition(pillar_name: str, score: float) -> tuple: """ - Mengembalikan (condition_en, condition_id) berdasarkan skor dan nama pilar. + Mengembalikan 4-tuple kondisi pilar berdasarkan skor dan nama pilar. Tier mengacu GFSI 2022 (Economist Impact) + IPC Phase Classification (2019): >= 75 -> Secure / Aman @@ -320,10 +320,15 @@ def get_pillar_condition(pillar_name: str, score: float) -> tuple: score : Skor ternormalisasi skala 1-100. Returns: - Tuple (condition_en: str, condition_id: str) + Tuple ( + condition_en : str — label tier bahasa Inggris, e.g. "At Risk" + condition_id : str — label tier bahasa Indonesia, e.g. "Berisiko" + condition_desc_en : str — deskripsi kontekstual bahasa Inggris + condition_desc_id : str — deskripsi kontekstual bahasa Indonesia + ) """ if score is None or (isinstance(score, float) and np.isnan(score)): - return ("N/A", "N/A") + return ("N/A", "N/A", "N/A", "N/A") # Tentukan tier tier_label_en = _CONDITION_TIERS[-1][1] # default: Critical @@ -334,7 +339,7 @@ def get_pillar_condition(pillar_name: str, score: float) -> tuple: tier_label_id = lbl_id break - # Ambil konteks per pilar + # Ambil deskripsi kontekstual per pilar ctx = _PILLAR_CONTEXT.get(pillar_name, None) if ctx: ctx_en, ctx_id = ctx.get( @@ -344,10 +349,7 @@ def get_pillar_condition(pillar_name: str, score: float) -> tuple: 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 + return tier_label_en, tier_label_id, ctx_en, ctx_id # ============================================================================= @@ -938,11 +940,17 @@ class FoodSecurityAggregator: df["pillar_country_score_1_100"] = global_minmax(df["pillar_country_norm"]) # --------------------------------------------------------------- - # TAMBAHAN: kolom kondisi pilar + # TAMBAHAN: kolom kondisi pilar (tier + deskripsi dipisah) # 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. + # + # Kolom yang dihasilkan: + # pillar_condition_en — label tier EN, e.g. "At Risk" + # pillar_condition_id — label tier ID, e.g. "Berisiko" + # pillar_condition_desc_en — deskripsi kontekstual EN + # pillar_condition_desc_id — deskripsi kontekstual ID # --------------------------------------------------------------- conditions = df.apply( lambda row: get_pillar_condition( @@ -951,8 +959,10 @@ class FoodSecurityAggregator: ), axis=1 ) - df["pillar_condition_en"] = conditions.apply(lambda x: x[0]) - df["pillar_condition_id"] = conditions.apply(lambda x: x[1]) + df["pillar_condition_en"] = conditions.apply(lambda x: x[0]) + df["pillar_condition_id"] = conditions.apply(lambda x: x[1]) + df["pillar_condition_desc_en"] = conditions.apply(lambda x: x[2]) + df["pillar_condition_desc_id"] = conditions.apply(lambda x: x[3]) # Rank hanya di antara negara asli country_only = df[df["country_id"] != ASEAN_COUNTRY_ID].copy() @@ -976,8 +986,10 @@ 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) + df["pillar_condition_en"] = df["pillar_condition_en"].astype(str) + df["pillar_condition_id"] = df["pillar_condition_id"].astype(str) + df["pillar_condition_desc_en"] = df["pillar_condition_desc_en"].astype(str) + df["pillar_condition_desc_id"] = df["pillar_condition_desc_id"].astype(str) self.logger.info( f" Total rows: {len(df):,} " @@ -1002,10 +1014,13 @@ 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) + # --- KOLOM KONDISI --- + # Tier label (GFSI 2022): Secure / Adequate / Moderate / At Risk / Critical bigquery.SchemaField("pillar_condition_en", "STRING", mode="REQUIRED"), bigquery.SchemaField("pillar_condition_id", "STRING", mode="REQUIRED"), + # Deskripsi kontekstual per pilar (FAO/CFS 4-pillar; IPC 2019; FAO SOFI 2024) + bigquery.SchemaField("pillar_condition_desc_en", "STRING", mode="REQUIRED"), + bigquery.SchemaField("pillar_condition_desc_id", "STRING", mode="REQUIRED"), ] rows = load_to_bigquery( self.client, df, table_name, layer='gold', @@ -1313,31 +1328,35 @@ class FoodSecurityAggregator: ) # 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")) + cond_en = str(row.get("pillar_condition_en", "N/A")) + cond_id = str(row.get("pillar_condition_id", "N/A")) + cond_desc_en = str(row.get("pillar_condition_desc_en", "N/A")) + cond_desc_id = str(row.get("pillar_condition_desc_id", "N/A")) records.append({ - "year": yr, - "country_id": c_id, - "country_name": c_name, - "country_name_id": c_name_id, - "pillar_id": int(row["pillar_id"]), - "pillar_name": p_name, - "pillar_name_id": p_name_id, - "pillar_score": round(p_score, 2), - "rank_in_year": p_rank, - "yoy_change": p_yoy_val, - "top_country": top_country if is_asean else None, - "top_country_id": translate_country(top_country) if (is_asean and top_country) else None, - "top_country_score": top_score if is_asean else None, - "bottom_country": bot_country if is_asean else None, - "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, + "year": yr, + "country_id": c_id, + "country_name": c_name, + "country_name_id": c_name_id, + "pillar_id": int(row["pillar_id"]), + "pillar_name": p_name, + "pillar_name_id": p_name_id, + "pillar_score": round(p_score, 2), + "rank_in_year": p_rank, + "yoy_change": p_yoy_val, + "top_country": top_country if is_asean else None, + "top_country_id": translate_country(top_country) if (is_asean and top_country) else None, + "top_country_score": top_score if is_asean else None, + "bottom_country": bot_country if is_asean else None, + "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, + "pillar_condition_desc_en": cond_desc_en, + "pillar_condition_desc_id": cond_desc_id, + "narrative_en": narrative_en, + "narrative_id": narrative_id, }) df = pd.DataFrame(records) @@ -1348,8 +1367,10 @@ 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["pillar_condition_en"] = df["pillar_condition_en"].astype(str) + df["pillar_condition_id"] = df["pillar_condition_id"].astype(str) + df["pillar_condition_desc_en"] = df["pillar_condition_desc_en"].astype(str) + df["pillar_condition_desc_id"] = df["pillar_condition_desc_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"]: @@ -1360,27 +1381,31 @@ class FoodSecurityAggregator: self.logger.info(f" Country rows: {(~df['is_asean_aggregate']).sum():,}") schema = [ - bigquery.SchemaField("year", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("country_id", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("country_name", "STRING", mode="REQUIRED"), - bigquery.SchemaField("country_name_id", "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("pillar_score", "FLOAT", mode="REQUIRED"), - bigquery.SchemaField("rank_in_year", "INTEGER", mode="REQUIRED"), - bigquery.SchemaField("yoy_change", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("top_country", "STRING", mode="NULLABLE"), - bigquery.SchemaField("top_country_id", "STRING", mode="NULLABLE"), - bigquery.SchemaField("top_country_score", "FLOAT", mode="NULLABLE"), - bigquery.SchemaField("bottom_country", "STRING", mode="NULLABLE"), - 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"), + bigquery.SchemaField("year", "INTEGER", mode="REQUIRED"), + bigquery.SchemaField("country_id", "INTEGER", mode="REQUIRED"), + bigquery.SchemaField("country_name", "STRING", mode="REQUIRED"), + bigquery.SchemaField("country_name_id", "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("pillar_score", "FLOAT", mode="REQUIRED"), + bigquery.SchemaField("rank_in_year", "INTEGER", mode="REQUIRED"), + bigquery.SchemaField("yoy_change", "FLOAT", mode="NULLABLE"), + bigquery.SchemaField("top_country", "STRING", mode="NULLABLE"), + bigquery.SchemaField("top_country_id", "STRING", mode="NULLABLE"), + bigquery.SchemaField("top_country_score", "FLOAT", mode="NULLABLE"), + bigquery.SchemaField("bottom_country", "STRING", mode="NULLABLE"), + bigquery.SchemaField("bottom_country_id", "STRING", mode="NULLABLE"), + bigquery.SchemaField("bottom_country_score", "FLOAT", mode="NULLABLE"), + bigquery.SchemaField("is_asean_aggregate", "BOOL", mode="REQUIRED"), + # Tier label: Secure / Adequate / Moderate / At Risk / Critical + bigquery.SchemaField("pillar_condition_en", "STRING", mode="REQUIRED"), + bigquery.SchemaField("pillar_condition_id", "STRING", mode="REQUIRED"), + # Deskripsi kontekstual per pilar + bigquery.SchemaField("pillar_condition_desc_en", "STRING", mode="REQUIRED"), + bigquery.SchemaField("pillar_condition_desc_id", "STRING", mode="REQUIRED"), + bigquery.SchemaField("narrative_en", "STRING", mode="REQUIRED"), + bigquery.SchemaField("narrative_id", "STRING", mode="REQUIRED"), ] rows = load_to_bigquery( self.client, df, table_name, layer='gold',