ZakyF commited on
Commit
d2169e1
Β·
1 Parent(s): 9219e7a

fitur baru, visualisasi, llm, summary

Browse files
Files changed (1) hide show
  1. app.py +81 -69
app.py CHANGED
@@ -1,105 +1,117 @@
 
1
  import pandas as pd
2
  import numpy as np
3
  import gradio as gr
4
  import plotly.graph_objects as go
5
  from transformers import pipeline
6
  from huggingface_hub import InferenceClient
7
- import os
8
 
9
- # --- KONFIGURASI PILAR ---
10
- # Pastikan HF_TOKEN ada di Settings > Secrets di Hugging Face Space kamu
11
  HF_TOKEN = os.getenv("HF_TOKEN")
12
  client = InferenceClient(model="mistralai/Mistral-7B-Instruct-v0.3", token=HF_TOKEN)
13
 
14
- class ArchonFinalEngine:
15
  def __init__(self):
16
- # Fase 2: AI Classifier (BERT)
17
- self.classifier = pipeline("text-classification", model="archon_v1", tokenizer="archon_v1")
 
 
 
18
  self.load_data()
19
 
20
  def load_data(self):
21
- # Fase 1: Single Source of Truth
22
  self.df_txn = pd.read_csv('transactions.csv', parse_dates=['date'])
23
  self.df_cust = pd.read_csv('customers.csv')
24
  self.df_bal = pd.read_csv('balances_revised.csv', parse_dates=['month'])
25
  self.df_rep = pd.read_csv('repayments_revised.csv', parse_dates=['due_date'])
26
 
27
- def get_risk_analysis(self, customer_id):
28
- # --- FASE 3 & 4: DETERMINISTIC CALCULATION (Banking Standard) ---
29
- user_txn = self.df_txn[self.df_txn['customer_id'] == customer_id].copy()
30
- user_bal = self.df_bal[self.df_bal['customer_id'] == customer_id].sort_values('month')
 
31
 
32
- # 1. Expense Ratio Score (30%)
33
- income = user_txn[user_txn['transaction_type'] == 'credit']['amount'].sum()
34
- expense = user_txn[user_txn['transaction_type'] == 'debit']['amount'].sum()
35
- er = expense / income if income > 0 else 1.0
36
- er_score = 1 if er > 0.8 else (0.5 if er > 0.5 else 0)
37
 
38
- # 2. Balance Trend Score (20%)
39
- bt_score = 1 if len(user_bal) >= 2 and user_bal.iloc[-1]['avg_balance'] < user_bal.iloc[-2]['avg_balance'] else 0
 
 
40
 
41
- # 3. Overdraft Score (20%)
42
- od_score = 1 if (user_bal['min_balance'] <= 0).any() else 0
43
-
44
- # Formula Final Risk Score
45
- # risk_score = 0.3 * ER + 0.2 * BT + 0.2 * OD + 0.2 * MP + 0.1 * Vol
46
- final_score = (0.3 * er_score) + (0.2 * bt_score) + (0.2 * od_score) + 0.1
47
-
48
- risk_level = "HIGH" if final_score >= 0.7 else ("MEDIUM" if final_score >= 0.4 else "LOW")
49
 
50
- return risk_level, final_score, er, user_bal
 
51
 
52
- def generate_llm_advice(self, risk_level, er_ratio, customer_id):
53
- # --- FASE 5: GENERATIVE NBO (Action Layer) ---
54
- # Menggunakan LLM agar saran tidak kaku seperti robot
55
- prompt = f"""
56
- Role: Senior Financial Advisor di Bank Indonesia.
57
- Nasabah: {customer_id}
58
- Kondisi: Risiko {risk_level}, Rasio Pengeluaran {er_ratio:.2%}.
59
- Tugas: Berikan 1 paragraf saran yang natural, empati, dan sangat personal dalam Bahasa Indonesia.
60
- Gunakan gaya bahasa profesional tapi bersahabat. Jangan kaku.
 
 
 
61
  """
 
 
 
 
62
  try:
63
- response = client.chat_completion(messages=[{"role": "user", "content": prompt}], max_tokens=150)
64
- return response.choices[0].message.content
65
  except:
66
- return "Tetap pantau pengeluaran Anda agar kesehatan finansial terjaga."
67
 
68
- def create_plots(self, user_bal):
69
- # --- FASE 6: INSIGHT VISUALIZATION
70
- fig = go.Figure()
71
- fig.add_trace(go.Scatter(x=user_bal['month'], y=user_bal['avg_balance'], name='Avg Balance', line=dict(color='blue', width=4)))
72
- fig.add_trace(go.Bar(x=user_bal['month'], y=user_bal['min_balance'], name='Min Balance', marker_color='orange', opacity=0.6))
73
- fig.update_layout(title='Tren Saldo Bulanan', xaxis_title='Bulan', yaxis_title='Jumlah (Rp)', template='plotly_white')
74
- return fig
75
 
76
- # --- UI LOGIC ---
77
- engine = ArchonFinalEngine()
 
 
 
78
 
79
- def run_archon(cust_id):
80
- risk_lv, score, er, bal_data = engine.get_risk_analysis(cust_id)
81
- advice = engine.generate_llm_advice(risk_lv, er, cust_id)
82
- plot = engine.create_plots(bal_data)
83
-
84
- summary = {
85
- "Risk Level": risk_lv,
86
- "Risk Score": round(score, 2),
87
- "Expense Ratio": f"{round(er*100, 2)}%"
88
- }
89
- return summary, advice, plot
90
 
91
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
92
- gr.Markdown("# πŸͺ™ Archon-AI: Industrial Dashboard")
 
 
 
 
93
  with gr.Row():
94
  with gr.Column(scale=1):
95
- input_id = gr.Textbox(label="Customer ID", placeholder="C0001")
96
- btn = gr.Button("Analyze Customer", variant="primary")
97
- out_json = gr.JSON(label="Deterministic Metrics")
 
98
  with gr.Column(scale=2):
99
- out_plot = gr.Plot(label="Financial Timeline")
100
-
101
- out_advice = gr.Textbox(label="Archon Generative Advice (LLM)", lines=5)
102
-
103
- btn.click(fn=run_archon, inputs=input_id, outputs=[out_json, out_advice, out_plot])
 
 
 
 
104
 
105
  demo.launch()
 
1
+ import os
2
  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
+ # --- 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()