import pandas as pd from openpyxl import Workbook from openpyxl.styles import Font, Alignment, Border, Side from openpyxl.chart import LineChart, Reference from openpyxl.chart.series import SeriesLabel from datetime import date import os def generate_excel_report(today, recent_dates, prev_year_dates, recent_data, prev_year_data, filename="visitor_report.xlsx"): weekday_names = ['월', '화', '수', '목', '금', '토', '일'] wb = Workbook() ws = wb.active ws.title = "방문자 리포트" bold = Font(bold=True) center = Alignment(horizontal='center', vertical='center') thick_border = Border( left=Side(style='thick'), right=Side(style='thick'), top=Side(style='thick'), bottom=Side(style='thick') ) def fmt(d): return f"{d.month}월 {d.day}일 {weekday_names[d.weekday()]}" headers = ["구분"] + [fmt(d) for d in recent_dates] ws.append([]) for _ in range(23): ws.append([]) data_start_row = 24 ws.append(headers) # 범례 영역 ws.merge_cells(start_row=data_start_row, start_column=1, end_row=data_start_row + 6, end_column=1) ws.merge_cells(start_row=data_start_row + 7, start_column=1, end_row=data_start_row + 13, end_column=1) ws.cell(row=data_start_row, column=1, value=f"{today.year}년").font = bold ws.cell(row=data_start_row + 7, column=1, value=f"{today.year - 1}년").font = bold def row(label, key, data, suffix="", fmt_func=None): r = [label] for d in recent_dates: v = data.get(d, {}).get(key, "") if fmt_func: v = fmt_func(v) if v == 0 or v == '': r.append("") else: r.append(f"{v}{suffix}") return r # 올해 예측 포함 입장객 merged_visitors = ["입장객수"] for d in recent_dates: actual = recent_data.get(d, {}).get("입장객 수", 0) forecast = recent_data.get(d, {}).get("예상 방문자", None) if d >= today and forecast: merged_visitors.append(f"{actual} ({int(forecast)})") else: merged_visitors.append(actual if actual else "") year_rows = [ row("홈페이지", "웹 방문자 수", recent_data), merged_visitors, row("최저기온", "최저기온", recent_data), row("최고기온", "최고기온", recent_data), row("습도", "습도", recent_data, "%"), row("강수량", "강수량", recent_data), row("미세먼지지수", "미세먼지", recent_data), ] for r in year_rows: ws.append(r) # 작년 데이터 def prev_row(label, key, suffix="", fmt_func=None): r = [label] for d in prev_year_dates: v = prev_year_data.get(d, {}).get(key, "") if fmt_func: v = fmt_func(v) if v == 0 or v == '': r.append("") else: r.append(f"{v}{suffix}") return r prev_rows = [ prev_row("홈페이지", "웹 방문자 수"), prev_row("입장객수", "입장객 수"), prev_row("최저기온", "최저기온"), prev_row("최고기온", "최고기온"), prev_row("습도", "습도", "%"), prev_row("강수량", "강수량"), prev_row("미세먼지지수", "미세먼지"), ] for r in prev_rows: ws.append(r) # 증감 비교 diff = ["입장객 증감"] rate = ["입장객 변동률"] temp_dev = ["최고기온 편차"] for i, d in enumerate(recent_dates): cur = recent_data.get(d, {}).get("입장객 수", 0) prev = prev_year_data.get(prev_year_dates[i], {}).get("입장객 수", 0) if prev: diff.append(cur - prev) rate.append(f"{(cur - prev) / prev * 100:.1f}%") else: diff.append("") rate.append("") t1 = recent_data.get(d, {}).get("최고기온") t2 = prev_year_data.get(prev_year_dates[i], {}).get("최고기온") temp_dev.append(round(t1 - t2, 1) if t1 is not None and t2 is not None else "") for row in [diff, rate, temp_dev]: ws.append(row) # 굵은 테두리 처리 for col, d in enumerate(recent_dates, start=2): if d >= today: for r in range(data_start_row + 1, data_start_row + 18): ws.cell(row=r, column=col).border = thick_border # 차트 chart = LineChart() chart.title = "입장객 비교 (예상 포함 vs 작년)" chart.height = 10 chart.width = 22 chart.y_axis.title = "명" chart.x_axis.title = "날짜" label_ref = Reference(ws, min_col=2, min_row=data_start_row, max_col=1 + len(recent_dates)) this_year_ref = Reference(ws, min_col=2, min_row=data_start_row + 2, max_col=1 + len(recent_dates)) last_year_ref = Reference(ws, min_col=2, min_row=data_start_row + 9, max_col=1 + len(recent_dates)) chart.set_categories(label_ref) chart.add_data(this_year_ref, titles_from_data=False) chart.add_data(last_year_ref, titles_from_data=False) chart.series[0].tx = SeriesLabel(v="입장객수 (예상 포함)") chart.series[1].tx = SeriesLabel(v="작년 입장객수") chart.series[1].graphicalProperties.solidFill = "999999" ws.add_chart(chart, "A1") wb.save(filename) print(f"✅ 엑셀 저장 완료: {filename}")