diff --git a/public/downloads/accounting_automation_v1.0.py b/public/downloads/accounting_automation_v1.0.py
new file mode 100644
index 0000000..df807af
--- /dev/null
+++ b/public/downloads/accounting_automation_v1.0.py
@@ -0,0 +1,1243 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+쟁승메이드 사업장 회계 장부 자동화 프로그램 v1.0
+문의: bgg8988@gmail.com | jaengseung-made.vercel.app
+"""
+
+import os
+import sys
+import json
+from datetime import datetime
+from typing import Optional
+
+# 필수 패키지 설치 확인
+try:
+ import pandas as pd
+ import openpyxl
+ from openpyxl.styles import (
+ Font, PatternFill, Alignment, Border, Side, numbers
+ )
+ from openpyxl.utils import get_column_letter
+ from openpyxl.styles.numbers import FORMAT_NUMBER_COMMA_SEPARATED1
+except ImportError:
+ print("필수 패키지가 설치되어 있지 않습니다.")
+ print("다음 명령어를 실행하세요: pip install pandas openpyxl")
+ sys.exit(1)
+
+
+# ─────────────────────────────────────────────
+# 상수 정의
+# ─────────────────────────────────────────────
+
+BRAND_NAME = "쟁승메이드"
+BRAND_EMAIL = "bgg8988@gmail.com"
+BRAND_URL = "jaengseung-made.vercel.app"
+VERSION = "v1.0"
+
+# 업종 정의
+INDUSTRIES = {
+ "1": "쇼핑몰/이커머스",
+ "2": "음식점/카페",
+ "3": "제조업",
+ "4": "서비스업/프리랜서",
+ "5": "기타",
+}
+
+# 업종별 수입 카테고리
+INCOME_CATEGORIES = {
+ "쇼핑몰/이커머스": [
+ ("product_sales", "제품판매"),
+ ("shipping_income", "배송비수입"),
+ ("refund_deduct", "반품환불차감 (마이너스 입력)"),
+ ],
+ "음식점/카페": [
+ ("hall_sales", "홀매출"),
+ ("baemin_sales", "배달의민족 매출"),
+ ("coupang_sales", "쿠팡이츠 매출"),
+ ("yogiyo_sales", "요기요 매출"),
+ ("takeout_sales", "포장매출"),
+ ],
+ "제조업": [
+ ("product_sales", "제품판매"),
+ ("b2b_sales", "B2B 납품"),
+ ("inventory_change", "원자재 재고변동"),
+ ],
+ "서비스업/프리랜서": [
+ ("project_income", "프로젝트 수입"),
+ ("monthly_income", "월정액 수입"),
+ ("consulting_income","컨설팅 수입"),
+ ],
+ "기타": [
+ ("sales_1", "매출1"),
+ ("sales_2", "매출2"),
+ ("other_income", "기타 수입"),
+ ],
+}
+
+# 공통 지출 카테고리
+EXPENSE_CATEGORIES = [
+ # 매출원가
+ ("cogs_goods", "상품/재료비", "매출원가", "변동"),
+ ("cogs_outsource", "외주비", "매출원가", "변동"),
+ ("cogs_packaging", "포장비", "매출원가", "변동"),
+ # 인건비
+ ("labor_salary", "급여", "인건비", "고정"),
+ ("labor_insurance", "4대보험", "인건비", "고정"),
+ ("labor_severance", "퇴직금적립", "인건비", "고정"),
+ # 임대료
+ ("rent", "임대료/관리비", "임대료", "고정"),
+ # 공과금
+ ("util_electricity", "전기요금", "공과금", "고정"),
+ ("util_water", "수도요금", "공과금", "고정"),
+ ("util_gas", "가스요금", "공과금", "고정"),
+ ("util_telecom", "통신비", "공과금", "고정"),
+ # 마케팅
+ ("mkt_ads", "광고비", "마케팅", "변동"),
+ ("mkt_platform", "플랫폼수수료", "마케팅", "변동"),
+ # 세금
+ ("tax_vat", "부가세", "세금", "변동"),
+ ("tax_income", "종합소득세", "세금", "변동"),
+ ("tax_local", "지방소득세", "세금", "변동"),
+ # 기타
+ ("etc_supplies", "소모품비", "기타", "변동"),
+ ("etc_depreciation", "감가상각비", "기타", "고정"),
+ ("etc_insurance", "보험료", "기타", "고정"),
+]
+
+MONTHS_KR = [
+ "1월","2월","3월","4월","5월","6월",
+ "7월","8월","9월","10월","11월","12월",
+]
+
+QUARTERS_KR = ["1분기 (1~3월)", "2분기 (4~6월)", "3분기 (7~9월)", "4분기 (10~12월)"]
+
+
+# ─────────────────────────────────────────────
+# 유틸리티 함수
+# ─────────────────────────────────────────────
+
+def print_banner():
+ """프로그램 시작 배너 출력"""
+ print("\n" + "=" * 60)
+ print(f" {BRAND_NAME} 사업장 회계 장부 자동화 프로그램 {VERSION}")
+ print(f" 문의: {BRAND_EMAIL}")
+ print(f" 웹사이트: {BRAND_URL}")
+ print("=" * 60 + "\n")
+
+
+def print_separator(title: str = ""):
+ """구분선 출력"""
+ if title:
+ print(f"\n{'─' * 20} {title} {'─' * 20}")
+ else:
+ print("─" * 60)
+
+
+def input_int(prompt: str, default: int = 0) -> int:
+ """정수 입력 (기본값 지원)"""
+ while True:
+ raw = input(prompt).strip()
+ if raw == "" and default is not None:
+ return default
+ try:
+ return int(raw.replace(",", ""))
+ except ValueError:
+ print(" [오류] 숫자만 입력하세요. (천단위 콤마 허용)")
+
+
+def input_float(prompt: str) -> float:
+ """실수 입력"""
+ while True:
+ raw = input(prompt).strip()
+ try:
+ return float(raw.replace(",", ""))
+ except ValueError:
+ print(" [오류] 숫자만 입력하세요.")
+
+
+def fmt_krw(value: float) -> str:
+ """원화 포맷 (천단위 콤마)"""
+ if value < 0:
+ return f"-{abs(value):,.0f}원"
+ return f"{value:,.0f}원"
+
+
+def fmt_pct(value: float) -> str:
+ """퍼센트 포맷"""
+ return f"{value:.1f}%"
+
+
+def select_industry() -> str:
+ """업종 선택 메뉴"""
+ print_separator("업종 선택")
+ for key, name in INDUSTRIES.items():
+ print(f" [{key}] {name}")
+ while True:
+ choice = input("\n업종을 선택하세요 (1~5): ").strip()
+ if choice in INDUSTRIES:
+ selected = INDUSTRIES[choice]
+ print(f" → '{selected}' 선택됨\n")
+ return selected
+ print(" [오류] 1~5 사이의 번호를 입력하세요.")
+
+
+def select_year() -> int:
+ """분석 연도 선택"""
+ current_year = datetime.now().year
+ year = input_int(f"분석 연도를 입력하세요 (기본값: {current_year}): ", default=current_year)
+ return year
+
+
+# ─────────────────────────────────────────────
+# 데이터 수집
+# ─────────────────────────────────────────────
+
+def collect_data_manual(industry: str, year: int) -> dict:
+ """직접 입력 방식으로 월별 데이터 수집"""
+ income_cats = INCOME_CATEGORIES[industry]
+ data = {
+ "industry": industry,
+ "year": year,
+ "months": {},
+ }
+
+ print_separator(f"{year}년 월별 데이터 입력")
+ print(" ※ 금액은 원(₩) 단위로 입력하세요. (천단위 콤마 허용)")
+ print(" ※ 입력을 건너뛰려면 그냥 Enter를 누르세요 (0원 처리).\n")
+
+ for month_idx in range(1, 13):
+ month_label = MONTHS_KR[month_idx - 1]
+ print(f"\n ── {year}년 {month_label} ──")
+
+ month_data = {"income": {}, "expense": {}}
+
+ # 수입 입력
+ print(" [수입]")
+ for cat_key, cat_name in income_cats:
+ val = input_int(f" {cat_name}: ", default=0)
+ month_data["income"][cat_key] = val
+
+ # 지출 입력
+ print(" [지출]")
+ for cat_key, cat_name, cat_group, cost_type in EXPENSE_CATEGORIES:
+ val = input_int(f" {cat_name} ({cat_group}): ", default=0)
+ month_data["expense"][cat_key] = val
+
+ data["months"][month_idx] = month_data
+
+ return data
+
+
+def collect_data_excel(industry: str, year: int) -> Optional[dict]:
+ """Excel 파일에서 데이터 가져오기"""
+ print_separator("Excel 파일에서 가져오기")
+ print(" ※ 먼저 메인 메뉴에서 '입력양식 생성'을 선택하여 양식을 만드세요.")
+ file_path = input(" 가져올 Excel 파일 경로를 입력하세요: ").strip().strip('"')
+
+ if not os.path.exists(file_path):
+ print(f" [오류] 파일을 찾을 수 없습니다: {file_path}")
+ return None
+
+ try:
+ print(" Excel 파일을 읽는 중...")
+ xl = pd.ExcelFile(file_path)
+ data = {
+ "industry": industry,
+ "year": year,
+ "months": {},
+ }
+ income_cats = INCOME_CATEGORIES[industry]
+
+ for month_idx in range(1, 13):
+ sheet_name = f"{month_idx}월"
+ if sheet_name not in xl.sheet_names:
+ # 해당 월 시트가 없으면 0으로 초기화
+ month_data = {
+ "income": {k: 0 for k, _ in income_cats},
+ "expense": {k: 0 for k, _, __, ___ in EXPENSE_CATEGORIES},
+ }
+ data["months"][month_idx] = month_data
+ continue
+
+ df = pd.read_excel(file_path, sheet_name=sheet_name, header=0)
+ # 양식: A열=항목키, B열=항목명, C열=금액
+ df.columns = ["key", "name", "amount"]
+ df["amount"] = pd.to_numeric(df["amount"].fillna(0), errors="coerce").fillna(0)
+ amount_map = dict(zip(df["key"].astype(str), df["amount"]))
+
+ month_data = {
+ "income": {k: int(amount_map.get(k, 0)) for k, _ in income_cats},
+ "expense": {k: int(amount_map.get(k, 0)) for k, _, __, ___ in EXPENSE_CATEGORIES},
+ }
+ data["months"][month_idx] = month_data
+
+ print(f" [완료] {file_path} 파일에서 데이터를 가져왔습니다.")
+ return data
+
+ except Exception as e:
+ print(f" [오류] Excel 파일 읽기 실패: {e}")
+ return None
+
+
+# ─────────────────────────────────────────────
+# 재무 계산
+# ─────────────────────────────────────────────
+
+def calc_month_financials(month_data: dict) -> dict:
+ """월별 재무 지표 계산"""
+ income = month_data["income"]
+ expense = month_data["expense"]
+
+ # 총 매출 (반품은 마이너스로 입력됨)
+ total_revenue = sum(income.values())
+
+ # 매출원가
+ cogs = (
+ expense.get("cogs_goods", 0) +
+ expense.get("cogs_outsource", 0) +
+ expense.get("cogs_packaging", 0)
+ )
+
+ # 매출총이익
+ gross_profit = total_revenue - cogs
+
+ # 판매관리비 (인건비 + 임대료 + 공과금 + 마케팅 + 기타)
+ sg_and_a = (
+ expense.get("labor_salary", 0) +
+ expense.get("labor_insurance", 0) +
+ expense.get("labor_severance", 0) +
+ expense.get("rent", 0) +
+ expense.get("util_electricity", 0) +
+ expense.get("util_water", 0) +
+ expense.get("util_gas", 0) +
+ expense.get("util_telecom", 0) +
+ expense.get("mkt_ads", 0) +
+ expense.get("mkt_platform", 0) +
+ expense.get("etc_supplies", 0) +
+ expense.get("etc_depreciation", 0) +
+ expense.get("etc_insurance", 0)
+ )
+
+ # 영업이익
+ operating_profit = gross_profit - sg_and_a
+
+ # 세금
+ taxes = (
+ expense.get("tax_vat", 0) +
+ expense.get("tax_income", 0) +
+ expense.get("tax_local", 0)
+ )
+
+ # 순이익
+ net_profit = operating_profit - taxes
+
+ # 총 지출
+ total_expense = sum(expense.values())
+
+ # 고정비 / 변동비 분리
+ fixed_cost = sum(
+ expense.get(k, 0)
+ for k, _, __, cost_type in EXPENSE_CATEGORIES
+ if cost_type == "고정"
+ )
+ variable_cost = sum(
+ expense.get(k, 0)
+ for k, _, __, cost_type in EXPENSE_CATEGORIES
+ if cost_type == "변동"
+ )
+
+ # 비율 계산 (0 나누기 방지)
+ def safe_pct(numerator, denominator):
+ if denominator == 0:
+ return 0.0
+ return round(numerator / denominator * 100, 2)
+
+ gross_margin = safe_pct(gross_profit, total_revenue)
+ operating_margin = safe_pct(operating_profit, total_revenue)
+ net_margin = safe_pct(net_profit, total_revenue)
+ labor_ratio = safe_pct(
+ expense.get("labor_salary", 0) + expense.get("labor_insurance", 0) + expense.get("labor_severance", 0),
+ total_revenue
+ )
+ mkt_ratio = safe_pct(
+ expense.get("mkt_ads", 0) + expense.get("mkt_platform", 0),
+ total_revenue
+ )
+
+ # 손익분기점 (변동비율 기반)
+ variable_ratio = safe_pct(variable_cost, total_revenue) / 100
+ if variable_ratio < 1:
+ bep = round(fixed_cost / (1 - variable_ratio))
+ else:
+ bep = 0 # 계산 불가
+
+ return {
+ "total_revenue": total_revenue,
+ "cogs": cogs,
+ "gross_profit": gross_profit,
+ "sg_and_a": sg_and_a,
+ "operating_profit": operating_profit,
+ "taxes": taxes,
+ "net_profit": net_profit,
+ "total_expense": total_expense,
+ "fixed_cost": fixed_cost,
+ "variable_cost": variable_cost,
+ "gross_margin": gross_margin,
+ "operating_margin": operating_margin,
+ "net_margin": net_margin,
+ "labor_ratio": labor_ratio,
+ "mkt_ratio": mkt_ratio,
+ "bep": bep,
+ }
+
+
+def calc_all_financials(data: dict) -> dict:
+ """전체 월별 재무 계산 후 분기/연간 집계"""
+ results = {}
+ for month_idx, month_data in data["months"].items():
+ results[month_idx] = calc_month_financials(month_data)
+
+ # 분기별 합산
+ quarters = {}
+ for q in range(1, 5):
+ month_range = range((q - 1) * 3 + 1, q * 3 + 1)
+ q_data = {
+ key: sum(results[m][key] for m in month_range if m in results)
+ for key in results[1].keys()
+ if key not in ("gross_margin", "operating_margin", "net_margin", "labor_ratio", "mkt_ratio", "bep")
+ }
+ # 분기 비율 재계산
+ rev = q_data["total_revenue"]
+ q_data["gross_margin"] = round(q_data["gross_profit"] / rev * 100, 2) if rev else 0
+ q_data["operating_margin"] = round(q_data["operating_profit"] / rev * 100, 2) if rev else 0
+ q_data["net_margin"] = round(q_data["net_profit"] / rev * 100, 2) if rev else 0
+ q_data["labor_ratio"] = 0
+ q_data["mkt_ratio"] = 0
+ q_data["bep"] = 0
+ quarters[q] = q_data
+
+ # 연간 합산
+ annual = {
+ key: sum(results[m][key] for m in results)
+ for key in results[1].keys()
+ if key not in ("gross_margin", "operating_margin", "net_margin", "labor_ratio", "mkt_ratio", "bep")
+ }
+ rev = annual["total_revenue"]
+ annual["gross_margin"] = round(annual["gross_profit"] / rev * 100, 2) if rev else 0
+ annual["operating_margin"] = round(annual["operating_profit"] / rev * 100, 2) if rev else 0
+ annual["net_margin"] = round(annual["net_profit"] / rev * 100, 2) if rev else 0
+ annual["labor_ratio"] = 0
+ annual["mkt_ratio"] = 0
+ annual["bep"] = 0
+
+ return {"monthly": results, "quarterly": quarters, "annual": annual}
+
+
+# ─────────────────────────────────────────────
+# 회계 전문가 조언 엔진
+# ─────────────────────────────────────────────
+
+def generate_advice(data: dict, financials: dict) -> list:
+ """규칙 기반 회계 조언 생성"""
+ advice_list = []
+ annual = financials["annual"]
+ year = data["year"]
+
+ # 1. 마진율 경보
+ if annual["gross_margin"] < 20:
+ advice_list.append({
+ "category": "수익성 경고",
+ "priority": "긴급",
+ "title": f"매출총이익률 {fmt_pct(annual['gross_margin'])} — 위험 수준",
+ "detail": (
+ "매출총이익률이 20% 미만입니다. 원가율이 너무 높아 사업 지속성이 위협받을 수 있습니다.\n"
+ "• 원재료/상품 공급처 재협상 또는 대체 공급처 탐색\n"
+ "• 저마진 제품/서비스 라인 구조조정 검토\n"
+ "• 판매가격 인상 가능 여부 시장 조사 필요"
+ ),
+ })
+ elif annual["gross_margin"] < 35:
+ advice_list.append({
+ "category": "수익성 주의",
+ "priority": "주의",
+ "title": f"매출총이익률 {fmt_pct(annual['gross_margin'])} — 개선 필요",
+ "detail": (
+ "매출총이익률이 35% 미만으로 업종 평균 대비 낮은 수준입니다.\n"
+ "• 고부가가치 제품/서비스 비중 확대 전략 수립\n"
+ "• 원가 절감 가능 항목 면밀히 검토"
+ ),
+ })
+
+ # 2. 인건비 비율
+ labor_total = (
+ sum(
+ data["months"][m]["expense"].get("labor_salary", 0) +
+ data["months"][m]["expense"].get("labor_insurance", 0) +
+ data["months"][m]["expense"].get("labor_severance", 0)
+ for m in data["months"]
+ )
+ )
+ labor_ratio = labor_total / annual["total_revenue"] * 100 if annual["total_revenue"] else 0
+ if labor_ratio > 30:
+ advice_list.append({
+ "category": "비용 구조",
+ "priority": "주의",
+ "title": f"인건비 비율 {fmt_pct(labor_ratio)} — 업무 자동화 검토 필요",
+ "detail": (
+ "인건비가 매출의 30%를 초과합니다. 생산성 향상 방안을 모색하세요.\n"
+ "• RPA, 엑셀 자동화 등 업무 자동화 도입으로 인건비 절감\n"
+ "• 아웃소싱·프리랜서 활용으로 고정 인건비를 변동비화\n"
+ "• 직원별 생산성 KPI 측정 및 성과급 체계 도입"
+ ),
+ })
+
+ # 3. 마케팅비 비율
+ mkt_total = sum(
+ data["months"][m]["expense"].get("mkt_ads", 0) +
+ data["months"][m]["expense"].get("mkt_platform", 0)
+ for m in data["months"]
+ )
+ mkt_ratio = mkt_total / annual["total_revenue"] * 100 if annual["total_revenue"] else 0
+ if mkt_ratio > 10:
+ advice_list.append({
+ "category": "마케팅 효율",
+ "priority": "주의",
+ "title": f"마케팅비 비율 {fmt_pct(mkt_ratio)} — ROI 분석 필요",
+ "detail": (
+ "마케팅비가 매출의 10%를 초과합니다. 채널별 ROI를 정밀 분석하세요.\n"
+ "• 채널별 전환율·CAC(고객획득비용) 측정 체계 구축\n"
+ "• 효과 낮은 광고 채널 예산 축소, 고ROI 채널에 집중 투자\n"
+ "• 리타겟팅·CRM을 통한 재구매율 향상으로 CAC 절감"
+ ),
+ })
+
+ # 4. 순이익 적자 판단
+ loss_months = [m for m, f in financials["monthly"].items() if f["net_profit"] < 0]
+ if len(loss_months) >= 6:
+ advice_list.append({
+ "category": "경영 위기",
+ "priority": "긴급",
+ "title": f"연간 {len(loss_months)}개월 순손실 — 고정비 구조 즉시 재검토",
+ "detail": (
+ f"총 {len(loss_months)}개월 순손실이 발생했습니다. 사업 구조 전면 재검토가 필요합니다.\n"
+ "• 임대료 협상 또는 이전을 통한 고정비 절감\n"
+ "• 손실 유발 서비스/제품 라인 즉시 중단 또는 개편\n"
+ "• 단기 자금 확보: 소상공인 정책자금, 신용보증기금 활용 검토"
+ ),
+ })
+ elif annual["net_profit"] < 0:
+ advice_list.append({
+ "category": "수익성 경고",
+ "priority": "긴급",
+ "title": f"연간 순손실 {fmt_krw(annual['net_profit'])} — 고정비 구조 재검토",
+ "detail": (
+ "연간 순손실이 발생했습니다. 고정비 절감이 최우선 과제입니다.\n"
+ "• 임대료·인건비 등 고정비 재협상\n"
+ "• 불필요한 구독 서비스, 보험료 등 점검"
+ ),
+ })
+
+ # 5. 부가세 신고 안내
+ advice_list.append({
+ "category": "세무 일정",
+ "priority": "정보",
+ "title": "부가세 신고 시기 안내",
+ "detail": (
+ f"■ {year}년 부가세 신고 일정\n"
+ f" • 1기 확정신고: {year}년 7월 1일 ~ 7월 25일 (1~6월 매출분)\n"
+ f" • 2기 확정신고: {year + 1}년 1월 1일 ~ 1월 25일 (7~12월 매출분)\n\n"
+ "■ 준비 서류\n"
+ " • 세금계산서 합계표 (매입/매출)\n"
+ " • 신용카드 매출전표 합계표\n"
+ " • 현금영수증 발행·수취 내역\n\n"
+ " ※ 홈택스(hometax.go.kr)에서 전자신고 가능"
+ ),
+ })
+
+ # 6. 세금 납부 시뮬레이션 (종합소득세)
+ taxable_income = annual["net_profit"]
+ if taxable_income > 0:
+ # 2024년 기준 종합소득세 세율 구간
+ tax_brackets = [
+ (14_000_000, 0.06, 0),
+ (50_000_000, 0.15, 1_260_000),
+ (88_000_000, 0.24, 5_760_000),
+ (150_000_000, 0.35, 15_440_000),
+ (300_000_000, 0.38, 19_940_000),
+ (500_000_000, 0.40, 25_940_000),
+ (1_000_000_000, 0.42, 35_940_000),
+ (float("inf"), 0.45, 65_940_000),
+ ]
+ estimated_tax = 0
+ for limit, rate, deduction in tax_brackets:
+ if taxable_income <= limit:
+ estimated_tax = int(taxable_income * rate - deduction)
+ break
+ local_tax = int(estimated_tax * 0.1)
+ advice_list.append({
+ "category": "세금 시뮬레이션",
+ "priority": "정보",
+ "title": f"종합소득세 예상 납부액: {fmt_krw(estimated_tax + local_tax)}",
+ "detail": (
+ f"■ {year}년 종합소득세 시뮬레이션 (과세표준: {fmt_krw(taxable_income)})\n"
+ f" • 종합소득세: {fmt_krw(estimated_tax)}\n"
+ f" • 지방소득세 (10%): {fmt_krw(local_tax)}\n"
+ f" • 합계 예상 납부액: {fmt_krw(estimated_tax + local_tax)}\n\n"
+ " ※ 각종 공제항목에 따라 실제 납부액은 달라집니다.\n"
+ " ※ 세무사 상담을 통해 절세 전략을 수립하세요."
+ ),
+ })
+
+ return advice_list
+
+
+# ─────────────────────────────────────────────
+# 콘솔 요약 출력
+# ─────────────────────────────────────────────
+
+def print_summary(data: dict, financials: dict, advice_list: list):
+ """콘솔에 연간 요약 출력"""
+ annual = financials["annual"]
+ year = data["year"]
+
+ print_separator(f"{year}년 연간 재무 요약")
+ print(f" 업종 : {data['industry']}")
+ print(f" 총 매출 : {fmt_krw(annual['total_revenue'])}")
+ print(f" 총 지출 : {fmt_krw(annual['total_expense'])}")
+ print(f" 매출총이익 : {fmt_krw(annual['gross_profit'])} ({fmt_pct(annual['gross_margin'])})")
+ print(f" 영업이익 : {fmt_krw(annual['operating_profit'])} ({fmt_pct(annual['operating_margin'])})")
+ print(f" 순이익 : {fmt_krw(annual['net_profit'])} ({fmt_pct(annual['net_margin'])})")
+
+ print_separator("분기별 순이익")
+ for q in range(1, 5):
+ q_data = financials["quarterly"][q]
+ profit = q_data["net_profit"]
+ sign = "▲" if profit >= 0 else "▼"
+ print(f" {QUARTERS_KR[q-1]}: {sign} {fmt_krw(profit)}")
+
+ print_separator("회계사 조언 요약")
+ for i, adv in enumerate(advice_list, 1):
+ print(f" [{adv['priority']}] {i}. {adv['title']}")
+
+
+# ─────────────────────────────────────────────
+# Excel 스타일 헬퍼
+# ─────────────────────────────────────────────
+
+HEADER_FILL = PatternFill(start_color="1D4ED8", end_color="1D4ED8", fill_type="solid")
+ALT_FILL = PatternFill(start_color="EFF6FF", end_color="EFF6FF", fill_type="solid")
+PROFIT_FONT = Font(color="1D4ED8", bold=True)
+LOSS_FONT = Font(color="DC2626", bold=True)
+HEADER_FONT = Font(color="FFFFFF", bold=True, size=11)
+BORDER_SIDE = Side(style="thin", color="CBD5E1")
+THIN_BORDER = Border(
+ left=BORDER_SIDE, right=BORDER_SIDE,
+ top=BORDER_SIDE, bottom=BORDER_SIDE,
+)
+NUM_FMT = '#,##0"원"'
+PCT_FMT = '0.0"%"'
+
+
+def style_header_row(ws, row_num: int, num_cols: int):
+ """헤더 행 스타일 적용"""
+ for col in range(1, num_cols + 1):
+ cell = ws.cell(row=row_num, column=col)
+ cell.fill = HEADER_FILL
+ cell.font = HEADER_FONT
+ cell.alignment = Alignment(horizontal="center", vertical="center")
+ cell.border = THIN_BORDER
+
+
+def style_data_cell(cell, value, is_alt_row: bool = False, is_amount: bool = True, is_pct: bool = False):
+ """데이터 셀 스타일 적용"""
+ cell.value = value
+ cell.border = THIN_BORDER
+ cell.alignment = Alignment(horizontal="right" if (is_amount or is_pct) else "left", vertical="center")
+ if is_alt_row:
+ cell.fill = ALT_FILL
+ if is_amount and isinstance(value, (int, float)):
+ cell.number_format = NUM_FMT
+ if value < 0:
+ cell.font = LOSS_FONT
+ elif value > 0:
+ cell.font = PROFIT_FONT
+ elif is_pct and isinstance(value, (int, float)):
+ cell.number_format = PCT_FMT
+
+
+def auto_col_width(ws, min_width: int = 10):
+ """열 너비 자동 조정"""
+ for col in ws.columns:
+ max_len = min_width
+ col_letter = get_column_letter(col[0].column)
+ for cell in col:
+ try:
+ cell_len = len(str(cell.value)) if cell.value else 0
+ if cell_len > max_len:
+ max_len = cell_len
+ except Exception:
+ pass
+ ws.column_dimensions[col_letter].width = min(max_len + 4, 30)
+
+
+# ─────────────────────────────────────────────
+# Excel 보고서 생성
+# ─────────────────────────────────────────────
+
+def create_report_excel(data: dict, financials: dict, advice_list: list, output_path: str):
+ """Excel 보고서 생성 (5개 시트)"""
+ wb = openpyxl.Workbook()
+ wb.remove(wb.active) # 기본 시트 제거
+
+ _sheet1_monthly_pl(wb, data, financials)
+ _sheet2_quarterly(wb, data, financials)
+ _sheet3_cost_structure(wb, data, financials)
+ _sheet4_advice(wb, data, advice_list)
+ _sheet5_vat(wb, data, financials)
+
+ wb.save(output_path)
+ print(f"\n [완료] 보고서가 저장되었습니다: {output_path}")
+
+
+def _sheet1_monthly_pl(wb, data: dict, financials: dict):
+ """시트1: 월별 손익계산서"""
+ ws = wb.create_sheet("월별 손익계산서")
+ year = data["year"]
+
+ # 타이틀
+ ws.merge_cells("A1:N1")
+ title_cell = ws["A1"]
+ title_cell.value = f"{year}년 월별 손익계산서 — {data['industry']}"
+ title_cell.font = Font(bold=True, size=14)
+ title_cell.alignment = Alignment(horizontal="center")
+
+ # 헤더
+ headers = ["항목"] + [f"{m}월" for m in range(1, 13)] + ["연간 합계"]
+ for col_idx, h in enumerate(headers, 1):
+ ws.cell(row=2, column=col_idx, value=h)
+ style_header_row(ws, 2, len(headers))
+
+ # 항목 정의
+ rows = [
+ ("총 매출", "total_revenue", True, False),
+ ("매출원가", "cogs", True, False),
+ ("매출총이익", "gross_profit", True, False),
+ (" 매출총이익률", "gross_margin", False, True),
+ ("판매관리비", "sg_and_a", True, False),
+ ("영업이익", "operating_profit", True, False),
+ (" 영업이익률", "operating_margin", False, True),
+ ("세금", "taxes", True, False),
+ ("순이익", "net_profit", True, False),
+ (" 순이익률", "net_margin", False, True),
+ ("고정비", "fixed_cost", True, False),
+ ("변동비", "variable_cost", True, False),
+ ("손익분기점", "bep", True, False),
+ ]
+
+ annual = financials["annual"]
+ for row_offset, (label, key, is_amount, is_pct) in enumerate(rows):
+ row_num = row_offset + 3
+ is_alt = row_offset % 2 == 0
+
+ # 항목명
+ name_cell = ws.cell(row=row_num, column=1, value=label)
+ name_cell.border = THIN_BORDER
+ name_cell.alignment = Alignment(horizontal="left")
+ if is_alt:
+ name_cell.fill = ALT_FILL
+ if key in ("gross_profit", "operating_profit", "net_profit"):
+ name_cell.font = Font(bold=True)
+
+ # 월별 값
+ for m in range(1, 13):
+ val = financials["monthly"][m][key]
+ cell = ws.cell(row=row_num, column=m + 1)
+ style_data_cell(cell, val, is_alt, is_amount, is_pct)
+
+ # 연간 합계
+ ann_val = annual[key]
+ ann_cell = ws.cell(row=row_num, column=14)
+ style_data_cell(ann_cell, ann_val, is_alt, is_amount, is_pct)
+ if key in ("gross_profit", "operating_profit", "net_profit"):
+ ann_cell.font = Font(
+ color="1D4ED8" if ann_val >= 0 else "DC2626",
+ bold=True, size=11
+ )
+
+ # 틀 고정 (헤더 + 항목명)
+ ws.freeze_panes = "B3"
+ auto_col_width(ws, min_width=8)
+ ws.column_dimensions["A"].width = 16
+
+
+def _sheet2_quarterly(wb, data: dict, financials: dict):
+ """시트2: 분기별 요약"""
+ ws = wb.create_sheet("분기별 요약")
+ year = data["year"]
+
+ ws.merge_cells("A1:F1")
+ ws["A1"].value = f"{year}년 분기별 재무 요약"
+ ws["A1"].font = Font(bold=True, size=14)
+ ws["A1"].alignment = Alignment(horizontal="center")
+
+ headers = ["항목", "1분기 (1~3월)", "2분기 (4~6월)", "3분기 (7~9월)", "4분기 (10~12월)", "연간 합계"]
+ for col_idx, h in enumerate(headers, 1):
+ ws.cell(row=2, column=col_idx, value=h)
+ style_header_row(ws, 2, 6)
+
+ keys = [
+ ("총 매출", "total_revenue", True, False),
+ ("매출원가", "cogs", True, False),
+ ("매출총이익", "gross_profit", True, False),
+ (" 이익률", "gross_margin", False, True),
+ ("영업이익", "operating_profit", True, False),
+ (" 이익률", "operating_margin", False, True),
+ ("순이익", "net_profit", True, False),
+ (" 이익률", "net_margin", False, True),
+ ]
+ annual = financials["annual"]
+ for row_offset, (label, key, is_amount, is_pct) in enumerate(keys):
+ row_num = row_offset + 3
+ is_alt = row_offset % 2 == 0
+
+ name_cell = ws.cell(row=row_num, column=1, value=label)
+ name_cell.border = THIN_BORDER
+ name_cell.alignment = Alignment(horizontal="left")
+ if is_alt:
+ name_cell.fill = ALT_FILL
+
+ for q in range(1, 5):
+ val = financials["quarterly"][q][key]
+ cell = ws.cell(row=row_num, column=q + 1)
+ style_data_cell(cell, val, is_alt, is_amount, is_pct)
+
+ ann_cell = ws.cell(row=row_num, column=6)
+ style_data_cell(ann_cell, annual[key], is_alt, is_amount, is_pct)
+
+ ws.freeze_panes = "B3"
+ auto_col_width(ws, min_width=12)
+ ws.column_dimensions["A"].width = 14
+
+
+def _sheet3_cost_structure(wb, data: dict, financials: dict):
+ """시트3: 비용 구조 분석"""
+ ws = wb.create_sheet("비용 구조 분석")
+ year = data["year"]
+ annual_revenue = financials["annual"]["total_revenue"]
+
+ ws.merge_cells("A1:E1")
+ ws["A1"].value = f"{year}년 비용 구조 분석 — 항목별 금액 및 매출 비율"
+ ws["A1"].font = Font(bold=True, size=14)
+ ws["A1"].alignment = Alignment(horizontal="center")
+
+ headers = ["비용 항목", "분류", "성격", "연간 금액", "매출 비율"]
+ for col_idx, h in enumerate(headers, 1):
+ ws.cell(row=2, column=col_idx, value=h)
+ style_header_row(ws, 2, 5)
+
+ # 항목별 연간 합산
+ annual_expenses = {}
+ for m, month_data in data["months"].items():
+ for k, v in month_data["expense"].items():
+ annual_expenses[k] = annual_expenses.get(k, 0) + v
+
+ for row_offset, (cat_key, cat_name, cat_group, cost_type) in enumerate(EXPENSE_CATEGORIES):
+ row_num = row_offset + 3
+ is_alt = row_offset % 2 == 0
+ amount = annual_expenses.get(cat_key, 0)
+ ratio = (amount / annual_revenue * 100) if annual_revenue else 0
+
+ for col_idx, val in enumerate([cat_name, cat_group, cost_type, amount, ratio], 1):
+ cell = ws.cell(row=row_num, column=col_idx)
+ is_amount = col_idx == 4
+ is_pct = col_idx == 5
+ style_data_cell(cell, val, is_alt, is_amount, is_pct)
+ if col_idx <= 3:
+ cell.alignment = Alignment(horizontal="left")
+
+ # 합계 행
+ total_row = len(EXPENSE_CATEGORIES) + 3
+ total_expense = sum(annual_expenses.values())
+ total_ratio = (total_expense / annual_revenue * 100) if annual_revenue else 0
+
+ ws.cell(row=total_row, column=1, value="합 계").font = Font(bold=True)
+ total_amt_cell = ws.cell(row=total_row, column=4, value=total_expense)
+ total_amt_cell.number_format = NUM_FMT
+ total_amt_cell.font = Font(bold=True)
+ total_pct_cell = ws.cell(row=total_row, column=5, value=total_ratio)
+ total_pct_cell.number_format = PCT_FMT
+ total_pct_cell.font = Font(bold=True)
+
+ for col in range(1, 6):
+ ws.cell(row=total_row, column=col).border = THIN_BORDER
+ ws.cell(row=total_row, column=col).fill = PatternFill(start_color="DBEAFE", end_color="DBEAFE", fill_type="solid")
+
+ auto_col_width(ws, min_width=10)
+ ws.column_dimensions["A"].width = 16
+ ws.freeze_panes = "A3"
+
+
+def _sheet4_advice(wb, data: dict, advice_list: list):
+ """시트4: 회계사 조언 리포트"""
+ ws = wb.create_sheet("회계사 조언 리포트")
+ year = data["year"]
+
+ ws.merge_cells("A1:D1")
+ ws["A1"].value = f"{year}년 회계 전문가 조언 리포트 — {data['industry']}"
+ ws["A1"].font = Font(bold=True, size=14)
+ ws["A1"].alignment = Alignment(horizontal="center")
+
+ ws.merge_cells("A2:D2")
+ ws["A2"].value = f"생성일시: {datetime.now().strftime('%Y-%m-%d %H:%M')} | {BRAND_NAME} ({BRAND_URL})"
+ ws["A2"].font = Font(size=10, color="64748B")
+ ws["A2"].alignment = Alignment(horizontal="center")
+
+ headers = ["No.", "분류", "우선순위", "조언 제목 및 상세 내용"]
+ for col_idx, h in enumerate(headers, 1):
+ ws.cell(row=3, column=col_idx, value=h)
+ style_header_row(ws, 3, 4)
+
+ priority_colors = {
+ "긴급": "FEE2E2",
+ "주의": "FEF3C7",
+ "정보": "EFF6FF",
+ }
+ priority_font_colors = {
+ "긴급": "DC2626",
+ "주의": "D97706",
+ "정보": "1D4ED8",
+ }
+
+ current_row = 4
+ for i, adv in enumerate(advice_list, 1):
+ # 상세 내용은 줄 수에 따라 행 병합
+ detail_lines = adv["detail"].split("\n")
+ row_height = max(len(detail_lines) * 15, 40)
+
+ for col_idx in range(1, 5):
+ cell = ws.cell(row=current_row, column=col_idx)
+ cell.border = THIN_BORDER
+ fill_color = priority_colors.get(adv["priority"], "FFFFFF")
+ cell.fill = PatternFill(start_color=fill_color, end_color=fill_color, fill_type="solid")
+
+ ws.cell(row=current_row, column=1, value=i).alignment = Alignment(horizontal="center", vertical="top")
+ ws.cell(row=current_row, column=2, value=adv["category"]).alignment = Alignment(horizontal="center", vertical="top")
+
+ pri_cell = ws.cell(row=current_row, column=3, value=adv["priority"])
+ pri_cell.alignment = Alignment(horizontal="center", vertical="top")
+ pri_cell.font = Font(
+ color=priority_font_colors.get(adv["priority"], "000000"),
+ bold=True
+ )
+
+ content = f"■ {adv['title']}\n\n{adv['detail']}"
+ content_cell = ws.cell(row=current_row, column=4, value=content)
+ content_cell.alignment = Alignment(
+ horizontal="left", vertical="top", wrap_text=True
+ )
+
+ ws.row_dimensions[current_row].height = row_height
+ current_row += 1
+
+ ws.column_dimensions["A"].width = 5
+ ws.column_dimensions["B"].width = 14
+ ws.column_dimensions["C"].width = 10
+ ws.column_dimensions["D"].width = 70
+ ws.freeze_panes = "A4"
+
+
+def _sheet5_vat(wb, data: dict, financials: dict):
+ """시트5: 부가세 신고 준비 자료"""
+ ws = wb.create_sheet("부가세 신고 준비")
+ year = data["year"]
+
+ ws.merge_cells("A1:F1")
+ ws["A1"].value = f"{year}년 부가세 신고 준비 자료"
+ ws["A1"].font = Font(bold=True, size=14)
+ ws["A1"].alignment = Alignment(horizontal="center")
+
+ # 1기 (1~6월) / 2기 (7~12월) 분리
+ periods = {
+ f"1기 (1~6월) — {year}년 7월 신고": range(1, 7),
+ f"2기 (7~12월) — {year + 1}년 1월 신고": range(7, 13),
+ }
+
+ income_cats = INCOME_CATEGORIES[data["industry"]]
+ current_row = 3
+
+ for period_label, month_range in periods.items():
+ # 기간 헤더
+ ws.merge_cells(f"A{current_row}:F{current_row}")
+ period_cell = ws.cell(row=current_row, column=1, value=period_label)
+ period_cell.fill = PatternFill(start_color="1E40AF", end_color="1E40AF", fill_type="solid")
+ period_cell.font = Font(color="FFFFFF", bold=True, size=12)
+ period_cell.alignment = Alignment(horizontal="center")
+ current_row += 1
+
+ # 소계 헤더
+ headers = ["구분", "항목", "과세표준(공급가액)", "세율", "부가세액", "비고"]
+ for col_idx, h in enumerate(headers, 1):
+ ws.cell(row=current_row, column=col_idx, value=h)
+ style_header_row(ws, current_row, 6)
+ current_row += 1
+
+ # 매출 내역
+ period_revenue = 0
+ for cat_key, cat_name in income_cats:
+ period_amount = sum(
+ data["months"][m]["income"].get(cat_key, 0)
+ for m in month_range if m in data["months"]
+ )
+ supply_value = int(period_amount / 1.1) # 공급가액 (부가세 포함 금액 기준)
+ vat = period_amount - supply_value
+ period_revenue += period_amount
+
+ is_alt = (current_row % 2 == 0)
+ for col_idx in range(1, 7):
+ cell = ws.cell(row=current_row, column=col_idx)
+ cell.border = THIN_BORDER
+ if is_alt:
+ cell.fill = ALT_FILL
+
+ ws.cell(row=current_row, column=1, value="매출").alignment = Alignment(horizontal="center")
+ ws.cell(row=current_row, column=2, value=cat_name)
+ supply_cell = ws.cell(row=current_row, column=3, value=supply_value)
+ supply_cell.number_format = NUM_FMT
+ ws.cell(row=current_row, column=4, value=0.10).number_format = PCT_FMT
+ vat_cell = ws.cell(row=current_row, column=5, value=vat)
+ vat_cell.number_format = NUM_FMT
+ ws.cell(row=current_row, column=6, value="과세")
+ current_row += 1
+
+ # 매입 세금계산서 관련 비용
+ vatable_expenses = [
+ ("cogs_goods", "상품/재료비"),
+ ("cogs_outsource","외주비"),
+ ("mkt_ads", "광고비"),
+ ("etc_supplies", "소모품비"),
+ ]
+ for exp_key, exp_name in vatable_expenses:
+ period_amount = sum(
+ data["months"][m]["expense"].get(exp_key, 0)
+ for m in month_range if m in data["months"]
+ )
+ if period_amount == 0:
+ continue
+ supply_value = int(period_amount / 1.1)
+ vat = period_amount - supply_value
+
+ is_alt = (current_row % 2 == 0)
+ for col_idx in range(1, 7):
+ cell = ws.cell(row=current_row, column=col_idx)
+ cell.border = THIN_BORDER
+ if is_alt:
+ cell.fill = ALT_FILL
+
+ ws.cell(row=current_row, column=1, value="매입").alignment = Alignment(horizontal="center")
+ ws.cell(row=current_row, column=2, value=exp_name)
+ supply_cell = ws.cell(row=current_row, column=3, value=supply_value)
+ supply_cell.number_format = NUM_FMT
+ ws.cell(row=current_row, column=4, value=0.10).number_format = PCT_FMT
+ vat_cell = ws.cell(row=current_row, column=5, value=vat)
+ vat_cell.number_format = NUM_FMT
+ ws.cell(row=current_row, column=6, value="매입공제")
+ current_row += 1
+
+ current_row += 2 # 간격
+
+ auto_col_width(ws, min_width=10)
+ ws.column_dimensions["A"].width = 8
+ ws.column_dimensions["B"].width = 16
+ ws.column_dimensions["F"].width = 12
+
+
+# ─────────────────────────────────────────────
+# 입력 양식 Excel 생성
+# ─────────────────────────────────────────────
+
+def create_input_template(industry: str, year: int, output_path: str):
+ """업종별 입력 양식 Excel 생성"""
+ wb = openpyxl.Workbook()
+ wb.remove(wb.active)
+ income_cats = INCOME_CATEGORIES[industry]
+
+ for month_idx in range(1, 13):
+ sheet_name = f"{month_idx}월"
+ ws = wb.create_sheet(sheet_name)
+
+ # 타이틀
+ ws.merge_cells("A1:C1")
+ ws["A1"].value = f"{year}년 {MONTHS_KR[month_idx - 1]} 입력 양식 — {industry}"
+ ws["A1"].font = Font(bold=True, size=12)
+ ws["A1"].alignment = Alignment(horizontal="center")
+
+ # 컬럼 헤더
+ for col_idx, h in enumerate(["항목 키", "항목명", "금액 (원)"], 1):
+ ws.cell(row=2, column=col_idx, value=h)
+ style_header_row(ws, 2, 3)
+
+ # 수입 섹션
+ ws.cell(row=3, column=1, value="──── 수입 ────")
+ ws.cell(row=3, column=1).font = Font(bold=True, color="1D4ED8")
+ ws.merge_cells(f"A3:C3")
+ ws["A3"].fill = PatternFill(start_color="DBEAFE", end_color="DBEAFE", fill_type="solid")
+ ws["A3"].alignment = Alignment(horizontal="center")
+
+ row = 4
+ for cat_key, cat_name in income_cats:
+ ws.cell(row=row, column=1, value=cat_key)
+ ws.cell(row=row, column=2, value=cat_name)
+ amount_cell = ws.cell(row=row, column=3, value=0)
+ amount_cell.number_format = NUM_FMT
+ for col in range(1, 4):
+ ws.cell(row=row, column=col).border = THIN_BORDER
+ row += 1
+
+ # 지출 섹션
+ ws.cell(row=row, column=1, value="──── 지출 ────")
+ ws.merge_cells(f"A{row}:C{row}")
+ ws[f"A{row}"].font = Font(bold=True, color="DC2626")
+ ws[f"A{row}"].fill = PatternFill(start_color="FEE2E2", end_color="FEE2E2", fill_type="solid")
+ ws[f"A{row}"].alignment = Alignment(horizontal="center")
+ row += 1
+
+ for cat_key, cat_name, cat_group, cost_type in EXPENSE_CATEGORIES:
+ ws.cell(row=row, column=1, value=cat_key)
+ ws.cell(row=row, column=2, value=f"[{cat_group}] {cat_name} ({cost_type})")
+ amount_cell = ws.cell(row=row, column=3, value=0)
+ amount_cell.number_format = NUM_FMT
+ for col in range(1, 4):
+ ws.cell(row=row, column=col).border = THIN_BORDER
+ row += 1
+
+ ws.column_dimensions["A"].width = 20
+ ws.column_dimensions["B"].width = 30
+ ws.column_dimensions["C"].width = 18
+ ws.freeze_panes = "A3"
+
+ wb.save(output_path)
+ print(f" [완료] 입력 양식이 생성되었습니다: {output_path}")
+ print(" ※ C열 (금액) 셀에 숫자를 입력하고 저장하세요.")
+
+
+# ─────────────────────────────────────────────
+# 메인 실행 흐름
+# ─────────────────────────────────────────────
+
+def main():
+ print_banner()
+
+ # 작업 디렉토리 설정
+ work_dir = os.getcwd()
+ print(f" 작업 디렉토리: {work_dir}\n")
+
+ # ── 메인 메뉴 ──
+ while True:
+ print_separator("메인 메뉴")
+ print(" [1] 새 회계 분석 시작")
+ print(" [2] 입력 양식(Excel) 생성")
+ print(" [0] 종료")
+ choice = input("\n선택하세요: ").strip()
+
+ if choice == "0":
+ print("\n 프로그램을 종료합니다. 감사합니다!\n")
+ break
+
+ elif choice == "2":
+ # 입력 양식 생성
+ industry = select_industry()
+ year = select_year()
+ template_path = os.path.join(
+ work_dir,
+ f"입력양식_{industry}_{year}.xlsx"
+ )
+ create_input_template(industry, year, template_path)
+
+ elif choice == "1":
+ # 분석 시작
+ industry = select_industry()
+ year = select_year()
+
+ # 데이터 입력 방식 선택
+ print_separator("데이터 입력 방식")
+ print(" [A] 직접 입력 (프로그램에서 월별 금액 입력)")
+ print(" [B] Excel 파일에서 가져오기")
+ input_method = input("\n선택하세요 (A/B): ").strip().upper()
+
+ data = None
+ if input_method == "B":
+ data = collect_data_excel(industry, year)
+ if data is None: # A 선택 or B 실패 시 직접 입력
+ if input_method == "B":
+ print(" → 직접 입력 방식으로 전환합니다.")
+ data = collect_data_manual(industry, year)
+
+ # 재무 계산
+ print("\n 재무 지표를 계산하는 중...")
+ financials = calc_all_financials(data)
+
+ # 조언 생성
+ advice_list = generate_advice(data, financials)
+
+ # 콘솔 요약 출력
+ print_summary(data, financials, advice_list)
+
+ # Excel 보고서 출력
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M")
+ report_path = os.path.join(
+ work_dir,
+ f"회계보고서_{industry}_{year}_{timestamp}.xlsx"
+ )
+
+ print_separator("Excel 보고서 생성")
+ print(f" 보고서 저장 경로: {report_path}")
+ create_report_excel(data, financials, advice_list, report_path)
+
+ # 완료 메시지
+ print_separator("완료")
+ print(f" 보고서가 성공적으로 생성되었습니다!")
+ print(f" 파일: {report_path}")
+ print("\n 포함된 시트:")
+ print(" • 시트1: 월별 손익계산서 (12개월)")
+ print(" • 시트2: 분기별 요약")
+ print(" • 시트3: 비용 구조 분석")
+ print(" • 시트4: 회계사 조언 리포트")
+ print(" • 시트5: 부가세 신고 준비 자료")
+
+ # 데이터 저장 여부 (JSON 백업)
+ save_json = input("\n 입력 데이터를 JSON으로 백업하시겠습니까? (y/N): ").strip().lower()
+ if save_json == "y":
+ json_path = report_path.replace(".xlsx", "_data.json")
+ with open(json_path, "w", encoding="utf-8") as f:
+ json.dump(data, f, ensure_ascii=False, indent=2)
+ print(f" [완료] 데이터 백업: {json_path}")
+
+ else:
+ print(" [오류] 0, 1, 2 중 하나를 입력하세요.")
+
+
+# ─────────────────────────────────────────────
+# 진입점
+# ─────────────────────────────────────────────
+
+if __name__ == "__main__":
+ try:
+ main()
+ except KeyboardInterrupt:
+ print("\n\n [중단] 사용자가 프로그램을 종료했습니다.")
+ print(" 저장된 데이터는 유지됩니다.\n")
+ sys.exit(0)
+ except Exception as e:
+ print(f"\n [오류] 예기치 않은 오류가 발생했습니다: {e}")
+ print(" 문의: bgg8988@gmail.com\n")
+ sys.exit(1)
+
+
+# ═══════════════════════════════════════════════════════════════
+# 쟁승메이드 (JaengseungMade) — 프리미엄 개발 서비스
+# ▸ 웹사이트 : jaengseung-made.vercel.app
+# ▸ 이메일 : bgg8988@gmail.com
+# ▸ 연락처 : 010-3907-1392
+# 7년차 대기업 백엔드 개발자가 만드는 실전 자동화 솔루션
+# ═══════════════════════════════════════════════════════════════