StartUp-Value / app.py
openfree's picture
Create app.py
b378e61 verified
raw
history blame
15.4 kB
import gradio as gr
import pandas as pd
import numpy as np
from datetime import datetime
import plotly.graph_objects as go
class StartupValuationCalculator:
def __init__(self):
# ์—…์ข…๋ณ„ ๋ฒค์น˜๋งˆํฌ ๋ฉ€ํ‹ฐํ”Œ (EV/ARR)
self.industry_multiples = {
"SaaS - B2B": {"low": 3, "mid": 6, "high": 10},
"SaaS - B2C": {"low": 2, "mid": 4, "high": 7},
"๋งˆ์ผ“ํ”Œ๋ ˆ์ด์Šค": {"low": 2, "mid": 5, "high": 8},
"์ด์ปค๋จธ์Šค": {"low": 1, "mid": 2.5, "high": 4},
"ํ•€ํ…Œํฌ": {"low": 3, "mid": 5, "high": 8},
"ํ—ฌ์Šค์ผ€์–ด": {"low": 4, "mid": 7, "high": 12},
"AI/๋”ฅํ…Œํฌ": {"low": 5, "mid": 8, "high": 15},
"๊ธฐํƒ€": {"low": 2, "mid": 4, "high": 6}
}
# ์„ฑ์žฅ๋ฅ  ์กฐ์ • ๊ณ„์ˆ˜
self.growth_adjustments = {
"0-20%": 0.7,
"20-50%": 0.9,
"50-100%": 1.1,
"100-200%": 1.3,
"200%+": 1.5
}
# ๋‹จ์œ„๊ฒฝ์ œ ์ ์ˆ˜ ๊ฐ€์ค‘์น˜
self.unit_economics_weights = {
"ltv_cac_ratio": 0.3,
"gross_margin": 0.3,
"retention": 0.2,
"payback": 0.2
}
def calculate_arr(self, monthly_revenue, revenue_type):
"""์›” ๋งค์ถœ์„ ์—ฐ๊ฐ„ ๋ฐ˜๋ณต ๋งค์ถœ(ARR)๋กœ ๋ณ€ํ™˜"""
if revenue_type == "๊ตฌ๋…ํ˜• (SaaS)":
return monthly_revenue * 12
elif revenue_type == "๊ฑฐ๋ž˜์ˆ˜์ˆ˜๋ฃŒํ˜•":
return monthly_revenue * 12 * 0.8 # ๊ฑฐ๋ž˜์ˆ˜์ˆ˜๋ฃŒ๋Š” ๋ณ€๋™์„ฑ ๊ณ ๋ ค
else:
return monthly_revenue * 12 * 0.6 # ์ผํšŒ์„ฑ ๋งค์ถœ์€ ๋” ํ• ์ธ
def calculate_ltv(self, arpu, gross_margin, monthly_churn):
"""LTV ๊ณ„์‚ฐ"""
if monthly_churn == 0:
monthly_churn = 0.01 # ์ตœ์†Œ ์ดํƒˆ๋ฅ 
return arpu * (gross_margin / 100) / monthly_churn
def calculate_cac(self, monthly_marketing, monthly_sales, new_customers):
"""CAC ๊ณ„์‚ฐ"""
if new_customers == 0:
return 0
return (monthly_marketing + monthly_sales) / new_customers
def calculate_payback(self, cac, arpu, gross_margin):
"""Payback Period ๊ณ„์‚ฐ (๊ฐœ์›”)"""
if arpu * (gross_margin / 100) == 0:
return 999
return cac / (arpu * (gross_margin / 100))
def get_unit_economics_score(self, ltv_cac_ratio, gross_margin, retention_rate, payback_months):
"""๋‹จ์œ„๊ฒฝ์ œ ์ ์ˆ˜ ๊ณ„์‚ฐ (0-100)"""
scores = {
"ltv_cac_ratio": min(100, (ltv_cac_ratio / 3) * 100) if ltv_cac_ratio > 0 else 0,
"gross_margin": min(100, gross_margin * 1.25),
"retention": retention_rate,
"payback": max(0, 100 - (payback_months / 24) * 100) if payback_months < 999 else 0
}
total_score = sum(scores[key] * self.unit_economics_weights[key] for key in scores)
return total_score
def get_growth_category(self, growth_rate):
"""์„ฑ์žฅ๋ฅ  ์นดํ…Œ๊ณ ๋ฆฌ ๊ฒฐ์ •"""
if growth_rate < 20:
return "0-20%"
elif growth_rate < 50:
return "20-50%"
elif growth_rate < 100:
return "50-100%"
elif growth_rate < 200:
return "100-200%"
else:
return "200%+"
def calculate_valuation(self, data):
"""์ข…ํ•ฉ ๊ฐ€์น˜ํ‰๊ฐ€ ๊ณ„์‚ฐ"""
# ARR ๊ณ„์‚ฐ
arr = self.calculate_arr(data["monthly_revenue"], data["revenue_type"])
# ๋‹จ์œ„๊ฒฝ์ œ ๊ณ„์‚ฐ
ltv = self.calculate_ltv(data["arpu"], data["gross_margin"], data["monthly_churn"])
cac = self.calculate_cac(data["monthly_marketing"], data["monthly_sales"], data["new_customers"])
ltv_cac_ratio = ltv / cac if cac > 0 else 0
payback = self.calculate_payback(cac, data["arpu"], data["gross_margin"])
# ๋‹จ์œ„๊ฒฝ์ œ ์ ์ˆ˜
ue_score = self.get_unit_economics_score(
ltv_cac_ratio, data["gross_margin"], data["retention_rate"], payback
)
# ๊ธฐ๋ณธ ๋ฉ€ํ‹ฐํ”Œ ์„ ํƒ
multiples = self.industry_multiples[data["industry"]]
if ue_score >= 80:
base_multiple = multiples["high"]
elif ue_score >= 50:
base_multiple = multiples["mid"]
else:
base_multiple = multiples["low"]
# ์„ฑ์žฅ๋ฅ  ์กฐ์ •
growth_adj = self.growth_adjustments[self.get_growth_category(data["growth_rate"])]
adjusted_multiple = base_multiple * growth_adj
# ์Šคํ…Œ์ด์ง€ ์กฐ์ •
stage_adj = {
"MVP/๋ฒ ํƒ€": 0.7,
"์ดˆ๊ธฐ ๋งค์ถœ": 0.85,
"์„ฑ์žฅ ๋‹จ๊ณ„": 1.0,
"์ˆ˜์ต์„ฑ ํ™•๋ณด": 1.2
}
final_multiple = adjusted_multiple * stage_adj[data["stage"]]
# ์ตœ์ข… ๊ฐ€์น˜ํ‰๊ฐ€
valuation = arr * final_multiple
# ๋Ÿฐ์›จ์ด ๊ณ„์‚ฐ
runway = data["cash_balance"] / data["burn_rate"] if data["burn_rate"] > 0 else 999
return {
"valuation": valuation,
"arr": arr,
"multiple": final_multiple,
"ltv": ltv,
"cac": cac,
"ltv_cac_ratio": ltv_cac_ratio,
"payback": payback,
"ue_score": ue_score,
"runway": runway
}
def create_comparison_chart(self, valuation, industry, arr):
"""๋™์ข…์—…๊ณ„ ๋น„๊ต ์ฐจํŠธ ์ƒ์„ฑ"""
multiples = self.industry_multiples[industry]
fig = go.Figure()
# ์—…๊ณ„ ๋ฒ”์œ„
low_val = arr * multiples["low"]
mid_val = arr * multiples["mid"]
high_val = arr * multiples["high"]
# ๋ง‰๋Œ€ ๊ทธ๋ž˜ํ”„
fig.add_trace(go.Bar(
x=["ํ•˜์œ„ 25%", "์ค‘๊ฐ„๊ฐ’", "์ƒ์œ„ 25%", "ํ˜„์žฌ ๊ธฐ์—…"],
y=[low_val, mid_val, high_val, valuation],
text=[f"${low_val/1000000:.1f}M", f"${mid_val/1000000:.1f}M",
f"${high_val/1000000:.1f}M", f"${valuation/1000000:.1f}M"],
textposition="outside",
marker_color=["lightgray", "gray", "darkgray", "blue"]
))
fig.update_layout(
title=f"{industry} ์—…๊ณ„ ๊ฐ€์น˜ํ‰๊ฐ€ ๋น„๊ต",
yaxis_title="๊ธฐ์—…๊ฐ€์น˜ (USD)",
showlegend=False,
height=400
)
return fig
def create_ui():
calculator = StartupValuationCalculator()
def process_valuation(
company_name, founded_year, industry, stage, revenue_type,
monthly_revenue, growth_rate, arpu, gross_margin, monthly_churn,
retention_rate, new_customers, monthly_marketing, monthly_sales,
cash_balance, burn_rate
):
# ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ
if monthly_revenue <= 0:
return "์›” ๋งค์ถœ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.", None, None
# ๋ฐ์ดํ„ฐ ์ค€๋น„
data = {
"company_name": company_name,
"founded_year": founded_year,
"industry": industry,
"stage": stage,
"revenue_type": revenue_type,
"monthly_revenue": monthly_revenue * 1000, # ์ฒœ ๋‹ฌ๋Ÿฌ ๋‹จ์œ„๋กœ ์ž…๋ ฅ๋ฐ›์Œ
"growth_rate": growth_rate,
"arpu": arpu,
"gross_margin": gross_margin,
"monthly_churn": monthly_churn / 100,
"retention_rate": retention_rate,
"new_customers": new_customers,
"monthly_marketing": monthly_marketing * 1000,
"monthly_sales": monthly_sales * 1000,
"cash_balance": cash_balance * 1000,
"burn_rate": burn_rate * 1000
}
# ๊ฐ€์น˜ํ‰๊ฐ€ ๊ณ„์‚ฐ
results = calculator.calculate_valuation(data)
# ๊ฒฐ๊ณผ ํฌ๋งทํŒ…
valuation_text = f"""
# ๐Ÿš€ {company_name} ๊ฐ€์น˜ํ‰๊ฐ€ ๊ฒฐ๊ณผ
## ๐Ÿ“Š ์ฃผ์š” ์ง€ํ‘œ
- **๊ธฐ์—…๊ฐ€์น˜**: ${results['valuation']/1000000:.1f}M (โ‚ฉ{results['valuation']/1000000*1300:.0f}์–ต)
- **ARR**: ${results['arr']/1000000:.1f}M
- **์ ์šฉ ๋ฉ€ํ‹ฐํ”Œ**: {results['multiple']:.1f}x
## ๐Ÿ’ฐ ๋‹จ์œ„๊ฒฝ์ œ
- **LTV**: ${results['ltv']:.0f}
- **CAC**: ${results['cac']:.0f}
- **LTV/CAC**: {results['ltv_cac_ratio']:.1f}x
- **Payback Period**: {results['payback']:.1f}๊ฐœ์›”
- **๋‹จ์œ„๊ฒฝ์ œ ์ ์ˆ˜**: {results['ue_score']:.0f}/100
## ๐Ÿƒ ์žฌ๋ฌด ๊ฑด์ „์„ฑ
- **ํ˜„๊ธˆ ๋Ÿฐ์›จ์ด**: {results['runway']:.1f}๊ฐœ์›”
- **์›”๊ฐ„ ๋ฒˆ๋ ˆ์ดํŠธ**: ${burn_rate}K
## ๐Ÿ’ก ํ‰๊ฐ€ ์ธ์‚ฌ์ดํŠธ
"""
# ์ธ์‚ฌ์ดํŠธ ์ถ”๊ฐ€
if results['ltv_cac_ratio'] < 1:
valuation_text += "- โš ๏ธ LTV/CAC ๋น„์œจ์ด 1 ๋ฏธ๋งŒ์ž…๋‹ˆ๋‹ค. ๋งˆ์ผ€ํŒ… ํšจ์œจ์„ฑ ๊ฐœ์„ ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.\n"
elif results['ltv_cac_ratio'] > 3:
valuation_text += "- โœ… ์šฐ์ˆ˜ํ•œ LTV/CAC ๋น„์œจ์„ ๋ณด์ด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.\n"
if results['runway'] < 12:
valuation_text += "- โš ๏ธ ๋Ÿฐ์›จ์ด๊ฐ€ 12๊ฐœ์›” ๋ฏธ๋งŒ์ž…๋‹ˆ๋‹ค. ์ถ”๊ฐ€ ์ž๊ธˆ์กฐ๋‹ฌ์„ ๊ณ ๋ คํ•˜์„ธ์š”.\n"
if gross_margin < 60:
valuation_text += "- ๐Ÿ“ˆ ๋งค์ถœ์ด์ด์ต๋ฅ  ๊ฐœ์„  ์—ฌ์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. (์—…๊ณ„ ํ‰๊ท : 70-80%)\n"
# ๋น„๊ต ์ฐจํŠธ ์ƒ์„ฑ
comparison_chart = calculator.create_comparison_chart(
results['valuation'], industry, results['arr']
)
# ์ƒ์„ธ ๋ถ„์„ ํ…Œ์ด๋ธ”
metrics_df = pd.DataFrame({
"์ง€ํ‘œ": ["์›” ๋งค์ถœ", "์—ฐ ์„ฑ์žฅ๋ฅ ", "๋งค์ถœ์ด์ด์ต๋ฅ ", "์›” ์ดํƒˆ๋ฅ ", "๊ณ ๊ฐ ์œ ์ง€์œจ"],
"ํ˜„์žฌ ๊ฐ’": [f"${monthly_revenue}K", f"{growth_rate}%", f"{gross_margin}%",
f"{monthly_churn}%", f"{retention_rate}%"],
"์—…๊ณ„ ํ‰๊ท ": ["N/A", "50-100%", "70-80%", "2-5%", "80-90%"]
})
return valuation_text, comparison_chart, metrics_df
# Gradio UI
with gr.Blocks(title="์Šคํƒ€ํŠธ์—… ๊ฐ€์น˜ํ‰๊ฐ€ ๊ณ„์‚ฐ๊ธฐ", theme=gr.themes.Soft()) as demo:
gr.Markdown("""
# ๐Ÿฆ„ ์Šคํƒ€ํŠธ์—… ๊ฐ€์น˜ํ‰๊ฐ€ ์ž๋™ํ™” ์‹œ์Šคํ…œ
๊ฐ„๋‹จํ•œ ์ •๋ณด ์ž…๋ ฅ๋งŒ์œผ๋กœ ๊ท€์‚ฌ์˜ ์˜ˆ์ƒ ๊ธฐ์—…๊ฐ€์น˜๋ฅผ ์‚ฐ์ถœํ•˜๊ณ  ๋™์ข…์—…๊ณ„์™€ ๋น„๊ตํ•ด๋“œ๋ฆฝ๋‹ˆ๋‹ค.
""")
with gr.Tab("๊ธฐ๋ณธ ์ •๋ณด"):
with gr.Row():
company_name = gr.Textbox(label="ํšŒ์‚ฌ๋ช…", value="์šฐ๋ฆฌ ์Šคํƒ€ํŠธ์—…")
founded_year = gr.Slider(2015, 2024, value=2022, step=1, label="์„ค๋ฆฝ์—ฐ๋„")
with gr.Row():
industry = gr.Dropdown(
choices=list(calculator.industry_multiples.keys()),
value="SaaS - B2B",
label="์‚ฐ์—… ๋ถ„๋ฅ˜"
)
stage = gr.Radio(
choices=["MVP/๋ฒ ํƒ€", "์ดˆ๊ธฐ ๋งค์ถœ", "์„ฑ์žฅ ๋‹จ๊ณ„", "์ˆ˜์ต์„ฑ ํ™•๋ณด"],
value="์ดˆ๊ธฐ ๋งค์ถœ",
label="์‚ฌ์—… ๋‹จ๊ณ„"
)
revenue_type = gr.Radio(
choices=["๊ตฌ๋…ํ˜• (SaaS)", "๊ฑฐ๋ž˜์ˆ˜์ˆ˜๋ฃŒํ˜•", "์ผํšŒ์„ฑ ํŒ๋งค"],
value="๊ตฌ๋…ํ˜• (SaaS)",
label="์ˆ˜์ต ๋ชจ๋ธ"
)
with gr.Tab("๋งค์ถœ ๋ฐ ์„ฑ์žฅ"):
gr.Markdown("### ๐Ÿ’ฐ ๋งค์ถœ ์ •๋ณด (๋‹จ์œ„: ์ฒœ ๋‹ฌ๋Ÿฌ)")
with gr.Row():
monthly_revenue = gr.Number(label="์›” ๋งค์ถœ ($K)", value=50)
growth_rate = gr.Slider(0, 300, value=100, step=10,
label="์—ฐ๊ฐ„ ์„ฑ์žฅ๋ฅ  (%)")
with gr.Row():
arpu = gr.Number(label="๊ณ ๊ฐ๋‹น ํ‰๊ท  ๋งค์ถœ (ARPU) ($)", value=100)
gross_margin = gr.Slider(0, 100, value=70, step=5,
label="๋งค์ถœ์ด์ด์ต๋ฅ  (%)")
with gr.Tab("๊ณ ๊ฐ ๋ฐ ๋งˆ์ผ€ํŒ…"):
gr.Markdown("### ๐Ÿ‘ฅ ๊ณ ๊ฐ ์ง€ํ‘œ")
with gr.Row():
retention_rate = gr.Slider(0, 100, value=85, step=5,
label="์›”๊ฐ„ ๊ณ ๊ฐ ์œ ์ง€์œจ (%)")
monthly_churn = gr.Slider(0, 20, value=3, step=0.5,
label="์›” ์ดํƒˆ๋ฅ  (%)")
gr.Markdown("### ๐Ÿ“ข ๋งˆ์ผ€ํŒ… ํšจ์œจ์„ฑ")
with gr.Row():
new_customers = gr.Number(label="์›” ์‹ ๊ทœ ๊ณ ๊ฐ ์ˆ˜", value=50)
monthly_marketing = gr.Number(label="์›” ๋งˆ์ผ€ํŒ… ๋น„์šฉ ($K)", value=20)
monthly_sales = gr.Number(label="์›” ์˜์—… ๋น„์šฉ ($K)", value=15)
with gr.Tab("์žฌ๋ฌด ํ˜„ํ™ฉ"):
gr.Markdown("### ๐Ÿ’ธ ํ˜„๊ธˆ ์ƒํ™ฉ (๋‹จ์œ„: ์ฒœ ๋‹ฌ๋Ÿฌ)")
with gr.Row():
cash_balance = gr.Number(label="ํ˜„๊ธˆ ์ž”๊ณ  ($K)", value=1000)
burn_rate = gr.Number(label="์›” ๋ฒˆ๋ ˆ์ดํŠธ ($K)", value=80)
# ํ‰๊ฐ€ ์‹คํ–‰ ๋ฒ„ํŠผ
evaluate_btn = gr.Button("๐Ÿ” ๊ฐ€์น˜ํ‰๊ฐ€ ์‹คํ–‰", variant="primary", size="lg")
# ๊ฒฐ๊ณผ ์ถœ๋ ฅ
with gr.Row():
with gr.Column(scale=2):
valuation_output = gr.Markdown(label="ํ‰๊ฐ€ ๊ฒฐ๊ณผ")
with gr.Column(scale=1):
metrics_table = gr.DataFrame(label="์ฃผ์š” ์ง€ํ‘œ ๋น„๊ต")
comparison_chart = gr.Plot(label="๋™์ข…์—…๊ณ„ ๋น„๊ต")
# ์ด๋ฒคํŠธ ์—ฐ๊ฒฐ
evaluate_btn.click(
process_valuation,
inputs=[
company_name, founded_year, industry, stage, revenue_type,
monthly_revenue, growth_rate, arpu, gross_margin, monthly_churn,
retention_rate, new_customers, monthly_marketing, monthly_sales,
cash_balance, burn_rate
],
outputs=[valuation_output, comparison_chart, metrics_table]
)
# ์˜ˆ์‹œ ๋ฐ์ดํ„ฐ ๋ฒ„ํŠผ๋“ค
gr.Markdown("### ๐Ÿ“ ์˜ˆ์‹œ ๋ฐ์ดํ„ฐ๋กœ ํ…Œ์ŠคํŠธํ•˜๊ธฐ")
with gr.Row():
gr.Button("SaaS ์Šคํƒ€ํŠธ์—… ์˜ˆ์‹œ").click(
lambda: [
"ํ…Œํฌ ์Šคํƒ€ํŠธ์—…", 2021, "SaaS - B2B", "์„ฑ์žฅ ๋‹จ๊ณ„", "๊ตฌ๋…ํ˜• (SaaS)",
100, 150, 200, 75, 2,
90, 40, 30, 20,
2000, 120
],
outputs=[
company_name, founded_year, industry, stage, revenue_type,
monthly_revenue, growth_rate, arpu, gross_margin, monthly_churn,
retention_rate, new_customers, monthly_marketing, monthly_sales,
cash_balance, burn_rate
]
)
gr.Button("์ด์ปค๋จธ์Šค ์˜ˆ์‹œ").click(
lambda: [
"์˜จ๋ผ์ธ ์‡ผํ•‘๋ชฐ", 2022, "์ด์ปค๋จธ์Šค", "์ดˆ๊ธฐ ๋งค์ถœ", "์ผํšŒ์„ฑ ํŒ๋งค",
80, 80, 50, 40, 5,
70, 100, 25, 10,
500, 70
],
outputs=[
company_name, founded_year, industry, stage, revenue_type,
monthly_revenue, growth_rate, arpu, gross_margin, monthly_churn,
retention_rate, new_customers, monthly_marketing, monthly_sales,
cash_balance, burn_rate
]
)
return demo
# ์‹คํ–‰
if __name__ == "__main__":
demo = create_ui()
demo.launch(share=True)