keeper-out 검증 시스템 통합 스키마v2

2026-05-05 23:30 · v1 + 병렬 세션(Codex P1 박힘) + 키퍼 admin RE + Referio RE 합본 · 상태 통합 설계 — 어뷰징 시스템은 P3 대기
★ v1 → v2 무엇이 새로 들어왔나

목차

  1. 한 줄 요약 (v2)
  2. 병렬 세션 진척 — Codex P1 PASS
  3. ★ 3-layer 통합 스키마
  4. L1 — keeper-out 자체 DB (LeadDetail)
  5. L2 — 키퍼 admin (User OWNER + Order)
  6. L3 — Referio (Partner attribution)
  7. 횡단 매핑 키 + 사후 검수 쿼리
  8. Phase 분할 — 박힌 것 / 대기 / P3 어뷰징
  9. P3 어뷰징 시스템이 들어갈 정확한 위치
  10. 자동님 결정 5건
  11. 결정 대기 + 다음 액션

01.한 줄 요약 (v2)

v1 보고서가 설계만 다뤘다면 v2는 이미 박힌 코드(Codex P1 8섹션, staging에 3 commits) + 외부 시스템(키퍼 admin·Referio) RE 결과같은 도면 위에 겹친다. 3-layer 통합 스키마(L1 keeper-out 자체 DB / L2 키퍼 admin / L3 Referio)에 어뷰징 검증 시스템(V1~V8)이 박힐 정확한 위치를 못박는 것이 v2의 목표. 코드 수정은 자동님 명시 지시로 보류 — P3(어뷰징 시스템) 진입 시점만 자동님 결정 대기.

02.병렬 세션 진척 — Codex P1 PASS (오늘 22:50)

다른 Claude 세션(PM/CTO 모드) → Codex CLI 디스패치로 /keeper-out/admin/leads/[id]/ 신규 페이지 8섹션이 staging에 박혔음. 어제 박은 pain_points 백엔드도 이 라운드에 포함.

e8f22edfeat(keeper-out/admin): lead detail view P1 (8섹션)
+654 줄 — LeadDetailClient.tsx (615) + page.tsx + PipelineRow + AppHeader (img 강등)
dc4d9a0feat(keeper-out/admin): lead GET endpoint with PostgREST embed
+47 줄 — lead/[id]/route.ts (GET 추가, LEAD_DETAIL_SELECT 배열 + URLSearchParams) + types.ts (LeadDetail 인터페이스)
dbba9d1feat(keeper-out): /diagnose pain_points·pain_note 백엔드 처리
+18 줄 — supabase/migrations + types.ts + diagnose/route.ts

PM 검증 6/6: 3 commit 분리 / 색깔 룰(admin 청록 0건) / 텍스트 룰(키퍼·한화·하나비전 미박입) / untracked 보존 / tsc clean / 8섹션 모두 셔브. 실제 라이브 데이터 확인: 고반식당 이수역점 / B 92만 통신결합 / 기대혜택 ₩920,000 / 지급 합계 ₩30,000.

03.★ 3-layer 통합 스키마

keeper-out lead 1건이 시스템 3개에 어떻게 매핑되는지 단일 도면:

