ableman82 commited on
Commit
ed2f048
ยท
verified ยท
1 Parent(s): be5f414

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +55 -58
app.py CHANGED
@@ -3,109 +3,108 @@ import numpy as np
3
  import pandas as pd
4
  from sklearn.linear_model import LogisticRegression
5
  from xgboost import XGBClassifier
6
- import anthropic
7
- import os
8
  import time
9
- import spaces
10
 
11
  # ============================================
12
- # 1. ๋ชจ๋ธ ์ดˆ๊ธฐํ™” ๋ฐ ํ•™์Šต (์‹œ์ž‘ ์‹œ 1ํšŒ๋งŒ ์‹คํ–‰)
13
  # ============================================
14
- print("FDS ๋ฐ๋ชจ ์ดˆ๊ธฐํ™” ์ค‘...")
15
-
16
- # 2์„ธ๋Œ€ ๋ชจ๋ธ ํ•™์Šต
17
  def train_gen2():
18
  np.random.seed(42)
19
- normal = pd.DataFrame({'๊ธˆ์•ก': np.random.normal(100, 50, 150), '์‹œ๊ฐ„': np.random.normal(14, 4, 150), '์‹ ๊ทœ์ˆ˜์ทจ์ธ': np.random.binomial(1, 0.1, 150), '๊ธˆ์•ก๋น„์œจ': np.random.normal(1, 0.5, 150), '๋ผ๋ฒจ': 0})
20
- fraud = pd.DataFrame({'๊ธˆ์•ก': np.random.normal(500, 200, 50), '์‹œ๊ฐ„': np.random.normal(3, 2, 50), '์‹ ๊ทœ์ˆ˜์ทจ์ธ': np.random.binomial(1, 0.8, 50), '๊ธˆ์•ก๋น„์œจ': np.random.normal(10, 5, 50), '๋ผ๋ฒจ': 1})
 
21
  data = pd.concat([normal, fraud], ignore_index=True)
22
- model = LogisticRegression(random_state=42, max_iter=1000)
23
  model.fit(data.drop('๋ผ๋ฒจ', axis=1), data['๋ผ๋ฒจ'])
24
  return model
25
 
26
- # 3์„ธ๋Œ€ ๋ชจ๋ธ ํ•™์Šต
27
  def train_gen3():
28
  np.random.seed(42)
29
- normal = pd.DataFrame({'๊ธˆ์•ก': np.random.normal(100, 50, 150), '์‹œ๊ฐ„': np.random.normal(14, 4, 150), '์‹ ๊ทœ์ˆ˜์ทจ์ธ': np.random.binomial(1, 0.1, 150), '๊ธˆ์•ก๋น„์œจ': np.random.normal(1, 0.5, 150), '๋ผ๋ฒจ': 0})
30
- fraud = pd.DataFrame({'๊ธˆ์•ก': np.random.normal(500, 200, 50), '์‹œ๊ฐ„': np.random.normal(3, 2, 50), '์‹ ๊ทœ์ˆ˜์ทจ์ธ': np.random.binomial(1, 0.8, 50), '๊ธˆ์•ก๋น„์œจ': np.random.normal(10, 5, 50), '๋ผ๋ฒจ': 1})
31
  data = pd.concat([normal, fraud], ignore_index=True)
32
- model = XGBClassifier(n_estimators=50, max_depth=6, learning_rate=0.1, random_state=42)
 
33
  model.fit(data.drop('๋ผ๋ฒจ', axis=1), data['๋ผ๋ฒจ'])
34
  return model
35
 
36
  gen2_model = train_gen2()
37
  gen3_model = train_gen3()
38
- print("๋ชจ๋ธ ์ดˆ๊ธฐํ™” ์™„๋ฃŒ!")
39
 
40
  # ============================================
41
- # 2. ์„ธ๋Œ€๋ณ„ ๋ถ„์„ ๋กœ์ง
42
  # ============================================
