Skip to content
winenyPublic

About

디딤칸 — 흩어진 동네 공공 강좌를 한곳에 모아 「다시 나가는 첫걸음」을 돕는 서비스 (2026 AI·디지털 기반 사회문제 해결 챌린지)

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

디딤칸

흩어진 동네 공공 강좌를 한곳에 모아, 지금 어디까지 나갈 수 있는지에 맞춰 다시 나가는 첫걸음 강좌를 골라 주는 모바일 웹입니다.

  • 공모전: 2026 AI·디지털 기반 사회문제 해결 챌린지
  • 일정: 서류 통과 10/7 · 상세기획서 11/3 · 본선 12월 초
  • 주 이용자: 퇴직·사별 뒤 나가던 자리가 끊긴 50~60대(청년 병행)
  • 첫 현장: 서울 강남구

화면 흐름

  1. 상태 질문: 「아직은 집에서」·「한 번 나가 볼 수 있어요」·「정기적으로 다닐 곳을 찾아요」 중 하나를 고릅니다.
  2. 동네 고르기: 시도와 시군구를 고릅니다. 집에서 고른 사람은 건너뛸 수 있습니다.
  3. 홈(계단): 지금 칸을 크게 보여 줍니다. 칸은 1단 집에서, 2단 한 번 나가기, 3단 정기적으로 세 개입니다. 지금 칸 강좌가 가까운 순으로 나옵니다.
  4. 강좌 카드: 수강료, 기초 여부, 정원, 뽑는 방식, 기간, 장소를 타일로 보여 줍니다. 기관 신청 링크와 전화 버튼이 있습니다.

화면 상단의 「가 크게보기」를 켜면 글자와 버튼이 커지고, 선택지 그림이 빠집니다. 화면 캡처는 e2e/shots/ 에 저장되지만 커밋하지 않습니다.

구조

폴더 내용
src/ Next.js(App Router, TypeScript) 화면, API, DB 조회(Drizzle)
pipeline/ Python 수집·정규화·파생 규칙 파이프라인과 테스트
data/ e2e 용 축약 DB fixture.db 만 커밋. 실데이터 DB 는 커밋하지 않음
e2e/ Playwright 시나리오와 셀렉터 기준 fixture-ids.json
docs/ 데이터 계약(contract.md), 셀렉터 계약(selectors.md), 다음 작업(다음-작업.md)

실행

필요한 환경: Node 22, pnpm 11, Python 3.12.

pnpm install
cp data/fixture.db data/dev.db                        # 처음 한 번(개발 DB. 로그인·알림 행이 여기 쌓인다)
pnpm dev                                              # 로컬 개발 서버(.env.development.local → dev.db)
pnpm test                                             # vitest 단위 테스트
pytest pipeline                                       # 파이프라인 테스트
pnpm e2e                                              # Playwright e2e(헤드리스, 390×844)
pnpm build                                            # 프로덕션 빌드
pnpm lint                                             # 린트
  • TURSO_DATABASE_URL=file:... 로 주면 같은 코드가 파일 DB 를 읽습니다. 원격 Turso 가 필요 없습니다. 커밋되는 fixture.db 를 개발 서버에 직접 물리지 마세요(로그인하면 전화번호가 그 파일에 남습니다).
  • 모든 화면은 요청 때 렌더됩니다(상단 「로그인/내 알림」이 쿠키를 읽음). 쿠키가 없으면 DB 를 치지 않습니다.
  • 로컬 pnpm e2e 는 이미 떠 있는 3000번 서버를 재사용합니다(e2e.db 를 새로 복사하지 않음). 깨끗이 돌리려면 CI=1 pnpm e2e.
  • e2e 는 e2e/fixture-ids.json 의 today 값을 DIDIM_TODAY 로(DIDIM_ALLOW_TODAY_OVERRIDE=1 과 함께) 넘겨 기준일을 고정합니다. 그래서 접수 상태 문구가 날짜에 흔들리지 않습니다.

