jeongkee commited on
Commit
5e1f972
ยท
verified ยท
1 Parent(s): 8be69b5

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +978 -0
app.py ADDED
@@ -0,0 +1,978 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ============================================
2
+ # ๐Ÿ“˜ Vision LLM Agentic AI ํ”„๋กœ์ ํŠธ ๊ฒฌ์  ์‹œ์Šคํ…œ
3
+ # - ๋ณด์ˆ˜์  MM ์‚ฐ์ • / ์˜คํ”ˆ์†Œ์Šค ์ค‘์‹ฌ
4
+ # - Phase๋ณ„ ์ƒ์„ธ ์‚ฌ์ด์ง•
5
+ # - PDF/Excel ๋‹ค์šด๋กœ๋“œ ๊ธฐ๋Šฅ
6
+ # - Hugging Face Spaces ์ตœ์ ํ™”
7
+ # ============================================
8
+
9
+ import sys
10
+ import os
11
+ import math
12
+ import time
13
+ import warnings
14
+ warnings.filterwarnings("ignore")
15
+
16
+ # Hugging Face Spaces๋Š” requirements.txt๋กœ ํŒจํ‚ค์ง€ ๊ด€๋ฆฌ
17
+ import gradio as gr
18
+ import pandas as pd
19
+ import plotly.graph_objects as go
20
+ from plotly.subplots import make_subplots
21
+ from fpdf import FPDF
22
+ import json
23
+ from datetime import datetime
24
+
25
+ print("โœ… ํŒจํ‚ค์ง€ ๋กœ๋“œ ์™„๋ฃŒ!\n" + "="*70)
26
+
27
+ # ============================================
28
+ # ๐Ÿ’ฐ ๊ธˆ์•ก ์‚ฐ์ถœ ๊ธฐ์ค€ (๋ช…ํ™•ํžˆ ์ •์˜)
29
+ # ============================================
30
+
31
+ CURRENCY_INFO = {
32
+ "base": "KRW",
33
+ "unit": "์–ต์›",
34
+ "display_factor": 100_000_000,
35
+ }
36
+
37
+ # ์ธ๊ฑด๋น„ (์›ํ™” ๊ธฐ์ค€)
38
+ LABOR_RATES = {
39
+ "Senior_Engineer": 18_000_000,
40
+ "Mid_Engineer": 15_000_000,
41
+ "Junior_Engineer": 12_000_000,
42
+ "Architect": 20_000_000,
43
+ "Average": 15_000_000,
44
+ }
45
+
46
+ LABOR_COST_PER_MM = LABOR_RATES["Average"]
47
+
48
+ # GPU ๋‹จ๊ฐ€ (์›ํ™” ๊ธฐ์ค€)
49
+ GPU_COSTS_KRW = {
50
+ 'A100_80GB': 4_000_000_000,
51
+ 'H100_80GB': 6_000_000_000,
52
+ 'A100_40GB': 2_500_000_000,
53
+ 'L40S': 1_500_000_000,
54
+ }
55
+
56
+ GPU_COST = {k: v / CURRENCY_INFO['display_factor'] for k, v in GPU_COSTS_KRW.items()}
57
+
58
+ # ์„œ๋ฒ„ ๋‹จ๊ฐ€
59
+ SERVER_COSTS_KRW = {
60
+ 'CPU_High_End': 50_000_000,
61
+ 'CPU_Mid_Range': 30_000_000,
62
+ 'RAM_256GB': 10_000_000,
63
+ 'RAM_512GB': 20_000_000,
64
+ }
65
+
66
+ STORAGE_COST_PER_TB = 2_000_000
67
+ OSS_SETUP_COST_PER_MM = 3_000_000
68
+
69
+ EXCHANGE_RATE_INFO = {
70
+ "USD_to_KRW": 1330,
71
+ "note": "GPU ๊ฐ€๊ฒฉ์€ ๋ฏธ๊ตญ ์‹œ์žฅ๊ฐ€๋ฅผ ํ™˜์œจ ์ ์šฉํ•˜์—ฌ ์‚ฐ์ •"
72
+ }
73
+
74
+ # ๋ณด์ˆ˜์  ์•ˆ์ „๊ณ„์ˆ˜
75
+ SAFETY_FACTOR = 1.4
76
+ GPU_EFFICIENCY = 0.65
77
+ PARALLEL_EFFICIENCY = 0.70
78
+ OSS_LABOR_MULTIPLIER = 1.5
79
+
80
+ # ============================================
81
+ # Value Chain Phase ์ •์˜
82
+ # ============================================
83
+
84
+ VALUE_CHAIN_PHASES = {
85
+ "Phase1_DataPrep": {
86
+ "name": "Phase 1: ๋ฐ์ดํ„ฐ ์ค€๋น„ ๋ฐ ๊ธฐ๋ฐ˜ ๊ตฌ์ถ•",
87
+ "description": "๋ฌธ์„œ ์ˆ˜์ง‘, ์ „์ฒ˜๋ฆฌ, ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ, ์˜จํ†จ๋กœ์ง€ ์„ค๊ณ„",
88
+ "base_mm_ratio": 0.39,
89
+ "roles": ["Data Engineer", "Data Architect", "Ontology Engineer"],
90
+ "oss_stack": [
91
+ "PyMuPDF (๋ฌธ์„œ ํŒŒ์‹ฑ)",
92
+ "OpenCV (์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ)",
93
+ "LayoutParser (ํ‘œ ์ธ์‹)",
94
+ "spaCy (NLP)",
95
+ "PostgreSQL (๋ฉ”ํƒ€DB)",
96
+ "Neo4j (๊ทธ๋ž˜ํ”„DB)",
97
+ "Apache Airflow (์›Œํฌํ”Œ๋กœ)",
98
+ "DVC (๋ฐ์ดํ„ฐ ๋ฒ„์ „๊ด€๋ฆฌ)"
99
+ ]
100
+ },
101
+ "Phase2_Training": {
102
+ "name": "Phase 2: ๋ชจ๋ธ ํ•™์Šต ๋ฐ ์ตœ์ ํ™”",
103
+ "description": "Vision LLM Fine-Tuning, Q-LoRA, Validation",
104
+ "base_mm_ratio": 0.23,
105
+ "roles": ["ML Engineer", "AI Architect", "Research Engineer"],
106
+ "oss_stack": [
107
+ "PyTorch (๋”ฅ๋Ÿฌ๋‹)",
108
+ "PEFT/LoRA (ํšจ์œจ์  ํ•™์Šต)",
109
+ "Hugging Face Transformers",
110
+ "Albumentations (์ฆ๊ฐ•)",
111
+ "InternVL 3.5 (Vision LLM)",
112
+ "Unsloth (ํ•™์Šต ๊ฐ€์†)",
113
+ "MLflow (์‹คํ—˜ ์ถ”์ )",
114
+ "Weights & Biases (๋ชจ๋‹ˆํ„ฐ๋ง)"
115
+ ]
116
+ },
117
+ "Phase3_AgenticDev": {
118
+ "name": "Phase 3: Agentic AI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ",
119
+ "description": "Agent ์„ค๊ณ„, Workflow, Tool ์—ฐ๋™, RAG",
120
+ "base_mm_ratio": 0.17,
121
+ "roles": ["AI Architect", "Backend Engineer", "ML Engineer"],
122
+ "oss_stack": [
123
+ "LangGraph (Agent ํ”„๋ ˆ์ž„์›Œํฌ)",
124
+ "LangChain (LLM ์ฒด์ธ)",
125
+ "CrewAI (Multi-Agent)",
126
+ "Qdrant (Vector DB)",
127
+ "FAISS (๋ฒกํ„ฐ ๊ฒ€์ƒ‰)",
128
+ "Redis (Memory)",
129
+ "FastAPI (API)",
130
+ "n8n (์›Œํฌํ”Œ๋กœ ์ž๋™ํ™”)"
131
+ ]
132
+ },
133
+ "Phase4_Deployment": {
134
+ "name": "Phase 4: ๋ฐฐํฌยท์šด์˜ ๋ฐ ์ง€์† ๊ฐœ์„ ",
135
+ "description": "Docker/K8s ๋ฐฐํฌ, ๋ชจ๋‹ˆํ„ฐ๋ง, ์žฌํ•™์Šต ์ž๋™ํ™”",
136
+ "base_mm_ratio": 0.21,
137
+ "roles": ["DevOps Engineer", "MLOps Engineer", "SRE"],
138
+ "oss_stack": [
139
+ "Docker (์ปจํ…Œ์ด๋„ˆ)",
140
+ "Kubernetes (์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜)",
141
+ "Prometheus (๋ฉ”ํŠธ๋ฆญ)",
142
+ "Grafana (์‹œ๊ฐํ™”)",
143
+ "ELK Stack (๋กœ๊ทธ)",
144
+ "ArgoCD (GitOps)",
145
+ "Kubeflow (ML ํŒŒ์ดํ”„๋ผ์ธ)",
146
+ "GitLab CI/CD"
147
+ ]
148
+ }
149
+ }
150
+
151
+ # ============================================
152
+ # Function Points
153
+ # ============================================
154
+
155
+ FP_COEFFICIENTS = {
156
+ "A-01": 0.00003, "A-02": 0.000015, "A-03": 0.00004, "A-04": 0.00008,
157
+ "A-05": 0.00006, "A-06": 0.00005, "A-07": 0.000055,
158
+ "B-01": 0.00004, "B-02": 0.00003, "B-03": 0.00005, "B-04": 0.00004,
159
+ "C-01": 0.00005, "C-02": 0.00007, "C-03": 0.00004, "C-04": 0.00003,
160
+ "D-01": 0.00009, "D-02": 0.00006, "D-03": 0.00015, "D-04": 0.00006, "D-05": 0.00004,
161
+ "F-01": 0.00005, "F-02": 0.00008, "F-03": 0.00007, "F-04": 0.00009, "F-05": 0.00007,
162
+ "G-01": 0.00008, "G-02": 0.00006, "G-03": 0.00004, "G-04": 0.00007,
163
+ "H-01": 0.00004, "H-02": 0.00005, "H-03": 0.00008, "H-04": 0.00003,
164
+ }
165
+
166
+ FP_TO_PHASE = {
167
+ "Phase1_DataPrep": ["A-01", "A-02", "A-03", "A-04", "A-05", "A-06", "A-07",
168
+ "B-01", "B-02", "B-03", "B-04", "C-01", "C-02", "C-03", "C-04"],
169
+ "Phase2_Training": ["D-01", "D-02", "D-03", "D-04", "D-05"],
170
+ "Phase3_AgenticDev": ["F-01", "F-02", "F-03", "F-04", "F-05"],
171
+ "Phase4_Deployment": ["G-01", "G-02", "G-03", "G-04", "H-01", "H-02", "H-03", "H-04"]
172
+ }
173
+
174
+ # ============================================
175
+ # ํ•ต์‹ฌ ๊ณ„์‚ฐ ํ•จ์ˆ˜
176
+ # ============================================
177
+
178
+ def calculate_phase_mm(phase_id, N_pages, doc_complexity, language_mix,
179
+ table_ratio, image_quality, model_size,
180
+ training_epochs, agent_complexity):
181
+ """Phase๋ณ„ MM ๊ณ„์‚ฐ"""
182
+
183
+ fps = FP_TO_PHASE.get(phase_id, [])
184
+ total_mm = 0.0
185
+ fp_details = []
186
+
187
+ if N_pages <= 10000:
188
+ scale_factor = 1.0
189
+ elif N_pages <= 100000:
190
+ scale_factor = 4.5
191
+ else:
192
+ scale_factor = 12.0
193
+
194
+ for fp in fps:
195
+ coeff = FP_COEFFICIENTS.get(fp, 0.00005)
196
+ base_mm = coeff * N_pages * scale_factor
197
+
198
+ if phase_id == "Phase1_DataPrep":
199
+ weight = (doc_complexity * language_mix *
200
+ (1 + table_ratio) * image_quality)
201
+ if fp in ["A-04", "A-05"]:
202
+ weight *= (1 + table_ratio*0.5)
203
+
204
+ elif phase_id == "Phase2_Training":
205
+ weight = model_size * (1 + training_epochs/10) * image_quality
206
+ if fp == "D-03":
207
+ weight *= 1.8
208
+
209
+ elif phase_id == "Phase3_AgenticDev":
210
+ weight = agent_complexity * doc_complexity
211
+
212
+ else:
213
+ weight = (doc_complexity + agent_complexity) / 2
214
+
215
+ mm = base_mm * weight * OSS_LABOR_MULTIPLIER * SAFETY_FACTOR
216
+ total_mm += mm
217
+
218
+ fp_details.append({
219
+ "FP": fp,
220
+ "Base_MM": round(base_mm, 3),
221
+ "Weight": round(weight, 2),
222
+ "Final_MM": round(mm, 2)
223
+ })
224
+
225
+ return total_mm, fp_details
226
+
227
+ def calculate_hardware(phase_id, N_pages, model_size, training_epochs,
228
+ deployment_type, sla_level):
229
+ """Phase๋ณ„ HW ์‚ฌ์ด์ง•"""
230
+
231
+ hw = {}
232
+
233
+ if phase_id == "Phase1_DataPrep":
234
+ base_gpu = max(2, math.ceil(N_pages / 5000 * SAFETY_FACTOR))
235
+ hw["Preprocess_GPU"] = min(base_gpu, 8)
236
+ hw["GPU_Type"] = "L40S"
237
+ hw["CPU_Cores"] = 16 * hw["Preprocess_GPU"]
238
+ hw["RAM_GB"] = 64 * hw["Preprocess_GPU"]
239
+ hw["Storage_TB"] = round(max(10, N_pages * 0.5 / 1000000), 1)
240
+
241
+ elif phase_id == "Phase2_Training":
242
+ base_gpu = max(4, math.ceil(N_pages * training_epochs / 3000 * model_size * SAFETY_FACTOR))
243
+ hw["Training_GPU"] = base_gpu
244
+ hw["GPU_Type"] = "A100_80GB"
245
+ hw["CPU_Cores"] = 32 * hw["Training_GPU"]
246
+ hw["RAM_GB"] = 128 * hw["Training_GPU"]
247
+ hw["Storage_TB"] = round(max(50, N_pages * training_epochs * 2.0 / 1000000), 1)
248
+
249
+ elif phase_id == "Phase3_AgenticDev":
250
+ hw["Dev_GPU"] = max(2, math.ceil(model_size * 2))
251
+ hw["GPU_Type"] = "A100_40GB"
252
+ hw["VectorDB_Instances"] = max(2, math.ceil(N_pages / 100000))
253
+ hw["CPU_Cores"] = 32
254
+ hw["RAM_GB"] = 256
255
+ hw["Storage_TB"] = round(max(20, N_pages * 1.0 / 1000000), 1)
256
+
257
+ else:
258
+ qps_estimate = N_pages / 10000
259
+ base_gpu = max(2, math.ceil(qps_estimate * sla_level * SAFETY_FACTOR))
260
+ hw["Inference_GPU"] = base_gpu
261
+ hw["GPU_Type"] = "A100_80GB"
262
+ hw["K8s_Nodes"] = max(3, math.ceil(base_gpu / 2))
263
+ hw["CPU_Cores"] = 64
264
+ hw["RAM_GB"] = 512
265
+ hw["Storage_TB"] = round(max(100, N_pages * 2.0 / 1000000), 1)
266
+
267
+ return hw
268
+
269
+ def estimate_cost(hw, mm, phase_id):
270
+ """๋น„์šฉ ์‚ฐ์ •"""
271
+
272
+ labor_cost_krw = mm * LABOR_COST_PER_MM
273
+ labor_cost = labor_cost_krw / CURRENCY_INFO['display_factor']
274
+
275
+ gpu_cost = 0
276
+ if "Training_GPU" in hw:
277
+ gpu_type = hw.get("GPU_Type", "A100_80GB")
278
+ gpu_cost = hw["Training_GPU"] * GPU_COST.get(gpu_type, 40.0)
279
+ elif "Preprocess_GPU" in hw:
280
+ gpu_type = hw.get("GPU_Type", "L40S")
281
+ gpu_cost = hw["Preprocess_GPU"] * GPU_COST.get(gpu_type, 15.0)
282
+ elif "Inference_GPU" in hw:
283
+ gpu_type = hw.get("GPU_Type", "A100_80GB")
284
+ gpu_cost = hw["Inference_GPU"] * GPU_COST.get(gpu_type, 40.0)
285
+ elif "Dev_GPU" in hw:
286
+ gpu_type = hw.get("GPU_Type", "A100_40GB")
287
+ gpu_cost = hw["Dev_GPU"] * GPU_COST.get(gpu_type, 25.0)
288
+
289
+ server_cost_krw = 0
290
+ if hw.get("CPU_Cores", 0) >= 32:
291
+ server_cost_krw += SERVER_COSTS_KRW['CPU_High_End']
292
+ elif hw.get("CPU_Cores", 0) >= 16:
293
+ server_cost_krw += SERVER_COSTS_KRW['CPU_Mid_Range']
294
+
295
+ if hw.get("RAM_GB", 0) >= 512:
296
+ server_cost_krw += SERVER_COSTS_KRW['RAM_512GB']
297
+ elif hw.get("RAM_GB", 0) >= 256:
298
+ server_cost_krw += SERVER_COSTS_KRW['RAM_256GB']
299
+
300
+ server_cost = server_cost_krw / CURRENCY_INFO['display_factor']
301
+
302
+ storage_cost_krw = 0
303
+ if "Storage_TB" in hw:
304
+ storage_cost_krw = hw["Storage_TB"] * STORAGE_COST_PER_TB
305
+ storage_cost = storage_cost_krw / CURRENCY_INFO['display_factor']
306
+
307
+ oss_setup_cost_krw = mm * OSS_SETUP_COST_PER_MM
308
+ oss_setup_cost = oss_setup_cost_krw / CURRENCY_INFO['display_factor']
309
+
310
+ hw_total = gpu_cost + server_cost + storage_cost
311
+ infra_cost = hw_total * 0.3
312
+
313
+ total_cost = labor_cost + gpu_cost + server_cost + storage_cost + oss_setup_cost + infra_cost
314
+
315
+ return {
316
+ "Labor": round(labor_cost, 1),
317
+ "GPU": round(gpu_cost, 1),
318
+ "Server": round(server_cost, 1),
319
+ "Storage": round(storage_cost, 1),
320
+ "OSS_Setup": round(oss_setup_cost, 1),
321
+ "Infrastructure": round(infra_cost, 1),
322
+ "Total": round(total_cost, 1),
323
+ }
324
+
325
+ # ============================================
326
+ # ๋ฉ”์ธ ๊ฒฌ์  ํ•จ์ˆ˜
327
+ # ============================================
328
+
329
+ def run_estimation(N_pages, doc_complexity, language_mix, table_ratio,
330
+ image_quality, model_size, training_epochs,
331
+ agent_complexity, deployment_type, sla_level,
332
+ security_level):
333
+ """์ „์ฒด ๊ฒฌ์  ์‹คํ–‰"""
334
+
335
+ try:
336
+ if N_pages < 1000:
337
+ return ("โŒ ๋ฌธ์„œ ์ˆ˜๋Š” ์ตœ์†Œ 1,000์žฅ ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.",) + (None,) * 7
338
+
339
+ results = {}
340
+ total_mm = 0
341
+
342
+ phase_ids = ["Phase1_DataPrep", "Phase2_Training", "Phase3_AgenticDev", "Phase4_Deployment"]
343
+
344
+ for phase_id in phase_ids:
345
+ phase_info = VALUE_CHAIN_PHASES[phase_id]
346
+
347
+ mm, fp_details = calculate_phase_mm(
348
+ phase_id, N_pages, doc_complexity, language_mix,
349
+ table_ratio/100, image_quality, model_size,
350
+ training_epochs, agent_complexity
351
+ )
352
+
353
+ if phase_id == "Phase4_Deployment":
354
+ mm *= (sla_level * security_level)
355
+
356
+ mm *= deployment_type
357
+ total_mm += mm
358
+
359
+ hw = calculate_hardware(
360
+ phase_id, N_pages, model_size, training_epochs,
361
+ deployment_type, sla_level
362
+ )
363
+
364
+ cost = estimate_cost(hw, mm, phase_id)
365
+
366
+ results[phase_id] = {
367
+ "name": phase_info["name"],
368
+ "mm": round(mm, 1),
369
+ "hw": hw,
370
+ "cost": cost,
371
+ "fp_details": fp_details,
372
+ "oss_stack": phase_info["oss_stack"]
373
+ }
374
+
375
+ duration_months = (
376
+ results["Phase1_DataPrep"]["mm"] / 8 +
377
+ max(results["Phase2_Training"]["mm"] / 10,
378
+ results["Phase3_AgenticDev"]["mm"] / 6) * PARALLEL_EFFICIENCY +
379
+ results["Phase4_Deployment"]["mm"] / 8
380
+ )
381
+ duration_months = math.ceil(duration_months * 1.2)
382
+
383
+ summary = generate_summary(results, total_mm, duration_months, N_pages)
384
+ phase_chart = create_phase_chart(results)
385
+ cost_chart = create_cost_breakdown(results)
386
+ timeline_chart = create_timeline(results, duration_months)
387
+ hw_table = create_hw_table(results)
388
+ oss_table = create_oss_table(results)
389
+
390
+ pdf_path = generate_pdf(results, total_mm, duration_months, N_pages)
391
+ excel_path = generate_excel(results, total_mm, duration_months, N_pages)
392
+
393
+ return summary, phase_chart, cost_chart, timeline_chart, hw_table, oss_table, pdf_path, excel_path
394
+
395
+ except Exception as e:
396
+ import traceback
397
+ error_detail = traceback.format_exc()
398
+ error_msg = f"โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}\n\n์ƒ์„ธ:\n{error_detail}"
399
+ return (error_msg,) + (None,) * 7
400
+
401
+ def generate_summary(results, total_mm, duration_months, N_pages):
402
+ """์š”์•ฝ ์ƒ์„ฑ"""
403
+
404
+ total_cost = sum(r["cost"]["Total"] for r in results.values())
405
+ total_labor = sum(r["cost"]["Labor"] for r in results.values())
406
+ total_gpu = sum(r["cost"]["GPU"] for r in results.values())
407
+ total_oss = sum(r["cost"]["OSS_Setup"] for r in results.values())
408
+
409
+ summary = f"""
410
+ # ๐Ÿ“Š Vision LLM Agentic AI ํ”„๋กœ์ ํŠธ ๊ฒฌ์ ์„œ
411
+
412
+ ## ๐Ÿ’ฐ ๊ธˆ์•ก ์‚ฐ์ถœ ๊ธฐ์ค€
413
+ - **๊ธฐ์ค€ ํ†ตํ™”**: ์›ํ™” (KRW)
414
+ - **ํ‘œ์‹œ ๋‹จ์œ„**: ์–ต์› (1์–ต = 100,000,000์›)
415
+ - **ํ™˜์œจ ์ฐธ์กฐ**: USD 1,330์› (2024๋…„ 11์›” ๊ธฐ์ค€)
416
+
417
+ ### ๋‹จ๊ฐ€ ์ •๋ณด
418
+ - **์ธ๊ฑด๋น„**: 1,500๋งŒ์›/MM (Man-Month)
419
+ - **GPU ๋‹จ๊ฐ€**: A100 80GB 40์–ต์›, H100 80GB 60์–ต์›
420
+ - **OSS ๊ตฌ์ถ•๋น„**: 300๋งŒ์›/MM
421
+ - **์Šคํ† ๋ฆฌ์ง€**: 200๋งŒ์›/TB
422
+
423
+ ## ํ”„๋กœ์ ํŠธ ๊ฐœ์š”
424
+ - **์ฒ˜๋ฆฌ ๋Œ€์ƒ**: {N_pages:,}์žฅ
425
+ - **์ด ์†Œ์š” ์ธ๋ ฅ**: {total_mm:.1f} MM
426
+ - **์˜ˆ์ƒ ๊ธฐ๊ฐ„**: {duration_months}๊ฐœ์›”
427
+ - **์ด ์˜ˆ์ƒ ๋น„์šฉ**: {total_cost:.1f}์–ต์› ({int(total_cost * 100_000_000):,}์›)
428
+
429
+ ## Phase๋ณ„ ์š”์•ฝ
430
+
431
+ ### {results["Phase1_DataPrep"]["name"]}
432
+ - ์ธ๋ ฅ: {results["Phase1_DataPrep"]["mm"]:.1f} MM
433
+ - ๋น„์šฉ: {results["Phase1_DataPrep"]["cost"]["Total"]:.1f}์–ต์›
434
+ - ์ฃผ์š” HW: {results["Phase1_DataPrep"]["hw"].get("GPU_Type", "N/A")} ร— {results["Phase1_DataPrep"]["hw"].get("Preprocess_GPU", 0)}๋Œ€
435
+
436
+ ### {results["Phase2_Training"]["name"]}
437
+ - ์ธ๋ ฅ: {results["Phase2_Training"]["mm"]:.1f} MM
438
+ - ๋น„์šฉ: {results["Phase2_Training"]["cost"]["Total"]:.1f}์–ต์›
439
+ - ์ฃผ์š” HW: {results["Phase2_Training"]["hw"].get("GPU_Type", "N/A")} ร— {results["Phase2_Training"]["hw"].get("Training_GPU", 0)}๋Œ€
440
+
441
+ ### {results["Phase3_AgenticDev"]["name"]}
442
+ - ์ธ๋ ฅ: {results["Phase3_AgenticDev"]["mm"]:.1f} MM
443
+ - ๋น„์šฉ: {results["Phase3_AgenticDev"]["cost"]["Total"]:.1f}์–ต์›
444
+ - ์ฃผ์š” HW: {results["Phase3_AgenticDev"]["hw"].get("GPU_Type", "N/A")} ร— {results["Phase3_AgenticDev"]["hw"].get("Dev_GPU", 0)}๋Œ€
445
+
446
+ ### {results["Phase4_Deployment"]["name"]}
447
+ - ์ธ๋ ฅ: {results["Phase4_Deployment"]["mm"]:.1f} MM
448
+ - ๋น„์šฉ: {results["Phase4_Deployment"]["cost"]["Total"]:.1f}์–ต์›
449
+ - ์ฃผ์š” HW: {results["Phase4_Deployment"]["hw"].get("GPU_Type", "N/A")} ร— {results["Phase4_Deployment"]["hw"].get("Inference_GPU", 0)}๋Œ€
450
+
451
+ ## ๋น„์šฉ ๊ตฌ์„ฑ
452
+ - **์ธ๊ฑด๋น„**: {total_labor:.1f}์–ต์› ({total_labor/total_cost*100:.1f}%)
453
+ - **GPU**: {total_gpu:.1f}์–ต์› ({total_gpu/total_cost*100:.1f}%)
454
+ - **OSS ๊ตฌ์ถ•**: {total_oss:.1f}์–ต์› ({total_oss/total_cost*100:.1f}%)
455
+ - **๊ธฐํƒ€ ์ธํ”„๋ผ**: {sum(r["cost"]["Infrastructure"] for r in results.values()):.1f}์–ต์›
456
+
457
+ ---
458
+ ๐Ÿ’ก **์ฐธ๊ณ ์‚ฌํ•ญ**
459
+ - ๋ณธ ๊ฒฌ์ ์€ ๋ณด์ˆ˜์  ๊ด€์ ์—์„œ ์‚ฐ์ • (์•ˆ์ „๊ณ„์ˆ˜ 1.4)
460
+ - ์˜คํ”ˆ์†Œ์Šค ๊ธฐ๋ฐ˜์œผ๋กœ ์ƒ์šฉ ๋Œ€๋น„ 1.5๋ฐฐ ์ธ๋ ฅ ๋ฐ˜์˜
461
+ - ์‹ค์ œ ํ”„๋กœ์ ํŠธ ์ง„ํ–‰ ์‹œ ยฑ15% ์กฐ์ • ๊ฐ€๋Šฅ
462
+ """
463
+
464
+ return summary
465
+
466
+ # ============================================
467
+ # PDF ์ƒ์„ฑ ํ•จ์ˆ˜
468
+ # ============================================
469
+
470
+ def generate_pdf(results, total_mm, duration_months, N_pages):
471
+ """PDF ๊ฒฌ์ ์„œ ์ƒ์„ฑ"""
472
+
473
+ try:
474
+ pdf = FPDF(orientation='P', unit='mm', format='A4')
475
+ pdf.add_page()
476
+ pdf.set_margins(25, 15, 25)
477
+ pdf.set_auto_page_break(auto=True, margin=15)
478
+
479
+ # ๊ธฐ๋ณธ ํฐํŠธ ์‚ฌ์šฉ (ํ•œ๊ธ€ ์ง€์› ์ œํ•œ์ )
480
+ pdf.set_font('Arial', '', 10)
481
+
482
+ # ์ œ๋ชฉ
483
+ pdf.set_font('Arial', 'B', 16)
484
+ pdf.cell(0, 10, 'Vision LLM Agentic AI Estimate', ln=1, align='C')
485
+ pdf.ln(3)
486
+
487
+ pdf.set_font('Arial', '', 9)
488
+ pdf.cell(0, 5, f'Date: {datetime.now().strftime("%Y-%m-%d")}', ln=1, align='R')
489
+ pdf.ln(5)
490
+
491
+ # 1. ๊ธˆ์•ก ๊ธฐ์ค€
492
+ pdf.set_font('Arial', 'B', 11)
493
+ pdf.cell(0, 7, '1. Cost Basis', ln=1)
494
+ pdf.set_font('Arial', '', 9)
495
+
496
+ pdf.cell(0, 5, 'Currency: KRW (Korean Won)', ln=1)
497
+ pdf.cell(0, 5, 'Unit: 100M KRW', ln=1)
498
+ pdf.cell(0, 5, 'Labor: 15M KRW/MM', ln=1)
499
+ pdf.cell(0, 5, 'GPU A100 80GB: 4B KRW', ln=1)
500
+ pdf.cell(0, 5, 'OSS Setup: 3M KRW/MM', ln=1)
501
+ pdf.ln(3)
502
+
503
+ # 2. ๊ฐœ์š”
504
+ pdf.set_font('Arial', 'B', 11)
505
+ pdf.cell(0, 7, '2. Project Overview', ln=1)
506
+ pdf.set_font('Arial', '', 9)
507
+
508
+ total_cost = sum(r["cost"]["Total"] for r in results.values())
509
+
510
+ pdf.cell(0, 5, f'Documents: {N_pages:,} pages', ln=1)
511
+ pdf.cell(0, 5, f'Manpower: {total_mm:.1f} MM', ln=1)
512
+ pdf.cell(0, 5, f'Duration: {duration_months} months', ln=1)
513
+ pdf.cell(0, 5, f'Total Cost: {total_cost:.1f}B KRW', ln=1)
514
+ pdf.ln(3)
515
+
516
+ # 3. Phase๋ณ„ ์ƒ์„ธ
517
+ pdf.set_font('Arial', 'B', 11)
518
+ pdf.cell(0, 7, '3. Phase Details', ln=1)
519
+ pdf.ln(2)
520
+
521
+ for i, (phase_id, data) in enumerate(results.items(), 1):
522
+ pdf.set_font('Arial', 'B', 10)
523
+ phase_name = f"Phase {i}"
524
+ pdf.cell(0, 6, phase_name, ln=1)
525
+
526
+ pdf.set_font('Arial', '', 9)
527
+ pdf.cell(0, 4, f' MM: {data["mm"]:.1f}', ln=1)
528
+ pdf.cell(0, 4, f' Cost: {data["cost"]["Total"]:.1f}B KRW', ln=1)
529
+ pdf.cell(0, 4, f' Labor: {data["cost"]["Labor"]:.1f}B', ln=1)
530
+ pdf.cell(0, 4, f' GPU: {data["cost"]["GPU"]:.1f}B', ln=1)
531
+ pdf.ln(1)
532
+
533
+ pdf.ln(3)
534
+
535
+ # 4. HW ์š”์•ฝ
536
+ pdf.add_page()
537
+ pdf.set_font('Arial', 'B', 11)
538
+ pdf.cell(0, 7, '4. Hardware Summary', ln=1)
539
+ pdf.ln(2)
540
+
541
+ for phase_id, data in results.items():
542
+ hw = data["hw"]
543
+ pdf.set_font('Arial', '', 9)
544
+
545
+ if "Training_GPU" in hw:
546
+ pdf.cell(0, 4, f'Training: {hw["GPU_Type"]} x{hw["Training_GPU"]}', ln=1)
547
+ elif "Preprocess_GPU" in hw:
548
+ pdf.cell(0, 4, f'Preprocess: {hw["GPU_Type"]} x{hw["Preprocess_GPU"]}', ln=1)
549
+ elif "Inference_GPU" in hw:
550
+ pdf.cell(0, 4, f'Inference: {hw["GPU_Type"]} x{hw["Inference_GPU"]}', ln=1)
551
+ elif "Dev_GPU" in hw:
552
+ pdf.cell(0, 4, f'Dev: {hw["GPU_Type"]} x{hw["Dev_GPU"]}', ln=1)
553
+
554
+ pdf.cell(0, 4, f' CPU: {hw.get("CPU_Cores", 0)} cores', ln=1)
555
+ pdf.cell(0, 4, f' RAM: {hw.get("RAM_GB", 0)} GB', ln=1)
556
+ pdf.cell(0, 4, f' Storage: {hw.get("Storage_TB", 0)} TB', ln=1)
557
+ pdf.ln(1)
558
+
559
+ timestamp = int(time.time())
560
+ pdf_path = f'/tmp/Estimate_{timestamp}.pdf'
561
+ pdf.output(pdf_path)
562
+
563
+ return pdf_path
564
+
565
+ except Exception as e:
566
+ print(f"PDF generation error: {e}")
567
+ return None
568
+
569
+ # ============================================
570
+ # Excel ์ƒ์„ฑ ํ•จ์ˆ˜
571
+ # ============================================
572
+
573
+ def generate_excel(results, total_mm, duration_months, N_pages):
574
+ """Excel ๊ฒฌ์ ์„œ ์ƒ์„ฑ"""
575
+
576
+ try:
577
+ timestamp = int(time.time())
578
+ excel_path = f'/tmp/Estimate_{timestamp}.xlsx'
579
+
580
+ with pd.ExcelWriter(excel_path, engine='xlsxwriter') as writer:
581
+
582
+ # 1. ์š”์•ฝ
583
+ total_cost = sum(r["cost"]["Total"] for r in results.values())
584
+ summary_data = {
585
+ 'Item': ['Documents', 'Manpower', 'Duration', 'Total Cost (100M KRW)', 'Total Cost (KRW)'],
586
+ 'Value': [
587
+ f'{N_pages:,} pages',
588
+ f'{total_mm:.1f} MM',
589
+ f'{duration_months} months',
590
+ f'{total_cost:.1f}',
591
+ f'{int(total_cost * 100_000_000):,}'
592
+ ]
593
+ }
594
+ pd.DataFrame(summary_data).to_excel(writer, sheet_name='Summary', index=False)
595
+
596
+ # 2. Phase๋ณ„ ์ƒ์„ธ
597
+ phase_data = []
598
+ for phase_id, data in results.items():
599
+ phase_data.append({
600
+ 'Phase': data["name"].split(":")[1].strip(),
601
+ 'MM': round(data["mm"], 1),
602
+ 'Labor (100M)': round(data["cost"]["Labor"], 1),
603
+ 'GPU (100M)': round(data["cost"]["GPU"], 1),
604
+ 'OSS Setup (100M)': round(data["cost"]["OSS_Setup"], 1),
605
+ 'Total (100M)': round(data["cost"]["Total"], 1)
606
+ })
607
+ pd.DataFrame(phase_data).to_excel(writer, sheet_name='Phase Details', index=False)
608
+
609
+ # 3. HW
610
+ hw_data = []
611
+ for phase_id, data in results.items():
612
+ hw = data["hw"]
613
+ hw_row = {'Phase': data["name"].split(":")[1].strip()}
614
+ hw_row.update(hw)
615
+ hw_data.append(hw_row)
616
+ pd.DataFrame(hw_data).to_excel(writer, sheet_name='Hardware', index=False)
617
+
618
+ # 4. OSS
619
+ oss_data = []
620
+ for phase_id, data in results.items():
621
+ for oss in data["oss_stack"]:
622
+ oss_data.append({
623
+ 'Phase': data["name"].split(":")[1].strip(),
624
+ 'Open Source': oss
625
+ })
626
+ pd.DataFrame(oss_data).to_excel(writer, sheet_name='Open Source', index=False)
627
+
628
+ return excel_path
629
+
630
+ except Exception as e:
631
+ print(f"Excel generation error: {e}")
632
+ return None
633
+
634
+ # ============================================
635
+ # ์‹œ๊ฐํ™” ํ•จ์ˆ˜
636
+ # ============================================
637
+
638
+ def create_phase_chart(results):
639
+ """Phase๋ณ„ ๋น„๊ต ์ฐจํŠธ"""
640
+
641
+ names = [results[p]["name"].split(":")[1].strip() for p in results.keys()]
642
+ mms = [results[p]["mm"] for p in results.keys()]
643
+ costs = [results[p]["cost"]["Total"] for p in results.keys()]
644
+
645
+ fig = make_subplots(
646
+ rows=1, cols=2,
647
+ subplot_titles=('Phase Manpower (MM)', 'Phase Cost (100M KRW)'),
648
+ specs=[[{'type':'bar'}, {'type':'bar'}]]
649
+ )
650
+
651
+ fig.add_trace(
652
+ go.Bar(x=names, y=mms, text=[f"{m:.1f}" for m in mms],
653
+ textposition='auto', marker_color='#3498db'),
654
+ row=1, col=1
655
+ )
656
+
657
+ fig.add_trace(
658
+ go.Bar(x=names, y=costs, text=[f"{c:.1f}" for c in costs],
659
+ textposition='auto', marker_color='#e74c3c'),
660
+ row=1, col=2
661
+ )
662
+
663
+ fig.update_layout(height=400, showlegend=False, title_text="Phase Comparison", title_x=0.5)
664
+ return fig
665
+
666
+ def create_cost_breakdown(results):
667
+ """๋น„์šฉ ๊ตฌ์„ฑ ์ฐจํŠธ"""
668
+
669
+ categories = []
670
+ values = []
671
+
672
+ for phase_id, data in results.items():
673
+ name = data["name"].split(":")[1].strip()
674
+ categories.append(f"{name}\nLabor")
675
+ values.append(data["cost"]["Labor"])
676
+ categories.append(f"{name}\nGPU")
677
+ values.append(data["cost"]["GPU"])
678
+
679
+ fig = go.Figure(data=[go.Pie(
680
+ labels=categories,
681
+ values=values,
682
+ hole=.4,
683
+ textinfo='label+percent',
684
+ textposition='outside'
685
+ )])
686
+
687
+ fig.update_layout(title_text="Cost Breakdown", height=500)
688
+ return fig
689
+
690
+ def create_timeline(results, duration_months):
691
+ """ํƒ€์ž„๋ผ์ธ ์ฐจํŠธ"""
692
+
693
+ phase_durations = {}
694
+ start = 0
695
+
696
+ p1_dur = math.ceil(results["Phase1_DataPrep"]["mm"] / 8)
697
+ phase_durations["Phase1_DataPrep"] = (start, p1_dur)
698
+ start += p1_dur
699
+
700
+ p2_dur = math.ceil(results["Phase2_Training"]["mm"] / 10)
701
+ p3_dur = math.ceil(results["Phase3_AgenticDev"]["mm"] / 6)
702
+ parallel = max(p2_dur, p3_dur)
703
+
704
+ phase_durations["Phase2_Training"] = (start, p2_dur)
705
+ phase_durations["Phase3_AgenticDev"] = (start, p3_dur)
706
+ start += parallel
707
+
708
+ p4_dur = math.ceil(results["Phase4_Deployment"]["mm"] / 8)
709
+ phase_durations["Phase4_Deployment"] = (start, p4_dur)
710
+
711
+ fig = go.Figure()
712
+ colors = ['#3498db', '#e74c3c', '#2ecc71', '#f39c12']
713
+
714
+ for i, (phase_id, (s, d)) in enumerate(phase_durations.items()):
715
+ name = results[phase_id]["name"].split(":")[1].strip()
716
+ fig.add_trace(go.Bar(
717
+ y=[name], x=[d], base=s, orientation='h',
718
+ marker=dict(color=colors[i]), text=f"{d}mo",
719
+ textposition='inside', name=name
720
+ ))
721
+
722
+ fig.update_layout(
723
+ title="Project Timeline", xaxis_title="Months", yaxis_title="Phase",
724
+ barmode='overlay', height=400, showlegend=False
725
+ )
726
+ return fig
727
+
728
+ def create_hw_table(results):
729
+ """HW ํ…Œ์ด๋ธ”"""
730
+
731
+ rows = []
732
+ for phase_id, data in results.items():
733
+ row = {"Phase": data["name"].split(":")[1].strip()}
734
+ row.update(data["hw"])
735
+ rows.append(row)
736
+ return pd.DataFrame(rows)
737
+
738
+ def create_oss_table(results):
739
+ """OSS ํ…Œ์ด๋ธ”"""
740
+
741
+ rows = []
742
+ for phase_id, data in results.items():
743
+ name = data["name"].split(":")[1].strip()
744
+ for i, oss in enumerate(data["oss_stack"]):
745
+ rows.append({
746
+ "Phase": name if i == 0 else "",
747
+ "No": i+1,
748
+ "Open Source": oss
749
+ })
750
+ return pd.DataFrame(rows)
751
+
752
+ # ============================================
753
+ # Gradio UI
754
+ # ============================================
755
+
756
+ EXAMPLES = [
757
+ [10000, 0.7, 0.8, 20, 0.8, 0.8, 3, 0.8, 1.0, 1.0, 1.0],
758
+ [50000, 1.0, 1.0, 40, 1.0, 1.0, 3, 1.0, 1.0, 1.0, 1.0],
759
+ [100000, 1.3, 1.3, 60, 1.0, 1.0, 5, 1.3, 1.0, 1.2, 1.3],
760
+ [500000, 1.6, 1.5, 80, 1.3, 1.3, 5, 1.6, 1.4, 1.5, 1.6],
761
+ ]
762
+
763
+ with gr.Blocks(title="Vision LLM Agentic AI ๊ฒฌ์ ", theme=gr.themes.Soft()) as demo:
764
+
765
+ gr.Markdown("""
766
+ # ๐Ÿ“˜ Vision LLM ๊ธฐ๋ฐ˜ Agentic AI ํ”„๋กœ์ ํŠธ ๊ฒฌ์  ์‹œ์Šคํ…œ
767
+
768
+ **๋ณด์ˆ˜์  ๊ฒฌ์  / ์˜คํ”ˆ์†Œ์Šค ์ค‘์‹ฌ / Phase๋ณ„ ์ƒ์„ธ ๋ถ„์„ / PDFยทExcel ๋‹ค์šด๋กœ๋“œ**
769
+
770
+ ๐Ÿ’ฐ **๊ธˆ์•ก ๊ธฐ์ค€**: ์›ํ™”(KRW), ๋‹จ์œ„: ์–ต์› | ํ™˜์œจ: USD 1,330์›
771
+ """)
772
+
773
+ with gr.Tab("๐Ÿ“‹ ํŒŒ๋ผ๋ฏธํ„ฐ ์ž…๋ ฅ"):
774
+
775
+ gr.Markdown("## 1๏ธโƒฃ ํ”„๋กœ์ ํŠธ ๊ทœ๋ชจ")
776
+ N_pages = gr.Number(label="๐Ÿ“„ ๋ฌธ์„œ ํŽ˜์ด์ง€ ์ˆ˜", value=10000)
777
+
778
+ gr.Markdown("## 2๏ธโƒฃ ๋ฌธ์„œ ํŠน์„ฑ")
779
+ with gr.Row():
780
+ doc_complexity = gr.Slider(0.7, 1.6, value=1.0, step=0.1,
781
+ label="๐Ÿ“Š ๋ฌธ์„œ ๋ณต์žก๋„", info="0.7=๋‹จ์ˆœ, 1.0=๋ณดํ†ต, 1.3=๋ณต์žก, 1.6=๋งค์šฐ๋ณต์žก")
782
+ language_mix = gr.Slider(0.8, 1.5, value=1.0, step=0.1,
783
+ label="๐ŸŒ ์–ธ์–ด ๋ณต์žก๋„", info="0.8=๋‹จ์ผ, 1.0=์ด์ค‘, 1.3=๋‹ค๊ตญ์–ด")
784
+
785
+ with gr.Row():
786
+ table_ratio = gr.Slider(0, 100, value=40, step=5,
787
+ label="๐Ÿ“‹ ํ‘œ ๋น„์œจ (%)")
788
+ image_quality = gr.Slider(0.8, 1.6, value=1.0, step=0.1,
789
+ label="๐Ÿ–ผ๏ธ ์ด๋ฏธ์ง€ ํ’ˆ์งˆ", info="0.8=๊ณ , 1.0=์ค‘, 1.3=์ €")
790
+
791
+ gr.Markdown("## 3๏ธโƒฃ ๋ชจ๋ธ ๋ฐ ํ•™์Šต")
792
+ with gr.Row():
793
+ model_size = gr.Slider(0.8, 1.6, value=1.0, step=0.1,
794
+ label="๐Ÿค– ๋ชจ๋ธ ํฌ๊ธฐ", info="0.8=์†Œํ˜•, 1.0=์ค‘ํ˜•, 1.3=๋Œ€ํ˜•")
795
+ training_epochs = gr.Slider(1, 10, value=3, step=1,
796
+ label="๐Ÿ”„ ํ•™์Šต Epoch")
797
+
798
+ gr.Markdown("## 4๏ธโƒฃ Agentic AI ๋ฐ ๋ฐฐํฌ")
799
+ with gr.Row():
800
+ agent_complexity = gr.Slider(0.8, 1.6, value=1.0, step=0.1,
801
+ label="๐ŸŽฏ Agent ๋ณต์žก๋„", info="0.8=๊ธฐ๋ณธ, 1.0=ํ‘œ์ค€, 1.3=๊ณ ๊ธ‰")
802
+ deployment_type = gr.Slider(0.9, 1.4, value=1.0, step=0.1,
803
+ label="๐Ÿ—๏ธ ๋ฐฐํฌ ํ™˜๊ฒฝ", info="0.9=Cloud, 1.0=On-Prem, 1.4=Air-Gap")
804
+
805
+ gr.Markdown("## 5๏ธโƒฃ ์šด์˜ ์š”๊ตฌ์‚ฌํ•ญ")
806
+ with gr.Row():
807
+ sla_level = gr.Slider(1.0, 1.5, value=1.0, step=0.1,
808
+ label="๐Ÿ“ˆ SLA ๋“ฑ๊ธ‰", info="1.0=ํ‘œ์ค€, 1.2=๋†’์Œ, 1.5=๋ฏธ์…˜ํฌ๋ฆฌํ‹ฐ์ปฌ")
809
+ security_level = gr.Slider(1.0, 1.6, value=1.0, step=0.1,
810
+ label="๐Ÿ” ๋ณด์•ˆ ๋“ฑ๊ธ‰", info="1.0=์ผ๋ฐ˜, 1.3=๊ฐ•ํ™”, 1.6=์ตœ๊ณ ")
811
+
812
+ estimate_btn = gr.Button("๐Ÿš€ ๊ฒฌ์  ์‚ฐ์ •", variant="primary", size="lg")
813
+
814
+ gr.Markdown("### ๐Ÿ“Œ ์˜ˆ์ œ ์‹œ๋‚˜๋ฆฌ์˜ค")
815
+ gr.Examples(
816
+ examples=EXAMPLES,
817
+ inputs=[N_pages, doc_complexity, language_mix, table_ratio,
818
+ image_quality, model_size, training_epochs, agent_complexity,
819
+ deployment_type, sla_level, security_level],
820
+ )
821
+
822
+ with gr.Tab("๐Ÿ“Š ๊ฒฌ์  ๊ฒฐ๊ณผ"):
823
+
824
+ summary_text = gr.Markdown()
825
+
826
+ gr.Markdown("### ๐Ÿ“ฅ ๊ฒฌ์ ์„œ ๋‹ค์šด๋กœ๋“œ")
827
+ with gr.Row():
828
+ pdf_download = gr.File(label="๐Ÿ“„ PDF")
829
+ excel_download = gr.File(label="๐Ÿ“Š Excel")
830
+
831
+ with gr.Row():
832
+ phase_chart = gr.Plot()
833
+ cost_chart = gr.Plot()
834
+
835
+ timeline_chart = gr.Plot()
836
+
837
+ gr.Markdown("### ๐Ÿ’ป ํ•˜๋“œ์›จ์–ด")
838
+ hw_table = gr.Dataframe()
839
+
840
+ gr.Markdown("### ๐Ÿ”ง ์˜คํ”ˆ์†Œ์Šค")
841
+ oss_table = gr.Dataframe()
842
+
843
+ with gr.Tab("โ„น๏ธ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ€์ด๋“œ"):
844
+
845
+ gr.Markdown("""
846
+ # ๐Ÿ“– ํŒŒ๋ผ๋ฏธํ„ฐ ์ƒ์„ธ ์„ค๋ช…
847
+
848
+ ## ๐Ÿ“Š ๋ฌธ์„œ ๋ณต์žก๋„
849
+ - **0.7 (๋‹จ์ˆœ)**: ํ…์ŠคํŠธ ์œ„์ฃผ, ํ‘œ ์—†์Œ
850
+ - **1.0 (๋ณดํ†ต)**: ํ…์ŠคํŠธ + ๋‹จ์ˆœ ํ‘œ
851
+ - **1.3 (๋ณต์žก)**: ํ…์ŠคํŠธ + ๋ณต์žกํ•œ ํ‘œ + ์ด๋ฏธ์ง€
852
+ - **1.6 (๋งค์šฐ๋ณต์žก)**: ๋‹ค๋‹จ ๋ ˆ์ด์•„์›ƒ + ์ˆ˜์‹ + ๋‹ค๊ตญ์–ด
853
+
854
+ ## ๐ŸŒ ์–ธ์–ด ๋ณต์žก๋„
855
+ - **0.8 (๋‹จ์ผ)**: ํ•œ๊ตญ์–ด๋งŒ
856
+ - **1.0 (์ด์ค‘)**: ํ•œ๊ตญ์–ด + ์˜์–ด
857
+ - **1.3 (๋‹ค๊ตญ์–ด)**: ํ•œ/์˜/์ผ/์ค‘ ํ˜ผํ•ฉ
858
+ - **1.5 (ํŠน์ˆ˜)**: ๋‹ค๊ตญ์–ด + ํŠน์ˆ˜๋ฌธ์ž
859
+
860
+ ## ๐Ÿ“‹ ํ‘œ ๋น„์œจ
861
+ - ์ „์ฒด ๋ฌธ์„œ ์ค‘ ํ‘œ๊ฐ€ ํฌํ•จ๋œ ๋น„์œจ (%)
862
+ - ํ‘œ ๊ตฌ์กฐ ์ธ์‹์€ ๊ฐ€์žฅ ๋ณต์žกํ•œ ์ž‘์—…
863
+
864
+ ## ๐Ÿ–ผ๏ธ ์ด๋ฏธ์ง€ ํ’ˆ์งˆ
865
+ - **0.8 (๊ณ ํ’ˆ์งˆ)**: ์Šค์บ” 300dpi+
866
+ - **1.0 (๋ณดํ†ต)**: ์Šค์บ” 200dpi
867
+ - **1.3 (์ €ํ’ˆ์งˆ)**: ์Šค์บ” 150dpi
868
+ - **1.6 (๋งค์šฐ๋‚ฎ์Œ)**: ์‚ฌ์ง„์ดฌ์˜, ์™œ๊ณก
869
+
870
+ ## ๐Ÿค– ๋ชจ๋ธ ํฌ๊ธฐ
871
+ - **0.8 (์†Œํ˜•)**: 2B~7B ํŒŒ๋ผ๋ฏธํ„ฐ
872
+ - **1.0 (์ค‘ํ˜•)**: 8B~14B
873
+ - **1.3 (๋Œ€ํ˜•)**: 20B~40B
874
+ - **1.6 (์ดˆ๋Œ€ํ˜•)**: 70B+
875
+
876
+ ## ๐ŸŽฏ Agent ๋ณต์žก๋„
877
+ - **0.8 (๊ธฐ๋ณธ)**: ๋‹จ์ผ Agent, ๋‹จ์ˆœ RAG
878
+ - **1.0 (ํ‘œ์ค€)**: 2~3 Agent
879
+ - **1.3 (๊ณ ๊ธ‰)**: Multi-Agent, ๋ณต์žกํ•œ Tool
880
+ - **1.6 (์—”ํ„ฐํ”„๋ผ์ด์ฆˆ)**: Self-Learning
881
+
882
+ ## ๐Ÿ—๏ธ ๋ฐฐํฌ ํ™˜๊ฒฝ
883
+ - **0.9 (Cloud)**: AWS/GCP/Azure
884
+ - **1.0 (On-Premise)**: ์ž์ฒด ์„œ๋ฒ„
885
+ - **1.4 (Air-Gap)**: ํ์‡„๋ง, ๊ณ ๋ณด์•ˆ
886
+
887
+ ## ๐Ÿ“ˆ SLA ๋“ฑ๊ธ‰
888
+ - **1.0 (ํ‘œ์ค€)**: 99% ๊ฐ€์šฉ์„ฑ
889
+ - **1.2 (๋†’์Œ)**: 99.5% ๊ฐ€์šฉ์„ฑ
890
+ - **1.5 (๋ฏธ์…˜ํฌ๋ฆฌํ‹ฐ์ปฌ)**: 99.9% ๊ฐ€์šฉ์„ฑ
891
+
892
+ ## ๐Ÿ” ๋ณด์•ˆ ๋“ฑ๊ธ‰
893
+ - **1.0 (์ผ๋ฐ˜)**: ๊ธฐ๋ณธ ์ธ์ฆ/์•”ํ˜ธํ™”
894
+ - **1.3 (๊ฐ•ํ™”)**: ๋‹ค์ค‘ ์ธ์ฆ, ๊ฐ์‚ฌ ๋กœ๊ทธ
895
+ - **1.6 (์ตœ๊ณ )**: Zero-Trust, ์™„์ „ ๊ฒฉ๋ฆฌ
896
+
897
+ ---
898
+
899
+ ## ๐Ÿ’ฐ ๊ธˆ์•ก ์‚ฐ์ถœ ๊ธฐ์ค€
900
+
901
+ ### ํ†ตํ™” ๋ฐ ๋‹จ์œ„
902
+ - **๊ธฐ์ค€**: ์›ํ™” (KRW)
903
+ - **ํ‘œ์‹œ**: ์–ต์›
904
+ - **ํ™˜์œจ**: USD 1,330์›
905
+
906
+ ### ์ธ๊ฑด๋น„
907
+ - **1 MM = 15,000,000์›** (1,500๋งŒ์›/์›”)
908
+
909
+ ### GPU ๋‹จ๊ฐ€
910
+ - **A100 80GB: 40์–ต์›** (โ‰ˆ $30,000)
911
+ - **H100 80GB: 60์–ต์›** (โ‰ˆ $45,000)
912
+
913
+ ### ๊ธฐํƒ€
914
+ - **OSS ๊ตฌ์ถ•: 300๋งŒ์›/MM**
915
+ - **์Šคํ† ๋ฆฌ์ง€: 200๋งŒ์›/TB**
916
+ """)
917
+
918
+ with gr.Tab("๐Ÿ’ฐ ๊ธˆ์•ก ์ƒ์„ธ"):
919
+
920
+ gr.Markdown(f"""
921
+ # ๐Ÿ’ฐ ๊ธˆ์•ก ์‚ฐ์ถœ ์ƒ์„ธ
922
+
923
+ ## ์ธ๊ฑด๋น„ (์›ํ™”)
924
+
925
+ | ์ง๊ธ‰ | ์›” ๋‹จ๊ฐ€ | ์—ฐ๋ด‰ ํ™˜์‚ฐ |
926
+ |------|---------|----------|
927
+ | ์•„ํ‚คํ…ํŠธ | {LABOR_RATES['Architect']:,}์› | {LABOR_RATES['Architect']*12:,}์› |
928
+ | ์‹œ๋‹ˆ์–ด | {LABOR_RATES['Senior_Engineer']:,}์› | {LABOR_RATES['Senior_Engineer']*12:,}์› |
929
+ | ์ค‘๊ธ‰ | {LABOR_RATES['Mid_Engineer']:,}์› | {LABOR_RATES['Mid_Engineer']*12:,}์› |
930
+ | ์ฃผ๋‹ˆ์–ด | {LABOR_RATES['Junior_Engineer']:,}์› | {LABOR_RATES['Junior_Engineer']*12:,}์› |
931
+ | **ํ‰๊ท ** | **{LABOR_RATES['Average']:,}์›** | **{LABOR_RATES['Average']*12:,}์›** |
932
+
933
+ ## GPU ๋‹จ๊ฐ€ (์›ํ™”)
934
+
935
+ | GPU | ๋‹จ๊ฐ€ (์›) | ๋‹จ๊ฐ€ (๋‹ฌ๋Ÿฌ) | ์šฉ๋„ |
936
+ |-----|-----------|------------|------|
937
+ | H100 80GB | {GPU_COSTS_KRW['H100_80GB']:,}์› | $45,000 | ๋Œ€๊ทœ๋ชจ ํ•™์Šต |
938
+ | A100 80GB | {GPU_COSTS_KRW['A100_80GB']:,}์› | $30,000 | ํ‘œ์ค€ ํ•™์Šต |
939
+ | A100 40GB | {GPU_COSTS_KRW['A100_40GB']:,}์› | $18,750 | ๊ฐœ๋ฐœ/ํ…Œ์ŠคํŠธ |
940
+ | L40S | {GPU_COSTS_KRW['L40S']:,}์› | $11,250 | ์ „์ฒ˜๋ฆฌ |
941
+
942
+ ## ๊ธฐํƒ€ ๋น„์šฉ
943
+
944
+ | ํ•ญ๋ชฉ | ๋‹จ๊ฐ€ | ๋‹จ์œ„ |
945
+ |------|------|------|
946
+ | ์Šคํ† ๋ฆฌ์ง€ (NVMe) | {STORAGE_COST_PER_TB:,}์› | TB |
947
+ | OSS ๊ตฌ์ถ• | {OSS_SETUP_COST_PER_MM:,}์› | MM |
948
+ | ๊ณ ๊ธ‰ ์„œ๋ฒ„ | {SERVER_COSTS_KRW['CPU_High_End']:,}์› | ๋Œ€ |
949
+ | RAM 512GB | {SERVER_COSTS_KRW['RAM_512GB']:,}์› | ์„ธํŠธ |
950
+
951
+ ## ๋น„์šฉ ์‚ฐ์ • ๊ณต์‹
952
+ ```
953
+ Total = Labor + GPU + Server + Storage + OSS + Infrastructure
954
+
955
+ Labor = MM ร— 15,000,000์›
956
+ GPU = GPU_Count ร— GPU_Unit_Price
957
+ OSS = MM ร— 3,000,000์›
958
+ Infrastructure = (GPU + Server + Storage) ร— 0.3
959
+ ```
960
+
961
+ ## ์•ˆ์ „๊ณ„์ˆ˜
962
+ - **์•ˆ์ „๊ณ„์ˆ˜**: 1.4 (40% ๋ฒ„ํผ)
963
+ - **GPU ํšจ์œจ**: 0.65 (65% ํ™œ์šฉ๋ฅ )
964
+ - **OSS ์ธ๋ ฅ**: 1.5๋ฐฐ (์ƒ์šฉ ๋Œ€๋น„)
965
+ """)
966
+
967
+ estimate_btn.click(
968
+ fn=run_estimation,
969
+ inputs=[N_pages, doc_complexity, language_mix, table_ratio,
970
+ image_quality, model_size, training_epochs, agent_complexity,
971
+ deployment_type, sla_level, security_level],
972
+ outputs=[summary_text, phase_chart, cost_chart, timeline_chart,
973
+ hw_table, oss_table, pdf_download, excel_download]
974
+ )
975
+
976
+ if __name__ == "__main__":
977
+ print("๐Ÿš€ Starting Gradio app...")
978
+ demo.launch()