43
- @spaces.GPU # <--- ์ด ์ค„์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”! (Hugging Face ๊ฒ€์‚ฌ ํ†ต๊ณผ์šฉ)
44
  def analyze_transaction(amount, hour, new_payee, ratio):
45
  start_time = time.time()
46
  new_payee_bin = 1 if new_payee == "์˜ˆ" else 0
47
  input_df = pd.DataFrame({'๊ธˆ์•ก': [amount], '์‹œ๊ฐ„': [hour], '์‹ ๊ทœ์ˆ˜์ทจ์ธ': [new_payee_bin], '๊ธˆ์•ก๋น„์œจ': [ratio]})
48
 
49
- # 1์„ธ๋Œ€ (Rule-based)
50
  score = 0
51
  reasons = []
52
- if amount >= 500: score += 40; reasons.append("๊ณ ์•ก ๊ฑฐ๋ž˜")
53
- if hour >= 22 or hour <= 6: score += 30; reasons.append("์ƒˆ๋ฒฝ ์‹œ๊ฐ„๋Œ€")
54
- if new_payee == "์˜ˆ": score += 20; reasons.append("์‹ ๊ทœ ์ˆ˜์ทจ์ธ")
55
- if ratio >= 5: score += 10; reasons.append("ํ‰์†Œ ๋Œ€๋น„ 5๋ฐฐ ์ด์ƒ")
56
  dec_1 = "์ฐจ๋‹จ" if score >= 70 else "์ถ”๊ฐ€ ์ธ์ฆ" if score >= 40 else "ํ†ต๊ณผ"
57
- res_1 = f"**{dec_1}** ({score}% ์˜์‹ฌ) | ์‚ฌ์œ : {', '.join(reasons) if reasons else '์ •์ƒ'}"
58
 
59
- # 2์„ธ๋Œ€ (Logistic)
60
  prob_2 = gen2_model.predict_proba(input_df)[0][1]
61
  dec_2 = "์ฐจ๋‹จ" if prob_2 >= 0.7 else "์ถ”๊ฐ€ ์ธ์ฆ" if prob_2 >= 0.5 else "ํ†ต๊ณผ"
62
- res_2 = f"**{dec_2}** ({prob_2*100:.1f}% ์˜์‹ฌ) | ์„ ํ˜• ๊ฐ€์ค‘์น˜ ํ•ฉ์‚ฐ"
63
 
64
- # 3์„ธ๋Œ€ (XGBoost)
65
  prob_3 = gen3_model.predict_proba(input_df)[0][1]
66
  dec_3 = "์ฐจ๋‹จ" if prob_3 >= 0.7 else "์ถ”๊ฐ€ ์ธ์ฆ" if prob_3 >= 0.5 else "ํ†ต๊ณผ"
67
- res_3 = f"**{dec_3}** ({prob_3*100:.1f}% ์˜์‹ฌ) | ๋น„์„ ํ˜• ์•™์ƒ๋ธ” ์กฐํ•ฉ ํƒ์ง€"
68
 
69
- # 4์„ธ๋Œ€ (GNN ์‹œ๋ฎฌ๋ ˆ์ด์…˜)
70
  prob_4 = min(prob_3 + 0.05, 0.98) if (new_payee_bin == 1 and hour >= 22 and amount >= 500) else prob_3 + 0.02
71
  dec_4 = "์ฐจ๋‹จ" if prob_4 >= 0.7 else "์ถ”๊ฐ€ ์ธ์ฆ" if prob_4 >= 0.5 else "ํ†ต๊ณผ"
72
- res_4 = f"**{dec_4}** ({prob_4*100:.1f}% ์˜์‹ฌ) | ์ˆ˜์ทจ์ธ ์—ฐ๊ฒฐ๋ง ๋ฐ ๊ณผ๊ฑฐ ์ด๋ ฅ ์ถ”์ (์‹œ๋ฎฌ๋ ˆ์ด์…˜)"
73
 
