From 52b7360f4ace022f1d3886db579fd2e6e4e7462e Mon Sep 17 00:00:00 2001 From: KWON Date: Mon, 21 Jul 2025 17:40:37 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A3=BC=EA=B0=84=20=EB=B0=A9=EB=AC=B8?= =?UTF-8?q?=EA=B0=9D=EC=9D=84=20=EC=97=91=EC=85=80=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=ED=99=98=ED=95=98=EB=8A=94=20=EC=BD=94=EB=93=9C,=20=EC=A0=95?= =?UTF-8?q?=EC=83=81=20=EC=B6=94=EC=B6=9C=EB=90=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/weekly_visitor_forecast_to_excel.py | 150 ++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 lib/weekly_visitor_forecast_to_excel.py diff --git a/lib/weekly_visitor_forecast_to_excel.py b/lib/weekly_visitor_forecast_to_excel.py new file mode 100644 index 0000000..3d7aa66 --- /dev/null +++ b/lib/weekly_visitor_forecast_to_excel.py @@ -0,0 +1,150 @@ +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}")