ZakyF commited on
Commit
9d82a8d
·
1 Parent(s): d48cca8

fix runtime error

Browse files
Files changed (2) hide show
  1. app.py +70 -77
  2. requirements.txt +1 -1
app.py CHANGED
@@ -3,68 +3,50 @@ import pandas as pd
3
  import numpy as np
4
  import gradio as gr
5
  import plotly.graph_objects as go
6
- import google.generativeai as genai
7
  from transformers import pipeline
8
 
9
- # --- KONFIGURASI AI ---
10
- # Pastikan GOOGLE_API_KEY sudah ada di Secrets Hugging Face
11
  GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
12
- genai.configure(api_key=GOOGLE_API_KEY)
13
- gemini = genai.GenerativeModel('gemini-1.5-flash')
14
 
15
- # --- STYLE CSS BANK NAGARI (Marun & Emas) ---
16
  custom_css = """
17
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap');
18
- body { font-family: 'Inter', sans-serif; background-color: #f0f2f5; }
19
- .gradio-container { max-width: 1200px !important; }
20
- .nagari-card {
21
- background: white;
22
- border-radius: 15px;
23
- padding: 25px;
24
- box-shadow: 0 10px 25px rgba(0,0,0,0.05);
25
- border-top: 6px solid #800000;
26
- }
27
- .header-box {
28
- background: linear-gradient(135deg, #800000 0%, #a52a2a 100%);
29
- color: white;
30
- padding: 30px;
31
- border-radius: 15px;
32
- margin-bottom: 20px;
33
- border-bottom: 5px solid #FFD700;
34
- }
35
  """
36
 
37
- class ArchonProductionEngine:
38
  def __init__(self):
39
  self.load_data()
40
- # Pilar 1: BERT Classifier (Fase 2)
41
  try:
42
  self.classifier = pipeline("text-classification", model="archon_v1", tokenizer="archon_v1")
43
  except:
44
  self.classifier = None
45
 
46
  def load_data(self):
47
- # Fase 1: Data Foundation (Fixing C0014 error with fillna)
48
- self.df_txn = pd.read_csv('transactions.csv', parse_dates=['date']).fillna("")
49
  self.df_cust = pd.read_csv('customers.csv').fillna(0)
50
  self.df_bal = pd.read_csv('balances_revised.csv', parse_dates=['month']).fillna(0)
51
  self.df_rep = pd.read_csv('repayments_revised.csv', parse_dates=['due_date']).fillna("on_time")
52
 
53
- def analyze_customer(self, customer_id):
54
- # Filter & Validate
55
  u_txn = self.df_txn[self.df_txn['customer_id'] == customer_id].copy()
56
  u_bal = self.df_bal[self.df_bal['customer_id'] == customer_id].sort_values('month')
57
  u_rep = self.df_rep[self.df_rep['customer_id'] == customer_id]
58
 
59
  if u_txn.empty or u_bal.empty:
60
- return None, "ID Nasabah tidak ditemukan.", None, None
61
 
62
- # --- FASE 4: RISK SCORING (WEIGHTED) ---
63
  income = u_txn[u_txn['transaction_type'] == 'credit']['amount'].sum()
64
  expense = u_txn[u_txn['transaction_type'] == 'debit']['amount'].sum()
65
  er = expense / income if income > 0 else 1.2
66
 
67
- # Scoring Logic
68
  er_s = 1.0 if er > 0.8 else (0.5 if er > 0.5 else 0.0)
69
  bt_s = 1.0 if len(u_bal) >= 2 and u_bal.iloc[-1]['avg_balance'] < u_bal.iloc[-2]['avg_balance'] else 0.0
70
  od_s = 1.0 if (u_bal['min_balance'] <= 0).any() else 0.0
@@ -72,68 +54,79 @@ class ArchonProductionEngine:
72
 
73
  score = (0.3 * er_s) + (0.2 * bt_s) + (0.2 * od_s) + (0.2 * mp_s) + 0.1
74
  risk_lv = "HIGH" if score >= 0.7 else ("MEDIUM" if score >= 0.4 else "LOW")
 
 
75
 