┌─────────────────────────────────────────────────────────────────────────────┐ │ L1 — keeper-out 자체 DB Supabase hvcrbdpjxfszhwrtcygq │ │ │ │ keeper_outbound_leads (LeadDetail 메인) │ │ id uuid · phone · naver_place_id · store_name · ceo_name │ │ pain_points text[] · pain_note text · photo_urls text[] │ │ current_security · device_age · camera_count · monthly_fee · combined_isp│ │ scenario · expected_benefit │ │ agent_1/2/3 FK agents │ │ place_id FK places (P2) │ │ owner_id FK owners (P2) │ │ status (new→contacted→quoted→installed→dropped) │ │ payout_status · scheduled_sms_at │ │ │ │ │ └── PostgREST embed (한 방 fetch) ──┐ │ │ ▼ │ │ agents · payouts · funnel_events · visit_logs · places · owners │ └──────────────┬──────────────────────────────────────────────────────────────┘ │ phone match (사후 검수 ②③룰) ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ L2 — 키퍼 admin api-account.keeperplatform.com (우리 권한 ceo@bizdirect)│ │ │ │ User (role=OWNER, 4136명) │ │ id · name · loginInfo · email · mobile · memo · createdAt │ │ shopName · shopAddress · shops[] · subscribes[] ← 비면 가입만/있으면 계약│ │ 회원번호 KP{yy}{mm}{dd}{HH}{MM}{ID4} │ │ │ │ │ Order (설치, 42건) │ │ orderNumber yymmddHHMMSSrnd · status (접수→일시확정→완료) │ │ type (고객/관리자/외부채널/한달무료/기타) · 상품 · 단가 ₩396k/대 │ │ 주문자 {매장명, 이름, mobile, 주소} │ └──────────────┬──────────────────────────────────────────────────────────────┘ │ ?ref={code} URL attribution ▼ ┌─────────────────────────────────────────────────────────────────────────────┐ │ L3 — Referio (키퍼메이트) referio.puzl.co.kr Supabase eqdnirtgmevhobmycxzn│ │ │ │ Partner (어필리에이트 = 키퍼메이트 = 자동님 본인 코드 86Af9) │ │ code (5자리 영숫자 mixed case) · authorized · tier(Authorized/Silver/...)│ │ 추천 URL keeper.ceo/keeper-mate?ref={code} │ │ 보상: 유효리드 1.5만 + 계약성사 2만 = 건당 3.5만 │ │ 어뷰징 정책: 자기추천금지 / 검색광고금지 / 허위과장금지 │ │ 가입: Puzl(noreply@updates.puzl.co.kr) 사후 수동 승인 │ └─────────────────────────────────────────────────────────────────────────────┘
★ 통합 스키마의 결정적 발견

04.L1 — keeper-out 자체 DB (LeadDetail)

Codex P1이 박은 LeadDetail 인터페이스 (Phase별 필드 표시):

// lib/keeper-out/types.ts (commit dc4d9a0 + P2 예정)
export interface LeadDetail extends Lead {
  // === P1 박힘 ===
  agent_1_data: Agent | null;
  agent_2_data: Agent | null;
  agent_3_data: Agent | null;
  payouts: Payout[];        // lead_30k / install_50k_per_unit / naver_pay_10k
  events: FunnelEvent[];     // diagnose.* / naver_pay.* / 시간순

  // === P2 대기 ===
  visit_logs: VisitLog[];    // promoter+step+outcome+GPS+사진+녹음
  place: Place | null;     // 매장 마스터 (closure_rate, priority_score)
  owner: Owner | null;     // 점주 (total_payback)
  other_leads: Lead[];      // 같은 owner의 다른 매장

  // === P3 어뷰징 시스템 (이번 보고서 신규 제안) ===
  agent_attribution: AgentAttribution | null;  // V3 게이트 결과
  otp_session: OtpSession | null;             // V1 OTP 검증 ts
  fraud_signals: FraudSignal[];                // V7 패턴 탐지 hit
  external_user: KeeperUser | null;            // L2 phone match 결과
}

API endpoint 진척

endpoint상태역할
POST /api/keeper-out/diagnose박힘진단 제출 + payouts 3건 + Giftishow fire-and-forget
GET /api/keeper-out/lead/[id]박힘 P1LeadDetail PostgREST embed 한 방
PATCH /api/keeper-out/lead/[id]박힘status 이동
POST /api/keeper-out/visits박힘방문 메모 (P2 폼이 사용)
POST /api/keeper-out/diagnose/request-otpP3 신설V3 게이트 통과 + V1 OTP 발송
POST /api/keeper-out/diagnose/verifyP3 신설V1 OTP 검증 → leads INSERT + 발송
GET /api/keeper-out/lead/[id]/externalP3 신설L2 admin User phone match (사후 검수)

05.L2 — 키퍼 admin (User OWNER + Order)