데이터 수집

소스

소스 출처 법적 전제 설명 원문 저장
전국평생학습강좌 표준데이터 공공데이터포털 #15013110 이용허락 제한 없음 저장
지자체 개별 파일 공공데이터포털, 지자체별 데이터셋 데이터셋별 공공누리 표기 저장
강남구 통합예약 강남구 생활 예약 REST 약관상 원문 재게시 금지 저장 안 함
서울 자치구 예약사이트 구별 예약 사이트 robots 준수, 요청 간 1초 이상 저장 안 함

실행

.venv/bin/python pipeline/build.py --today $(date +%F)     # 실제 수집
.venv/bin/python pipeline/build.py --today $(date +%F) --offline   # 네트워크 없이 축약본으로 빌드
  • --sources 로 소스를 골라 돌릴 수 있습니다. 값은 std,portal,gangnam,seoul 입니다.
  • 산출물은 data/didimkan.db(SQLite), pipeline/out/build_report.json(소스별 건수와 계단별 건수), pipeline/out/failures.json(실패 목록)입니다.

데이터 현황

항목 값
기준일 2026-10-11 (수집·빌드)
총 강좌 수 34,567 (1단 2,274 · 2단 6,033 · 3단 21,232 · 계단 없음 5,028 — 어린이·청소년 전용은 계단에서 제외)
화면에 나오는 지역 시군구 227곳 · 기관 1,275곳
소스별 건수 표준데이터 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건은 다운로드 중 연결이 끊겨 빠졌다.

Turso 업로드

.venv/bin/python pipeline/push_turso.py --db data/didimkan.db --env .env.local
  • 새 테이블을 먼저 만들고 채운 뒤, 한 트랜잭션에서 이름을 바꿔 끼웁니다. 그래서 서비스 중에도 중단 없이 교체됩니다.
  • 끝나면 행 수를 세어 원본과 맞는지 확인합니다.
  • --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자리로 로그인하면 강좌 카드의 「관심 강좌로 접수 알림 받기」가 실제 알림이 됩니다.

흐름

  1. 강좌 카드(접수 전 강좌만) → 「관심 강좌로 접수 알림 받기」 → 로그인(처음 한 번, /login)
  2. 등록 화면(/alert/[id])에서 문자 2번의 날짜를 보여 줍니다. 「문자 미리보기」(/alert/[id]/sms)에서 실제 문안을 볼 수 있습니다.
  3. 「내 알림」(/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_KEY SOLAPI_API_SECRET SOLAPI_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개(users otp_codes sessions alerts sms_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 를 막습니다.

규칙 요약

  • 날짜와 마감일을 추측해서 채우지 않습니다. 확인하지 못하면 「확인 안 됨」으로 둡니다.
  • 재게시가 금지된 소스는 설명 원문을 저장하지 않습니다. 강좌명, 요일, 시간, 정원, 수강료, 장소, 링크만 저장합니다.
  • 수집은 각 사이트의 robots 규칙을 따릅니다. 차단된 구는 수집하지 않고 실패 기록에 남깁니다.
  • 화면 문구에 금지어를 쓰지 않습니다. 금지어 목록은 명세 §5에 있습니다. 담당자는 「복지관 담당자」라고 부릅니다.
  • 사진은 모두 AI 생성 시범 이미지입니다. 사진을 쓰는 화면에 「AI 생성 시범 이미지」를 표기합니다.

출처

  • 공공데이터포털 전국평생학습강좌 표준데이터 (#15013110)
  • 강남구 통합예약 서비스
  • 서울 자치구 평생학습 예약 사이트
  • 지자체별 공공데이터포털 데이터셋

About

디딤칸 — 흩어진 동네 공공 강좌를 한곳에 모아 「다시 나가는 첫걸음」을 돕는 서비스 (2026 AI·디지털 기반 사회문제 해결 챌린지)

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages