Founding as a Developer — Validate Before You Build
Pin Definitions Down in Code and Recount the Metrics
한국어 원문으로 표시합니다.
목표
'모아노트' 16주치 제품 기록에서 DAU·WAU·MAU·스티키니스·주간 공유 사용자·7일 활성화율을 정의대로 계산하는 함수를 만들고, 정의가 바뀔 때 숫자가 얼마나 흔들리는지 잰다.
왜 중요한가
지표는 정의가 적혀 있을 때만 숫자다. 내부 계정·시간대·중복·활성의 기준 가운데 하나만 달라져도 같은 기록에서 전혀 다른 추세가 나온다. 창업 초기에는 이 숫자로 투자와 채용을 결정하므로, 정의를 코드로 못박고 다시 계산해 같은 값이 나오게 하는 습관이 제품 판단의 바닥이다.
재료와 정의
/opt/fixtures/founder/product/users.csv—user_id,signup_at,is_internal(시각은 UTC, 끝에 Z)/opt/fixtures/founder/product/events.csv—user_id,ts,event(event: session_start · create_doc · share_doc · invite_sent · upgrade)- 사업 시간대는 Asia/Seoul(UTC+9). 날짜는 서울 날짜, 주는 서울 시각 월요일 00:00 시작이고 0주차는 2026-03-02 다(0~15주차).
- 내부 계정(is_internal=1)은 모든 지표에서 뺀다. 활성 = 그 기간에
session_start가 1번 이상. 사람 수로 센다. - 모든 함수는 첫 인자로 재료 디렉터리 경로를 받는다(채점기가 다른 재료로도 부른다). 날짜 인자는
"2026-06-14"같은 문자열이다.
단계
/root/founder/metrics/summary.json에 재료 개요를 쓴다:users(전체 사용자 수),internal_users,events(전체 사건 줄 수),event_counts(사건 종류별 줄 수 객체)./root/founder/metrics/metrics.py에dau(fix, day)를 만든다 — 서울 날짜day에 session_start 가 있는 외부 사용자 수(정수)./root/founder/metrics/internal.json에 2026-06-14 의dau_all(내부 계정 포함),dau_external(제외),inflation_pct(= (dau_all − dau_external) / dau_external × 100, 소수 둘째 자리 반올림)을 쓴다.metrics.py에wau(fix, day)·mau(fix, day)를 더한다 —day를 포함해 거슬러 7일·28일 안의 활성 외부 사용자 수.metrics.py에stickiness(fix, day)를 더한다 —day까지 28일 동안의 DAU 평균 ÷mau(fix, day), 소수 넷째 자리 반올림./root/founder/metrics/definitions.json에 2026-06-14 기준 WAU 를 세 정의로 쓴다:session(session_start),any_event(사건 종류 무관),core(share_doc). 모두 외부 사용자, 사람 수.metrics.py에weekly_sharers(fix)를 더한다 — 0~15주차 각각의 '그 주에 share_doc 을 한 외부 사용자 수' 를{"0": n, "1": n, ...}처럼 문자열 주차 키의 딕셔너리로 돌려준다.metrics.py에activation(fix)를 더하고(가입 주 0~11 의 외부 사용자 중 가입 시각부터 7×24시간 안에 create_doc 을 한 비율 →{"signups": n, "activated": m, "rate": 0.0000}),/root/founder/metrics/report.json에 2026-06-14 의dau·wau·mau·stickiness,activation_rate,north_star_peak_week(주간 공유 사용자가 가장 많은 주차, 정수),north_star_week15(15주차 값)를 모아 쓴다.
참고
- 서울 날짜:
datetime.strptime(ts, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc).astimezone(ZoneInfo("Asia/Seoul")).date() - 주차:
(서울시각 - datetime(2026, 3, 2, tzinfo=서울)).days // 7 - 흔한 실수: UTC 날짜로 묶기, 사건 수를 사람 수로 적기, 내부 계정을 빼지 않기, 30일이나 달력의 달로 MAU 를 세기, 스티키니스를 하루치 DAU 로 나누기.
- 표준 라이브러리(csv·datetime·zoneinfo·collections)만으로 충분하다. sqlite3 CLI 로 확인해 봐도 좋다.
재료를 먼저 센다
/root/founder/metrics/summary.json 에 users·internal_users·events·event_counts 를 쓴다.
csv.DictReader 로 두 파일을 읽고 collections.Counter 로 사건 종류를 세면 됩니다. is_internal 은 문자열 "1" 입니다.
DAU — 서울 날짜, 외부 사용자, 사람 수
/root/founder/metrics/metrics.py 에 dau(fix, day) 를 만든다. 서울 날짜 day 에 session_start 가 있는 외부 사용자 수.
UTC 시각을 Asia/Seoul 로 바꾼 뒤 .date() 로 묶고, 사용자 id 를 set 에 모아 크기를 셉니다. 내부 계정은 users.csv 의 is_internal 로 거릅니다.
내부 계정이 지표를 얼마나 부풀리나
/root/founder/metrics/internal.json 에 2026-06-14 의 dau_all·dau_external·inflation_pct 를 쓴다.
같은 날·같은 정의(session_start)에서 내부 계정 포함 여부만 바꿔 두 번 셉니다. 비율의 분모는 외부 사용자 DAU 입니다.
WAU·MAU — 기준일을 포함한 7일·28일
metrics.py 에 wau(fix, day)·mau(fix, day) 를 더한다. day 를 포함해 거슬러 7일·28일 안의 활성 외부 사용자 수.
날짜별 활성 집합을 만들어 두고 day, day-1, …, day-6(또는 -27)의 합집합 크기를 셉니다. 달력의 달이나 30일이 아닙니다.
스티키니스 — 28일 평균 DAU ÷ MAU
metrics.py 에 stickiness(fix, day) 를 더한다. day 까지 28일 동안의 DAU 평균을 mau(fix, day) 로 나누고 소수 넷째 자리로 반올림.
그날 하루의 DAU 로 나누면 요일에 따라 출렁입니다. 28일 각각의 DAU 를 더해 28 로 나눈 평균을 씁니다.
활성의 정의만 바꿔 같은 창을 센다
/root/founder/metrics/definitions.json 에 2026-06-14 기준 7일 창의 외부 사용자 수를 session·any_event·core(share_doc) 세 정의로 쓴다.
창과 사용자 필터는 그대로 두고 세는 사건 종류만 바꿉니다. any_event 는 사건 종류를 가리지 않습니다.
North Star 후보 — 주간 공유 사용자
metrics.py 에 weekly_sharers(fix) 를 더한다. 0~15주차 각각의 share_doc 외부 사용자 수를 문자열 주차 키 딕셔너리로 돌려준다.
주차는 (서울 시각 − 2026-03-02 00:00 서울).days // 7 입니다. 사건이 없는 주도 0 으로 16개 키를 모두 채웁니다.
활성화율과 한 장 보고서
metrics.py 에 activation(fix) 를 더하고 /root/founder/metrics/report.json 에 dau·wau·mau·stickiness·activation_rate·north_star_peak_week·north_star_week15 를 모은다.
활성화는 달력 날짜가 아니라 가입 시각부터 7×24시간(timedelta(days=7)) 안의 첫 create_doc 입니다. 대상은 가입 주 0~11 의 외부 사용자입니다. 가장 많은 주가 여럿이면 앞 주차를 고릅니다.