74
- # 5์„ธ๋Œ€ (LLM ์‹œ๋ฎฌ๋ ˆ์ด์…˜ - ์‹œ์—ฐ ์•ˆ์ •์„ฑ์„ ์œ„ํ•ด API ํ˜ธ์ถœ ์ œ์™ธ ๋ฐ ์ฆ‰๊ฐ ์‘๋‹ต)
75
- res_5 = f"""**์ฐจ๋‹จ ๊ถŒ๊ณ ** (์˜์‹ฌ๋„ 95%)
76
- ๐Ÿ‘‰ [LLM ์ถ”๋ก ] ์ด ๊ฑฐ๋ž˜๋Š” ์ „ํ˜•์ ์ธ ๋ณด์ด์Šคํ”ผ์‹ฑ ํŒจํ„ด์ž…๋‹ˆ๋‹ค. {hour}์‹œ ์ƒˆ๋ฒฝ ์‹œ๊ฐ„๋Œ€์— ํ‰์†Œ๋ณด๋‹ค {ratio}๋ฐฐ ํฐ {amount}๋งŒ์›์˜ ๊ฑฐ์•ก์ด ์‹ ๊ทœ ์ˆ˜์ทจ์ธ์—๊ฒŒ ์ด์ฒด๋˜๋Š” ๊ฒƒ์€ ๊ทนํžˆ ๋น„์ •์ƒ์ ์ž…๋‹ˆ๋‹ค. ์ฆ‰์‹œ ๊ณ ๊ฐ ๋ณธ์ธ ํ™•์ธ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค."""
 
 
 
77
 
78
  elapsed = time.time() - start_time
79
 
80
- # ์‹ค๋ฌด์ž ๋ณด๊ธฐ ํŽธํ•˜๊ฒŒ ๋งˆํฌ๋‹ค์šด ํฌ๋งท์œผ๋กœ ๋ฆฌํฌํŠธ ์ƒ์„ฑ
81
- report = f"""## ๐Ÿ“Š ์„ธ๋Œ€๋ณ„ ํƒ์ง€ ๊ฒฐ๊ณผ ํ†ตํ•ฉ ๋ฆฌํฌํŠธ (๋ถ„์„ ์†Œ์š” ์‹œ๊ฐ„: {elapsed:.2f}์ดˆ)
82
-
83
- | FDS ์„ธ๋Œ€ | ํŒ๋‹จ ๊ธฐ์ˆ  | ๋ถ„์„ ๊ฒฐ๊ณผ |
84
- | :--- | :--- | :--- |
85
- | **1์„ธ๋Œ€** | ๋ฃฐ ์—”์ง„ (Rule-Based) | {res_1} |
86
- | **2์„ธ๋Œ€** | ํ†ต๊ณ„ ๋จธ์‹ ๋Ÿฌ๋‹ (Logistic) | {res_2} |
87
- | **3์„ธ๋Œ€** | ์•™์ƒ๋ธ” (XGBoost) | {res_3} |
88
- | **4์„ธ๋Œ€** | ๋”ฅ๋Ÿฌ๋‹ (GNN) | {res_4} |
89
-
90
- ### ๐Ÿค– 5์„ธ๋Œ€ (Foundation Model / LLM) ์‹ฌ์ธต ๋ถ„์„
91
- {res_5}
92
- """
93
- return report
 
 
 
 
94
 
95
  # ============================================
96
- # 3. UI ๊ตฌ์„ฑ (Gradio 5.x ํ‘œ์ค€)
97
  # ============================================
98
  with gr.Blocks(theme=gr.themes.Default()) as demo:
99
- gr.Markdown("# ๐Ÿ›ก๏ธ FDS 1์„ธ๋Œ€ โ†’ 5์„ธ๋Œ€ ์ง„ํ™” ์ฒดํ—˜ ๋ฐ๋ชจ")
100
- gr.Markdown("์‹ค๋ฌด ์šด์˜ ๋‹ด๋‹น์ž ์‹œ์—ฐ์„ ์œ„ํ•œ ๋ฐ๋ชจ ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค. ์ขŒ์ธก์— ๊ฑฐ๋ž˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ์„ธ๋Œ€๋ณ„ ํŒ๋‹จ ๊ฒฐ๊ณผ๋ฅผ ๋น„๊ตํ•ด ๋ณด์„ธ์š”.")
101
 