자동님 본인 권한(ceo@bizdirect.co.kr ADMIN, group=13 "키퍼 아웃바운드")으로 직접 호출 가능. JWT는 RS256 + 5h 만료. 우리 cron이 사후 검수에 사용.

// 사후 검수 핵심 쿼리 — 24h cron이 leads.phone으로 admin 조회
GET https://api-account.keeperplatform.com/v1/account/admin/users
    ?role=OWNER&phone={leads.phone}
Authorization: Bearer {RS256_JWT}

// Response
{
  "data": {
    "items": [
      {
        "id": 6245,
        "name": "이정희",
        "mobile": "010-3111-1165",
        "shopName": "아지트",
        "subscribes": []  // ★ 비어있음 = 계약 X
      }
    ],
    "total": 1
  }
}
L1 → L2 매핑일치 시 의미dual-layer 룰
leads.phone == User.mobile사장님 키퍼 가입 확인② 사업자 확인 통과
User.subscribes.length > 0키퍼 요금제 구독③ 도입 의향 통과 (계약 성사 직전)
Order.status === '설치 완료'설치 완료 매장④ 계약 완료 = install_50k 인센 트리거

06.L3 — Referio (Partner attribution)

키퍼메이트 = Puzl 운영 Referio. 자동님 본인이 가입돼있어 본인 대시보드 접근 가능(referio.puzl.co.kr/login). 우리 keeper-out과 직접 통합은 없으나, 5자리 영숫자 mixed case 추천코드 형식이 keeper-out agent.code 표준이 됨.

// keeper-out agent ↔ Referio partner 매핑 가능 컬럼
interface Agent {
  id: string;
  name: string;
  phone: string | null;
  role: 'promoter' | 'tm' | 'installer' | 'multi';
  code: string;                  // ★ 5자리 영숫자 mixed case (예: K9bR2)
  active: boolean;
  daily_rate: number;

  // === P3 어뷰징 + 향후 통합 컬럼 ===
  referio_partner_id: string | null;       // L3 매핑 (예: 86Af9)
  keeper_partner_id: number | null;        // 키퍼 partners 통합 시
  kakao_user_id: string | null;            // 향후 SSO
  invalid_rate_30d: number;                  // V7 누적 invalid률
  day_count: number;                         // V3 day cap (25/일)
  week_count: number;                        // V3 week cap (100/주)
}

Referio 어뷰징 정책 차용(FAQ Q5): "허위/과장 광고, 검색 광고 집행, 스팸성 홍보, 자기 추천(셀프 가입) 금지 — 위반 시 자격 해지". 이걸 keeper-out V7 cron 룰 매트릭스에 반영.

07.횡단 매핑 키 + 사후 검수 쿼리

L1 keeper-outL2 키퍼 adminL3 Referio
사장님 leads.phone + ceo_name User.mobile + name (직접 없음, ref URL로만)
매장 leads.naver_place_id + store_name User.shopName + Order.주문자.매장명 (없음)
영업원/메이트 agents.code (5자리) (별도 — partners 테이블 있지만 미통합) Partner.code (자동님 86Af9)
계약 시점 leads.status='installed' Order.status='설치 완료' + User.subscribes>0 (없음)
보상 payouts(naver_pay_10k + lead_30k + install_50k) (자체 정산) 유효리드 1.5만 + 계약 2만 = 3.5만 (월별)

dual-layer 사후 검수 cron 정확한 쿼리

// [24h cron — TM 통화 후]
async function checkLeadCompletion(lead: Lead) {
  // L2 admin BFF 호출 (자동님 JWT 사용)
  const res = await fetch(
    `https://api-account.keeperplatform.com/v1/account/admin/users`
    + `?role=OWNER&phone=${lead.phone}`,
    { headers: { Authorization: `Bearer ${KEEPER_ADMIN_JWT}` } }
  );
  const { data: { items } } = await res.json();

  if (items.length === 0) {
    // 사장님 키퍼 가입 안 함 = ②③ 미달
    await updatePayoutStatus(lead.id, 'lead_30k', 'invalid');
    await incrementAgentInvalidRate(lead.agent_1);
    return;
  }
  const user = items[0];
  if (user.subscribes.length > 0) {
    // ②③ 모두 통과
    await updatePayoutStatus(lead.id, 'lead_30k', 'confirmed');
    // 보너스: external_user 캐싱 (LeadDetail에 표시용)
    await upsertExternalUser(lead.id, user);
  } else {
    // 가입했지만 미구독 — 7일 추가 대기
    await scheduleRecheck(lead.id, 7 * 24 * 3600);
  }
}

