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

requirements

Browse files
Files changed (2) hide show
  1. app.py +81 -85
  2. requirements.txt +3 -5
app.py CHANGED
@@ -4,130 +4,125 @@ import numpy as np
4
  import gradio as gr
5
  import plotly.graph_objects as go
6
  import google.generativeai as genai
 
7
 
8
- # --- KONFIGURASI GEMINI (Ganti dari Mistral ke Gemini) ---
 
9
  GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
10
  genai.configure(api_key=GOOGLE_API_KEY)
11
- gemini_model = genai.GenerativeModel('gemini-1.5-flash')
12
 
13
- # --- CSS CUSTOM: STYLE BANK NAGARI ---
14
  custom_css = """
15
- @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
16
- body { font-family: 'Roboto', sans-serif; background-color: #f4f4f4; }
17
- .gradio-container { max-width: 1250px !important; }
18
- .nagari-header { background-color: #800000; color: white; padding: 20px; border-radius: 10px 10px 0 0; border-bottom: 4px solid #FFD700; }
19
- .risk-card { padding: 25px; border-radius: 12px; background: white; box-shadow: 0 4px 6px rgba(0,0,0,0.1); border-top: 5px solid #800000; }
20
- .risk-high { border-left: 10px solid #d32f2f; }
21
- .risk-medium { border-left: 10px solid #f9a825; }
22
- .risk-low { border-left: 10px solid #2e7d32; }
 
 
 
 
 
 
 
 
 
 
23
  """
24
 
25
- class ArchonNagariEngine:
26
  def __init__(self):
27
  self.load_data()
 
 
 
 
 
28
 
29
  def load_data(self):
30
- # Fase 1: Foundation (Cek error pada data mentah)
31
  self.df_txn = pd.read_csv('transactions.csv', parse_dates=['date']).fillna("")
32
  self.df_cust = pd.read_csv('customers.csv').fillna(0)
33
  self.df_bal = pd.read_csv('balances_revised.csv', parse_dates=['month']).fillna(0)
34
  self.df_rep = pd.read_csv('repayments_revised.csv', parse_dates=['due_date']).fillna("on_time")
35
 
36
- def run_analysis(self, customer_id):
37
- # Validasi Keberadaan Data
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 (WEIGHTED 30/20/20/20/10) ---
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.0
49
 
50
- # Scoring Rules (Fase 4)
51
  er_s = 1.0 if er > 0.8 else (0.5 if er > 0.5 else 0.0)
52
  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
53
  od_s = 1.0 if (u_bal['min_balance'] <= 0).any() else 0.0
54
  mp_s = 1.0 if (u_rep['status'] == 'late').any() else 0.0
55
- vol_s = 0.5 # Placeholder Volatility
56
-
57
- final_score = (0.3 * er_s) + (0.2 * bt_s) + (0.2 * od_s) + (0.2 * mp_s) + (0.1 * vol_s)
58
- risk_lv = "HIGH" if final_score >= 0.7 else ("MEDIUM" if final_score >= 0.4 else "LOW")
59
 
60
- return risk_lv, final_score, er, u_bal, u_txn
 
61
 
62
- def get_gemini_advice(self, risk_lv, er, cust_id, u_txn):
63
- # FASE 5: NBO DENGAN GEMINI (Sangat Natural & Tidak Template)
64
- tx_history = u_txn.tail(5)['raw_description'].tolist()
65
- context = f"Nasabah {cust_id} memiliki risiko {risk_lv}, rasio belanja {er:.1%}. Transaksi terakhir: {', '.join(tx_history)}."
66
-
 
 
 
67
  prompt = f"""
68
- Tugas: Sebagai Virtual Advisor Bank Nagari yang bijak.
69
- Data: {context}
70
- Instruksi: Berikan saran finansial yang personal, hangat, dan solutif dalam Bahasa Indonesia.
71
- Gunakan sapaan 'Bapak/Ibu'. Jangan terlihat seperti bot. Fokus pada peningkatan ketahanan finansial (Resilience).
72
- Maksimal 3 kalimat.
73
  """
74
  try:
75
- response = gemini_model.generate_content(prompt)
76
- return response.text
77
  except:
78
- return "Kami menyarankan Bapak/Ibu untuk meninjau kembali pos pengeluaran bulan ini agar saldo tetap terjaga sehat."
79
 
