보고서
보고서 자동화
대시보드는 담당자가 보고, 보고서는 경영진과 고객사가 봅니다. 결재 라인에 올라가는 것은 결국 PDF 한 장입니다. 그래서 에너지 시스템에는 거의 항상 정기 보고서 자동 생성이 요구사항으로 들어옵니다.
1. 보고서 종류
| 주기 | 대상 | 주요 내용 |
|---|---|---|
| 일간 | 현장 담당자 | 전일 사용량, 피크, 알람 |
| 주간 | 팀장 | 전주 대비, 공정별 순위 |
| 월간 | 경영진 | 요금, 절감 실적, 탄소 |
| 연간 | 경영진, 대외 | 목표 달성률, ESG 지표 |
| 수시 | 요청자 | 임의 기간 |
2. 처리 흐름
스케줄 트리거
→ 기간 계산 (period_start, period_end)
→ 데이터 조회 (집계 테이블)
→ HTML 렌더링 (템플릿)
→ PDF 변환
→ 파일 저장 (로컬 / S3 / MinIO)
→ report 테이블 기록 (status=completed)
→ 알림 (선택)
생성 시작 시 status=generating으로 먼저 기록하고, 실패하면 failed와 오류 메시지를 남깁니다. 화면에서 재생성 버튼을 제공합니다.
3. 기간 계산
보고서에서 가장 많이 틀리는 부분입니다.
| 유형 | 실행 예 | 기간 |
|---|---|---|
| PREVIOUS_DAY | 매일 07:00 | 전일 00:00 ~ 당일 00:00 |
| PREVIOUS_WEEK | 매주 월 07:00 | 전주 월 00:00 ~ 이번주 월 00:00 |
| PREVIOUS_MONTH | 매월 1일 07:00 | 전월 1일 ~ 당월 1일 |
| PREVIOUS_YEAR | 매년 1월 2일 | 전년 1월 1일 ~ 당해 1월 1일 |
규칙:
- 종료 시각은 미포함(exclusive) 으로 통일합니다.
[start, end) - 타임존을 고정합니다 (Asia/Seoul)
- 하루 시작이 06:00인 현장이면
period_end_hour같은 설정으로 컷 시각을 조정합니다 - 실행 시각은 집계 배치가 끝난 이후로 잡습니다. 00:05에 돌리면 전날 집계가 아직 안 끝났을 수 있습니다
4. 스케줄 관리
report_schedule
- schedule_type: DAILY / WEEKLY / MONTHLY / YEARLY
- hour, minute
- day_of_week (1=월 ~ 7=일)
- day_of_month
- month_of_year
- is_active
- 스케줄은 DB에 두고, 스케줄러가 주기적으로 동기화합니다. 화면에서 바꾸면 재배포 없이 반영됩니다
- 요일 체계가 라이브러리마다 다릅니다. DB는 1=월요일, APScheduler는 0=월요일, cron은 0=일요일. 변환 함수를 한 곳에 두세요
day_of_month=31이면 짧은 달에 실행이 안 됩니다. 말일 옵션(L)을 별도로 두는 것이 안전합니다- 서버를 여러 대 띄우면 같은 보고서가 중복 생성됩니다. 분산 락이나 단일 스케줄러 인스턴스를 보장해야 합니다
5. 템플릿 구성
월간 보고서의 일반적인 구성입니다.
- 요약: 총 사용량, 요금, 탄소, 전월/전년 동월 대비 증감률
- 사용량: 에너지원별, 사업장별, 공정별 사용량과 순위
- 피크와 품질: 최대수요, 부하율, 역률
- 절감 성과: 기준선 대비 절감 kWh, 금액, 누적 실적
- 탄소: Scope 1/2 배출량, 원단위
- 알람과 이슈: 주요 알람, 통신 장애, 결측률
숫자 포맷은 템플릿 필터로 통일합니다 (천 단위 구분, 소수 1자리, 퍼센트).
6. HTML → PDF
| 도구 | 언어 | 특징 |
|---|---|---|
| WeasyPrint | Python | CSS Paged Media 지원, 서버 친화적 |
| Puppeteer / Playwright | Node, Python | 브라우저 렌더링, 차트 JS 그대로 |
| wkhtmltopdf | 바이너리 | 오래됨, 유지보수 중단 |
| @react-pdf/renderer | React | 컴포넌트로 PDF 작성 |
- WeasyPrint는 JavaScript를 실행하지 않습니다. 차트는 서버에서 이미지(SVG/PNG)로 만들어 넣어야 합니다 (matplotlib, 서버 사이드 차트)
- 한글 폰트를 서버에 설치하고 CSS
@font-face로 지정하지 않으면 글자가 깨집니다 - A4 기준
@page { size: A4; margin: 15mm; }, 표가 페이지를 넘을 때thead반복과page-break-inside: avoid를 지정합니다
7. 저장
- 파일 경로 규칙:
reports/{company}/{yyyy}/{mm}/{report_id}.pdf - 스토리지는 전략 패턴으로 추상화합니다 (LocalStorage / S3Storage). 개발은 로컬, 운영은 S3/MinIO
- DB에는 파일 경로, 포맷, 크기, 생성 시각만 저장하고 파일 자체는 넣지 않습니다
- 다운로드는 API를 거치거나 presigned URL을 짧은 만료로 발급합니다
8. 재생성 정책
결측이 나중에 채워지거나 기준선이 확정되면 보고서 숫자가 바뀝니다.
- 재생성 시 기존 파일을 덮어쓸지, 버전을 남길지 결정
- 이미 외부로 발송된 보고서는 버전을 남기는 쪽이 안전
- 보고서에 "생성 시각"과 "데이터 기준 시각"을 표기
9. 체크리스트
- 기간 경계가 exclusive인가
- 집계 배치 완료 후 실행되는가
- 요일/말일 변환이 맞는가
- 다중 인스턴스에서 중복 생성되지 않는가
- 한글 폰트가 PDF에 포함되는가
- 실패 시 재시도와 알림이 있는가