08.Phase 분할 — 박힌 것 / 대기 / P3 어뷰징

P1
★ 박힘
3 commits staging (오늘 22:50): pain_points 백엔드 + GET endpoint + 8섹션 (Hero/고객/보안/Pain/사진/영업자/지급/타임라인). PM 검증 6/6 PASS.
P1.5
대기
admin layout 분리 — 마케팅 헤더(AppHeader 캐시온)를 admin path에서 격리. Codex 후속 brief 작성됨, 자동님 결정 대기. 1 commit, ~15분.
P2
대기
4섹션 추가 — visit_logs / 점주 다른 매장 / 매장 마스터(closure_rate·priority_score) / 메모 추가 폼. GET embed 확장 + LeadDetail types.ts 4 필드 추가. 3 commits, ~90분.
P3
이번 v2 신설
★ 어뷰징 검증 시스템 통합 — V1 OTP / V3 영업원 코드 게이트 / V5 잔액 preflight + cap / V7 cron / dual-layer 사후 검수. /diagnose 진입점 + admin LeadDetail에 fraud_signals/external_user 섹션 추가. 신규 다수 + 기존 route.ts 분리.

Phase별 commit 단위 (예상)

Phasecommits예상 시간핵심 파일
P13 ★ 박힘type.ts·route.ts·LeadDetailClient.tsx 615줄
P1.5115madmin/layout.tsx 신설
P2390mGET embed 확장 + 4섹션 + 메모 폼
P3~81~2일migration(otp/agent.code/fraud) + request-otp/verify 라우트 분리 + agent 모달 + cron + admin BFF 호출 + LeadDetailClient에 검증 결과 섹션

09.P3 어뷰징 시스템이 들어갈 정확한 위치

9-1. /diagnose 클라이언트 — 영업원 모달

파일변경
app/diagnose/DiagnoseFlow2.tsx"지원금 받기" 버튼 클릭 → AgentGateModal 띄움 (자동님 5/5 정정 흐름)
app/diagnose/components/AgentGateModal.tsx 신규이름 + 5자리 코드 입력 + 본사 확인 톤

9-2. 서버 — route.ts 분리

// 현재 (단일 route)
POST /api/keeper-out/diagnose
   4 non-null + sbInsert + payouts 3 + Giftishow fire-and-forget

// P3 분리 (3 단계)
POST /api/keeper-out/diagnose/request-otp
   V3(agent code+name) + V4(place_id NOT NULL+phone unique)
     + V5(잔액·cap) + V8(place 실재) + V2(GPS)
   통과 시 OTP 발송 (balerion SMS)

POST /api/keeper-out/diagnose/verify
   V1(OTP 5분/3회) + V6(EXIF)
   leads INSERT (agent_id=verified) + funnel_event 'diagnose.verified'
   Giftishow 발송 (사장님 1만원 즉시)
   payouts.lead_30k status='pending' (사후 검수 대기)

GET /api/keeper-out/lead/[id]/external
   L2 admin User phone match (LeadDetail 표시용)

9-3. admin LeadDetail — 검증 결과 섹션 신설

Codex P1이 박은 8섹션에 P3에서 2개 더:

#섹션표시 데이터
9방문 로그 P2visit_logs
10점주 다른 매장 P2other_leads
11매장 마스터 P2place
13메모 폼 P2visits POST
14검증 결과 P3otp_session(언제·횟수·통과여부) / agent_attribution(이름·코드 매칭) / fraud_signals[]
15키퍼 admin 매칭 P3external_user (User.id·shopName·subscribes·createdAt 비교) / 매칭 시 24h ②룰 통과 표시

