ZakyF commited on
Commit
552dfdc
·
1 Parent(s): 5af9f11

update ui and use google ai

Browse files
Files changed (1) hide show
  1. app.py +84 -78
app.py CHANGED
@@ -3,135 +3,141 @@ import pandas as pd
3
  import numpy as np
4
  import gradio as gr
5
  import plotly.graph_objects as go
6
- from transformers import pipeline
7
- from huggingface_hub import InferenceClient
8
 
9
- # --- CONFIG & STYLING ---
10
- HF_TOKEN = os.getenv("HF_TOKEN")
11
- client = InferenceClient(model="mistralai/Mistral-7B-Instruct-v0.3", token=HF_TOKEN)
 
12
 
13
- # CSS Kustom untuk font dan tampilan mewah
14
  custom_css = """
15
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap');
16
- body { font-family: 'Inter', sans-serif; background-color: #f8fafc; }
17
- .gradio-container { max-width: 1200px !important; }
18
- .risk-card { padding: 20px; border-radius: 12px; border: 1px solid #e2e8f0; background: white; }
19
- .high-risk { border-left: 8px solid #ef4444; }
20
- .medium-risk { border-left: 8px solid #f59e0b; }
21
- .low-risk { border-left: 8px solid #10b981; }
 
22
  """
23
 
24
- class ArchonExecutive:
25
  def __init__(self):
26
  self.load_data()
27
- try:
28
- self.classifier = pipeline("text-classification", model="archon_v1")
29
- except:
30
- self.classifier = None
31
 
32
  def load_data(self):
33
- self.df_txn = pd.read_csv('transactions.csv', parse_dates=['date'])
34
- self.df_bal = pd.read_csv('balances_revised.csv', parse_dates=['month'])
35
- self.df_rep = pd.read_csv('repayments_revised.csv', parse_dates=['due_date'])
 
 
36
 
37
- def calculate_logic(self, customer_id):
38
- # Filter Data
39
  u_txn = self.df_txn[self.df_txn['customer_id'] == customer_id].copy()
40
  u_bal = self.df_bal[self.df_bal['customer_id'] == customer_id].sort_values('month')
41
  u_rep = self.df_rep[self.df_rep['customer_id'] == customer_id]
42
 
43
- if u_txn.empty or u_bal.empty: return None
 
44
 
45
- # --- FASE 4: DETERMINISTIC SCORING (WEIGHTED) ---
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 per variabel (0, 0.5, 1)
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
 
56
- # Rumus Bobot Manajemen (Fase 4)
57
- score = (0.3 * er_s) + (0.2 * bt_s) + (0.2 * od_s) + (0.2 * mp_s) + 0.1
58
- risk_lv = "HIGH" if score >= 0.7 else ("MEDIUM" if score >= 0.4 else "LOW")
59
 
60
- return risk_lv, score, er, u_bal, u_txn
61
 
62
- def get_llm_advice(self, risk_lv, er, cust_id, u_txn):
63
- # FASE 5: NBO ENGINE (LLM)
64
- last_txn = u_txn.iloc[-1]['raw_description'] if not u_txn.empty else "N/A"
65
-
66
- prompt = f"<s>[INST] Anda adalah AI Financial Advisor Bank. Nasabah {cust_id} memiliki risiko {risk_lv} dengan pengeluaran {er:.1%}. Transaksi terakhirnya adalah '{last_txn}'. Berikan 1 saran finansial singkat yang SANGAT NATURAL (tidak kaku), empati, dan solutif dalam Bahasa Indonesia. Jangan gunakan kata 'Nasabah'. [/INST]</s>"
67
 
 
 
 
 
 
 
 
68
  try:
69
- response = client.text_generation(prompt, max_new_tokens=150, temperature=0.7)
70
- return response.strip()
71
  except:
72
- return "Halo! Kami melihat rasio pengeluaran Anda cukup tinggi. Disarankan untuk mulai memantau pengeluaran discretionary bulan ini."
73
 
74
  def create_viz(self, u_bal, u_txn):
75
- # Grafik Tren Saldo
 
76
  fig_bal = go.Figure()
77
- fig_bal.add_trace(go.Scatter(x=u_bal['month'], y=u_bal['avg_balance'], name='Avg Balance', line=dict(color='#10b981', width=4)))
78
- fig_bal.add_trace(go.Bar(x=u_bal['month'], y=u_bal['min_balance'], name='Min Balance', marker_color='#94a3b8', opacity=0.4))
79
- fig_bal.update_layout(title="Kesehatan Saldo (Fase 6)", template="plotly_white", margin=dict(t=40, b=0, l=0, r=0))
80
 
81
- # Grafik Income vs Expense
82
- u_txn['month'] = u_txn['date'].dt.to_period('M').dt.to_timestamp()
83
- monthly = u_txn.groupby(['month', 'transaction_type'])['amount'].sum().unstack().fillna(0)
84
-
85
  fig_cf = go.Figure()
86
- fig_cf.add_trace(go.Bar(x=monthly.index, y=monthly.get('credit', 0), name='Income', marker_color='#3b82f6'))
87
- fig_cf.add_trace(go.Bar(x=monthly.index, y=monthly.get('debit', 0), name='Expense', marker_color='#f43f5e'))
88
- fig_cf.update_layout(title="Income vs Expense", barmode='group', template="plotly_white", margin=dict(t=40, b=0, l=0, r=0))
89
 
90
  return fig_bal, fig_cf
91
 
92
  # --- INTERFACE ---
93
- engine = ArchonExecutive()
94
 
95
- def analyze_and_show(cust_id):
96
- data = engine.calculate_logic(cust_id)
97
- if not data: return "## ❌ ID Tidak Ditemukan", "Mohon masukkan ID C0001 - C0120", None, None
 
98
 
99
- risk_lv, score, er, u_bal, u_txn = data
100
- advice = engine.get_llm_advice(risk_lv, er, cust_id, u_txn)
101
- v_bal, v_cf = engine.create_viz(u_bal, u_txn)
102
 
103
- color_class = "high-risk" if risk_lv == "HIGH" else ("medium-risk" if risk_lv == "MEDIUM" else "low-risk")
104
 
105
- report_md = f"""
106
- <div class="risk-card {color_class}">
107
- <h3>🛡️ Hasil Analisis: {risk_lv}</h3>
108
  <p><b>Risk Score:</b> {score:.2f} | <b>Expense Ratio:</b> {er:.1%}</p>
109
- <hr>
110
- <p><i>Sistem mendeteksi aktivitas keuangan nasabah memerlukan perhatian khusus pada manajemen saldo harian.</i></p>
111
  </div>
112
  """
113
- return report_md, advice, v_bal, v_cf
114
 
115
- with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
116
- gr.Markdown("# 🏦 ARCHON: Financial Intelligence Dashboard")
117
- gr.Markdown("Transformasi data transaksi menjadi insight prediktif dan aksi nyata.")
118
-
119
- with gr.Row():
 
120
  with gr.Column(scale=1):
121
- input_id = gr.Textbox(label="Customer ID", placeholder="e.g. C0005")
122
- btn = gr.Button("RUN ANALYSIS", variant="primary")
123
- report_out = gr.HTML()
124
 
125
  with gr.Column(scale=2):
126
  with gr.Tabs():
127
- with gr.TabItem("Balance Trend"):
128
- plot_bal = gr.Plot()
129
- with gr.TabItem("Cashflow Insight"):
130
  plot_cf = gr.Plot()
131
-
132
- with gr.Row():
133
- advice_out = gr.Textbox(label="Archon AI Advice (Pilar 3 & 5)", lines=4)
 
134
 
135
- btn.click(fn=analyze_and_show, inputs=input_id, outputs=[report_out, advice_out, plot_bal, plot_cf])
136
 
137
  demo.launch()
 
3
  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():
134
+ with gr.TabItem("Analisis Arus Kas"):
 
 
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()