76
- # --- FASE 6: RINGKASAN LAPORAN ---
77
- summary = f"### 📊 ANALISIS RISIKO: {risk_lv}\n"
78
- summary += f"- **Rasio Pengeluaran**: {er:.1%}\n"
79
- summary += f"- **Kesehatan Saldo**: {'⚠️ Tren Menurun' if bt_s == 1 else '✅ Tren Stabil'}\n"
80
- summary += f"- **Status Kredit**: {'⚠️ Ada Keterlambatan' if mp_s == 1 else '✅ Lancar'}"
81
-
82
- # --- FASE 5: NBO (DYNAMIC GEMINI) ---
83
- recent_desc = ", ".join(u_txn.tail(3)['raw_description'].tolist())
84
- prompt = f"""
85
- Bertindaklah sebagai Senior Advisor Bank Nagari. Nasabah {customer_id} memiliki risiko {risk_lv}.
86
- Rasio belanja terhadap gaji: {er:.2%}. Transaksi terakhir: {recent_desc}.
87
- Berikan saran finansial yang SANGAT spesifik (bukan template), hangat, dan gunakan sapaan 'Bapak/Ibu'.
88
- Berikan 1 solusi produk bank (tabungan/investasi/kredit) yang relevan. Maksimal 3 kalimat.
89
- """
90
  try:
91
- advice = gemini.generate_content(prompt).text
 
92
  except:
93
- advice = "Mohon maaf, layanan konsultasi AI sedang sibuk. Silakan hubungi Relationship Manager Anda."
94
 
95
- # --- FASE 6: VISUALISASI ---
96
- # Plot 1: Income vs Expense
97
  u_txn['m'] = u_txn['date'].dt.to_period('M').dt.to_timestamp()
98
- monthly_cf = u_txn.groupby(['m', 'transaction_type'])['amount'].sum().unstack().fillna(0)
99
-
100
  fig1 = go.Figure()
101
- fig1.add_trace(go.Bar(x=monthly_cf.index, y=monthly_cf.get('credit', 0), name='Income', marker_color='#2e7d32'))
102
- fig1.add_trace(go.Bar(x=monthly_cf.index, y=monthly_cf.get('debit', 0), name='Expense', marker_color='#800000'))
103
  fig1.update_layout(title="Inflow vs Outflow", barmode='group', template='plotly_white')
104
 
105
- # Plot 2: Balance Trend
106
  fig2 = go.Figure()
107
- fig2.add_trace(go.Scatter(x=u_bal['month'], y=u_bal['avg_balance'], mode='lines+markers', name='Avg Balance', line=dict(color='#800000', width=4)))
108
- fig2.update_layout(title="Tren Saldo Rata-rata", template='plotly_white')
109
-
110
- return summary, advice, fig1, fig2
111
-
112
- # --- UI DASHBOARD ---
113
- engine = ArchonProductionEngine()
114
-
115
- with gr.Blocks(css=custom_css) as demo:
116
- with gr.Div(elem_id="header", elem_classes="header-box"):
117
- gr.Markdown("# 🛡️ ARCHON-AI EXECUTIVE DASHBOARD")
118
- gr.Markdown("Financial Resilience Engine | Bank Nagari Edition")
119
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  with gr.Row():
121
  with gr.Column(scale=1):
122
- with gr.Div(elem_classes="nagari-card"):
123
- id_in = gr.Textbox(label="Customer ID", placeholder="C0001 - C0120")
124
- btn = gr.Button("PROSES ANALISIS", variant="primary")
125
- out_sum = gr.Markdown()
126
 
127
  with gr.Column(scale=2):
128
  with gr.Tabs():
129
- with gr.TabItem("Analisis Arus Kas"):
130
  plot_cf = gr.Plot()
131
  with gr.TabItem("Tren Saldo"):
132
  plot_bal = gr.Plot()
133
 
134
- with gr.Div(elem_classes="nagari-card", style="margin-top: 20px;"):
135
- out_adv = gr.Textbox(label="Virtual Advisor (Gemini Generative AI)", lines=4)
136
 
137
- btn.click(fn=engine.analyze_customer, inputs=id_in, outputs=[out_sum, out_adv, plot_cf, plot_bal])
138
 
