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

perbaiikan UI

Browse files
Files changed (1) hide show
  1. app.py +87 -67
app.py CHANGED
@@ -6,112 +6,132 @@ import plotly.graph_objects as go
6
  from transformers import pipeline
7
  from huggingface_hub import InferenceClient
8
 
9
- # --- KONFIGURASI ENGINE ---
10
  HF_TOKEN = os.getenv("HF_TOKEN")
11
  client = InferenceClient(model="mistralai/Mistral-7B-Instruct-v0.3", token=HF_TOKEN)
12
 
13
- class ArchonIndustrialEngine:
 
 
 
 
 
 
 
 
 
 
 
14
  def __init__(self):
15
- # Fase 2: AI Classifier (Automasi Kategori)
16
  try:
17
- self.classifier = pipeline("text-classification", model="archon_v1", tokenizer="archon_v1")
18
  except:
19
  self.classifier = None
20
- self.load_data()
21
 
22
  def load_data(self):
23
- # Fase 1: Data Foundation (Single Source of Truth)
24
  self.df_txn = pd.read_csv('transactions.csv', parse_dates=['date'])
25
- self.df_cust = pd.read_csv('customers.csv')
26
  self.df_bal = pd.read_csv('balances_revised.csv', parse_dates=['month'])
27
  self.df_rep = pd.read_csv('repayments_revised.csv', parse_dates=['due_date'])
28
 
29
- def analyze(self, customer_id):
30
- # 1. Validation ID
31
  u_txn = self.df_txn[self.df_txn['customer_id'] == customer_id].copy()
32
  u_bal = self.df_bal[self.df_bal['customer_id'] == customer_id].sort_values('month')
33
  u_rep = self.df_rep[self.df_rep['customer_id'] == customer_id]
34
 
35
- if u_txn.empty or u_bal.empty:
36
- return None, "ID Nasabah Tidak Ditemukan", None, None
37
 
38
- # 2. Fase 4: Risk Scoring (Logika Bobot 30/20/20/20/10)
39
  income = u_txn[u_txn['transaction_type'] == 'credit']['amount'].sum()
40
  expense = u_txn[u_txn['transaction_type'] == 'debit']['amount'].sum()
41
- er = expense / income if income > 0 else 1.1
42
 
43
- er_score = 1.0 if er > 0.8 else (0.5 if er > 0.5 else 0.0)
44
- bt_score = 1.0 if len(u_bal) >= 2 and u_bal.iloc[-1]['avg_balance'] < u_bal.iloc[-2]['avg_balance'] else 0.0
45
- od_score = 1.0 if (u_bal['min_balance'] <= 0).any() else 0.0
46
- mp_score = 1.0 if (u_rep['status'] == 'late').any() else 0.0
 
47
 
48
- final_score = (0.3 * er_score) + (0.2 * bt_score) + (0.2 * od_score) + (0.2 * mp_score) + 0.1
49
- risk_lv = "HIGH" if final_score >= 0.7 else ("MEDIUM" if final_score >= 0.4 else "LOW")
50
-
51
- # 3. Fase 6: Explainable Summary (Ringkasan Laporan)
52
- flags = []
53
- if er_score == 1: flags.append("⚠️ Pengeluaran Kritis (>80%)")
54
- if bt_score == 1: flags.append("📉 Tren Saldo Menurun")
55
- if od_score == 1: flags.append("🚫 Saldo Pernah Minus")
56
- if mp_score == 1: flags.append("❌ Riwayat Telat Bayar")
57
 
58
- summary_report = f"""
59
- ### 📋 LAPORAN RINGKAS NASABAH ({customer_id})
60
- - **Status Risiko**: {risk_lv} (Skor: {final_score:.2f})
61
- - **Cashflow**: Pemasukan Rp{income:,.0f} | Pengeluaran Rp{expense:,.0f}
62
- - **Sinyal Perilaku**: {', '.join(flags) if flags else '✅ Keuangan Stabil'}
63
- """
64
 
65
- # 4. Fase 5: Generative NBO (Dynamic Advice)
66
- prompt = f"""[INST] Anda adalah penasihat keuangan bank. Nasabah {customer_id} memiliki risiko {risk_lv} dengan rasio pengeluaran {er:.2%}.
67
- Gunakan data ini untuk memberi 1 paragraf saran yang personal, tidak kaku, dan solutif dalam Bahasa Indonesia. [/INST]"""
 
 
 
68
  try:
69
- advice = client.text_generation(prompt, max_new_tokens=150)
 
70
  except:
71
- advice = "Disarankan melakukan efisiensi pada kategori non-esensial."
72
-
73
- # 5. Fase 6: Visualizations (Income vs Expense & Balance Trend)
74
- monthly_data = u_txn.groupby(u_txn['date'].dt.to_period('M')).agg(
75
- Inflow=('amount', lambda x: x[u_txn.loc[x.index, 'transaction_type'] == 'credit'].sum()),
76
- Outflow=('amount', lambda x: x[u_txn.loc[x.index, 'transaction_type'] == 'debit'].sum())
77
- ).reset_index()
78
- monthly_data['date'] = monthly_data['date'].dt.to_timestamp()
79
 
80
- # Grafik 1: Inflow vs Outflow
81
- fig1 = go.Figure()
82
- fig1.add_trace(go.Bar(x=monthly_data['date'], y=monthly_data['Inflow'], name='Inflow', marker_color='#2ecc71'))
83
- fig1.add_trace(go.Bar(x=monthly_data['date'], y=monthly_data['Outflow'], name='Outflow', marker_color='#e74c3c'))
84
- fig1.update_layout(title="Inflow vs Outflow Bulanan", barmode='group', template='plotly_white')
 
85
 
86
- # Grafik 2: Balance Trend
87
- fig2 = go.Figure()
88
- fig2.add_trace(go.Scatter(x=u_bal['month'], y=u_bal['avg_balance'], mode='lines+markers', name='Avg Balance', line=dict(color='#3498db', width=3)))
89
- fig2.add_trace(go.Scatter(x=u_bal['month'], y=u_bal['min_balance'], mode='lines', name='Min Balance', line=dict(dash='dot', color='#f1c40f')))
90
- fig2.update_layout(title="Tren Saldo (Avg vs Min)", template='plotly_white')
 
 
 
 
 
91
 
92
- return summary_report, advice, fig1, fig2
 
93
 
94
- # --- UI INTERFACE ---
95
- engine = ArchonIndustrialEngine()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
- with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue")) as demo:
98
- gr.Markdown("# 🪙 Archon-AI: Financial Resilience Dashboard")
 
99
 
100
  with gr.Row():
101
  with gr.Column(scale=1):
102
- input_id = gr.Textbox(label="Customer ID", placeholder="C0001 - C0120")
103
- btn = gr.Button("Jalankan Analisis AI", variant="primary")
104
- report_box = gr.Markdown(label="Summary Report")
105
 
106
  with gr.Column(scale=2):
107
  with gr.Tabs():
108
- with gr.TabItem("Cashflow Analysis"):
109
- plot_cash = gr.Plot()
110
- with gr.TabItem("Balance History"):
111
  plot_bal = gr.Plot()
 
 
112
 
113
- advice_box = gr.Textbox(label="NBO (Generative AI)", lines=4)
 
114
 
115
- btn.click(fn=engine.analyze, inputs=input_id, outputs=[report_box, advice_box, plot_cash, plot_bal])
116
 
117
  demo.launch()
 
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()