숨은 고승률 카드 글은 "매월 초 갱신됩니다"라고 적어뒀는데, 그럼 매달 누군가 데이터를 다시 뽑아서 표를 갈아끼워야 해요. 그 누군가가 저 혼자다 보니, 깜빡하고 안 하면 글이 거짓말을 하는 셈이 됩니다. 그래서 자동화를 만들었어요.

처음엔 PC 작업 스케줄러였다

처음 버전은 Windows 작업 스케줄러로 매월 1일 09:00에 로컬 PC에서 스크립트를 돌리는 방식이었어요. PC가 꺼져 있으면 "켜질 때" 실행되도록 catch-up 옵션도 넣어뒀었죠. 근데 여기엔 구조적인 문제가 두 개 있었습니다.

  • PC가 켜져 있어야만 돌아간다. 꺼져 있으면 늦게 돌거나, 그 PC를 아예 안 켜는 달엔 실행 자체가 안 됐어요.
  • 결과물이 그 PC 안에만 있었다. reports/hidden-cards-YYYY-MM-DD.md 파일로 저장했는데, git에도 안 올라가는 로컬 파일이라 다른 기기에서는 확인할 방법이 없었습니다.

그래서 서버(Vercel 크론)로 옮겼어요. 실행 주체가 제 PC가 아니라 서버가 되니까 PC 전원 상태랑 완전히 무관해졌고, 결과도 Supabase DB에 저장돼서 어디서든 운영자 리포트 페이지에서 확인할 수 있게 됐습니다.

크론 설정

vercel.json에 0 0 1 * *로 등록했어요. Vercel 크론은 UTC 기준이라 매월 1일 00:00 UTC = 한국 시간 매월 1일 09:00이에요. 새벽 05:00 KST에 도는 일일 데이터 수집 배치 다음에 실행되도록 맞춰서, 그날 아침 최신 데이터를 바로 쓸 수 있게 했습니다. (정확히는 매월 1일 오전 9시대 — 플랜에 따라 그 한 시간 안 어느 시점에 돌 수 있어요.)

같은 함수를 그대로 재사용한다

리포트가 티어리스트와 "같은 데이터 소스"를 쓴다는 건, 단순히 같은 원본 테이블을 참조한다는 정도가 아니라 티어리스트를 만드는 함수(getVoltaMetaView)를 리포트도 똑같이 호출한다는 뜻이에요. 순위, 티어 산정, 승률 전부 이 함수 하나에서 나오고, 리포트는 그 위에 랭커Pick·12경기 이상·사용률 TOP5 제외·포지션별 티어 점수 TOP3 같은 필터만 추가로 얹습니다.

이전의 로컬 스크립트는 원본 테이블에 직접 쿼리를 날리고 티어 계산 로직만 재사용하는 방식이라, 티어리스트 화면이랑 수치가 미묘하게 어긋날 여지가 있었는데, 서버로 옮기면서 아예 같은 함수를 쓰게 돼 그 위험도 같이 없어졌어요. (티어리스트 화면은 1시간 캐시를 쓰고 리포트는 캐시 없이 직접 호출하는 차이는 있지만, 원본 데이터가 하루 한 번만 바뀌어서 결과엔 영향 없어요.)

테이블 구조

monthly_reports 테이블은 kind, report_date, collected_at, markdown, created_at 컬럼으로 구성했고, PK는 (kind, report_date)예요. 리포트 종류·날짜 조합으로 하나씩만 남기는 구조라, 같은 날 여러 번 돌려도 마지막 결과로 덮어써집니다.

아직 정식 실행은 한 번도 안 됐다

이 글을 쓰는 시점 기준으로 DB엔 리포트가 딱 1건 저장돼 있어요. 그런데 이것도 사실 스케줄이 돌아서 생긴 게 아니라, 크론 코드를 배포하고 37분 뒤에 제가 수동으로 한 번 실행해본 기록이에요. 매월 1일에만 도는 구조라 진짜 첫 정식 실행은 아직 안 왔고, 12월 1일이 되어야 처음 돌아갑니다.

여전히 사람이 해야 하는 일

자동화됐다고 글까지 자동으로 바뀌는 건 아니에요. 리포트 페이지에서 복사하는 마크다운은 리포트 전체(검증용 승률 대안 표, "가장 높은" 체크리스트 포함)라, 실제 글에는 포지션별 "TOP3" 표만 골라서 교체해야 합니다. 더 손이 가는 건 본문 문장이에요 — "UC 호날두는 경기당 득점 3.39로 가장 높다" 같은 문장은 매달 1~3위가 바뀌면 같이 다시 써야 하는데, 이 부분은 자동 생성되지 않아서 리포트의 검증 리스트를 보고 사람이 직접 고쳐야 해요. 표 교체와 날짜 수정까지는 운영 문서에 정리해뒀는데, 이 문장 재검토 단계는 아직 문서화가 안 돼 있어서 다음에 보완할 계획입니다.