139
- demo.launch()
 
 
3
  import numpy as np
4
  import gradio as gr
5
  import plotly.graph_objects as go
6
+ from google import genai
7
  from transformers import pipeline
8
 
9
+ # --- KONFIGURASI AI (SDK 2026) ---
 
10
  GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
11
+ client = genai.Client(api_key=GOOGLE_API_KEY)
 
12
 
13
+ # --- STYLE CSS BANK NAGARI ---
14
  custom_css = """
15
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap');
16
+ .gradio-container { font-family: 'Inter', sans-serif !important; background-color: #f8fafc !important; }
17
+ .nagari-header { background: linear-gradient(135deg, #800000 0%, #a52a2a 100%); color: white; padding: 25px; border-radius: 15px; border-bottom: 5px solid #FFD700; margin-bottom: 20px; }
18
+ .risk-card { background: white; border-radius: 12px; padding: 20px; box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1); border-top: 6px solid #800000; }
19
+ .advice-box { background: #fffdf0; border-left: 5px solid #FFD700; padding: 15px; border-radius: 8px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  """
21
 
22
+ class ArchonNagariEngine:
23
  def __init__(self):
24
  self.load_data()
 
25
  try:
26
  self.classifier = pipeline("text-classification", model="archon_v1", tokenizer="archon_v1")
27
  except:
28
  self.classifier = None
29
 
30
  def load_data(self):
31
+ # Fix Error C0014 dengan penanganan data kosong yang lebih agresif
32
+ self.df_txn = pd.read_csv('transactions.csv', parse_dates=['date']).fillna({"raw_description": "Transaksi Umum"})
33
  self.df_cust = pd.read_csv('customers.csv').fillna(0)
34
  self.df_bal = pd.read_csv('balances_revised.csv', parse_dates=['month']).fillna(0)
35
  self.df_rep = pd.read_csv('repayments_revised.csv', parse_dates=['due_date']).fillna("on_time")
36
 
37
+ def run_analysis(self, customer_id):
 
38
  u_txn = self.df_txn[self.df_txn['customer_id'] == customer_id].copy()
39
  u_bal = self.df_bal[self.df_bal['customer_id'] == customer_id].sort_values('month')
40
  u_rep = self.df_rep[self.df_rep['customer_id'] == customer_id]
41
 
42
  if u_txn.empty or u_bal.empty:
43
+ return None
44
 
45
+ # --- FASE 4: RISK SCORING ---
46
  income = u_txn[u_txn['transaction_type'] == 'credit']['amount'].sum()
47
  expense = u_txn[u_txn['transaction_type'] == 'debit']['amount'].sum()
48
  er = expense / income if income > 0 else 1.2
49
 
 
50
  er_s = 1.0 if er > 0.8 else (0.5 if er > 0.5 else 0.0)
51
  bt_s = 1.0 if len(u_bal) >= 2 and u_bal.iloc[-1]['avg_balance'] < u_bal.iloc[-2]['avg_balance'] else 0.0
52
  od_s = 1.0 if (u_bal['min_balance'] <= 0).any() else 0.0
 
54
 
55
  score = (0.3 * er_s) + (0.2 * bt_s) + (0.2 * od_s) + (0.2 * mp_s) + 0.1
56
  risk_lv = "HIGH" if score >= 0.7 else ("MEDIUM" if score >= 0.4 else "LOW")
57
+
58
+ return risk_lv, score, er, u_bal, u_txn
59
 
60
+ def get_gemini_advice(self, risk_lv, er, cust_id, u_txn):
61
+ # FASE 5: NBO (NEW SDK 2026)
62
+ last_txn = u_txn.tail(3)['raw_description'].tolist()
63
+ prompt = f"Berikan saran finansial hangat khas perbankan (sapa Bapak/Ibu) untuk nasabah {cust_id} dengan risiko {risk_lv} dan rasio pengeluaran {er:.1%}. Transaksi terakhir: {last_txn}. Maks 3 kalimat."
64
+
 
 
 
 
 
 
 
 
 
65
  try:
66
+ response = client.models.generate_content(model="gemini-1.5-flash", contents=prompt)
67
+ return response.text
68
  except:
69
+ return "Kami menyarankan peninjauan berkala pada pengeluaran bulanan Bapak/Ibu untuk menjaga stabilitas saldo."
70
 
71
+ def create_plots(self, u_bal, u_txn):
72
+ # Plot 1: Cashflow
73
  u_txn['m'] = u_txn['date'].dt.to_period('M').dt.to_timestamp()
74
+ cf = u_txn.groupby(['m', 'transaction_type'])['amount'].sum().unstack().fillna(0)
 
75
  fig1 = go.Figure()
76
+ fig1.add_trace(go.Bar(x=cf.index, y=cf.get('credit', 0), name='Pemasukan', marker_color='#10b981'))
77
+ fig1.add_trace(go.Bar(x=cf.index, y=cf.get('debit', 0), name='Pengeluaran', marker_color='#800000'))
78
  fig1.update_layout(title="Inflow vs Outflow", barmode='group', template='plotly_white')
79
 
80
+ # Plot 2: Balance
81
  fig2 = go.Figure()
82
+ fig2.add_trace(go.Scatter(x=u_bal['month'], y=u_bal['avg_balance'], name='Saldo Rata-rata', line=dict(color='#800000', width=4)))
83
+ fig2.update_layout(title="Tren Saldo", template='plotly_white')
84
+
85
+ return fig1, fig2
86
+
87
+ # --- UI INTERFACE ---
88
+ engine = ArchonNagariEngine()
89
+
90
+ def process(cust_id):
91
+ res = engine.run_analysis(cust_id)
92
+ if not res: return "### ⚠️ Nasabah Tidak Ditemukan", "Data tidak tersedia.", None, None
93
+
94
+ risk_lv, score, er, u_bal, u_txn = res
95
+ advice = engine.get_gemini_advice(risk_lv, er, cust_id, u_txn)
96
+ f1, f2 = engine.create_plots(u_bal, u_txn)
97
+
98
+ report_html = f"""
99
+ <div style='padding: 20px; border-radius: 10px; background: white; border-left: 10px solid {"#d32f2f" if risk_lv=="HIGH" else "#f9a825"};'>
100
+ <h2 style='color: #800000; margin: 0;'>Status: {risk_lv}</h2>
101
+ <p><b>Risk Score:</b> {score:.2f} | <b>Expense Ratio:</b> {er:.1%}</p>
102
+ </div>
103
+ """
104
+ return report_html, advice, f1, f2
105
+
106
+ with gr.Blocks() as demo:
107
+ gr.HTML("""
108
+ <div class="nagari-header">
109
+ <h1 style="margin:0;">🛡️ ARCHON-AI: BANK NAGARI EDITION</h1>
110
+ <p style="margin:0; opacity: 0.8;">Automated Financial Resilience & Risk Intelligence</p>
111
+ </div>
112
+ """)
113
+
114
  with gr.Row():
115
  with gr.Column(scale=1):
116
+ id_input = gr.Textbox(label="Customer ID", placeholder="C0001 - C0120")
117
+ btn = gr.Button("ANALYZE NOW", variant="primary")
118
+ out_report = gr.HTML()
 
119
 
120
  with gr.Column(scale=2):
121
  with gr.Tabs():
122
+ with gr.TabItem("Arus Kas"):
123
  plot_cf = gr.Plot()
124
  with gr.TabItem("Tren Saldo"):
125
  plot_bal = gr.Plot()
126
 
127
+ out_advice = gr.Textbox(label="Personalized NBO (Powered by Gemini 1.5)", lines=4, elem_classes="advice-box")
 
128
 
129
+ btn.click(fn=process, inputs=id_input, outputs=[out_report, out_advice, plot_cf, plot_bal])
130
 
131
+ # Jalankan dengan CSS di launch
132
+ demo.launch(css=custom_css)
requirements.txt CHANGED
@@ -1,4 +1,4 @@
1
- google-generativeai
2
  gradio
3
  pandas
4
  numpy
 
1
+ google-genai
2
  gradio
3
  pandas
4
  numpy