102
  with gr.Row():
103
  with gr.Column(scale=1):
104
- amount_in = gr.Number(label="์ด์ฒด ๊ธˆ์•ก (๋งŒ์›)", value=700)
105
  hour_in = gr.Slider(label="๊ฑฐ๋ž˜ ์‹œ๊ฐ„ (0-23์‹œ)", minimum=0, maximum=23, value=3, step=1)
106
- payee_in = gr.Radio(label="์‹ ๊ทœ ์ˆ˜์ทจ์ธ ์—ฌ๋ถ€", choices=["์•„๋‹ˆ์˜ค", "์˜ˆ"], value="์˜ˆ")
107
- ratio_in = gr.Number(label="๊ณผ๊ฑฐ ํ‰๊ท  ์ด์ฒด์•ก ๋Œ€๋น„ ๋ฐฐ์œจ", value=14.0)
108
-
109
  submit_btn = gr.Button("๐Ÿ” ๋ถ„์„ ์‹คํ–‰", variant="primary")
110
 
111
  gr.Examples(
@@ -115,19 +114,17 @@ with gr.Blocks(theme=gr.themes.Default()) as demo:
115
  [45, 2, "์•„๋‹ˆ์˜ค", 1.2],
116
  [1500, 14, "์•„๋‹ˆ์˜ค", 2.0]
117
  ],
118
- inputs=[amount_in, hour_in, payee_in, ratio_in],
119
- label="ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค ์„ ํƒ"
120
  )
121
 
122
  with gr.Column(scale=2):
123
- output_report = gr.Markdown("์ด๊ณณ์— ๋ถ„์„ ๋ฆฌํฌํŠธ๊ฐ€ ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค.")
124
 
125
  submit_btn.click(
126
  fn=analyze_transaction,
127
  inputs=[amount_in, hour_in, payee_in, ratio_in],
128
- outputs=output_report
129
  )
130
 
131
  if __name__ == "__main__":
132
- # ์—ฌ๊ธฐ์— ssr_mode=False ๋ฅผ ๋„ฃ์–ด์ค๋‹ˆ๋‹ค.
133
  demo.launch(ssr_mode=False)
 
3
  import pandas as pd
4
  from sklearn.linear_model import LogisticRegression
5
  from xgboost import XGBClassifier
 
 
6
  import time
 
7
 
8
  # ============================================
9
+ # 1. ์ดˆ๊ณ ์† ๋ชจ๋ธ ์ดˆ๊ธฐํ™” (๋ฐ์ดํ„ฐ ์ตœ์†Œํ™”, GPU ๋ฏธ์‚ฌ์šฉ)
10
  # ============================================
 
 
 
11
  def train_gen2():
12
  np.random.seed(42)
13
+ # ํ•™์Šต ๋ฐ์ดํ„ฐ ๊ทน๋‹จ์  ์ตœ์†Œํ™” (์ •์ƒ 20๊ฑด, ์‚ฌ๊ธฐ 5๊ฑด)
14
+ normal = pd.DataFrame({'๊ธˆ์•ก': np.random.normal(100, 50, 20), '์‹œ๊ฐ„': np.random.normal(14, 4, 20), '์‹ ๊ทœ์ˆ˜์ทจ์ธ': np.random.binomial(1, 0.1, 20), '๊ธˆ์•ก๋น„์œจ': np.random.normal(1, 0.5, 20), '๋ผ๋ฒจ': 0})
15
+ fraud = pd.DataFrame({'๊ธˆ์•ก': np.random.normal(500, 200, 5), '์‹œ๊ฐ„': np.random.normal(3, 2, 5), '์‹ ๊ทœ์ˆ˜์ทจ์ธ': np.random.binomial(1, 0.8, 5), '๊ธˆ์•ก๋น„์œจ': np.random.normal(10, 5, 5), '๋ผ๋ฒจ': 1})
16
  data = pd.concat([normal, fraud], ignore_index=True)
17
+ model = LogisticRegression(random_state=42, max_iter=100)
18
  model.fit(data.drop('๋ผ๋ฒจ', axis=1), data['๋ผ๋ฒจ'])
19
  return model
20
 
 
21
  def train_gen3():
22
  np.random.seed(42)
23
+ normal = pd.DataFrame({'๊ธˆ์•ก': np.random.normal(100, 50, 20), '์‹œ๊ฐ„': np.random.normal(14, 4, 20), '์‹ ๊ทœ์ˆ˜์ทจ์ธ': np.random.binomial(1, 0.1, 20), '๊ธˆ์•ก๋น„์œจ': np.random.normal(1, 0.5, 20), '๋ผ๋ฒจ': 0})
24
+ fraud = pd.DataFrame({'๊ธˆ์•ก': np.random.normal(500, 200, 5), '์‹œ๊ฐ„': np.random.normal(3, 2, 5), '์‹ ๊ทœ์ˆ˜์ทจ์ธ': np.random.binomial(1, 0.8, 5), '๊ธˆ์•ก๋น„์œจ': np.random.normal(10, 5, 5), '๋ผ๋ฒจ': 1})
25
  data = pd.concat([normal, fraud], ignore_index=True)
26
+ # XGBoost ํŠธ๋ฆฌ ๊ฐœ์ˆ˜ ๋Œ€ํญ ์ถ•์†Œ (๋ฌด๊ฑฐ์›€ ๋ฐฉ์ง€)
27
+ model = XGBClassifier(n_estimators=10, max_depth=3, learning_rate=0.1, random_state=42)
28
  model.fit(data.drop('๋ผ๋ฒจ', axis=1), data['๋ผ๋ฒจ'])
29
  return model
30
 
31
  gen2_model = train_gen2()
32
  gen3_model = train_gen3()
 
33
 
34
  # ============================================
35
+ # 2. ๋ถ„์„ ๋กœ์ง (API ์™„์ „ ์ œ๊ฑฐ -> ์ฆ‰๊ฐ ์‘๋‹ต ์‹œ๋ฎฌ๋ ˆ์ด์…˜)
36
  # ============================================
 
37
  def analyze_transaction(amount, hour, new_payee, ratio):
38
  start_time = time.time()
39
  new_payee_bin = 1 if new_payee == "์˜ˆ" else 0
40
  input_df = pd.DataFrame({'๊ธˆ์•ก': [amount], '์‹œ๊ฐ„': [hour], '์‹ ๊ทœ์ˆ˜์ทจ์ธ': [new_payee_bin], '๊ธˆ์•ก๋น„์œจ': [ratio]})
41
 
42
+ # 1์„ธ๋Œ€
43
  score = 0
44
  reasons = []
45
+ if amount >= 500: score += 40; reasons.append("๊ณ ์•ก")
46
+ if hour >= 22 or hour <= 6: score += 30; reasons.append("์ƒˆ๋ฒฝ")
47
+ if new_payee == "์˜ˆ": score += 20; reasons.append("์‹ ๊ทœ")
48
+ if ratio >= 5: score += 10; reasons.append("ํ‰์†Œ๋Œ€๋น„ ๊ธ‰์ฆ")
49
  dec_1 = "์ฐจ๋‹จ" if score >= 70 else "์ถ”๊ฐ€ ์ธ์ฆ" if score >= 40 else "ํ†ต๊ณผ"
50
+ res_1 = f"<b>{dec_1}</b> ({score}% ์˜์‹ฌ) | ์‚ฌ์œ : {', '.join(reasons) if reasons else '์ •์ƒ'}"
51
 