80
- def create_viz(self, u_bal, u_txn):
81
- # FASE 6: VISUALIZATION (BANK NAGARI STYLE)
82
- # 1. Trend Saldo
83
- fig_bal = go.Figure()
84
- fig_bal.add_trace(go.Scatter(x=u_bal['month'], y=u_bal['avg_balance'], name='Rata-rata Saldo', line=dict(color='#800000', width=4)))
85
- fig_bal.add_trace(go.Bar(x=u_bal['month'], y=u_bal['min_balance'], name='Saldo Minimum', marker_color='#FFD700', opacity=0.5))
86
- fig_bal.update_layout(title="Laporan Tren Saldo Bulanan", template="plotly_white", legend=dict(orientation="h"))
87
-
88
- # 2. Income vs Expense
89
  u_txn['m'] = u_txn['date'].dt.to_period('M').dt.to_timestamp()
90
- cf = u_txn.groupby(['m', 'transaction_type'])['amount'].sum().unstack().fillna(0)
91
- fig_cf = go.Figure()
92
- fig_cf.add_trace(go.Bar(x=cf.index, y=cf.get('credit', 0), name='Pemasukan', marker_color='#2e7d32'))
93
- fig_cf.add_trace(go.Bar(x=cf.index, y=cf.get('debit', 0), name='Pengeluaran', marker_color='#800000'))
94
- fig_cf.update_layout(title="Arus Kas Pemasukan vs Pengeluaran", barmode='group', template="plotly_white")
95
 
96
- return fig_bal, fig_cf
97
-
98
- # --- INTERFACE ---
99
- engine = ArchonNagariEngine()
100
-
101
- def run_archon(cust_id):
102
- res = engine.run_analysis(cust_id)
103
- if not res:
104
- return "### ⚠️ Data Tidak Ditemukan", "ID tidak terdaftar di sistem.", None, None
105
-
106
- risk_lv, score, er, u_bal, u_txn = res
107
- advice = engine.get_gemini_advice(risk_lv, er, cust_id, u_txn)
108
- p_bal, p_cf = engine.create_viz(u_bal, u_txn)
109
-
110
- status_cls = "risk-high" if risk_lv == "HIGH" else ("risk-medium" if risk_lv == "MEDIUM" else "risk-low")
111
-
112
- report_html = f"""
113
- <div class="risk-card {status_cls}">
114
- <h2 style="color: #800000; margin-top:0;">📋 Ringkasan Risiko: {risk_lv}</h2>
115
- <p><b>Risk Score:</b> {score:.2f} | <b>Expense Ratio:</b> {er:.1%}</p>
116
- <p style="font-size: 0.9em; color: #666;">Berdasarkan bobot parameter Fase 4: Pengeluaran, Saldo, dan Riwayat Cicilan.</p>
117
- </div>
118
- """
119
- return report_html, advice, p_bal, p_cf
120
 
121
  with gr.Blocks(css=custom_css) as demo:
122
- with gr.Div(elem_classes="nagari-header"):
123
- gr.Markdown("# ARCHON-AI: FINANCIAL RESILIENCE ENGINE")
124
- gr.Markdown("Pusat Intelijen Manajemen Risiko & Perilaku Nasabah")
125
 
126
- with gr.Row(variant="panel"):
127
  with gr.Column(scale=1):
128
- id_input = gr.Textbox(label="Customer ID", placeholder="e.g., C0014")
129
- btn = gr.Button("PROSES ANALISIS", variant="primary")
130
- out_report = gr.HTML()
 
131
 
132
  with gr.Column(scale=2):
133
  with gr.Tabs():
@@ -135,9 +130,10 @@ with gr.Blocks(css=custom_css) as demo:
135
  plot_cf = gr.Plot()
136
  with gr.TabItem("Tren Saldo"):
137
  plot_bal = gr.Plot()
 
 
 
138
 
139
- out_advice = gr.Textbox(label="Saran AI Virtual Advisor (Powered by Gemini)", lines=4)
140
-
141
- btn.click(fn=run_archon, inputs=id_input, outputs=[out_report, out_advice, plot_bal, plot_cf])
142
 
143
  demo.launch()
 
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
71
  mp_s = 1.0 if (u_rep['status'] == 'late').any() else 0.0
 
 
 
 
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():
 
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()
requirements.txt CHANGED
@@ -1,9 +1,7 @@
1
- transformers
2
- torch
3
- huggingface_hub
4
  gradio
5
  pandas
6
  numpy
7
- accelerate
8
  plotly
9
- google-generativeai
 
 
1
+ google-generativeai
 
 
2
  gradio
3
  pandas
4
  numpy
 
5
  plotly
6
+ transformers
7
+ torch