Founding as a Developer — Validate Before You Build
An Ordered, Windowed Funnel and Channel Attribution
한국어 원문으로 표시합니다.
목표
'모아노트' 기록에서 순서와 14일 창을 지킨 사람 수 퍼널을 만들고, 흔한 실수가 숫자를 얼마나 부풀리는지 잰 뒤, 첫 접점·마지막 접점 귀속으로 채널별 CAC 를 계산해 비교한다.
왜 중요한가
퍼널은 어디서 사람이 새는지, 귀속과 CAC 는 어디에 돈을 쓸지를 정한다. 사건 수를 세거나 순서·창을 무시하면 칸이 부풀고, 귀속 모델을 말없이 바꾸면 같은 채널의 CAC 가 달라진다. 정의를 코드로 못박고 다시 계산되게 해야 팀이 같은 숫자로 이야기한다.
재료와 정의
/opt/fixtures/founder/product/users.csv·events.csv(지표 모듈과 같다),/opt/fixtures/founder/product/touches.csv(user_id,ts,channel가입 전 접점),/opt/fixtures/founder/product/spend.csv(month,channel,spend_krw2026-03~05).- 내부 계정(is_internal=1)은 뺀다. 시각은 UTC 그대로 비교해도 된다(창과 순서는 시각의 차이라 시간대와 무관하다).
- 퍼널: 가입 → create_doc → share_doc → invite_sent → upgrade. 각 단계는 앞 단계에 도달한 시각 **이후(같은 시각 포함)**의 첫 발생으로 도달, 모든 단계는 가입 시각부터 14×24시간 안(경계 포함). 사람 수로 센다. 결과는
[가입, create_doc, share_doc, invite_sent, upgrade]다섯 정수. - 유료 전환자: 관측 기간 안에 upgrade 가 있는 외부 사용자. 첫 접점 = touches.csv 에서 그 사람의 ts 가 가장 이른 줄, 마지막 접점 = 가장 늦은 줄.
- CAC(원) = 그 채널의 spend_krw 3개월 합 ÷ 그 채널에 귀속된 유료 전환자 수, 정수 내림(
//). 귀속이 0명이면null. - 채널 다섯: organic_search · paid_search · social_ads · referral · content.
단계
/root/founder/funnel/counts.json에 퍼널 사건 네 종류(create_doc·share_doc·invite_sent·upgrade) 각각의{"events": 줄 수, "users": 사람 수}를 외부 사용자 기준, 관측 전체로 쓴다./root/founder/funnel/funnel.py에funnel(fix)를 만든다 — 정의대로 다섯 정수 목록./root/founder/funnel/order.json에 순서를 무시했을 때(14일 창 안에 앞 단계 사건들이 순서와 무관하게 모두 있으면 도달)의 다섯 칸unordered와 정의대로의ordered를 쓴다./root/founder/funnel/window.json에 창을 없앴을 때(관측 끝까지, 순서는 지킴)의 다섯 칸no_window와window_14d를 쓴다./root/founder/funnel/conversion.json에step_rates(칸 i ÷ 칸 i−1, 네 개, 소수 넷째 자리),overall(upgrade ÷ 가입),biggest_drop(전환율이 가장 낮은 단계의 도착 단계 이름, 예: "share_doc")을 쓴다.funnel.py의funnel(fix, channel=None)이 channel 을 받으면 마지막 접점이 그 채널인 사람만으로 퍼널을 낸다./root/founder/funnel/channel.json에 다섯 채널의 퍼널을 쓴다./root/founder/funnel/attribution.json에first_touch·last_touch각각 채널별 유료 전환자 수를 쓴다./root/founder/funnel/cac.json에first_touch·last_touch각각 채널별 CAC 와,cheapest_paid_last(비용이 0 보다 큰 채널 가운데 마지막 접점 CAC 가 가장 낮은 채널),rank_changed(비용이 있는 채널의 CAC 순위가 두 모델에서 다르면 true)를 쓴다.
참고
- 사람별로 (시각, 사건) 목록을 정렬해 두고 단계 포인터를 한 칸씩 옮기면 순서 퍼널이 된다.
- 첫/마지막 접점은 문자열 시각 비교(
"2026-03-01T…Z")로도 순서가 맞다 — 형식이 고정 길이라서다. - 흔한 실수: 사건 줄 수를 사람 수로 적기, 순서 무시, 창 없음, 첫 접점과 마지막 접점을 섞기, CAC 를 반올림해 적기.
사건 수와 사람 수는 다르다
/root/founder/funnel/counts.json 에 create_doc·share_doc·invite_sent·upgrade 의 events(줄 수)·users(사람 수)를 외부 사용자 기준으로 쓴다.
같은 사건 목록에서 len(목록) 과 len(set(사용자)) 를 나란히 구하면 됩니다. 내부 계정은 users.csv 로 거릅니다.
순서와 14일 창을 지킨 사람 수 퍼널
/root/founder/funnel/funnel.py 에 funnel(fix) 를 만든다. [가입, create_doc, share_doc, invite_sent, upgrade] 다섯 정수.
사람마다 (시각, 사건) 을 정렬하고, 다음에 기다리는 단계와 같은 사건이 앞 단계 도달 시각 이후에 나오면 한 칸 전진합니다. 가입 후 14×24시간이 지난 사건은 버립니다.
순서를 무시하면 뒤 칸이 부푼다
/root/founder/funnel/order.json 에 ordered(정의대로)와 unordered(14일 창 안에 앞 단계 사건이 순서 무관하게 모두 있으면 도달) 두 퍼널을 쓴다.
창은 그대로 두고 도달 판정만 바꿉니다. unordered 는 창 안 사건 종류 집합에 앞 단계들이 모두 있는지만 봅니다.
창을 없애면 오래된 사람일수록 전환이 높아 보인다
/root/founder/funnel/window.json 에 window_14d(정의대로)와 no_window(관측 끝까지, 순서는 지킴) 두 퍼널을 쓴다.
순서 판정은 그대로 두고 14일 창만 없앱니다.
단계별 전환율과 가장 크게 새는 칸
/root/founder/funnel/conversion.json 에 step_rates(네 개)·overall·biggest_drop(전환율이 가장 낮은 단계의 도착 단계 이름)을 쓴다.
step_rates[i] 는 퍼널 칸 i+1 ÷ 칸 i 입니다. biggest_drop 은 가장 낮은 비율이 '도착하는' 단계 이름(create_doc~upgrade 가운데 하나)입니다.
가입 경로별 퍼널
funnel(fix, channel=None) 이 channel 을 받으면 마지막 접점이 그 채널인 사람만으로 퍼널을 낸다. /root/founder/funnel/channel.json 에 다섯 채널의 퍼널을 쓴다.
touches.csv 에서 사람마다 ts 가 가장 늦은 접점의 channel 로 가입자 명단만 좁히고, 나머지 계산은 그대로입니다.
첫 접점과 마지막 접점으로 전환을 귀속한다
/root/founder/funnel/attribution.json 에 first_touch·last_touch 각각 채널별 유료 전환자 수를 쓴다.
유료 전환자는 관측 기간 안에 upgrade 가 있는 외부 사용자입니다(14일 창이 아닙니다). 첫 접점은 가장 이른 ts, 마지막 접점은 가장 늦은 ts 의 channel 입니다.
귀속 모델이 채널 CAC 를 바꾼다
/root/founder/funnel/cac.json 에 first_touch·last_touch 채널별 CAC(정수 내림, 귀속 0 이면 null)와 cheapest_paid_last·rank_changed 를 쓴다.
CAC = 3개월 비용 합 // 귀속 전환자 수. 비용이 0 인 채널은 순위 비교에서 뺍니다. rank_changed 는 비용이 있는 채널을 CAC 오름차순으로 세운 순서가 두 모델에서 다른지입니다.