52
+ # 2์„ธ๋Œ€
53
  prob_2 = gen2_model.predict_proba(input_df)[0][1]
54
  dec_2 = "์ฐจ๋‹จ" if prob_2 >= 0.7 else "์ถ”๊ฐ€ ์ธ์ฆ" if prob_2 >= 0.5 else "ํ†ต๊ณผ"
55
+ res_2 = f"<b>{dec_2}</b> ({prob_2*100:.1f}% ์˜์‹ฌ)"
56
 
57
+ # 3์„ธ๋Œ€
58
  prob_3 = gen3_model.predict_proba(input_df)[0][1]
59
  dec_3 = "์ฐจ๋‹จ" if prob_3 >= 0.7 else "์ถ”๊ฐ€ ์ธ์ฆ" if prob_3 >= 0.5 else "ํ†ต๊ณผ"
60
+ res_3 = f"<b>{dec_3}</b> ({prob_3*100:.1f}% ์˜์‹ฌ)"
61
 
62
+ # 4์„ธ๋Œ€ (์‹œ๋ฎฌ๋ ˆ์ด์…˜)
63
  prob_4 = min(prob_3 + 0.05, 0.98) if (new_payee_bin == 1 and hour >= 22 and amount >= 500) else prob_3 + 0.02
64
  dec_4 = "์ฐจ๋‹จ" if prob_4 >= 0.7 else "์ถ”๊ฐ€ ์ธ์ฆ" if prob_4 >= 0.5 else "ํ†ต๊ณผ"
65
+ res_4 = f"<b>{dec_4}</b> ({prob_4*100:.1f}% ์˜์‹ฌ)"
66
 
67
+ # 5์„ธ๋Œ€ (ํ†ต์‹  ์—†์Œ, 100% ํ•˜๋“œ์ฝ”๋”ฉ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์œผ๋กœ 0.001์ดˆ ์‘๋‹ต ๋ณด์žฅ)
68
+ res_5 = f"""<div style='background-color:#fff3cd; padding:15px; border-radius:5px;'>
69
+ <b>์ฐจ๋‹จ ๊ถŒ๊ณ  (์˜์‹ฌ๋„ 95%)</b><br><br>
70
+ [LLM ์ถ”๋ก  ๋ถ„์„ ๋‚ด์šฉ]<br>
71
+ {hour}์‹œ ์ƒˆ๋ฒฝ ์‹œ๊ฐ„๋Œ€์— ํ‰์†Œ๋ณด๋‹ค {ratio}๋ฐฐ ๊ธ‰์ฆํ•œ {amount}๋งŒ์›์ด ์‹ ๊ทœ ์ˆ˜์ทจ์ธ์—๊ฒŒ ์ด์ฒด๋˜๋Š” ๊ฒƒ์€ ์ „ํ˜•์ ์ธ ๊ธˆ์œต ์‚ฌ๊ธฐ(๋ณด์ด์Šคํ”ผ์‹ฑ) ํŒจํ„ด์ž…๋‹ˆ๋‹ค. ๊ณ ๊ฐ์—๊ฒŒ ํ™•์ธ ์ „ํ™”๋ฅผ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.
72
+ </div>"""
73
 
74
  elapsed = time.time() - start_time
75
 