9-4. 신규 테이블/컬럼

-- supabase/migrations/2026MMDD_keeper_outbound_anti_abuse.sql

-- agents 테이블 컬럼 추가
alter table public.keeper_outbound_agents
  add column if not exists code text unique,           -- 5자리 영숫자
  add column if not exists referio_partner_id text,        -- 86Af9 매핑
  add column if not exists keeper_partner_id int,           -- 향후 통합
  add column if not exists kakao_user_id text,
  add column if not exists invalid_rate_30d numeric default 0;

-- leads 테이블 컬럼 추가
alter table public.keeper_outbound_leads
  alter column naver_place_id set not null,                -- V4
  add constraint uniq_phone unique (phone),               -- V4 lifetime
  add column if not exists ip_address inet,                -- V9
  add column if not exists user_agent text,                -- V9
  add column if not exists external_user_id int,             -- L2 매칭 캐시
  add column if not exists external_subscribed_at timestamptz; -- ③룰 ts

-- OTP 세션
create table public.keeper_outbound_otp_sessions (
  id uuid primary key default gen_random_uuid(),
  phone text not null,
  code char(6) not null,
  expires_at timestamptz not null,
  attempts int default 0,
  verified_at timestamptz,
  created_at timestamptz default now()
);

-- 어뷰징 신호
create table public.keeper_outbound_fraud_signals (
  id uuid primary key default gen_random_uuid(),
  lead_id uuid references public.keeper_outbound_leads(id) on delete cascade,
  rule text not null,                                  -- self_referral / unknown_responses / cluster / ...
  severity text check (severity in ('low','med','high')),
  payload jsonb,
  flagged_at timestamptz default now()
);

10.자동님 결정 5건 (v1+v2 누적)

Q결정출처
agent.code 형식5자리 영숫자 mixed case (Referio 표준)v1
ref URL 패턴?ref={code}v1
UX — 영업원 코드 시점"지원금 받기" 클릭 후 모달v1 (자동님 5/5 정정)
코드 수정 보류계획 끝난 후 추후 지시v1
v1 보고서 수용 + v2 통합대부분 수용 + 병렬 세션 합본v2 (이번)

11.결정 대기 + 다음 액션

다음 액션 6갈래 (병렬 세션 + v2 통합 후)

B. P3 어뷰징 시스템 즉시 진입 (P1.5/P2 보류)

5/5 출격 직후라 어뷰징 노출 위험이 P1.5/P2보다 큼. 다만 LeadDetail에 fraud 섹션 추가는 P2 끝난 후가 깔끔.

예상: 1~2일. ~8 commits.

C. 즉시 hotfix 4건만

naver_place_id NOT NULLphone unique lifetime ③ Giftishow checkBalance preflight ④ 일일 송금 카운터. P3 풀 시스템 전 즉시 효과 큰 4건.

예상: 30분. 1 migration + route.ts 수정.

D. main 머지 (P1만 production 배포)

staging 3 commits를 main에 fast-forward. keeper-direct.ceo/keeper-out/admin/leads/<id> 라이브.

E. referio.puzl.co.kr 자동님 본인 대시보드 RE

v1에서 보류한 B 옵션. 자동님 코드 86Af9 데이터 모델 + 정산 흐름 직접 확인.

F. review-on SSH 진입 — 자동님 본인 시스템 어뷰징 가드 확인

같은 Giftishow 키. balerion.cloud SSH (call-center.pem). P3 설계 보강용.

비결정 5건 (v1에서 이월)

  1. OTP 발송 채널 — balerion SMS proxy / KISA / NICE / NHN
  2. 영업원 우회로 — 관리자 PIN 백도어 / 코드 없는 진단 거부
  3. review-on과 잔액 격리 — Giftishow 키 별도 / 모니터링만 분리
  4. agent.code 발급 운영 — 본사 admin 수동 / 자율가입 후 승인 / Referio 매핑
  5. 05-policy.md 어뷰징 정책 명문화 별도 문서

12.참조 — 보고서 체인

in-repo 문서 (4건)