| """ |
| 🧮 CoBIZ 세금 절감 시뮬레이터 & R&D 예산 편성 계산기 |
| ================================================================================ |
| 1. 세금 절감 시뮬레이터: 기업 프로필 기반 7대 세제 혜택 자동 스캔 + 예상 절감액 |
| 2. R&D 예산 편성 계산기: 총 사업비 → 비목별 자동 배분 + 상한 체크 |
| ================================================================================ |
| """ |
|
|
| import gradio as gr |
| import math |
| from datetime import datetime, date |
|
|
| |
| |
| |
|
|
| |
| STARTUP_ELIGIBLE_INDUSTRIES = [ |
| "C. 제조업", "J. 정보통신업", "M. 전문·과학·기술", "N. 사업시설관리", |
| "G. 도매·소매업", "H. 운수·창고업", "F. 건설업", "R. 예술·스포츠", |
| "P. 교육서비스업", "Q. 보건·사회복지" |
| ] |
|
|
| |
| SME_SPECIAL_RATES = { |
| "C. 제조업": {"소기업": 0.20, "중기업": 0.10, "소기업_수도권": 0.10}, |
| "E. 수도·환경": {"소기업": 0.20, "중기업": 0.10, "소기업_수도권": 0.10}, |
| "F. 건설업": {"소기업": 0.20, "중기업": 0.10, "소기업_수도권": 0.10}, |
| "G. 도매·소매업": {"소기업": 0.10, "중기업": 0.05, "소기업_수도권": 0.05}, |
| "H. 운수·창고업": {"소기업": 0.20, "중기업": 0.10, "소기업_수도권": 0.10}, |
| "J. 정보통신업": {"소기업": 0.20, "중기업": 0.10, "소기업_수도권": 0.10}, |
| "K. 금융·보험업": {"소기업": 0.10, "중기업": 0.05, "소기업_수도권": 0.05}, |
| "L. 부동산업": {"소기업": 0.10, "중기업": 0.05, "소기업_수도권": 0.05}, |
| "M. 전문·과학·기술": {"소기업": 0.20, "중기업": 0.10, "소기업_수도권": 0.10}, |
| "N. 사업시설관리": {"소기업": 0.20, "중기업": 0.10, "소기업_수도권": 0.10}, |
| "P. 교육서비스업": {"소기업": 0.10, "중기업": 0.05, "소기업_수도권": 0.05}, |
| "Q. 보건·사회복지": {"소기업": 0.10, "중기업": 0.05, "소기업_수도권": 0.05}, |
| "R. 예술·스포츠": {"소기업": 0.10, "중기업": 0.05, "소기업_수도권": 0.05}, |
| "S. 수리·개인서비스": {"소기업": 0.10, "중기업": 0.05, "소기업_수도권": 0.05}, |
| "A. 농림어업": {"소기업": 0.20, "중기업": 0.10, "소기업_수도권": 0.10}, |
| "B. 광업": {"소기업": 0.20, "중기업": 0.10, "소기업_수도권": 0.10}, |
| } |
|
|
| |
| EMPLOYMENT_CREDIT = { |
| "청년": {"수도권": 1450, "비수도권": 1550}, |
| "장애인": {"수도권": 1450, "비수도권": 1550}, |
| "60세이상": {"수도권": 700, "비수도권": 770}, |
| "일반": {"수도권": 850, "비수도권": 950}, |
| } |
|
|
|
|
| def simulate_tax_saving( |
| industry, company_size, revenue_m, operating_profit_m, |
| is_capital_region, establish_years, ceo_age, |
| has_research_center, rd_investment_m, patent_count, |
| total_employees, new_hires_youth, new_hires_general, |
| facility_investment_m, has_venture, has_innobiz, |
| is_female_company, is_disabled_company |
| ): |
| """세금 절감 시뮬레이션 실행""" |
| |
| results = [] |
| total_saving = 0 |
| |
| |
| taxable_income = max(0, operating_profit_m * 1_000_000) |
| |
| |
| if taxable_income <= 500_000_000: |
| corp_tax = taxable_income * 0.09 |
| elif taxable_income <= 20_000_000_000: |
| corp_tax = 500_000_000 * 0.09 + (taxable_income - 500_000_000) * 0.19 |
| else: |
| corp_tax = 500_000_000 * 0.09 + 19_500_000_000 * 0.19 + (taxable_income - 20_000_000_000) * 0.21 |
| |
| |
| |
| |
| startup_saving = 0 |
| startup_rate = 0 |
| startup_eligible = (establish_years <= 5 and industry in STARTUP_ELIGIBLE_INDUSTRIES) |
| startup_note = "" |
| |
| if startup_eligible: |
| if not is_capital_region and ceo_age < 35: |
| startup_rate = 1.0 |
| startup_note = "비수도권 + 청년창업 → 100% 감면 (최저한세 배제)" |
| elif not is_capital_region: |
| startup_rate = 0.5 |
| startup_note = "비수도권 창업 → 50% 감면" |
| elif ceo_age < 35: |
| startup_rate = 0.5 |
| startup_note = "수도권 청년창업 → 50% 감면" |
| else: |
| startup_rate = 0.0 |
| startup_note = "수도권 일반 창업 → 해당 없음" |
| |
| startup_saving = int(corp_tax * startup_rate) |
| else: |
| if establish_years > 5: |
| startup_note = "업력 5년 초과 → 대상 외" |
| else: |
| startup_note = "비대상 업종" |
| |
| results.append({ |
| "name": "창업중소기업 세액감면", |
| "icon": "🌱", |
| "saving": startup_saving, |
| "rate": f"{int(startup_rate*100)}%", |
| "note": startup_note, |
| "eligible": startup_eligible and startup_rate > 0, |
| "conflict": "중소기업특별세액감면" |
| }) |
| |
| |
| |
| |
| sme_saving = 0 |
| sme_rate = 0 |
| sme_note = "" |
| sme_eligible = industry in SME_SPECIAL_RATES |
| |
| if sme_eligible: |
| rates = SME_SPECIAL_RATES[industry] |
| if company_size == "소기업" and is_capital_region: |
| sme_rate = rates.get("소기업_수도권", 0.05) |
| elif company_size == "소기업": |
| sme_rate = rates.get("소기업", 0.10) |
| else: |
| sme_rate = rates.get("중기업", 0.05) |
| |
| sme_saving = int(corp_tax * sme_rate) |
| sme_note = f"{company_size} / {'수도권' if is_capital_region else '비수도권'} → {int(sme_rate*100)}% 감면" |
| else: |
| sme_note = "비대상 업종" |
| |
| results.append({ |
| "name": "중소기업 특별세액감면", |
| "icon": "🏢", |
| "saving": sme_saving, |
| "rate": f"{int(sme_rate*100)}%", |
| "note": sme_note, |
| "eligible": sme_eligible and sme_rate > 0, |
| "conflict": "창업중소기업세액감면" |
| }) |
| |
| |
| |
| |
| rd_saving = 0 |
| rd_note = "" |
| rd_eligible = has_research_center and rd_investment_m > 0 |
| |
| if rd_eligible: |
| rd_amount = rd_investment_m * 1_000_000 |
| rd_rate = 0.25 |
| rd_saving = int(rd_amount * rd_rate) |
| rd_note = f"연구소 보유 + R&D {rd_investment_m:,.0f}백만원 × 25% = {rd_saving/10000:,.0f}만원" |
| else: |
| if not has_research_center: |
| rd_note = "기업부설연구소/전담부서 미등록" |
| else: |
| rd_note = "R&D 투자액 없음" |
| |
| results.append({ |
| "name": "R&D 세액공제", |
| "icon": "🔬", |
| "saving": rd_saving, |
| "rate": "25%", |
| "note": rd_note, |
| "eligible": rd_eligible, |
| "conflict": None |
| }) |
| |
| |
| |
| |
| emp_saving = 0 |
| emp_note_parts = [] |
| region_key = "수도권" if is_capital_region else "비수도권" |
| |
| if new_hires_youth > 0: |
| youth_credit = EMPLOYMENT_CREDIT["청년"][region_key] * new_hires_youth * 10000 |
| emp_saving += youth_credit |
| emp_note_parts.append(f"청년 {new_hires_youth}명 × {EMPLOYMENT_CREDIT['청년'][region_key]}만원") |
| |
| if new_hires_general > 0: |
| gen_credit = EMPLOYMENT_CREDIT["일반"][region_key] * new_hires_general * 10000 |
| emp_saving += gen_credit |
| emp_note_parts.append(f"일반 {new_hires_general}명 × {EMPLOYMENT_CREDIT['일반'][region_key]}만원") |
| |
| emp_saving = int(emp_saving) |
| emp_note = " + ".join(emp_note_parts) if emp_note_parts else "전년대비 신규 고용 증가분 없음" |
| if emp_saving > 0: |
| emp_note += f" (3년간 적용, 연 {emp_saving/10000:,.0f}만원)" |
| |
| results.append({ |
| "name": "고용증대 세액공제", |
| "icon": "👥", |
| "saving": emp_saving, |
| "rate": "인당 최대 1,550만원", |
| "note": emp_note, |
| "eligible": emp_saving > 0, |
| "conflict": None |
| }) |
| |
| |
| |
| |
| invest_saving = 0 |
| invest_note = "" |
| |
| if facility_investment_m > 0: |
| invest_rate = 0.10 |
| invest_saving = int(facility_investment_m * 1_000_000 * invest_rate) |
| invest_note = f"설비투자 {facility_investment_m:,.0f}백만원 × 10% = {invest_saving/10000:,.0f}만원" |
| if has_venture or has_innobiz: |
| additional = int(facility_investment_m * 1_000_000 * 0.02) |
| invest_saving += additional |
| invest_note += f" + 추가 2% ({additional/10000:,.0f}만원)" |
| else: |
| invest_note = "사업용 자산 투자 없음" |
| |
| results.append({ |
| "name": "통합투자 세액공제", |
| "icon": "🏭", |
| "saving": invest_saving, |
| "rate": "10~12%", |
| "note": invest_note, |
| "eligible": invest_saving > 0, |
| "conflict": None |
| }) |
| |
| |
| |
| |
| social_saving = 0 |
| social_note = "" |
| total_new = new_hires_youth + new_hires_general |
| |
| if total_new > 0: |
| avg_salary = 36_000_000 |
| employer_rate = 0.098 |
| social_saving = int(total_new * avg_salary * employer_rate * 0.5) |
| social_note = f"신규 {total_new}명 × 사업주부담 보험료 50% = {social_saving/10000:,.0f}만원" |
| else: |
| social_note = "신규 고용 없음" |
| |
| results.append({ |
| "name": "사회보험료 세액공제", |
| "icon": "🛡️", |
| "saving": social_saving, |
| "rate": "50%", |
| "note": social_note, |
| "eligible": social_saving > 0, |
| "conflict": None |
| }) |
| |
| |
| |
| |
| tech_saving = 0 |
| tech_note = "" |
| tech_eligible = patent_count > 0 |
| |
| if tech_eligible: |
| tech_note = f"특허 {patent_count}건 보유 → 이전 시 소득의 50%, 대여 시 25% 감면 가능 (실현 시 적용)" |
| else: |
| tech_note = "보유 특허 없음" |
| |
| results.append({ |
| "name": "기술이전 세액감면", |
| "icon": "📜", |
| "saving": tech_saving, |
| "rate": "이전50%/대여25%", |
| "note": tech_note, |
| "eligible": tech_eligible, |
| "conflict": None |
| }) |
| |
| |
| |
| |
| |
| best_choice = "" |
| if results[0]["eligible"] and results[1]["eligible"]: |
| if results[0]["saving"] >= results[1]["saving"]: |
| results[1]["saving"] = 0 |
| results[1]["note"] += " ⚠️ 창업감면과 중복 불가 (창업감면이 유리)" |
| results[1]["eligible"] = False |
| best_choice = "창업중소기업 세액감면" |
| else: |
| results[0]["saving"] = 0 |
| results[0]["note"] += " ⚠️ 특별감면과 중복 불가 (특별감면이 유리)" |
| results[0]["eligible"] = False |
| best_choice = "중소기업 특별세액감면" |
| elif results[0]["eligible"]: |
| best_choice = "창업중소기업 세액감면" |
| elif results[1]["eligible"]: |
| best_choice = "중소기업 특별세액감면" |
| |
| total_saving = sum(r["saving"] for r in results) |
| |
| return results, total_saving, corp_tax, taxable_income, best_choice |
|
|
|
|
| def generate_tax_html(results, total_saving, corp_tax, taxable_income, best_choice): |
| """세금 절감 시뮬레이션 결과 HTML 생성""" |
| |
| eligible_count = sum(1 for r in results if r["eligible"]) |
| saving_pct = (total_saving / corp_tax * 100) if corp_tax > 0 else 0 |
| |
| |
| waterfall_items = [] |
| cumulative = 0 |
| for r in results: |
| if r["saving"] > 0: |
| cumulative += r["saving"] |
| waterfall_items.append((r["icon"] + " " + r["name"][:6], r["saving"], cumulative)) |
| |
| max_val = max(corp_tax, cumulative) if cumulative > 0 else corp_tax |
| |
| |
| bars_html = "" |
| for r in results: |
| color = "#6fd9a8" if r["eligible"] else "#3d3d5c" |
| opacity = "1" if r["eligible"] else "0.4" |
| width_pct = min(95, (r["saving"] / max_val * 100)) if max_val > 0 and r["saving"] > 0 else 0 |
| saving_text = f"{r['saving']/10000:,.0f}만원" if r["saving"] > 0 else "해당없음" |
| |
| bars_html += f""" |
| <div style="margin-bottom:16px;opacity:{opacity};"> |
| <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;"> |
| <span style="font-size:14px;font-weight:600;color:#e8e8f0;"> |
| {r['icon']} {r['name']} |
| {'<span style="color:#6fd9a8;font-size:11px;margin-left:6px;">✓ 적용</span>' if r['eligible'] else '<span style="color:#666;font-size:11px;margin-left:6px;">미적용</span>'} |
| </span> |
| <span style="font-size:15px;font-weight:700;color:{color};">{saving_text}</span> |
| </div> |
| <div style="background:#1a1a2e;border-radius:8px;height:28px;overflow:hidden;position:relative;"> |
| <div style="width:{width_pct}%;height:100%;background:linear-gradient(90deg,{color},{color}88); |
| border-radius:8px;transition:width 0.8s ease;"></div> |
| </div> |
| <div style="font-size:11px;color:#8888a0;margin-top:4px;">{r['note']}</div> |
| </div> |
| """ |
| |
| |
| waterfall_html = "" |
| if waterfall_items: |
| wf_max = max(cumulative, 1) |
| for name, amount, cum in waterfall_items: |
| bottom_pct = (cum - amount) / wf_max * 100 |
| height_pct = amount / wf_max * 100 |
| waterfall_html += f""" |
| <div style="flex:1;display:flex;flex-direction:column;align-items:center;min-width:70px;"> |
| <div style="font-size:10px;color:#6fd9a8;font-weight:600;margin-bottom:4px;"> |
| +{amount/10000:,.0f}만 |
| </div> |
| <div style="width:100%;height:200px;position:relative;background:#1a1a2e;border-radius:6px;"> |
| <div style="position:absolute;bottom:{bottom_pct}%;width:100%;height:{max(height_pct, 3)}%; |
| background:linear-gradient(180deg,#6fd9a8,#4a9d7d);border-radius:4px;"></div> |
| </div> |
| <div style="font-size:9px;color:#8888a0;margin-top:6px;text-align:center; |
| word-break:keep-all;">{name}</div> |
| </div> |
| """ |
| |
| html = f""" |
| <div style="background:linear-gradient(135deg,#0d1117,#161b22,#0d1117);padding:30px; |
| border-radius:20px;border:1px solid rgba(111,217,168,0.2);font-family:'Noto Sans KR',sans-serif;"> |
| |
| <!-- 헤더 --> |
| <div style="text-align:center;margin-bottom:30px;"> |
| <h2 style="margin:0;font-size:26px;background:linear-gradient(135deg,#6fd9a8,#fff); |
| -webkit-background-clip:text;-webkit-text-fill-color:transparent;"> |
| 🧮 세금 절감 시뮬레이션 결과 |
| </h2> |
| <p style="color:#8888a0;margin:8px 0 0;font-size:13px;"> |
| 2026년 조세특례제한법 기준 | 중소기업 세제 혜택 자동 스캔 |
| </p> |
| </div> |
| |
| <!-- 핵심 KPI --> |
| <div style="display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:30px;"> |
| <div style="background:#1a1a2e;border-radius:14px;padding:20px;text-align:center; |
| border:1px solid rgba(111,217,168,0.15);"> |
| <div style="font-size:11px;color:#8888a0;">추정 법인세</div> |
| <div style="font-size:22px;font-weight:700;color:#ff6b6b;margin-top:6px;"> |
| {corp_tax/10000:,.0f}<span style="font-size:12px;color:#8888a0;">만원</span> |
| </div> |
| </div> |
| <div style="background:#1a1a2e;border-radius:14px;padding:20px;text-align:center; |
| border:1px solid rgba(111,217,168,0.3);"> |
| <div style="font-size:11px;color:#8888a0;">총 절감액</div> |
| <div style="font-size:22px;font-weight:700;color:#6fd9a8;margin-top:6px;"> |
| {total_saving/10000:,.0f}<span style="font-size:12px;color:#8888a0;">만원</span> |
| </div> |
| </div> |
| <div style="background:#1a1a2e;border-radius:14px;padding:20px;text-align:center; |
| border:1px solid rgba(255,215,0,0.2);"> |
| <div style="font-size:11px;color:#8888a0;">절감률</div> |
| <div style="font-size:22px;font-weight:700;color:#ffd700;margin-top:6px;"> |
| {saving_pct:.1f}<span style="font-size:12px;color:#8888a0;">%</span> |
| </div> |
| </div> |
| <div style="background:#1a1a2e;border-radius:14px;padding:20px;text-align:center; |
| border:1px solid rgba(100,149,237,0.2);"> |
| <div style="font-size:11px;color:#8888a0;">적용 가능</div> |
| <div style="font-size:22px;font-weight:700;color:#6495ed;margin-top:6px;"> |
| {eligible_count}<span style="font-size:12px;color:#8888a0;">건 / 7건</span> |
| </div> |
| </div> |
| </div> |
| |
| <!-- 항목별 절감액 바 차트 --> |
| <div style="background:#12121e;border-radius:14px;padding:24px;margin-bottom:24px; |
| border:1px solid rgba(111,217,168,0.1);"> |
| <h3 style="color:#e8e8f0;font-size:16px;margin:0 0 20px;"> |
| 📊 항목별 예상 절감액 |
| </h3> |
| {bars_html} |
| </div> |
| |
| <!-- 워터폴 차트 --> |
| {'<div style="background:#12121e;border-radius:14px;padding:24px;margin-bottom:24px;border:1px solid rgba(111,217,168,0.1);"><h3 style="color:#e8e8f0;font-size:16px;margin:0 0 16px;">💧 절감액 누적 워터폴</h3><div style="display:flex;gap:8px;align-items:flex-end;">' + waterfall_html + '</div></div>' if waterfall_html else ''} |
| |
| <!-- 중복 적용 안내 --> |
| <div style="background:linear-gradient(145deg,#1a2a30,#0d1a1f);border-radius:14px;padding:20px; |
| border-left:4px solid #ffd700;margin-bottom:20px;"> |
| <h4 style="color:#ffd700;margin:0 0 12px;font-size:14px;">⚠️ 중복 적용 안내</h4> |
| <ul style="margin:0;padding-left:20px;color:#c8c8d8;font-size:13px;line-height:2;"> |
| <li><strong>창업감면 ↔ 특별감면:</strong> 중복 불가 → {'<span style="color:#6fd9a8;">' + best_choice + ' 적용 (유리)</span>' if best_choice else '둘 다 미해당'}</li> |
| <li><strong>R&D 세액공제:</strong> 위 감면과 <span style="color:#6fd9a8;">중복 적용 가능</span></li> |
| <li><strong>고용증대 + 사회보험료:</strong> <span style="color:#6fd9a8;">중복 적용 가능</span> (3년간)</li> |
| <li><strong>통합투자:</strong> 창업감면과 중복 불가, 특별감면과는 가능</li> |
| </ul> |
| </div> |
| |
| <!-- 면책 --> |
| <div style="text-align:center;padding:12px;color:#555;font-size:11px; |
| border-top:1px solid rgba(111,217,168,0.1);margin-top:16px;"> |
| ※ 본 시뮬레이션은 참고용이며, 정확한 세금 계산은 세무사와 상담하시기 바랍니다. |
| <br>영업이익 기반 간이 추정 | 실제 과세표준은 세무조정 후 달라질 수 있습니다. |
| </div> |
| </div> |
| """ |
| return html |
|
|
|
|
| |
| |
| |
|
|
| RD_BUDGET_CATEGORIES = { |
| "인건비": { |
| "내부인건비": {"desc": "참여연구원 인건비 (연구책임자, 참여연구원)", "default_pct": 0.45}, |
| "외부인건비": {"desc": "외부 전문가 위촉 수당", "default_pct": 0.05}, |
| }, |
| "직접비": { |
| "연구장비·재료비": {"desc": "연구장비 구입·임차, 재료비, 시약", "default_pct": 0.12}, |
| "연구활동비": {"desc": "국내외출장, 기술정보수집, 학회참가", "default_pct": 0.05}, |
| "위탁연구비": {"desc": "외부기관 위탁 연구 비용", "default_pct": 0.08}, |
| "연구시설활용비": {"desc": "연구시설 임차료, 이용료", "default_pct": 0.03}, |
| }, |
| "간접비": { |
| "간접비": {"desc": "기관 관리비, 복리후생, 보험료 등", "default_pct": 0.10}, |
| } |
| } |
|
|
|
|
| def calculate_rd_budget( |
| total_budget_m, gov_ratio, cash_ratio, |
| personnel_pct, ext_personnel_pct, |
| equipment_pct, activity_pct, consign_pct, facility_pct, |
| overhead_pct |
| ): |
| """R&D 예산 편성 계산""" |
| |
| total = total_budget_m * 1_000_000 |
| gov_amount = total * gov_ratio / 100 |
| private_amount = total - gov_amount |
| cash_amount = private_amount * cash_ratio / 100 |
| inkind_amount = private_amount - cash_amount |
| |
| |
| pct_sum = personnel_pct + ext_personnel_pct + equipment_pct + activity_pct + consign_pct + facility_pct + overhead_pct |
| |
| |
| if pct_sum == 0: |
| pct_sum = 100 |
| |
| items = [ |
| {"category": "인건비", "name": "내부인건비", "pct": personnel_pct, "amount": gov_amount * personnel_pct / pct_sum}, |
| {"category": "인건비", "name": "외부인건비", "pct": ext_personnel_pct, "amount": gov_amount * ext_personnel_pct / pct_sum}, |
| {"category": "직접비", "name": "연구장비·재료비", "pct": equipment_pct, "amount": gov_amount * equipment_pct / pct_sum}, |
| {"category": "직접비", "name": "연구활동비", "pct": activity_pct, "amount": gov_amount * activity_pct / pct_sum}, |
| {"category": "직접비", "name": "위탁연구비", "pct": consign_pct, "amount": gov_amount * consign_pct / pct_sum}, |
| {"category": "직접비", "name": "연구시설활용비", "pct": facility_pct, "amount": gov_amount * facility_pct / pct_sum}, |
| {"category": "간접비", "name": "간접비", "pct": overhead_pct, "amount": gov_amount * overhead_pct / pct_sum}, |
| ] |
| |
| |
| warnings = [] |
| personnel_total = items[0]["amount"] + items[1]["amount"] |
| consign_amount = items[4]["amount"] |
| overhead_amount = items[6]["amount"] |
| direct_cost = sum(i["amount"] for i in items if i["category"] != "간접비") |
| |
| |
| if consign_amount > gov_amount * 0.40: |
| warnings.append(f"⚠️ 위탁연구비({consign_amount/10000:,.0f}만)가 정부지원금 40% 상한({gov_amount*0.40/10000:,.0f}만) 초과") |
| |
| |
| if overhead_pct > 15: |
| warnings.append(f"⚠️ 간접비 비율({overhead_pct:.0f}%)이 권장 상한(15%) 초과") |
| |
| |
| if personnel_pct + ext_personnel_pct > 70: |
| warnings.append(f"⚠️ 인건비 비율({personnel_pct+ext_personnel_pct:.0f}%)이 통상 상한(70%) 초과") |
| |
| |
| if abs(pct_sum - 100) > 0.1: |
| warnings.append(f"ℹ️ 비목 비율 합계가 {pct_sum:.1f}%입니다 (자동 정규화 적용)") |
| |
| return { |
| "total": total, |
| "gov_amount": gov_amount, |
| "private_amount": private_amount, |
| "cash_amount": cash_amount, |
| "inkind_amount": inkind_amount, |
| "items": items, |
| "warnings": warnings, |
| "personnel_total": personnel_total, |
| "direct_cost": direct_cost, |
| "overhead_amount": overhead_amount, |
| } |
|
|
|
|
| def generate_rd_budget_html(data): |
| """R&D 예산 편성 결과 HTML 생성""" |
| |
| total = data["total"] |
| gov = data["gov_amount"] |
| priv = data["private_amount"] |
| cash = data["cash_amount"] |
| inkind = data["inkind_amount"] |
| items = data["items"] |
| warnings = data["warnings"] |
| |
| |
| rows_html = "" |
| cat_colors = {"인건비": "#6fd9a8", "직접비": "#6495ed", "간접비": "#ffd700"} |
| prev_cat = "" |
| |
| for item in items: |
| cat_label = "" |
| if item["category"] != prev_cat: |
| cat_label = item["category"] |
| prev_cat = item["category"] |
| |
| color = cat_colors.get(item["category"], "#fff") |
| bar_width = min(90, item["amount"] / gov * 100) if gov > 0 else 0 |
| |
| rows_html += f""" |
| <tr style="border-bottom:1px solid rgba(255,255,255,0.06);"> |
| <td style="padding:12px;color:{color};font-weight:{'700' if cat_label else '400'};font-size:13px; |
| width:80px;">{cat_label}</td> |
| <td style="padding:12px;color:#e8e8f0;font-size:13px;">{item['name']}</td> |
| <td style="padding:12px;text-align:right;color:#8888a0;font-size:13px;">{item['pct']:.0f}%</td> |
| <td style="padding:12px;text-align:right;color:#e8e8f0;font-weight:600;font-size:14px;"> |
| {item['amount']/10000:,.0f}만원 |
| </td> |
| <td style="padding:12px;width:120px;"> |
| <div style="background:#1a1a2e;border-radius:4px;height:12px;overflow:hidden;"> |
| <div style="width:{bar_width}%;height:100%;background:{color};border-radius:4px;"></div> |
| </div> |
| </td> |
| </tr> |
| """ |
| |
| |
| warn_html = "" |
| if warnings: |
| warn_items = "".join(f'<li style="margin-bottom:6px;">{w}</li>' for w in warnings) |
| warn_html = f""" |
| <div style="background:rgba(255,107,107,0.1);border-radius:10px;padding:16px;margin-bottom:20px; |
| border:1px solid rgba(255,107,107,0.3);"> |
| <ul style="margin:0;padding-left:20px;color:#ff9999;font-size:13px;line-height:1.8;"> |
| {warn_items} |
| </ul> |
| </div> |
| """ |
| |
| |
| cat_totals = {} |
| for item in items: |
| cat_totals[item["category"]] = cat_totals.get(item["category"], 0) + item["amount"] |
| |
| pie_segments = "" |
| legend_html = "" |
| start_deg = 0 |
| for cat, amount in cat_totals.items(): |
| pct = amount / gov * 100 if gov > 0 else 0 |
| end_deg = start_deg + pct * 3.6 |
| color = cat_colors.get(cat, "#fff") |
| pie_segments += f"{color} {start_deg}deg {end_deg}deg," |
| legend_html += f""" |
| <div style="display:flex;align-items:center;gap:8px;margin-bottom:8px;"> |
| <div style="width:12px;height:12px;border-radius:3px;background:{color};"></div> |
| <span style="color:#c8c8d8;font-size:13px;">{cat} {pct:.1f}% ({amount/10000:,.0f}만)</span> |
| </div> |
| """ |
| start_deg = end_deg |
| |
| pie_gradient = pie_segments.rstrip(",") |
| |
| html = f""" |
| <div style="background:linear-gradient(135deg,#0d1117,#161b22,#0d1117);padding:30px; |
| border-radius:20px;border:1px solid rgba(100,149,237,0.2);font-family:'Noto Sans KR',sans-serif;"> |
| |
| <!-- 헤더 --> |
| <div style="text-align:center;margin-bottom:30px;"> |
| <h2 style="margin:0;font-size:26px;background:linear-gradient(135deg,#6495ed,#fff); |
| -webkit-background-clip:text;-webkit-text-fill-color:transparent;"> |
| 📊 R&D 예산 편성 결과 |
| </h2> |
| <p style="color:#8888a0;margin:8px 0 0;font-size:13px;"> |
| 국가연구개발사업 연구개발비 사용기준 적용 |
| </p> |
| </div> |
| |
| <!-- 총괄 KPI --> |
| <div style="display:grid;grid-template-columns:repeat(5,1fr);gap:12px;margin-bottom:24px;"> |
| <div style="background:#1a1a2e;border-radius:12px;padding:16px;text-align:center; |
| border:1px solid rgba(100,149,237,0.15);"> |
| <div style="font-size:10px;color:#8888a0;">총 사업비</div> |
| <div style="font-size:18px;font-weight:700;color:#e8e8f0;margin-top:4px;"> |
| {total/100000000:,.1f}<span style="font-size:11px;">억</span> |
| </div> |
| </div> |
| <div style="background:#1a1a2e;border-radius:12px;padding:16px;text-align:center; |
| border:1px solid rgba(111,217,168,0.2);"> |
| <div style="font-size:10px;color:#8888a0;">정부지원금</div> |
| <div style="font-size:18px;font-weight:700;color:#6fd9a8;margin-top:4px;"> |
| {gov/100000000:,.2f}<span style="font-size:11px;">억</span> |
| </div> |
| </div> |
| <div style="background:#1a1a2e;border-radius:12px;padding:16px;text-align:center; |
| border:1px solid rgba(255,215,0,0.15);"> |
| <div style="font-size:10px;color:#8888a0;">민간부담금</div> |
| <div style="font-size:18px;font-weight:700;color:#ffd700;margin-top:4px;"> |
| {priv/100000000:,.2f}<span style="font-size:11px;">억</span> |
| </div> |
| </div> |
| <div style="background:#1a1a2e;border-radius:12px;padding:16px;text-align:center;"> |
| <div style="font-size:10px;color:#8888a0;">현금</div> |
| <div style="font-size:18px;font-weight:700;color:#c8c8d8;margin-top:4px;"> |
| {cash/10000:,.0f}<span style="font-size:11px;">만</span> |
| </div> |
| </div> |
| <div style="background:#1a1a2e;border-radius:12px;padding:16px;text-align:center;"> |
| <div style="font-size:10px;color:#8888a0;">현물</div> |
| <div style="font-size:18px;font-weight:700;color:#c8c8d8;margin-top:4px;"> |
| {inkind/10000:,.0f}<span style="font-size:11px;">만</span> |
| </div> |
| </div> |
| </div> |
| |
| {warn_html} |
| |
| <!-- 비목별 상세 테이블 --> |
| <div style="background:#12121e;border-radius:14px;padding:24px;margin-bottom:24px; |
| border:1px solid rgba(100,149,237,0.1);overflow-x:auto;"> |
| <h3 style="color:#e8e8f0;font-size:16px;margin:0 0 16px;">📋 비목별 배분 내역 (정부지원금 기준)</h3> |
| <table style="width:100%;border-collapse:collapse;"> |
| <thead> |
| <tr style="border-bottom:2px solid rgba(100,149,237,0.2);"> |
| <th style="padding:10px;text-align:left;color:#6495ed;font-size:12px;">구분</th> |
| <th style="padding:10px;text-align:left;color:#6495ed;font-size:12px;">비목</th> |
| <th style="padding:10px;text-align:right;color:#6495ed;font-size:12px;">비율</th> |
| <th style="padding:10px;text-align:right;color:#6495ed;font-size:12px;">금액</th> |
| <th style="padding:10px;color:#6495ed;font-size:12px;width:120px;"></th> |
| </tr> |
| </thead> |
| <tbody> |
| {rows_html} |
| <tr style="border-top:2px solid rgba(100,149,237,0.3);"> |
| <td colspan="2" style="padding:12px;color:#fff;font-weight:700;font-size:14px;">합계</td> |
| <td style="padding:12px;text-align:right;color:#fff;font-weight:700;">100%</td> |
| <td style="padding:12px;text-align:right;color:#6fd9a8;font-weight:700;font-size:15px;"> |
| {gov/10000:,.0f}만원 |
| </td> |
| <td></td> |
| </tr> |
| </tbody> |
| </table> |
| </div> |
| |
| <!-- 비율 파이차트 + 구조도 --> |
| <div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-bottom:20px;"> |
| <!-- 파이 차트 --> |
| <div style="background:#12121e;border-radius:14px;padding:24px; |
| border:1px solid rgba(100,149,237,0.1);"> |
| <h3 style="color:#e8e8f0;font-size:16px;margin:0 0 16px;">🍩 비목 구성</h3> |
| <div style="display:flex;align-items:center;gap:24px;"> |
| <div style="width:120px;height:120px;border-radius:50%; |
| background:conic-gradient({pie_gradient});flex-shrink:0;"></div> |
| <div>{legend_html}</div> |
| </div> |
| </div> |
| |
| <!-- 구조도 --> |
| <div style="background:#12121e;border-radius:14px;padding:24px; |
| border:1px solid rgba(100,149,237,0.1);"> |
| <h3 style="color:#e8e8f0;font-size:16px;margin:0 0 16px;">🏗️ 재원 구조</h3> |
| <div style="font-size:13px;color:#c8c8d8;line-height:2.2;"> |
| <div>💰 총 사업비: <strong style="color:#fff;">{total/10000:,.0f}만원</strong></div> |
| <div style="padding-left:20px;"> |
| ├ 🏛️ 정부지원금 ({gov/total*100:.0f}%): <strong style="color:#6fd9a8;">{gov/10000:,.0f}만</strong> |
| </div> |
| <div style="padding-left:20px;"> |
| └ 🏢 민간부담금 ({priv/total*100:.0f}%): <strong style="color:#ffd700;">{priv/10000:,.0f}만</strong> |
| </div> |
| <div style="padding-left:48px;"> |
| ├ 현금 ({cash/priv*100:.0f}%): {cash/10000:,.0f}만 |
| </div> |
| <div style="padding-left:48px;"> |
| └ 현물 ({inkind/priv*100:.0f}%): {inkind/10000:,.0f}만 |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <!-- 사업계획서 복사용 텍스트 --> |
| <div style="background:#12121e;border-radius:14px;padding:20px; |
| border:1px solid rgba(100,149,237,0.1);"> |
| <h3 style="color:#e8e8f0;font-size:14px;margin:0 0 12px;">📝 사업계획서 복사용 (텍스트)</h3> |
| <pre style="background:#0d0d1a;color:#c8c8d8;padding:16px;border-radius:8px;font-size:12px; |
| overflow-x:auto;white-space:pre-wrap;margin:0;line-height:1.8;">총 사업비: {total/10000:,.0f}만원 ({total/100000000:,.2f}억원) |
| - 정부지원금: {gov/10000:,.0f}만원 ({gov/total*100:.0f}%) |
| - 민간부담금: {priv/10000:,.0f}만원 (현금 {cash/10000:,.0f}만 + 현물 {inkind/10000:,.0f}만) |
| |
| [비목별 배분 - 정부지원금 기준] |
| {''.join(f" {item['name']:12s}: {item['amount']/10000:>8,.0f}만원 ({item['pct']:.0f}%)" + chr(10) for item in items)}</pre> |
| </div> |
| |
| <div style="text-align:center;padding:12px;color:#555;font-size:11px; |
| border-top:1px solid rgba(100,149,237,0.1);margin-top:16px;"> |
| ※ 실제 예산 편성 시 해당 사업의 공고문과 연구비 사용기준을 반드시 확인하세요. |
| </div> |
| </div> |
| """ |
| return html |
|
|
|
|
| |
| |
| |
|
|
| INDUSTRY_OPTIONS = [ |
| "A. 농림어업", "B. 광업", "C. 제조업", "E. 수도·환경", "F. 건설업", |
| "G. 도매·소매업", "H. 운수·창고업", "J. 정보통신업", "K. 금융·보험업", |
| "L. 부동산업", "M. 전문·과학·기술", "N. 사업시설관리", |
| "P. 교육서비스업", "Q. 보건·사회복지", "R. 예술·스포츠", "S. 수리·개인서비스" |
| ] |
|
|
|
|
| def create_biz_tools_tab(): |
| """세금 절감 & R&D 예산 탭 생성""" |
| |
| with gr.Tab("🧮 세금·예산 계산기"): |
| gr.HTML(''' |
| <div style="background:linear-gradient(135deg,#0d1117,#161b22,#0d1117);padding:30px;border-radius:20px; |
| margin-bottom:24px;border:2px solid rgba(111,217,168,0.3);text-align:center; |
| box-shadow:0 10px 40px rgba(0,0,0,0.5);"> |
| <h2 style="margin:0 0 8px;font-size:28px; |
| background:linear-gradient(135deg,#6fd9a8,#6495ed,#fff); |
| -webkit-background-clip:text;-webkit-text-fill-color:transparent;"> |
| 🧮 세금 절감 시뮬레이터 & R&D 예산 편성기 |
| </h2> |
| <p style="color:#8888a0;margin:0;font-size:13px;"> |
| 기업 정보를 입력하면 적용 가능한 세제 혜택과 R&D 예산을 자동 계산합니다 |
| </p> |
| </div> |
| ''') |
| |
| with gr.Tabs(): |
| |
| with gr.Tab("💰 세금 절감 시뮬레이터"): |
| with gr.Row(): |
| with gr.Column(): |
| gr.HTML('<div style="background:#12121e;color:#6fd9a8;padding:10px 16px;border-radius:10px;font-weight:600;margin-bottom:12px;">📋 기업 기본정보</div>') |
| tax_industry = gr.Dropdown(label="업종", choices=INDUSTRY_OPTIONS, value="C. 제조업") |
| tax_company_size = gr.Radio(label="기업 규모", choices=["소기업", "중기업"], value="소기업") |
| tax_revenue = gr.Number(label="연매출 (백만원)", value=500, minimum=0) |
| tax_op_profit = gr.Number(label="영업이익 (백만원)", value=50, minimum=0) |
| tax_is_capital = gr.Checkbox(label="수도권 소재", value=True) |
| tax_establish_years = gr.Slider(label="업력 (년)", minimum=0, maximum=30, value=3, step=1) |
| tax_ceo_age = gr.Slider(label="대표자 나이", minimum=20, maximum=70, value=35, step=1) |
| |
| with gr.Column(): |
| gr.HTML('<div style="background:#12121e;color:#6495ed;padding:10px 16px;border-radius:10px;font-weight:600;margin-bottom:12px;">🔬 기술·인증·투자</div>') |
| tax_research_center = gr.Checkbox(label="기업부설연구소/전담부서 보유") |
| tax_rd_investment = gr.Number(label="연간 R&D 투자액 (백만원)", value=0, minimum=0) |
| tax_patents = gr.Number(label="보유 특허 수", value=0, minimum=0) |
| tax_has_venture = gr.Checkbox(label="벤처인증") |
| tax_has_innobiz = gr.Checkbox(label="이노비즈") |
| tax_is_female = gr.Checkbox(label="여성기업") |
| tax_is_disabled = gr.Checkbox(label="장애인기업") |
| |
| with gr.Column(): |
| gr.HTML('<div style="background:#12121e;color:#ffd700;padding:10px 16px;border-radius:10px;font-weight:600;margin-bottom:12px;">👥 고용·설비투자</div>') |
| tax_employees = gr.Number(label="총 직원 수", value=10, minimum=0) |
| tax_new_youth = gr.Number(label="신규 채용 - 청년 (전년 대비 증가)", value=0, minimum=0) |
| tax_new_general = gr.Number(label="신규 채용 - 일반 (전년 대비 증가)", value=0, minimum=0) |
| tax_facility_invest = gr.Number(label="설비투자액 (백만원)", value=0, minimum=0) |
| |
| tax_btn = gr.Button("🧮 세금 절감 시뮬레이션 실행", variant="primary", size="lg") |
| tax_result = gr.HTML() |
| |
| def run_tax_sim(ind, size, rev, op, cap, yrs, age, rc, rd, pat, |
| emp, ny, ng, fac, ven, inno, fem, dis): |
| results, total, corp_tax, taxable, best = simulate_tax_saving( |
| ind, size, rev, op, cap, yrs, age, rc, rd, pat, |
| emp, ny, ng, fac, ven, inno, fem, dis |
| ) |
| return generate_tax_html(results, total, corp_tax, taxable, best) |
| |
| tax_btn.click( |
| fn=run_tax_sim, |
| inputs=[ |
| tax_industry, tax_company_size, tax_revenue, tax_op_profit, |
| tax_is_capital, tax_establish_years, tax_ceo_age, |
| tax_research_center, tax_rd_investment, tax_patents, |
| tax_employees, tax_new_youth, tax_new_general, |
| tax_facility_invest, tax_has_venture, tax_has_innobiz, |
| tax_is_female, tax_is_disabled |
| ], |
| outputs=[tax_result] |
| ) |
| |
| |
| with gr.Tab("📊 R&D 예산 편성기"): |
| with gr.Row(): |
| with gr.Column(): |
| gr.HTML('<div style="background:#12121e;color:#6495ed;padding:10px 16px;border-radius:10px;font-weight:600;margin-bottom:12px;">💰 총괄 예산</div>') |
| rd_total = gr.Number(label="총 사업비 (백만원)", value=300, minimum=1) |
| rd_gov_ratio = gr.Slider(label="정부지원 비율 (%)", minimum=50, maximum=100, value=75, step=5) |
| rd_cash_ratio = gr.Slider(label="민간부담 중 현금 비율 (%)", minimum=0, maximum=100, value=50, step=10) |
| |
| with gr.Column(): |
| gr.HTML('<div style="background:#12121e;color:#6fd9a8;padding:10px 16px;border-radius:10px;font-weight:600;margin-bottom:12px;">📋 비목별 비율 (정부지원금 기준, %)</div>') |
| rd_personnel = gr.Slider(label="내부인건비", minimum=0, maximum=70, value=45, step=1) |
| rd_ext_personnel = gr.Slider(label="외부인건비", minimum=0, maximum=20, value=5, step=1) |
| rd_equipment = gr.Slider(label="연구장비·재료비", minimum=0, maximum=40, value=15, step=1) |
| rd_activity = gr.Slider(label="연구활동비", minimum=0, maximum=20, value=5, step=1) |
| |
| with gr.Column(): |
| gr.HTML('<div style="background:#12121e;color:#ffd700;padding:10px 16px;border-radius:10px;font-weight:600;margin-bottom:12px;">📋 비목별 비율 (계속)</div>') |
| rd_consign = gr.Slider(label="위탁연구비", minimum=0, maximum=40, value=10, step=1) |
| rd_facility = gr.Slider(label="연구시설활용비", minimum=0, maximum=20, value=5, step=1) |
| rd_overhead = gr.Slider(label="간접비", minimum=0, maximum=20, value=10, step=1) |
| |
| gr.HTML(''' |
| <div style="background:#1a1a2e;border-radius:10px;padding:14px;margin-top:12px; |
| border:1px solid rgba(255,215,0,0.15);font-size:12px;color:#8888a0;line-height:1.8;"> |
| 💡 <strong style="color:#ffd700;">비율 가이드</strong><br> |
| · 인건비 합계: 40~60% 권장<br> |
| · 위탁연구비: 정부지원금의 40% 이내<br> |
| · 간접비: 10~15% 권장<br> |
| · 비율 합계가 100%가 아니면 자동 정규화 |
| </div> |
| ''') |
| |
| rd_btn = gr.Button("📊 예산 편성 계산", variant="primary", size="lg") |
| rd_result = gr.HTML() |
| |
| def run_rd_calc(total, gov, cash, p1, p2, eq, act, con, fac, oh): |
| data = calculate_rd_budget(total, gov, cash, p1, p2, eq, act, con, fac, oh) |
| return generate_rd_budget_html(data) |
| |
| rd_btn.click( |
| fn=run_rd_calc, |
| inputs=[ |
| rd_total, rd_gov_ratio, rd_cash_ratio, |
| rd_personnel, rd_ext_personnel, rd_equipment, |
| rd_activity, rd_consign, rd_facility, rd_overhead |
| ], |
| outputs=[rd_result] |
| ) |
|
|
|
|
| |
| |
| |
|
|
| def create_app(): |
| """독립 실행""" |
| with gr.Blocks(title="세금·예산 계산기") as app: |
| gr.HTML(""" |
| <div style="text-align:center;padding:30px;background:linear-gradient(135deg,#0d1117,#161b22); |
| border-radius:20px;margin-bottom:20px;"> |
| <h1 style="font-size:36px;margin:0; |
| background:linear-gradient(135deg,#6fd9a8,#6495ed,#fff); |
| -webkit-background-clip:text;-webkit-text-fill-color:transparent;"> |
| 🧮 CoBIZ 세금·예산 계산기 |
| </h1> |
| </div> |
| """) |
| create_tax_calc_tab() |
| return app |
|
|
|
|
| if __name__ == "__main__": |
| app = create_app() |
| app.launch(server_name="0.0.0.0", server_port=7861) |