76
+ # HTML ํ…Œ์ด๋ธ” ํฌ๋งทํŒ…์œผ๋กœ Gradio ๋ Œ๋”๋ง ์—๋Ÿฌ ์™„๋ฒฝ ์ฐจ๋‹จ
77
+ html_report = f"""
78
+ <h3>๐Ÿ“Š ์„ธ๋Œ€๋ณ„ ํƒ์ง€ ๊ฒฐ๊ณผ (๋ถ„์„ ์†Œ์š”์‹œ๊ฐ„: {elapsed:.3f}์ดˆ)</h3>
79
+ <table border="1" style="border-collapse: collapse; width: 100%; text-align: left; font-size: 14px;">
80
+ <tr style="background-color: #f2f2f2;">
81
+ <th style="padding: 8px;">์„ธ๋Œ€</th>
82
+ <th style="padding: 8px;">ํŒ๋‹จ ๊ธฐ์ˆ </th>
83
+ <th style="padding: 8px;">๋ถ„์„ ๊ฒฐ๊ณผ</th>
84
+ </tr>
85
+ <tr><td style="padding: 8px;">1์„ธ๋Œ€</td><td style="padding: 8px;">Rule-Based</td><td style="padding: 8px;">{res_1}</td></tr>
86
+ <tr><td style="padding: 8px;">2์„ธ๋Œ€</td><td style="padding: 8px;">Logistic ML</td><td style="padding: 8px;">{res_2}</td></tr>
87
+ <tr><td style="padding: 8px;">3์„ธ๋Œ€</td><td style="padding: 8px;">XGBoost ML</td><td style="padding: 8px;">{res_3}</td></tr>
88
+ <tr><td style="padding: 8px;">4์„ธ๋Œ€</td><td style="padding: 8px;">GNN DL (์‹œ๋ฎฌ)</td><td style="padding: 8px;">{res_4}</td></tr>
89
+ </table>
90
+ <h4 style="margin-top: 20px;">๐Ÿค– 5์„ธ๋Œ€ (Foundation Model) ์‹ฌ์ธต ๋ฆฌํฌํŠธ</h4>
91
+ {res_5}
92
+ """
93
+ return html_report
94
 
95
  # ============================================
96
+ # 3. UI ๊ตฌ์„ฑ (Gradio)
97
  # ============================================
98
  with gr.Blocks(theme=gr.themes.Default()) as demo:
99
+ gr.HTML("<h1 style='text-align:center;'>๐Ÿ›ก๏ธ FDS 1-5์„ธ๋Œ€ ๋น„๊ต ๋ฐ๋ชจ</h1>")
100
+ gr.HTML("<p style='text-align:center;'>์‹ค๋ฌด ์šด์˜ ๋‹ด๋‹น์ž ์‹œ์—ฐ์šฉ (์ดˆ๊ณ ์† CPU ๋ชจ๋“œ)</p>")
101
 
102
  with gr.Row():
103
  with gr.Column(scale=1):
104
+ amount_in = gr.Number(label="๊ธˆ์•ก (๋งŒ์›)", value=700)
105
  hour_in = gr.Slider(label="๊ฑฐ๋ž˜ ์‹œ๊ฐ„ (0-23์‹œ)", minimum=0, maximum=23, value=3, step=1)
106
+ payee_in = gr.Radio(label="์‹ ๊ทœ ์ˆ˜์ทจ์ธ", choices=["์•„๋‹ˆ์˜ค", "์˜ˆ"], value="์˜ˆ")
107
+ ratio_in = gr.Number(label="๊ณผ๊ฑฐ ๋Œ€๋น„ ๋ฐฐ์œจ", value=14.0)
 
108
  submit_btn = gr.Button("๐Ÿ” ๋ถ„์„ ์‹คํ–‰", variant="primary")
109
 
110
  gr.Examples(
 
114
  [45, 2, "์•„๋‹ˆ์˜ค", 1.2],
115
  [1500, 14, "์•„๋‹ˆ์˜ค", 2.0]
116
  ],
117
+ inputs=[amount_in, hour_in, payee_in, ratio_in]
 
118
  )
119
 
120
  with gr.Column(scale=2):
121
+ output_html = gr.HTML("<div style='padding:20px; color:#666;'>๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ์ฆ‰์‹œ ๋ถ„์„ ๊ฒฐ๊ณผ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.</div>")
122
 
123
  submit_btn.click(
124
  fn=analyze_transaction,
125
  inputs=[amount_in, hour_in, payee_in, ratio_in],
126
+ outputs=output_html
127
  )
128
 
129
  if __name__ == "__main__":
 
130
  demo.launch(ssr_mode=False)