You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
표준데이터 27,408 · 포털 개별 파일 5,021 · 강남 통합예약 1,967 · 서울 구 예약사이트 171(서대문 106 · 성북 40 · 중구 13 · 강동 8 · 구로 2 · 관악 2)
서울 자치구 수
24 (서울 25개 구 중, 모든 소스 합산)
동주민센터 등 동 단위 기관 비율
26.3% (9,085 / 34,567)
서울 구 예약사이트 171건은 지금 접수 중·접수 예정인 강좌만 센 값이다(마감 강좌는 수집하지 않음). 종로·용산(robots 차단)·성동(TLS 오류)·강북·관악·송파(0건)·노원·서초·동대문(어댑터 미확정)은 비어 있다 → docs/다음-작업.md.
2026-10-11: 날짜 없이 요일만 있는 정기 운영 강좌(동주민센터 프로그램 꼴)를 3단으로 올렸다(계약 §9). 헬스 이용권·자유수영·개인레슨·물품 대여처럼 모임이 아닌 것은 제목으로 뺐다. 같은 날 2·3단의 「5만 원 이하」 수강료 조건도 없앴다(동주민센터 프로그램은 분기 6만 원 안팎이 흔하고, 수강료 칸이 없는 지자체 파일이 많아서). 「1만원」「3만원/분기」 표기를 못 읽던 포털 수집기 버그도 고쳤다. 강남 81→1,680건, 포털 개별 파일 825→3,810건이 화면에 나온다. 수강료가 사이트에만 있는 곳(인천 서구·연수 목록, 서울 서대문·성북·강동 상세)은 수집기 추가 후보.
2026-10-11(저녁): 요일을 3단 조건에서 뺐다(계약 §9). 원본에 요일 칸이 없는 강좌 2,643건(표준데이터 1,549·인천 서구 293·연수 427·천안·금천·서대문 등)이 3단에 올라와 화면 강좌가 26,947 → 29,539건이 됐다. 요일을 모르면 문장을 「N주 동안 정기적으로 만나요」·「정기적으로 만나는 강좌예요」로 쓰고 요일 줄은 「확인 안 됨」. 연수구 「횟수(1주)」 칸을 받아 기간 타일에 「주 N회」로 보여 준다(528건). 연도 없는 기간(「01-02~03-31」)엔 연도를 붙이지 않았다. 요일 조건이 우연히 막아 주던 시설·1:1 레슨 행(체력단련실·헬스·「1:1 피아노」·수영회원 등)은 제목으로 뺐다. 연수 48→474, 천안 9→168, 서대문 8→99, 금천 →192(197 중).
실패 기록 13건(pipeline/out/failures.json)의 요약은 method/SOURCES.md 실패 기록 표(2026-10-10 행)에 있다. 표준데이터 348파일 중 2건은 다운로드 중 연결이 끊겨 빠졌다.
새 테이블을 먼저 만들고 채운 뒤, 한 트랜잭션에서 이름을 바꿔 끼웁니다. 그래서 서비스 중에도 중단 없이 교체됩니다.
끝나면 행 수를 세어 원본과 맞는지 확인합니다.
--dump 로 SQL 파일만 만들 수 있고, --dry-run 으로 실제 쓰기 없이 확인할 수 있습니다.
.env.local 에는 키 두 개가 필요합니다. 이름은 TURSO_DATABASE_URL, TURSO_AUTH_TOKEN 입니다. 값은 커밋하지 않습니다.
배포
Vercel 수동 배포
개인 Vercel 팀(ynr0402) 계정으로 배포합니다. 기본 로그인 계정은 쓰지 않습니다.
vercel --global-config ~/.vercel-ynr0402 link # 처음 한 번
cat .vercel/project.json # orgId 가 개인 팀 값인지 확인
vercel --global-config ~/.vercel-ynr0402 --prod # 프로덕션 배포
배포 전에 Vercel 프로젝트에 TURSO_DATABASE_URL, TURSO_AUTH_TOKEN 을 등록해야 합니다.
GitHub Actions
워크플로
내용
ci.yml
push 와 PR 마다 린트, vitest, pytest, e2e 를 돌립니다. 시크릿을 쓰지 않습니다.
vercel.yml
push 마다 배포합니다. main 은 프로덕션, 그 밖의 브랜치는 프리뷰입니다.
vercel.yml 에는 레포 시크릿 VERCEL_TOKEN, VERCEL_ORG_ID, VERCEL_PROJECT_ID 가 필요합니다. 사용자가 레포 Settings 에 넣습니다.
시크릿을 아직 넣지 않았다면 vercel.yml 실패는 정상입니다.
본선 전에 민관협력 지원 플랫폼(클라우드)으로 옮길 예정입니다.
로그인·접수 알림 (M2-a, 2026-10-10)
전화번호와 문자 6자리로 로그인하면 강좌 카드의 「관심 강좌로 접수 알림 받기」가 실제 알림이 됩니다.
흐름
강좌 카드(접수 전 강좌만) → 「관심 강좌로 접수 알림 받기」 → 로그인(처음 한 번, /login)
등록 화면(/alert/[id])에서 문자 2번의 날짜를 보여 줍니다. 「문자 미리보기」(/alert/[id]/sms)에서 실제 문안을 볼 수 있습니다.
「내 알림」(/me)에서 강좌별로 알림을 끄거나 빼고, 「내 번호 지우기」로 번호와 알림을 모두 지웁니다.
문자 규칙
문자
언제
창
접수 3일 전
접수 시작일 − 3일, 저녁 6시
18시 이후 그날 안
접수 당일
접수 시작일, 아침 8시
8시~12시
접수 시작일은 확정값(apply_start)을 쓰고, 없으면 예상일(enrollment_patterns.next_open_estimate)을 쓰며 문안에 「예상이며 확정 아님」을 붙입니다.
접수 시각은 강남구 동복합문화센터만 「오전 9시」를 씁니다(계약 §11 권역 규칙). 다른 소스는 시각을 쓰지 않습니다.
창을 지나면 늦게 보내지 않습니다. 같은 알림·같은 종류는 한 번만 보냅니다(sms_log 유니크).
크론은 vercel.json 의 0 9 * * *·0 23 * * *(UTC = 18시·08시 KST)가 GET /api/cron/notify 를 부릅니다. Vercel Hobby 는 정각~59분 사이에 실행됩니다.
환경변수
이름
어디
뜻
CRON_SECRET
Vercel·.env.local
크론 호출 인증. Vercel 이 Authorization: Bearer 로 보냅니다
SOLAPI_API_KEYSOLAPI_API_SECRETSOLAPI_SENDER
Vercel
솔라피 문자 발송. 셋 다 있어야 보냅니다. 없으면 console 공급자(발송 없이 기록만, sms_log.status='dry'). Vercel(VERCEL_ENV 있음)에서 키가 없으면 /login 은 번호를 받자마자 「문자 로그인은 준비 중이에요」 를 보여 주고 멈춥니다(send-code 503 unavailable, 코드·기록 없음) — 오지 않는 문자를 기다리게 하지 않기 위해서입니다
DIDIM_OTP_ECHO
로컬·CI 만
1 이면 인증번호를 API 응답에 돌려줍니다(e2e 용). Vercel(VERCEL_ENV 있음)이거나 DB 가 file: 이 아니면 무시됩니다
DIDIM_NOW
e2e
YYYY-MM-DDTHH:MM. DIDIM_ALLOW_TODAY_OVERRIDE=1 일 때만
DIDIM_INSECURE_COOKIE
—
1 이면 세션 쿠키 secure 를 끕니다(https 가 아닌 곳에서 production 빌드를 돌릴 때만. Vercel 에서는 무시)
솔라피 가입·발신번호 등록·키 발급은 사용자가 합니다. 키를 Vercel 에 넣으면 코드 수정 없이 문자가 나갑니다.
pnpm dev 는 .env.development.local 의 TURSO_DATABASE_URL=file:./data/dev.db 를 씁니다. 운영 Turso 에 테스트 사용자를 쌓지 않기 위해서입니다.
데이터와 개인정보
앱 테이블 5개(usersotp_codessessionsalertssms_log)는 src/db/app-schema.ts 에 있고, 앱이 첫 접근 때 CREATE TABLE IF NOT EXISTS 로 만듭니다. pnpm db:app 으로 미리 만들 수도 있습니다. 파이프라인 schema.sql 과 Turso 교체 스크립트는 이 테이블을 건드리지 않습니다.
번호는 알림에만 씁니다. 「내 번호 지우기」는 사용자·세션·알림·인증 기록·문자 기록을 모두 지웁니다. 안내는 /privacy(초안).
인증번호는 해시로만 저장하고 5분 뒤 만료, 5번 틀리면 잠깁니다. 문자 요청은 번호당 10분 3회·하루 10회, 전체 1시간 100회·하루 500회로 제한합니다(집계와 기록을 한 문장으로 해 동시 요청도 상한을 넘지 못합니다). 한 사람이 켜 둘 수 있는 알림은 30개입니다.
IP 단위 제한은 없습니다(서버리스, KV 없음). 필요하면 Vercel Firewall 규칙으로 /api/auth/send-code 를 막습니다.