# ============================================ # ๐Ÿ“˜ Vision LLM Agentic AI ํ”„๋กœ์ ํŠธ ๊ฒฌ์  ์‹œ์Šคํ…œ # - ๋ณด์ˆ˜์  MM ์‚ฐ์ • / ์˜คํ”ˆ์†Œ์Šค ์ค‘์‹ฌ # - Phase๋ณ„ ์ƒ์„ธ ์‚ฌ์ด์ง• # - PDF/Excel ๋‹ค์šด๋กœ๋“œ ๊ธฐ๋Šฅ # - Hugging Face Spaces ์ตœ์ ํ™” # ============================================ import sys import os import math import time import warnings warnings.filterwarnings("ignore") # Hugging Face Spaces๋Š” requirements.txt๋กœ ํŒจํ‚ค์ง€ ๊ด€๋ฆฌ import gradio as gr import pandas as pd import plotly.graph_objects as go from plotly.subplots import make_subplots from fpdf import FPDF import json from datetime import datetime print("โœ… ํŒจํ‚ค์ง€ ๋กœ๋“œ ์™„๋ฃŒ!\n" + "="*70) # ============================================ # ๐Ÿ’ฐ ๊ธˆ์•ก ์‚ฐ์ถœ ๊ธฐ์ค€ (๋ช…ํ™•ํžˆ ์ •์˜) # ============================================ CURRENCY_INFO = { "base": "KRW", "unit": "์–ต์›", "display_factor": 100_000_000, } # ์ธ๊ฑด๋น„ (์›ํ™” ๊ธฐ์ค€) LABOR_RATES = { "Senior_Engineer": 18_000_000, "Mid_Engineer": 15_000_000, "Junior_Engineer": 12_000_000, "Architect": 20_000_000, "Average": 15_000_000, } LABOR_COST_PER_MM = LABOR_RATES["Average"] # GPU ๋‹จ๊ฐ€ (์›ํ™” ๊ธฐ์ค€) GPU_COSTS_KRW = { 'A100_80GB': 4_000_000_000, 'H100_80GB': 6_000_000_000, 'A100_40GB': 2_500_000_000, 'L40S': 1_500_000_000, } GPU_COST = {k: v / CURRENCY_INFO['display_factor'] for k, v in GPU_COSTS_KRW.items()} # ์„œ๋ฒ„ ๋‹จ๊ฐ€ SERVER_COSTS_KRW = { 'CPU_High_End': 50_000_000, 'CPU_Mid_Range': 30_000_000, 'RAM_256GB': 10_000_000, 'RAM_512GB': 20_000_000, } STORAGE_COST_PER_TB = 2_000_000 OSS_SETUP_COST_PER_MM = 3_000_000 EXCHANGE_RATE_INFO = { "USD_to_KRW": 1330, "note": "GPU ๊ฐ€๊ฒฉ์€ ๋ฏธ๊ตญ ์‹œ์žฅ๊ฐ€๋ฅผ ํ™˜์œจ ์ ์šฉํ•˜์—ฌ ์‚ฐ์ •" } # ๋ณด์ˆ˜์  ์•ˆ์ „๊ณ„์ˆ˜ SAFETY_FACTOR = 1.4 GPU_EFFICIENCY = 0.65 PARALLEL_EFFICIENCY = 0.70 OSS_LABOR_MULTIPLIER = 1.5 # ============================================ # Value Chain Phase ์ •์˜ # ============================================ VALUE_CHAIN_PHASES = { "Phase1_DataPrep": { "name": "Phase 1: ๋ฐ์ดํ„ฐ ์ค€๋น„ ๋ฐ ๊ธฐ๋ฐ˜ ๊ตฌ์ถ•", "description": "๋ฌธ์„œ ์ˆ˜์ง‘, ์ „์ฒ˜๋ฆฌ, ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ, ์˜จํ†จ๋กœ์ง€ ์„ค๊ณ„", "base_mm_ratio": 0.39, "roles": ["Data Engineer", "Data Architect", "Ontology Engineer"], "oss_stack": [ "PyMuPDF (๋ฌธ์„œ ํŒŒ์‹ฑ)", "OpenCV (์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ)", "LayoutParser (ํ‘œ ์ธ์‹)", "spaCy (NLP)", "PostgreSQL (๋ฉ”ํƒ€DB)", "Neo4j (๊ทธ๋ž˜ํ”„DB)", "Apache Airflow (์›Œํฌํ”Œ๋กœ)", "DVC (๋ฐ์ดํ„ฐ ๋ฒ„์ „๊ด€๋ฆฌ)" ] }, "Phase2_Training": { "name": "Phase 2: ๋ชจ๋ธ ํ•™์Šต ๋ฐ ์ตœ์ ํ™”", "description": "Vision LLM Fine-Tuning, Q-LoRA, Validation", "base_mm_ratio": 0.23, "roles": ["ML Engineer", "AI Architect", "Research Engineer"], "oss_stack": [ "PyTorch (๋”ฅ๋Ÿฌ๋‹)", "PEFT/LoRA (ํšจ์œจ์  ํ•™์Šต)", "Hugging Face Transformers", "Albumentations (์ฆ๊ฐ•)", "InternVL 3.5 (Vision LLM)", "Unsloth (ํ•™์Šต ๊ฐ€์†)", "MLflow (์‹คํ—˜ ์ถ”์ )", "Weights & Biases (๋ชจ๋‹ˆํ„ฐ๋ง)" ] }, "Phase3_AgenticDev": { "name": "Phase 3: Agentic AI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ", "description": "Agent ์„ค๊ณ„, Workflow, Tool ์—ฐ๋™, RAG", "base_mm_ratio": 0.17, "roles": ["AI Architect", "Backend Engineer", "ML Engineer"], "oss_stack": [ "LangGraph (Agent ํ”„๋ ˆ์ž„์›Œํฌ)", "LangChain (LLM ์ฒด์ธ)", "CrewAI (Multi-Agent)", "Qdrant (Vector DB)", "FAISS (๋ฒกํ„ฐ ๊ฒ€์ƒ‰)", "Redis (Memory)", "FastAPI (API)", "n8n (์›Œํฌํ”Œ๋กœ ์ž๋™ํ™”)" ] }, "Phase4_Deployment": { "name": "Phase 4: ๋ฐฐํฌยท์šด์˜ ๋ฐ ์ง€์† ๊ฐœ์„ ", "description": "Docker/K8s ๋ฐฐํฌ, ๋ชจ๋‹ˆํ„ฐ๋ง, ์žฌํ•™์Šต ์ž๋™ํ™”", "base_mm_ratio": 0.21, "roles": ["DevOps Engineer", "MLOps Engineer", "SRE"], "oss_stack": [ "Docker (์ปจํ…Œ์ด๋„ˆ)", "Kubernetes (์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜)", "Prometheus (๋ฉ”ํŠธ๋ฆญ)", "Grafana (์‹œ๊ฐํ™”)", "ELK Stack (๋กœ๊ทธ)", "ArgoCD (GitOps)", "Kubeflow (ML ํŒŒ์ดํ”„๋ผ์ธ)", "GitLab CI/CD" ] } } # ============================================ # Function Points # ============================================ FP_COEFFICIENTS = { "A-01": 0.00003, "A-02": 0.000015, "A-03": 0.00004, "A-04": 0.00008, "A-05": 0.00006, "A-06": 0.00005, "A-07": 0.000055, "B-01": 0.00004, "B-02": 0.00003, "B-03": 0.00005, "B-04": 0.00004, "C-01": 0.00005, "C-02": 0.00007, "C-03": 0.00004, "C-04": 0.00003, "D-01": 0.00009, "D-02": 0.00006, "D-03": 0.00015, "D-04": 0.00006, "D-05": 0.00004, "F-01": 0.00005, "F-02": 0.00008, "F-03": 0.00007, "F-04": 0.00009, "F-05": 0.00007, "G-01": 0.00008, "G-02": 0.00006, "G-03": 0.00004, "G-04": 0.00007, "H-01": 0.00004, "H-02": 0.00005, "H-03": 0.00008, "H-04": 0.00003, } FP_TO_PHASE = { "Phase1_DataPrep": ["A-01", "A-02", "A-03", "A-04", "A-05", "A-06", "A-07", "B-01", "B-02", "B-03", "B-04", "C-01", "C-02", "C-03", "C-04"], "Phase2_Training": ["D-01", "D-02", "D-03", "D-04", "D-05"], "Phase3_AgenticDev": ["F-01", "F-02", "F-03", "F-04", "F-05"], "Phase4_Deployment": ["G-01", "G-02", "G-03", "G-04", "H-01", "H-02", "H-03", "H-04"] } # ============================================ # ํ•ต์‹ฌ ๊ณ„์‚ฐ ํ•จ์ˆ˜ # ============================================ def calculate_phase_mm(phase_id, N_pages, doc_complexity, language_mix, table_ratio, image_quality, model_size, training_epochs, agent_complexity): """Phase๋ณ„ MM ๊ณ„์‚ฐ""" fps = FP_TO_PHASE.get(phase_id, []) total_mm = 0.0 fp_details = [] if N_pages <= 10000: scale_factor = 1.0 elif N_pages <= 100000: scale_factor = 4.5 else: scale_factor = 12.0 for fp in fps: coeff = FP_COEFFICIENTS.get(fp, 0.00005) base_mm = coeff * N_pages * scale_factor if phase_id == "Phase1_DataPrep": weight = (doc_complexity * language_mix * (1 + table_ratio) * image_quality) if fp in ["A-04", "A-05"]: weight *= (1 + table_ratio*0.5) elif phase_id == "Phase2_Training": weight = model_size * (1 + training_epochs/10) * image_quality if fp == "D-03": weight *= 1.8 elif phase_id == "Phase3_AgenticDev": weight = agent_complexity * doc_complexity else: weight = (doc_complexity + agent_complexity) / 2 mm = base_mm * weight * OSS_LABOR_MULTIPLIER * SAFETY_FACTOR total_mm += mm fp_details.append({ "FP": fp, "Base_MM": round(base_mm, 3), "Weight": round(weight, 2), "Final_MM": round(mm, 2) }) return total_mm, fp_details def calculate_hardware(phase_id, N_pages, model_size, training_epochs, deployment_type, sla_level): """Phase๋ณ„ HW ์‚ฌ์ด์ง•""" hw = {} if phase_id == "Phase1_DataPrep": base_gpu = max(2, math.ceil(N_pages / 5000 * SAFETY_FACTOR)) hw["Preprocess_GPU"] = min(base_gpu, 8) hw["GPU_Type"] = "L40S" hw["CPU_Cores"] = 16 * hw["Preprocess_GPU"] hw["RAM_GB"] = 64 * hw["Preprocess_GPU"] hw["Storage_TB"] = round(max(10, N_pages * 0.5 / 1000000), 1) elif phase_id == "Phase2_Training": base_gpu = max(4, math.ceil(N_pages * training_epochs / 3000 * model_size * SAFETY_FACTOR)) hw["Training_GPU"] = base_gpu hw["GPU_Type"] = "A100_80GB" hw["CPU_Cores"] = 32 * hw["Training_GPU"] hw["RAM_GB"] = 128 * hw["Training_GPU"] hw["Storage_TB"] = round(max(50, N_pages * training_epochs * 2.0 / 1000000), 1) elif phase_id == "Phase3_AgenticDev": hw["Dev_GPU"] = max(2, math.ceil(model_size * 2)) hw["GPU_Type"] = "A100_40GB" hw["VectorDB_Instances"] = max(2, math.ceil(N_pages / 100000)) hw["CPU_Cores"] = 32 hw["RAM_GB"] = 256 hw["Storage_TB"] = round(max(20, N_pages * 1.0 / 1000000), 1) else: qps_estimate = N_pages / 10000 base_gpu = max(2, math.ceil(qps_estimate * sla_level * SAFETY_FACTOR)) hw["Inference_GPU"] = base_gpu hw["GPU_Type"] = "A100_80GB" hw["K8s_Nodes"] = max(3, math.ceil(base_gpu / 2)) hw["CPU_Cores"] = 64 hw["RAM_GB"] = 512 hw["Storage_TB"] = round(max(100, N_pages * 2.0 / 1000000), 1) return hw def estimate_cost(hw, mm, phase_id): """๋น„์šฉ ์‚ฐ์ •""" labor_cost_krw = mm * LABOR_COST_PER_MM labor_cost = labor_cost_krw / CURRENCY_INFO['display_factor'] gpu_cost = 0 if "Training_GPU" in hw: gpu_type = hw.get("GPU_Type", "A100_80GB") gpu_cost = hw["Training_GPU"] * GPU_COST.get(gpu_type, 40.0) elif "Preprocess_GPU" in hw: gpu_type = hw.get("GPU_Type", "L40S") gpu_cost = hw["Preprocess_GPU"] * GPU_COST.get(gpu_type, 15.0) elif "Inference_GPU" in hw: gpu_type = hw.get("GPU_Type", "A100_80GB") gpu_cost = hw["Inference_GPU"] * GPU_COST.get(gpu_type, 40.0) elif "Dev_GPU" in hw: gpu_type = hw.get("GPU_Type", "A100_40GB") gpu_cost = hw["Dev_GPU"] * GPU_COST.get(gpu_type, 25.0) server_cost_krw = 0 if hw.get("CPU_Cores", 0) >= 32: server_cost_krw += SERVER_COSTS_KRW['CPU_High_End'] elif hw.get("CPU_Cores", 0) >= 16: server_cost_krw += SERVER_COSTS_KRW['CPU_Mid_Range'] if hw.get("RAM_GB", 0) >= 512: server_cost_krw += SERVER_COSTS_KRW['RAM_512GB'] elif hw.get("RAM_GB", 0) >= 256: server_cost_krw += SERVER_COSTS_KRW['RAM_256GB'] server_cost = server_cost_krw / CURRENCY_INFO['display_factor'] storage_cost_krw = 0 if "Storage_TB" in hw: storage_cost_krw = hw["Storage_TB"] * STORAGE_COST_PER_TB storage_cost = storage_cost_krw / CURRENCY_INFO['display_factor'] oss_setup_cost_krw = mm * OSS_SETUP_COST_PER_MM oss_setup_cost = oss_setup_cost_krw / CURRENCY_INFO['display_factor'] hw_total = gpu_cost + server_cost + storage_cost infra_cost = hw_total * 0.3 total_cost = labor_cost + gpu_cost + server_cost + storage_cost + oss_setup_cost + infra_cost return { "Labor": round(labor_cost, 1), "GPU": round(gpu_cost, 1), "Server": round(server_cost, 1), "Storage": round(storage_cost, 1), "OSS_Setup": round(oss_setup_cost, 1), "Infrastructure": round(infra_cost, 1), "Total": round(total_cost, 1), } # ============================================ # ๋ฉ”์ธ ๊ฒฌ์  ํ•จ์ˆ˜ # ============================================ def run_estimation(N_pages, doc_complexity, language_mix, table_ratio, image_quality, model_size, training_epochs, agent_complexity, deployment_type, sla_level, security_level): """์ „์ฒด ๊ฒฌ์  ์‹คํ–‰""" try: if N_pages < 1000: return ("โŒ ๋ฌธ์„œ ์ˆ˜๋Š” ์ตœ์†Œ 1,000์žฅ ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.",) + (None,) * 7 results = {} total_mm = 0 phase_ids = ["Phase1_DataPrep", "Phase2_Training", "Phase3_AgenticDev", "Phase4_Deployment"] for phase_id in phase_ids: phase_info = VALUE_CHAIN_PHASES[phase_id] mm, fp_details = calculate_phase_mm( phase_id, N_pages, doc_complexity, language_mix, table_ratio/100, image_quality, model_size, training_epochs, agent_complexity ) if phase_id == "Phase4_Deployment": mm *= (sla_level * security_level) mm *= deployment_type total_mm += mm hw = calculate_hardware( phase_id, N_pages, model_size, training_epochs, deployment_type, sla_level ) cost = estimate_cost(hw, mm, phase_id) results[phase_id] = { "name": phase_info["name"], "mm": round(mm, 1), "hw": hw, "cost": cost, "fp_details": fp_details, "oss_stack": phase_info["oss_stack"] } duration_months = ( results["Phase1_DataPrep"]["mm"] / 8 + max(results["Phase2_Training"]["mm"] / 10, results["Phase3_AgenticDev"]["mm"] / 6) * PARALLEL_EFFICIENCY + results["Phase4_Deployment"]["mm"] / 8 ) duration_months = math.ceil(duration_months * 1.2) summary = generate_summary(results, total_mm, duration_months, N_pages) phase_chart = create_phase_chart(results) cost_chart = create_cost_breakdown(results) timeline_chart = create_timeline(results, duration_months) hw_table = create_hw_table(results) oss_table = create_oss_table(results) pdf_path = generate_pdf(results, total_mm, duration_months, N_pages) excel_path = generate_excel(results, total_mm, duration_months, N_pages) return summary, phase_chart, cost_chart, timeline_chart, hw_table, oss_table, pdf_path, excel_path except Exception as e: import traceback error_detail = traceback.format_exc() error_msg = f"โŒ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}\n\n์ƒ์„ธ:\n{error_detail}" return (error_msg,) + (None,) * 7 def generate_summary(results, total_mm, duration_months, N_pages): """์š”์•ฝ ์ƒ์„ฑ""" total_cost = sum(r["cost"]["Total"] for r in results.values()) total_labor = sum(r["cost"]["Labor"] for r in results.values()) total_gpu = sum(r["cost"]["GPU"] for r in results.values()) total_oss = sum(r["cost"]["OSS_Setup"] for r in results.values()) summary = f""" # ๐Ÿ“Š Vision LLM Agentic AI ํ”„๋กœ์ ํŠธ ๊ฒฌ์ ์„œ ## ๐Ÿ’ฐ ๊ธˆ์•ก ์‚ฐ์ถœ ๊ธฐ์ค€ - **๊ธฐ์ค€ ํ†ตํ™”**: ์›ํ™” (KRW) - **ํ‘œ์‹œ ๋‹จ์œ„**: ์–ต์› (1์–ต = 100,000,000์›) - **ํ™˜์œจ ์ฐธ์กฐ**: USD 1,330์› (2024๋…„ 11์›” ๊ธฐ์ค€) ### ๋‹จ๊ฐ€ ์ •๋ณด - **์ธ๊ฑด๋น„**: 1,500๋งŒ์›/MM (Man-Month) - **GPU ๋‹จ๊ฐ€**: A100 80GB 40์–ต์›, H100 80GB 60์–ต์› - **OSS ๊ตฌ์ถ•๋น„**: 300๋งŒ์›/MM - **์Šคํ† ๋ฆฌ์ง€**: 200๋งŒ์›/TB ## ํ”„๋กœ์ ํŠธ ๊ฐœ์š” - **์ฒ˜๋ฆฌ ๋Œ€์ƒ**: {N_pages:,}์žฅ - **์ด ์†Œ์š” ์ธ๋ ฅ**: {total_mm:.1f} MM - **์˜ˆ์ƒ ๊ธฐ๊ฐ„**: {duration_months}๊ฐœ์›” - **์ด ์˜ˆ์ƒ ๋น„์šฉ**: {total_cost:.1f}์–ต์› ({int(total_cost * 100_000_000):,}์›) ## Phase๋ณ„ ์š”์•ฝ ### {results["Phase1_DataPrep"]["name"]} - ์ธ๋ ฅ: {results["Phase1_DataPrep"]["mm"]:.1f} MM - ๋น„์šฉ: {results["Phase1_DataPrep"]["cost"]["Total"]:.1f}์–ต์› - ์ฃผ์š” HW: {results["Phase1_DataPrep"]["hw"].get("GPU_Type", "N/A")} ร— {results["Phase1_DataPrep"]["hw"].get("Preprocess_GPU", 0)}๋Œ€ ### {results["Phase2_Training"]["name"]} - ์ธ๋ ฅ: {results["Phase2_Training"]["mm"]:.1f} MM - ๋น„์šฉ: {results["Phase2_Training"]["cost"]["Total"]:.1f}์–ต์› - ์ฃผ์š” HW: {results["Phase2_Training"]["hw"].get("GPU_Type", "N/A")} ร— {results["Phase2_Training"]["hw"].get("Training_GPU", 0)}๋Œ€ ### {results["Phase3_AgenticDev"]["name"]} - ์ธ๋ ฅ: {results["Phase3_AgenticDev"]["mm"]:.1f} MM - ๋น„์šฉ: {results["Phase3_AgenticDev"]["cost"]["Total"]:.1f}์–ต์› - ์ฃผ์š” HW: {results["Phase3_AgenticDev"]["hw"].get("GPU_Type", "N/A")} ร— {results["Phase3_AgenticDev"]["hw"].get("Dev_GPU", 0)}๋Œ€ ### {results["Phase4_Deployment"]["name"]} - ์ธ๋ ฅ: {results["Phase4_Deployment"]["mm"]:.1f} MM - ๋น„์šฉ: {results["Phase4_Deployment"]["cost"]["Total"]:.1f}์–ต์› - ์ฃผ์š” HW: {results["Phase4_Deployment"]["hw"].get("GPU_Type", "N/A")} ร— {results["Phase4_Deployment"]["hw"].get("Inference_GPU", 0)}๋Œ€ ## ๋น„์šฉ ๊ตฌ์„ฑ - **์ธ๊ฑด๋น„**: {total_labor:.1f}์–ต์› ({total_labor/total_cost*100:.1f}%) - **GPU**: {total_gpu:.1f}์–ต์› ({total_gpu/total_cost*100:.1f}%) - **OSS ๊ตฌ์ถ•**: {total_oss:.1f}์–ต์› ({total_oss/total_cost*100:.1f}%) - **๊ธฐํƒ€ ์ธํ”„๋ผ**: {sum(r["cost"]["Infrastructure"] for r in results.values()):.1f}์–ต์› --- ๐Ÿ’ก **์ฐธ๊ณ ์‚ฌํ•ญ** - ๋ณธ ๊ฒฌ์ ์€ ๋ณด์ˆ˜์  ๊ด€์ ์—์„œ ์‚ฐ์ • (์•ˆ์ „๊ณ„์ˆ˜ 1.4) - ์˜คํ”ˆ์†Œ์Šค ๊ธฐ๋ฐ˜์œผ๋กœ ์ƒ์šฉ ๋Œ€๋น„ 1.5๋ฐฐ ์ธ๋ ฅ ๋ฐ˜์˜ - ์‹ค์ œ ํ”„๋กœ์ ํŠธ ์ง„ํ–‰ ์‹œ ยฑ15% ์กฐ์ • ๊ฐ€๋Šฅ """ return summary # ============================================ # PDF ์ƒ์„ฑ ํ•จ์ˆ˜ # ============================================ def generate_pdf(results, total_mm, duration_months, N_pages): """PDF ๊ฒฌ์ ์„œ ์ƒ์„ฑ""" try: pdf = FPDF(orientation='P', unit='mm', format='A4') pdf.add_page() pdf.set_margins(25, 15, 25) pdf.set_auto_page_break(auto=True, margin=15) # ๊ธฐ๋ณธ ํฐํŠธ ์‚ฌ์šฉ (ํ•œ๊ธ€ ์ง€์› ์ œํ•œ์ ) pdf.set_font('Arial', '', 10) # ์ œ๋ชฉ pdf.set_font('Arial', 'B', 16) pdf.cell(0, 10, 'Vision LLM Agentic AI Estimate', ln=1, align='C') pdf.ln(3) pdf.set_font('Arial', '', 9) pdf.cell(0, 5, f'Date: {datetime.now().strftime("%Y-%m-%d")}', ln=1, align='R') pdf.ln(5) # 1. ๊ธˆ์•ก ๊ธฐ์ค€ pdf.set_font('Arial', 'B', 11) pdf.cell(0, 7, '1. Cost Basis', ln=1) pdf.set_font('Arial', '', 9) pdf.cell(0, 5, 'Currency: KRW (Korean Won)', ln=1) pdf.cell(0, 5, 'Unit: 100M KRW', ln=1) pdf.cell(0, 5, 'Labor: 15M KRW/MM', ln=1) pdf.cell(0, 5, 'GPU A100 80GB: 4B KRW', ln=1) pdf.cell(0, 5, 'OSS Setup: 3M KRW/MM', ln=1) pdf.ln(3) # 2. ๊ฐœ์š” pdf.set_font('Arial', 'B', 11) pdf.cell(0, 7, '2. Project Overview', ln=1) pdf.set_font('Arial', '', 9) total_cost = sum(r["cost"]["Total"] for r in results.values()) pdf.cell(0, 5, f'Documents: {N_pages:,} pages', ln=1) pdf.cell(0, 5, f'Manpower: {total_mm:.1f} MM', ln=1) pdf.cell(0, 5, f'Duration: {duration_months} months', ln=1) pdf.cell(0, 5, f'Total Cost: {total_cost:.1f}B KRW', ln=1) pdf.ln(3) # 3. Phase๋ณ„ ์ƒ์„ธ pdf.set_font('Arial', 'B', 11) pdf.cell(0, 7, '3. Phase Details', ln=1) pdf.ln(2) for i, (phase_id, data) in enumerate(results.items(), 1): pdf.set_font('Arial', 'B', 10) phase_name = f"Phase {i}" pdf.cell(0, 6, phase_name, ln=1) pdf.set_font('Arial', '', 9) pdf.cell(0, 4, f' MM: {data["mm"]:.1f}', ln=1) pdf.cell(0, 4, f' Cost: {data["cost"]["Total"]:.1f}B KRW', ln=1) pdf.cell(0, 4, f' Labor: {data["cost"]["Labor"]:.1f}B', ln=1) pdf.cell(0, 4, f' GPU: {data["cost"]["GPU"]:.1f}B', ln=1) pdf.ln(1) pdf.ln(3) # 4. HW ์š”์•ฝ pdf.add_page() pdf.set_font('Arial', 'B', 11) pdf.cell(0, 7, '4. Hardware Summary', ln=1) pdf.ln(2) for phase_id, data in results.items(): hw = data["hw"] pdf.set_font('Arial', '', 9) if "Training_GPU" in hw: pdf.cell(0, 4, f'Training: {hw["GPU_Type"]} x{hw["Training_GPU"]}', ln=1) elif "Preprocess_GPU" in hw: pdf.cell(0, 4, f'Preprocess: {hw["GPU_Type"]} x{hw["Preprocess_GPU"]}', ln=1) elif "Inference_GPU" in hw: pdf.cell(0, 4, f'Inference: {hw["GPU_Type"]} x{hw["Inference_GPU"]}', ln=1) elif "Dev_GPU" in hw: pdf.cell(0, 4, f'Dev: {hw["GPU_Type"]} x{hw["Dev_GPU"]}', ln=1) pdf.cell(0, 4, f' CPU: {hw.get("CPU_Cores", 0)} cores', ln=1) pdf.cell(0, 4, f' RAM: {hw.get("RAM_GB", 0)} GB', ln=1) pdf.cell(0, 4, f' Storage: {hw.get("Storage_TB", 0)} TB', ln=1) pdf.ln(1) timestamp = int(time.time()) pdf_path = f'/tmp/Estimate_{timestamp}.pdf' pdf.output(pdf_path) return pdf_path except Exception as e: print(f"PDF generation error: {e}") return None # ============================================ # Excel ์ƒ์„ฑ ํ•จ์ˆ˜ # ============================================ def generate_excel(results, total_mm, duration_months, N_pages): """Excel ๊ฒฌ์ ์„œ ์ƒ์„ฑ""" try: timestamp = int(time.time()) excel_path = f'/tmp/Estimate_{timestamp}.xlsx' with pd.ExcelWriter(excel_path, engine='xlsxwriter') as writer: # 1. ์š”์•ฝ total_cost = sum(r["cost"]["Total"] for r in results.values()) summary_data = { 'Item': ['Documents', 'Manpower', 'Duration', 'Total Cost (100M KRW)', 'Total Cost (KRW)'], 'Value': [ f'{N_pages:,} pages', f'{total_mm:.1f} MM', f'{duration_months} months', f'{total_cost:.1f}', f'{int(total_cost * 100_000_000):,}' ] } pd.DataFrame(summary_data).to_excel(writer, sheet_name='Summary', index=False) # 2. Phase๋ณ„ ์ƒ์„ธ phase_data = [] for phase_id, data in results.items(): phase_data.append({ 'Phase': data["name"].split(":")[1].strip(), 'MM': round(data["mm"], 1), 'Labor (100M)': round(data["cost"]["Labor"], 1), 'GPU (100M)': round(data["cost"]["GPU"], 1), 'OSS Setup (100M)': round(data["cost"]["OSS_Setup"], 1), 'Total (100M)': round(data["cost"]["Total"], 1) }) pd.DataFrame(phase_data).to_excel(writer, sheet_name='Phase Details', index=False) # 3. HW hw_data = [] for phase_id, data in results.items(): hw = data["hw"] hw_row = {'Phase': data["name"].split(":")[1].strip()} hw_row.update(hw) hw_data.append(hw_row) pd.DataFrame(hw_data).to_excel(writer, sheet_name='Hardware', index=False) # 4. OSS oss_data = [] for phase_id, data in results.items(): for oss in data["oss_stack"]: oss_data.append({ 'Phase': data["name"].split(":")[1].strip(), 'Open Source': oss }) pd.DataFrame(oss_data).to_excel(writer, sheet_name='Open Source', index=False) return excel_path except Exception as e: print(f"Excel generation error: {e}") return None # ============================================ # ์‹œ๊ฐํ™” ํ•จ์ˆ˜ # ============================================ def create_phase_chart(results): """Phase๋ณ„ ๋น„๊ต ์ฐจํŠธ""" names = [results[p]["name"].split(":")[1].strip() for p in results.keys()] mms = [results[p]["mm"] for p in results.keys()] costs = [results[p]["cost"]["Total"] for p in results.keys()] fig = make_subplots( rows=1, cols=2, subplot_titles=('Phase Manpower (MM)', 'Phase Cost (100M KRW)'), specs=[[{'type':'bar'}, {'type':'bar'}]] ) fig.add_trace( go.Bar(x=names, y=mms, text=[f"{m:.1f}" for m in mms], textposition='auto', marker_color='#3498db'), row=1, col=1 ) fig.add_trace( go.Bar(x=names, y=costs, text=[f"{c:.1f}" for c in costs], textposition='auto', marker_color='#e74c3c'), row=1, col=2 ) fig.update_layout(height=400, showlegend=False, title_text="Phase Comparison", title_x=0.5) return fig def create_cost_breakdown(results): """๋น„์šฉ ๊ตฌ์„ฑ ์ฐจํŠธ""" categories = [] values = [] for phase_id, data in results.items(): name = data["name"].split(":")[1].strip() categories.append(f"{name}\nLabor") values.append(data["cost"]["Labor"]) categories.append(f"{name}\nGPU") values.append(data["cost"]["GPU"]) fig = go.Figure(data=[go.Pie( labels=categories, values=values, hole=.4, textinfo='label+percent', textposition='outside' )]) fig.update_layout(title_text="Cost Breakdown", height=500) return fig def create_timeline(results, duration_months): """ํƒ€์ž„๋ผ์ธ ์ฐจํŠธ""" phase_durations = {} start = 0 p1_dur = math.ceil(results["Phase1_DataPrep"]["mm"] / 8) phase_durations["Phase1_DataPrep"] = (start, p1_dur) start += p1_dur p2_dur = math.ceil(results["Phase2_Training"]["mm"] / 10) p3_dur = math.ceil(results["Phase3_AgenticDev"]["mm"] / 6) parallel = max(p2_dur, p3_dur) phase_durations["Phase2_Training"] = (start, p2_dur) phase_durations["Phase3_AgenticDev"] = (start, p3_dur) start += parallel p4_dur = math.ceil(results["Phase4_Deployment"]["mm"] / 8) phase_durations["Phase4_Deployment"] = (start, p4_dur) fig = go.Figure() colors = ['#3498db', '#e74c3c', '#2ecc71', '#f39c12'] for i, (phase_id, (s, d)) in enumerate(phase_durations.items()): name = results[phase_id]["name"].split(":")[1].strip() fig.add_trace(go.Bar( y=[name], x=[d], base=s, orientation='h', marker=dict(color=colors[i]), text=f"{d}mo", textposition='inside', name=name )) fig.update_layout( title="Project Timeline", xaxis_title="Months", yaxis_title="Phase", barmode='overlay', height=400, showlegend=False ) return fig def create_hw_table(results): """HW ํ…Œ์ด๋ธ”""" rows = [] for phase_id, data in results.items(): row = {"Phase": data["name"].split(":")[1].strip()} row.update(data["hw"]) rows.append(row) return pd.DataFrame(rows) def create_oss_table(results): """OSS ํ…Œ์ด๋ธ”""" rows = [] for phase_id, data in results.items(): name = data["name"].split(":")[1].strip() for i, oss in enumerate(data["oss_stack"]): rows.append({ "Phase": name if i == 0 else "", "No": i+1, "Open Source": oss }) return pd.DataFrame(rows) # ============================================ # Gradio UI # ============================================ EXAMPLES = [ [10000, 0.7, 0.8, 20, 0.8, 0.8, 3, 0.8, 1.0, 1.0, 1.0], [50000, 1.0, 1.0, 40, 1.0, 1.0, 3, 1.0, 1.0, 1.0, 1.0], [100000, 1.3, 1.3, 60, 1.0, 1.0, 5, 1.3, 1.0, 1.2, 1.3], [500000, 1.6, 1.5, 80, 1.3, 1.3, 5, 1.6, 1.4, 1.5, 1.6], ] with gr.Blocks(title="Vision LLM Agentic AI ๊ฒฌ์ ", theme=gr.themes.Soft()) as demo: gr.Markdown(""" # ๐Ÿ“˜ Vision LLM ๊ธฐ๋ฐ˜ Agentic AI ํ”„๋กœ์ ํŠธ ๊ฒฌ์  ์‹œ์Šคํ…œ **๋ณด์ˆ˜์  ๊ฒฌ์  / ์˜คํ”ˆ์†Œ์Šค ์ค‘์‹ฌ / Phase๋ณ„ ์ƒ์„ธ ๋ถ„์„ / PDFยทExcel ๋‹ค์šด๋กœ๋“œ** ๐Ÿ’ฐ **๊ธˆ์•ก ๊ธฐ์ค€**: ์›ํ™”(KRW), ๋‹จ์œ„: ์–ต์› | ํ™˜์œจ: USD 1,330์› """) with gr.Tab("๐Ÿ“‹ ํŒŒ๋ผ๋ฏธํ„ฐ ์ž…๋ ฅ"): gr.Markdown("## 1๏ธโƒฃ ํ”„๋กœ์ ํŠธ ๊ทœ๋ชจ") N_pages = gr.Number(label="๐Ÿ“„ ๋ฌธ์„œ ํŽ˜์ด์ง€ ์ˆ˜", value=10000) gr.Markdown("## 2๏ธโƒฃ ๋ฌธ์„œ ํŠน์„ฑ") with gr.Row(): doc_complexity = gr.Slider(0.7, 1.6, value=1.0, step=0.1, label="๐Ÿ“Š ๋ฌธ์„œ ๋ณต์žก๋„", info="0.7=๋‹จ์ˆœ, 1.0=๋ณดํ†ต, 1.3=๋ณต์žก, 1.6=๋งค์šฐ๋ณต์žก") language_mix = gr.Slider(0.8, 1.5, value=1.0, step=0.1, label="๐ŸŒ ์–ธ์–ด ๋ณต์žก๋„", info="0.8=๋‹จ์ผ, 1.0=์ด์ค‘, 1.3=๋‹ค๊ตญ์–ด") with gr.Row(): table_ratio = gr.Slider(0, 100, value=40, step=5, label="๐Ÿ“‹ ํ‘œ ๋น„์œจ (%)") image_quality = gr.Slider(0.8, 1.6, value=1.0, step=0.1, label="๐Ÿ–ผ๏ธ ์ด๋ฏธ์ง€ ํ’ˆ์งˆ", info="0.8=๊ณ , 1.0=์ค‘, 1.3=์ €") gr.Markdown("## 3๏ธโƒฃ ๋ชจ๋ธ ๋ฐ ํ•™์Šต") with gr.Row(): model_size = gr.Slider(0.8, 1.6, value=1.0, step=0.1, label="๐Ÿค– ๋ชจ๋ธ ํฌ๊ธฐ", info="0.8=์†Œํ˜•, 1.0=์ค‘ํ˜•, 1.3=๋Œ€ํ˜•") training_epochs = gr.Slider(1, 10, value=3, step=1, label="๐Ÿ”„ ํ•™์Šต Epoch") gr.Markdown("## 4๏ธโƒฃ Agentic AI ๋ฐ ๋ฐฐํฌ") with gr.Row(): agent_complexity = gr.Slider(0.8, 1.6, value=1.0, step=0.1, label="๐ŸŽฏ Agent ๋ณต์žก๋„", info="0.8=๊ธฐ๋ณธ, 1.0=ํ‘œ์ค€, 1.3=๊ณ ๊ธ‰") deployment_type = gr.Slider(0.9, 1.4, value=1.0, step=0.1, label="๐Ÿ—๏ธ ๋ฐฐํฌ ํ™˜๊ฒฝ", info="0.9=Cloud, 1.0=On-Prem, 1.4=Air-Gap") gr.Markdown("## 5๏ธโƒฃ ์šด์˜ ์š”๊ตฌ์‚ฌํ•ญ") with gr.Row(): sla_level = gr.Slider(1.0, 1.5, value=1.0, step=0.1, label="๐Ÿ“ˆ SLA ๋“ฑ๊ธ‰", info="1.0=ํ‘œ์ค€, 1.2=๋†’์Œ, 1.5=๋ฏธ์…˜ํฌ๋ฆฌํ‹ฐ์ปฌ") security_level = gr.Slider(1.0, 1.6, value=1.0, step=0.1, label="๐Ÿ” ๋ณด์•ˆ ๋“ฑ๊ธ‰", info="1.0=์ผ๋ฐ˜, 1.3=๊ฐ•ํ™”, 1.6=์ตœ๊ณ ") estimate_btn = gr.Button("๐Ÿš€ ๊ฒฌ์  ์‚ฐ์ •", variant="primary", size="lg") gr.Markdown("### ๐Ÿ“Œ ์˜ˆ์ œ ์‹œ๋‚˜๋ฆฌ์˜ค") gr.Examples( examples=EXAMPLES, inputs=[N_pages, doc_complexity, language_mix, table_ratio, image_quality, model_size, training_epochs, agent_complexity, deployment_type, sla_level, security_level], ) with gr.Tab("๐Ÿ“Š ๊ฒฌ์  ๊ฒฐ๊ณผ"): summary_text = gr.Markdown() gr.Markdown("### ๐Ÿ“ฅ ๊ฒฌ์ ์„œ ๋‹ค์šด๋กœ๋“œ") with gr.Row(): pdf_download = gr.File(label="๐Ÿ“„ PDF") excel_download = gr.File(label="๐Ÿ“Š Excel") with gr.Row(): phase_chart = gr.Plot() cost_chart = gr.Plot() timeline_chart = gr.Plot() gr.Markdown("### ๐Ÿ’ป ํ•˜๋“œ์›จ์–ด") hw_table = gr.Dataframe() gr.Markdown("### ๐Ÿ”ง ์˜คํ”ˆ์†Œ์Šค") oss_table = gr.Dataframe() with gr.Tab("โ„น๏ธ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ€์ด๋“œ"): gr.Markdown(""" # ๐Ÿ“– ํŒŒ๋ผ๋ฏธํ„ฐ ์ƒ์„ธ ์„ค๋ช… ## ๐Ÿ“Š ๋ฌธ์„œ ๋ณต์žก๋„ - **0.7 (๋‹จ์ˆœ)**: ํ…์ŠคํŠธ ์œ„์ฃผ, ํ‘œ ์—†์Œ - **1.0 (๋ณดํ†ต)**: ํ…์ŠคํŠธ + ๋‹จ์ˆœ ํ‘œ - **1.3 (๋ณต์žก)**: ํ…์ŠคํŠธ + ๋ณต์žกํ•œ ํ‘œ + ์ด๋ฏธ์ง€ - **1.6 (๋งค์šฐ๋ณต์žก)**: ๋‹ค๋‹จ ๋ ˆ์ด์•„์›ƒ + ์ˆ˜์‹ + ๋‹ค๊ตญ์–ด ## ๐ŸŒ ์–ธ์–ด ๋ณต์žก๋„ - **0.8 (๋‹จ์ผ)**: ํ•œ๊ตญ์–ด๋งŒ - **1.0 (์ด์ค‘)**: ํ•œ๊ตญ์–ด + ์˜์–ด - **1.3 (๋‹ค๊ตญ์–ด)**: ํ•œ/์˜/์ผ/์ค‘ ํ˜ผํ•ฉ - **1.5 (ํŠน์ˆ˜)**: ๋‹ค๊ตญ์–ด + ํŠน์ˆ˜๋ฌธ์ž ## ๐Ÿ“‹ ํ‘œ ๋น„์œจ - ์ „์ฒด ๋ฌธ์„œ ์ค‘ ํ‘œ๊ฐ€ ํฌํ•จ๋œ ๋น„์œจ (%) - ํ‘œ ๊ตฌ์กฐ ์ธ์‹์€ ๊ฐ€์žฅ ๋ณต์žกํ•œ ์ž‘์—… ## ๐Ÿ–ผ๏ธ ์ด๋ฏธ์ง€ ํ’ˆ์งˆ - **0.8 (๊ณ ํ’ˆ์งˆ)**: ์Šค์บ” 300dpi+ - **1.0 (๋ณดํ†ต)**: ์Šค์บ” 200dpi - **1.3 (์ €ํ’ˆ์งˆ)**: ์Šค์บ” 150dpi - **1.6 (๋งค์šฐ๋‚ฎ์Œ)**: ์‚ฌ์ง„์ดฌ์˜, ์™œ๊ณก ## ๐Ÿค– ๋ชจ๋ธ ํฌ๊ธฐ - **0.8 (์†Œํ˜•)**: 2B~7B ํŒŒ๋ผ๋ฏธํ„ฐ - **1.0 (์ค‘ํ˜•)**: 8B~14B - **1.3 (๋Œ€ํ˜•)**: 20B~40B - **1.6 (์ดˆ๋Œ€ํ˜•)**: 70B+ ## ๐ŸŽฏ Agent ๋ณต์žก๋„ - **0.8 (๊ธฐ๋ณธ)**: ๋‹จ์ผ Agent, ๋‹จ์ˆœ RAG - **1.0 (ํ‘œ์ค€)**: 2~3 Agent - **1.3 (๊ณ ๊ธ‰)**: Multi-Agent, ๋ณต์žกํ•œ Tool - **1.6 (์—”ํ„ฐํ”„๋ผ์ด์ฆˆ)**: Self-Learning ## ๐Ÿ—๏ธ ๋ฐฐํฌ ํ™˜๊ฒฝ - **0.9 (Cloud)**: AWS/GCP/Azure - **1.0 (On-Premise)**: ์ž์ฒด ์„œ๋ฒ„ - **1.4 (Air-Gap)**: ํ์‡„๋ง, ๊ณ ๋ณด์•ˆ ## ๐Ÿ“ˆ SLA ๋“ฑ๊ธ‰ - **1.0 (ํ‘œ์ค€)**: 99% ๊ฐ€์šฉ์„ฑ - **1.2 (๋†’์Œ)**: 99.5% ๊ฐ€์šฉ์„ฑ - **1.5 (๋ฏธ์…˜ํฌ๋ฆฌํ‹ฐ์ปฌ)**: 99.9% ๊ฐ€์šฉ์„ฑ ## ๐Ÿ” ๋ณด์•ˆ ๋“ฑ๊ธ‰ - **1.0 (์ผ๋ฐ˜)**: ๊ธฐ๋ณธ ์ธ์ฆ/์•”ํ˜ธํ™” - **1.3 (๊ฐ•ํ™”)**: ๋‹ค์ค‘ ์ธ์ฆ, ๊ฐ์‚ฌ ๋กœ๊ทธ - **1.6 (์ตœ๊ณ )**: Zero-Trust, ์™„์ „ ๊ฒฉ๋ฆฌ --- ## ๐Ÿ’ฐ ๊ธˆ์•ก ์‚ฐ์ถœ ๊ธฐ์ค€ ### ํ†ตํ™” ๋ฐ ๋‹จ์œ„ - **๊ธฐ์ค€**: ์›ํ™” (KRW) - **ํ‘œ์‹œ**: ์–ต์› - **ํ™˜์œจ**: USD 1,330์› ### ์ธ๊ฑด๋น„ - **1 MM = 15,000,000์›** (1,500๋งŒ์›/์›”) ### GPU ๋‹จ๊ฐ€ - **A100 80GB: 40์–ต์›** (โ‰ˆ $30,000) - **H100 80GB: 60์–ต์›** (โ‰ˆ $45,000) ### ๊ธฐํƒ€ - **OSS ๊ตฌ์ถ•: 300๋งŒ์›/MM** - **์Šคํ† ๋ฆฌ์ง€: 200๋งŒ์›/TB** """) with gr.Tab("๐Ÿ’ฐ ๊ธˆ์•ก ์ƒ์„ธ"): gr.Markdown(f""" # ๐Ÿ’ฐ ๊ธˆ์•ก ์‚ฐ์ถœ ์ƒ์„ธ ## ์ธ๊ฑด๋น„ (์›ํ™”) | ์ง๊ธ‰ | ์›” ๋‹จ๊ฐ€ | ์—ฐ๋ด‰ ํ™˜์‚ฐ | |------|---------|----------| | ์•„ํ‚คํ…ํŠธ | {LABOR_RATES['Architect']:,}์› | {LABOR_RATES['Architect']*12:,}์› | | ์‹œ๋‹ˆ์–ด | {LABOR_RATES['Senior_Engineer']:,}์› | {LABOR_RATES['Senior_Engineer']*12:,}์› | | ์ค‘๊ธ‰ | {LABOR_RATES['Mid_Engineer']:,}์› | {LABOR_RATES['Mid_Engineer']*12:,}์› | | ์ฃผ๋‹ˆ์–ด | {LABOR_RATES['Junior_Engineer']:,}์› | {LABOR_RATES['Junior_Engineer']*12:,}์› | | **ํ‰๊ท ** | **{LABOR_RATES['Average']:,}์›** | **{LABOR_RATES['Average']*12:,}์›** | ## GPU ๋‹จ๊ฐ€ (์›ํ™”) | GPU | ๋‹จ๊ฐ€ (์›) | ๋‹จ๊ฐ€ (๋‹ฌ๋Ÿฌ) | ์šฉ๋„ | |-----|-----------|------------|------| | H100 80GB | {GPU_COSTS_KRW['H100_80GB']:,}์› | $45,000 | ๋Œ€๊ทœ๋ชจ ํ•™์Šต | | A100 80GB | {GPU_COSTS_KRW['A100_80GB']:,}์› | $30,000 | ํ‘œ์ค€ ํ•™์Šต | | A100 40GB | {GPU_COSTS_KRW['A100_40GB']:,}์› | $18,750 | ๊ฐœ๋ฐœ/ํ…Œ์ŠคํŠธ | | L40S | {GPU_COSTS_KRW['L40S']:,}์› | $11,250 | ์ „์ฒ˜๋ฆฌ | ## ๊ธฐํƒ€ ๋น„์šฉ | ํ•ญ๋ชฉ | ๋‹จ๊ฐ€ | ๋‹จ์œ„ | |------|------|------| | ์Šคํ† ๋ฆฌ์ง€ (NVMe) | {STORAGE_COST_PER_TB:,}์› | TB | | OSS ๊ตฌ์ถ• | {OSS_SETUP_COST_PER_MM:,}์› | MM | | ๊ณ ๊ธ‰ ์„œ๋ฒ„ | {SERVER_COSTS_KRW['CPU_High_End']:,}์› | ๋Œ€ | | RAM 512GB | {SERVER_COSTS_KRW['RAM_512GB']:,}์› | ์„ธํŠธ | ## ๋น„์šฉ ์‚ฐ์ • ๊ณต์‹ ``` Total = Labor + GPU + Server + Storage + OSS + Infrastructure Labor = MM ร— 15,000,000์› GPU = GPU_Count ร— GPU_Unit_Price OSS = MM ร— 3,000,000์› Infrastructure = (GPU + Server + Storage) ร— 0.3 ``` ## ์•ˆ์ „๊ณ„์ˆ˜ - **์•ˆ์ „๊ณ„์ˆ˜**: 1.4 (40% ๋ฒ„ํผ) - **GPU ํšจ์œจ**: 0.65 (65% ํ™œ์šฉ๋ฅ ) - **OSS ์ธ๋ ฅ**: 1.5๋ฐฐ (์ƒ์šฉ ๋Œ€๋น„) """) estimate_btn.click( fn=run_estimation, inputs=[N_pages, doc_complexity, language_mix, table_ratio, image_quality, model_size, training_epochs, agent_complexity, deployment_type, sla_level, security_level], outputs=[summary_text, phase_chart, cost_chart, timeline_chart, hw_table, oss_table, pdf_download, excel_download] ) if __name__ == "__main__": print("๐Ÿš€ Starting Gradio app...") demo.launch()