Keeper Out SPEC-AND-PLAN v2 — 3-Tier 아키텍처 + 7Phase 12주 로드맵

report sent · 72분 읽기 · 35967자

작성자 claude-code 시각 2026-05-07 11:35 KST 프로젝트 keeper-direct 수신 jadong
📥 MD 📥 PDF

Keeper Out Platform - SPEC-AND-PLAN v2

작성: 2026-05-06 KST 기반: FEATURE-MATRIX.md 126개 기능, SPEC-AND-PLAN.md v1, 김푸른 5/6 통화 노트, 현재 repo/Atlas/Vhagar 실측 목적: 기존 MVP 위에 기능을 덧대는 계획이 아니라, 처음부터 누락 없는 기능명세 + API/DB/Graph 아키텍처를 다시 정의한다.


Part 0. BLUF

결론

Graph DB 구현은 가능하다. 단, Neo4j를 트랜잭션 원장으로 쓰면 안 된다. Keeper Out의 정답 구조는 다음이다.

Vercel UI
  -> balerion.cloud /api/v2 (인증, 권한, 트랜잭션, 워커, 외부연동)
  -> Atlas Postgres (4.54M Store SSOT, 검색/지도/타겟 매장)
  -> Keeper OLTP Postgres (리드/정산/설치/콜/캠페인 원장)
  -> Vhagar Neo4j (관계 분석, 온톨로지, 퍼널/추천 read model)

Neo4j는 Store, Lead, Agent, Campaign, Installation, Payout, Call의 관계 read model이다. 쓰기 원장은 Postgres에 두고, API 서버가 outbox/worker로 Neo4j에 투영한다.

v1 대비 핵심 보정

항목 v1 v2 보정
기능 수 121 기준 일부 남음 김푸른 5/6 통화 포함 126개, 완료 16 / 미구현 110
Store 수 110K 전제 Atlas live stores=4,543,356, Vhagar mirror stores=4,539,474, 과거 Neo4j verify Store=4,541,196
Graph 상태 Neo4j live 가정 현재 Vhagar Neo4j 컨테이너는 내려가 있고 재시동 시 exit 3. 데이터/dump는 존재
balerion 역할 /api/v2 서버 예정 현재는 SMS/Giftishow v1 프록시 중심. /api/v2는 아직 없음
keeper-direct API balerion 호출 가정 현재는 Next API Route가 Supabase와 balerion v1을 직접 호출
DB 역할 Atlas/Supabase 대략 분리 Atlas=Store SSOT, Keeper Supabase=현재 OLTP, Vhagar PG=로컬 미러, Neo4j=projection
보안 role-based 예정 현재 hardcoded phone whitelist + 일부 admin mutation 인증 누락

v2 작성 중 정리한 문서 불일치


Part 1. Current State Snapshot

1.1 keeper-direct repo

항목 실측
브랜치 상태 최신 커밋 3f1c6e0 docs: SPEC v1에 Codex v2 작업 지시 추가 로컬 반영
프레임워크 Next 16.2.1, React 19.2.4
빌드 스크립트 npm run build, npm run lint
현재 API app/api/keeper-out/* Next Route Handlers
v2 API 아직 없음
rewrite /bapi/:path* -> https://atlas.balerion.cloud/api/v1/:path*만 존재
env 상태 현재 shell에는 Supabase/Balerion/Giftishow env 미주입

현재 API route surface:

GET/POST  /api/keeper-out/diagnose/request-otp
POST      /api/keeper-out/diagnose/verify
POST      /api/keeper-out/diagnose
GET       /api/keeper-out/kpi
GET       /api/keeper-out/leads
GET/PATCH/DELETE/PUT /api/keeper-out/lead/{id}
GET/POST  /api/keeper-out/expenses
GET       /api/keeper-out/incentives
GET       /api/keeper-out/leaderboard
GET/POST  /api/keeper-out/visits
GET/POST  /api/keeper-out/places
GET/PATCH /api/keeper-out/places/{id}
POST      /api/keeper-out/places/{id}/rank-snapshot
GET/POST  /api/keeper-out/demo-stores
POST      /api/keeper-out/payout/naver-pay
POST      /api/keeper-out/notify/sms
GET/POST  /api/keeper-out/sms/dispatch
GET/POST  /api/keeper-out/catchrank/refresh
POST/GET  /api/keeper-out/cron/*
POST      /api/keeper-out/admin/auth
GET       /api/keeper-out/admin/agents
GET       /api/keeper-out/admin/agents/{code}
GET       /api/keeper-out/admin/pending
GET       /api/keeper-out/admin/fallback
POST      /api/keeper-out/admin/leads/{id}/tm-call
POST      /api/keeper-out/admin/leads/{id}/dispatch
POST      /api/keeper-out/admin/leads/{id}/reject
GET       /api/keeper-out/admin/leads/{id}/external
GET/POST  /api/keeper-out/admin/dispatch/all-hold

1.2 현재 Supabase 스키마 (local migrations 기준)

Live Supabase는 세션 env/MCP가 없어 직접 조회하지 못했다. 현재 repo migrations 기준 테이블은 다음이다.

테이블 목적
keeper_outbound_agents 운영원/promoter/tm/installer
keeper_outbound_leads 진단 제출부터 설치까지 리드 원장
keeper_outbound_funnel_events 리드 이벤트 타임라인
keeper_outbound_payouts N페이/리드비/설치비 정산
keeper_outbound_places catchrank/keeper 자체 매장 마스터
keeper_outbound_owners 점주 단위 통합
keeper_outbound_place_keywords 네이버 키워드
keeper_outbound_rank_logs 순위 스냅샷
keeper_outbound_gift_reports 선물 3종/리포트 캐시
keeper_outbound_visit_logs 현장 방문/녹음/사진
keeper_outbound_demo_stores 현장 시연용 매장
keeper_outbound_otp_sessions 진단 OTP 세션
keeper_outbound_fraud_signals 반칙/부정 신호
keeper_outbound_system_state all-hold/cap 등 운영 상태
keeper_outbound_admin_sessions 관리자 OTP/세션
keeper_outbound_expenses 경비

스키마 드리프트:

1.3 Atlas AWS / atlas.balerion.cloud

Public API 실측:

항목 결과
GET /healthz {"ok": true, "tiro_key_configured": true}
GET /docs 200
GET /api/v1/store/search?q=봉피양 정상 결과 반환
OpenAPI title outbound-recorder-api

OpenAPI route surface:

/api/v1/address/building
/api/v1/address/lookup
/api/v1/admin/leads
/api/v1/admin/leads/{lead_id}/phone-reached
/api/v1/admin/timeline
/api/v1/auth/device
/api/v1/diagnose/full
/api/v1/estimates/quick-inquiry
/api/v1/franchises
/api/v1/franchises/{fr_id}
/api/v1/franchises/{fr_id}/activities
/api/v1/lead
/api/v1/recordings
/api/v1/recordings/{rec_id}
/api/v1/recordings/{rec_id}/chat
/api/v1/recordings/{rec_id}/download
/api/v1/recordings/{rec_id}/retry
/api/v1/recordings/{rec_id}/status
/api/v1/store/search
/api/v1/stores
/api/v1/stores/nearby
/api/v1/stores/search
/api/v1/stores/{store_id}/claim
/api/v1/stores/{store_id}/manual-tier
/api/v1/visits
/healthz

SSH/DB 실측:

항목 결과
SSH ubuntu@43.201.89.21 성공
Docker outbound_api, outbound_worker, outbound_db, outbound_redis
Postgres DB/user outbound
Tables stores, leads, diagnoses, claims, recordings, visits, crawl_tasks, franchises, franchise_activities, chat_messages, users, alembic_version
stores count 4,543,356
crawl_tasks count 243,710
diagnoses count 55
leads count 9
geo coverage 4,516,058 rows with lat/lng
target stores 4,533,595 rows is_target=true

stores 주요 컬럼:

id, name, phone, address, naver_place_id, meta, created_at,
franchise_id, lat, lng, road_addr, jibun_addr, sigungu_cd, bjdong_cd,
building_info, owner_name, contact_phone, name_seq, deficiency_tags,
is_target, last_visited_at, manual_tier, manual_tier_set_at

1.4 balerion.cloud

항목 결과
GET https://balerion.cloud/api/v1/sms/balance 401. 인증 필요이므로 정상 범주
GET https://balerion.cloud/ 404
SSH balerion.cloud publickey denied. 현재 세션에서 PM2/router 직접 조회 불가
현재 사용처 keeper-direct의 PPurio SMS / Giftishow NPay v1 forward

현재 문서와 코드 기준 v1 SMS surface:

POST /api/v1/sms/send
POST /api/v1/sms/naverpay
GET  /api/v1/sms/balance

/api/v2는 아직 운영 중이라고 볼 근거가 없다. Phase 0에서 새 서버 또는 기존 unified-api router에 명시적으로 추가해야 한다.

1.5 Vhagar / kontology

Hardware/OS 실측:

항목
Hostname kontology
Model Bosgame BeyondMax Series / BOSGAME M4 Plus 계열
CPU AMD Ryzen 9 7940HS, 8C/16T
RAM 25GiB total, 14GiB available at check time
Disk 936G root, 698G available
OS Ubuntu 24.04.4 LTS
Tailscale 100.110.33.98, online

Network 실측:

측정 결과
local -> 100.110.33.98 ping avg 0.034ms, same host path
balerion.cloud -> Vhagar latency 미측정. balerion SSH denied

Docker state:

컨테이너 상태
vhagar-neo4j 시작 시도 후 Exited (3). 현재 stopped
vhagar-postgres 기동 가능, 확인 후 다시 stopped

Neo4j state:

항목
Image neo4j:2026.02-community
Data dir /home/tlswk/projects/vhagar/data/neo4j
Data size 20G
Dump neo4j.dump, 2,261,322,036 bytes
Historical verify Store=4,541,196, Brand=6, USES=12,873, LOCATED_IN≈4.5M
Current live query 실패. HTTP/Bolt port closed because container exits

Neo4j 재가동 블로커:

Vhagar Postgres state:

항목
DB outbound
Tables Atlas와 거의 동일 + advising_cache
local mirror stores 4,539,474
Atlas live stores 4,543,356
lag estimate 3,882 rows
subscription atlas_sub=true, but inactive
current error publisher 100.113.6.15:5433 network unreachable

결론: Vhagar PG는 로컬 미러로 가치가 있지만 현재 CDC source endpoint가 stale하다. Keeper v2의 실시간 source로 쓰면 안 된다.


Part 2. Target Architecture v2

2.1 권장 구조

┌────────────────────────────────────────────────────────────┐
│ Vercel / keeper-direct                                      │
│ - UI only: ops admin, owner admin, diagnose, calculator, PWA │
│ - API 호출은 /api/v2 또는 NEXT_PUBLIC_API_BASE로 통일         │
│ - Next API Route는 Phase 1 이후 compatibility proxy로 축소   │
└───────────────────────┬────────────────────────────────────┘
                        │ HTTPS
┌───────────────────────▼────────────────────────────────────┐
│ balerion.cloud /api/v2                                      │
│ - Hono/FastAPI 중 하나로 통일                               │
│ - auth/session/RBAC, audit log, rate-limit                  │
│ - leads/installations/payouts/calls/campaigns CRUD          │
│ - PPurio/Giftishow/ChannelTalk/Tiro workers                 │
│ - Postgres transaction + outbox                             │
└───────────────┬───────────────────────┬────────────────────┘
                │                       │
┌───────────────▼──────────────┐ ┌──────▼─────────────────────┐
│ Atlas Postgres                │ │ Keeper OLTP Postgres        │
│ - 4.54M Store SSOT            │ │ - Lead/Agent/Payout/Call    │
│ - search/nearby/claim base    │ │ - current: Supabase         │
│ - store metadata              │ │ - target: balerion PG       │
└───────────────┬──────────────┘ └──────┬─────────────────────┘
                │ outbox/sync           │ outbox/sync
┌───────────────▼───────────────────────▼────────────────────┐
│ Vhagar Neo4j                                                │
│ - Store Ontology projection                                 │
│ - Agent 1/2/3 funnel path                                   │
│ - referral/campaign/cross-sell graph                        │
│ - recommendation and analytical read model                  │
└────────────────────────────────────────────────────────────┘

2.1.1 Admin route policy

/keeper-out/admin is the current operations admin. It remains the active route for field operations, lead verification, NPay dispatch, expenses, and the existing dashboard. Treat it as a production surface.

/admin is the new owner-only admin. It is for Shin Woosung direct use, fast feedback, and dogfooding. It must not replace or mutate /keeper-out/admin workflows until a later explicit promotion decision. Build it as a separate product surface that calls /api/v2 contracts.

2.2 DB별 역할 분리

DB 역할 쓰기 주체 읽기 주체 주의
Atlas Postgres Store SSOT, 검색, 지도, claim base Atlas API/ETL balerion v2, Vercel fallback 4.54M 규모. heavy query는 사전 인덱스 필수
Keeper Supabase 현재 keeper-outbound OLTP Next API 현재 Next API 현재 Phase 0-1 유지. 신규 v2는 직접 의존 축소
balerion Postgres 목표 OLTP 원장 /api/v2 /api/v2, workers Phase 1부터 신규 테이블 생성 권장
Vhagar Postgres Atlas 로컬 mirror/분석 logical replication Vhagar ETL 현재 subscription broken
Vhagar Neo4j graph projection/read model sync worker /api/v2/kpi/funnel, analytics 현재 container restart failure

2.3 Graph DB 채택 원칙

Neo4j에 넣을 것:

Neo4j에 넣지 말 것:


Part 3. Domain Model v2

3.1 Core entities

Entity Primary DB Graph label 핵심 필드
Store Atlas Postgres :Store store_id, name, naver_place_id, geo, sigungu, category, is_target
Lead Keeper OLTP :Lead status, source, scenario, pain, purchase intent, timestamps
Agent Keeper OLTP :Agent role, phone hash, code, active, level, permissions
Owner Keeper OLTP :Owner phone hash, name, stores
ReferralPartner Keeper OLTP :ReferralPartner type, name, phone hash, region
Campaign Keeper OLTP :Campaign channel, asset, message, audience
Quote Keeper OLTP :Quote package, capex/opex, link, opened_at
Call Keeper OLTP :Call recording_url, stt, duration, outcome, score
Installation Keeper OLTP :Installation schedule, job sheet, exceptions, status
Payout Keeper OLTP :Payout type, amount, status, approved_by, paid_at
Expense Keeper OLTP optional category, amount, receipt_url
Quest Keeper OLTP :Quest condition, evidence, reward
Device Keeper/Atlas :Device model, SKU, serial, channel

3.2 Required lead attribution fields

김푸른 통화 반영으로 agent_1/2/3만으로는 부족하다.

agent_submitted_id     -- 실제 입력한 사람
agent_attributed_id    -- 성과 귀속 대상
agent_owner_id         -- 운영상 lead owner, nullable
source_channel         -- diagnose, qr_card, real_estate_referral, channeltalk, ad, manual
referral_partner_id    -- 부동산/관리소/제휴처
display_channel        -- 한화 노출용 채널
internal_source        -- 내부 원천. 한화 화면에는 직접 노출 금지

3.3 Graph relationships

(:Agent)-[:SUBMITTED]->(:Lead)
(:Agent)-[:ATTRIBUTED]->(:Lead)
(:Agent)-[:TM_CALLED {outcome, duration, stt_score, at}]->(:Lead)
(:Agent)-[:INSTALLED {unit_count, at}]->(:Installation)
(:Lead)-[:AT]->(:Store)
(:Lead)-[:HAS_OWNER]->(:Owner)
(:Lead)-[:REFERRED_BY]->(:ReferralPartner)
(:Lead)-[:GENERATED_BY]->(:Campaign)
(:Lead)-[:HAS_QUOTE]->(:Quote)
(:Lead)-[:HAS_CALL]->(:Call)
(:Lead)-[:HAS_PAYOUT]->(:Payout)
(:Lead)-[:CONVERTED_TO]->(:Installation)
(:Store)-[:LOCATED_IN]->(:Sigungu)
(:Store)-[:USES]->(:Brand)
(:Store)-[:SIMILAR_TO]->(:Store)
(:Campaign)-[:SENT_TO]->(:Owner)
(:Agent)-[:COMPLETED]->(:Quest)
(:Installation)-[:USES_DEVICE]->(:Device)

Part 4. Feature Specification Index - 126 Features

FEATURE-MATRIX.md가 상세 문장 SSOT다. v2에서는 구현 모듈과 데이터 소유권을 붙인다.

4.1 Category summary

Category Count Done Todo Primary module
대시보드/KPI 15 6 9 dashboard, kpi
게이미피케이션 7 1 6 gamification
리드 관리 14 2 12 leads, attribution
견적/계산기 6 1 5 quotes
데이터 연동/자동화 12 0 12 sync, calls, workers
설치팀 관리 11 0 11 installations
진단/설문 앱 8 4 4 diagnose
CRM/파이프라인 7 0 7 crm, store-ontology
마케팅/캠페인 8 0 8 campaigns
영업 도구 12 1 11 field
정산/급여 7 1 6 payouts, payroll
패키지/가격 4 0 4 packages
랜딩/온보딩 4 0 4 landing
장기/탐색적 11 0 11 long-term
Total 126 16 110

4.2 Exact feature list by module

D. Dashboard/KPI (15)

ID Feature Target
D01 월 목표 프로그레스 current + v2 KPI
D02 전환 퍼널 시각화 current + v2 KPI
D03 Pain point 분석 카드 current + v2 KPI
D04 시간대별 리드 히트맵 current + v2 KPI
D05 리더보드 current + v2 leaderboard
D06 인센 정산 요약 current + v2 payouts
D07 세일즈 퍼널 대시보드, 에이전트 1/2/3 전환율 Neo4j path query
D08 한화 보고용 KPI 뷰 masked reporting API
D09 설치 일정 대시보드 installations
D10 인력 배정 현황 agents/assignments
D11 객단가/백마진/순익 리포트 margin analytics
D12 수익 구조 시뮬레이션 revenue simulator
D13 Role-based Views RBAC + frontend view model
D14 PPurio/Giftishow 운영 헬스 패널 dispatch health
D15 출격 준비 체크리스트 보드 operations checklist

G. Gamification (7)

G01 리더보드, G02 레벨 시스템, G03 도파민 피드백 루프, G04 3-way 커미션, G05 단계별 인센티브, G06 에이전트 간 경쟁 대시보드, G07 퀘스트/미션 엔진.

L. Lead Management (14)

L01 파이프라인, L02 Superadmin 삭제, L03 자동 필터링, L04 지역별 리드 자동 배포, L05 AI 리드 온도 측정, L06 리드 인정 자동 처리, L07 약정 만료 자동 타겟팅, L08 매장 온도 라벨링, L09 리드 귀속 추적, L10 Device ID 수집, L11 매장 Claim Lock, L12 리드 대리 등록/귀속 분리, L13 부동산 레퍼럴 채널 추적, L14 Superadmin 리드 필드 직접 수정.

Q. Quotes/Calculator (6)

Q01 견적 계산기, Q02 견적 계산기 고도화, Q03 건물관리자용 견적서, Q04 도면 기반 AI 견적 자동화, Q05 마진 계산기, Q06 견적계산기 링크 발송/열람 추적.

A. Automation/Sync (12)

A01 에어테이블 자동 연동, A02 콜 상태 자동 반영, A03 AI 노트 자동 기록, A04 TM 결과 자동 반영, A05 통화 기반 문자 자동 발송, A06 연락 금지 자동 필터링, A07 풀퍼널 세일즈 자동화, A08 전화 끊으면 자동 업데이트, A09 ChannelTalk 상담 인입 자동 동기화, A10 콜타임 추천 엔진, A11 유효콜 STT 자동 채점, A12 광고/브랜드콘텐츠 유입 데이터 병합.

I. Installation (11)

I01 설치 일정 조회, I02 작업 지시서 자동 생성, I03 LiDAR 층고 측정, I04 건축물대장 연동, I05 설치 리드타임 시각화, I06 재고/공정 대시보드, I07 현장 예외/추가공사비 플래그, I08 도면/첨부파일 인입 관리, I09 POC->신규 전환 추적, I10 SKU/시리얼-모델 매핑, I11 설치팀 이동거리/동선 최적화.

S. Diagnose/Survey (8)

S01 보안 진단 설문, S02 네이버페이 1만원 자동 지급, S03 Superadmin 수동 N페이 발송, S04 OTP 인증 게이트, S05 매장 사진 -> 설치 데이터 자동 추출, S06 셀프 실사 고객 포털, S07 태블릿 최적화 UI, S08 device_age="0년" 옵션.

C. CRM/Pipeline (7)

C01 에어테이블 대체 자체 CRM, C02 칸반 보드, C03 Cross-sell 파이프라인, C04 카테고리 자동 분류, C05 리드 통합 관리, C06 Store Ontology 통합 SSOT, C07 한화 노출용 데이터 마스킹.

M. Marketing/Campaign (8)

M01 문자 캠페인 자동화, M02 네이버 영수증 리뷰 프로그램, M03 자동 리뷰 답글, M04 바코드 기반 리뷰 수집, M05 인바운드 랜딩 추적, M06 키워드 플로우 분석, M07 인스타/네이버 광고 관리, M08 선물 3종/QR카드 캠페인 자산 관리.

F. Field/Sales Tools (12)

F01 경비 CRUD 모듈, F02 영업 웹앱, F03 녹음+AI 정리, F04 스크립트 관리, F05 A/B 테스트 프레임, F06 근처 매장 지도, F07 동별 브라우징, F08 페르소나 명함, F09 헌터/프로모터 개별 계정, F10 필드워크 PWA 설치/오프라인 모드, F11 베스트콜->스크립트 자동 진화, F12 운영원별 QR 명함/가상번호 발급 관리.

P. Payout/Payroll (7)

P01 인센 정산 요약, P02 프로모터 일급 자동 계산, P03 주급 시스템, P04 리드 보상 자동 분배, P05 비용 검증 + 지급 플로우, P06 녹음 품질 기반 정산 자동화, P07 TM N페이 발송 권한.

K. Packages/Pricing (4)

K01 CCTV+POS 번들 가격표, K02 인터넷 결합 할인 패키지, K03 대리점별 가격 차등 관리, K04 보조금 방식.

W. Landing/Onboarding (4)

W01 언브랜디드 랜딩 페이지, W02 전환율/PMF 트래킹, W03 강력한 CTA/부킹, W04 매장 전용 리포트/QR 랜딩 생성기.

X. Long-term (11)

X01 아웃바운드->인바운드 이식, X02 비상근 전환 완전 자동화, X03 ONVIF POS 연동, X04 출입통제 화이트라벨, X05 프랜차이즈 AI 품질 관리, X06 시스템 사용료/구독 모델, X07 토스앱 내 CCTV 모니터링, X08 도어락/보험/SaaS 확장, X09 Verkada식 Single Pane 물리보안 대시보드, X10 하이브리드 스토리지/메타데이터 업로드, X11 Plug-and-play 원격 핸드셰이크.


Part 5. API Contract v2

5.1 공통 규칙

Base URL:

https://balerion.cloud/api/v2

Headers:

Authorization: Bearer <session-or-service-token>
X-Request-Id: <uuid>
X-Client: keeper-direct-web | field-pwa | worker

Common response:

{
  "ok": true,
  "data": {},
  "meta": {
    "request_id": "uuid",
    "served_at": "2026-05-06T20:00:00+09:00"
  }
}

Error response:

{
  "ok": false,
  "error": {
    "code": "permission_denied",
    "message": "TM_NPAY_PERMISSION_REQUIRED",
    "details": {}
  }
}

5.2 Auth/RBAC

POST /auth/otp/request
POST /auth/otp/verify
GET  /auth/session
POST /auth/logout
GET  /auth/permissions

/auth/session response:

{
  "user": {
    "id": "agt_...",
    "name": "김푸른",
    "phone_masked": "010****5047",
    "roles": ["tm"],
    "permissions": ["lead.read", "lead.tm_call", "payout.npay.dispatch"]
  }
}

권한은 DB 관리형이어야 한다. hardcoded phone whitelist는 Phase 1에서 제거한다.

5.3 Leads

GET    /leads
POST   /leads
GET    /leads/{lead_id}
PATCH  /leads/{lead_id}
DELETE /leads/{lead_id}
POST   /leads/{lead_id}/status
POST   /leads/{lead_id}/assign
POST   /leads/{lead_id}/tm-call
POST   /leads/{lead_id}/dispatch/npay
POST   /leads/{lead_id}/reject-payout
GET    /leads/{lead_id}/timeline
GET    /leads/{lead_id}/graph

Create/diagnose request:

{
  "store": {
    "atlas_store_id": "uuid",
    "naver_place_id": "123",
    "store_name": "밀리클럽국수",
    "geo": { "lat": 37.0, "lng": 127.0 }
  },
  "owner": {
    "name": "대표",
    "phone": "010..."
  },
  "diagnosis": {
    "current_security": "에스원",
    "device_age": "0년",
    "camera_count": 0,
    "monthly_fee": 0,
    "combined_isp": "모름",
    "pain_points": ["요금부담"],
    "pain_note": "없음"
  },
  "attribution": {
    "agent_submitted_id": "agt_pureun",
    "agent_attributed_id": "agt_yunju",
    "source_channel": "real_estate_referral",
    "referral_partner_id": "ref_..."
  },
  "marketing_consent": true
}

Lead response:

{
  "id": "lead_...",
  "status": "pending_review",
  "payout_status": "pending_review",
  "display_channel": "field",
  "internal_source": "real_estate_referral",
  "created_at": "2026-05-06T20:00:00+09:00"
}

5.4 Stores/Atlas

GET  /stores/search?q=
GET  /stores/nearby?lat=&lng=&radius_m=
GET  /stores/{store_id}
POST /stores/{store_id}/claim
POST /stores/{store_id}/release-claim
PATCH /stores/{store_id}/manual-tier

Store reads should call Atlas. Claim writes may remain in Atlas if outbound-recorder and Keeper share the same field-work pool.

5.5 KPI/Graph

GET /kpi/summary
GET /kpi/funnel
GET /kpi/hanwha
GET /kpi/margin
GET /kpi/dispatch-health
GET /graph/leads/{lead_id}
GET /graph/agents/{agent_id}/paths
GET /graph/stores/{store_id}/neighborhood

Graph endpoints are read-only. No public arbitrary Cypher endpoint.

5.6 Campaigns/Calls/Automation

GET/POST /campaigns
POST     /campaigns/{id}/dispatch
GET      /dispatches
GET      /dispatches/health
POST     /calls/import/tiro
POST     /calls/{call_id}/score
POST     /sync/channeltalk
POST     /sync/airtable

5.7 Installations/Quotes/Payouts

GET/POST /quotes
POST     /quotes/{id}/send-link
POST     /quotes/{id}/open-event
GET/POST /installations
PATCH    /installations/{id}
POST     /installations/{id}/exceptions
GET/POST /payouts
POST     /payouts/{id}/approve
POST     /payouts/{id}/dispatch
GET      /payouts/balance
GET/POST /expenses

Part 6. Data Model Delta

6.1 Minimum SQL delta for 김푸른 5/6 requirements

alter table keeper_outbound_leads
  add column if not exists agent_submitted_id uuid references keeper_outbound_agents(id) on delete set null,
  add column if not exists agent_attributed_id uuid references keeper_outbound_agents(id) on delete set null,
  add column if not exists source_channel text default 'diagnose',
  add column if not exists internal_source text,
  add column if not exists display_channel text,
  add column if not exists referral_partner_id uuid,
  add column if not exists field_edit_count int default 0,
  add column if not exists last_edited_by text,
  add column if not exists last_edited_at timestamptz;

create table if not exists keeper_outbound_referral_partners (
  id uuid primary key default gen_random_uuid(),
  type text not null check (type in ('real_estate','building_manager','franchise','other')),
  name text not null,
  phone text,
  region text,
  active boolean default true,
  created_at timestamptz default now()
);

alter table keeper_outbound_leads
  add constraint keeper_outbound_leads_referral_partner_fk
  foreign key (referral_partner_id)
  references keeper_outbound_referral_partners(id)
  on delete set null;

create table if not exists keeper_outbound_lead_edit_audit (
  id uuid primary key default gen_random_uuid(),
  lead_id uuid not null references keeper_outbound_leads(id) on delete cascade,
  edited_by text not null,
  patch jsonb not null,
  reason text,
  created_at timestamptz default now()
);

6.2 RBAC tables

create table if not exists keeper_outbound_admin_users (
  id uuid primary key default gen_random_uuid(),
  phone text unique not null,
  name text not null,
  active boolean default true,
  created_at timestamptz default now()
);

create table if not exists keeper_outbound_roles (
  id text primary key,
  name text not null
);

create table if not exists keeper_outbound_permissions (
  id text primary key,
  description text not null
);

create table if not exists keeper_outbound_user_roles (
  user_id uuid references keeper_outbound_admin_users(id) on delete cascade,
  role_id text references keeper_outbound_roles(id) on delete cascade,
  primary key (user_id, role_id)
);

create table if not exists keeper_outbound_role_permissions (
  role_id text references keeper_outbound_roles(id) on delete cascade,
  permission_id text references keeper_outbound_permissions(id) on delete cascade,
  primary key (role_id, permission_id)
);

Minimum permissions:

lead.read
lead.write
lead.delete
lead.tm_call
lead.edit_fields
payout.npay.dispatch
payout.reject
agent.manage
system.all_hold
report.hanwha
report.internal

6.3 Dispatch/event columns currently missing

alter table keeper_outbound_leads
  add column if not exists scheduled_sms_at timestamptz,
  add column if not exists sms_dispatched_at timestamptz,
  add column if not exists sms_dispatch_status text,
  add column if not exists sms_dispatch_error text;

6.4 Graph projection constraints

CREATE CONSTRAINT ko_lead_id IF NOT EXISTS FOR (l:Lead) REQUIRE l.id IS UNIQUE;
CREATE CONSTRAINT ko_agent_id IF NOT EXISTS FOR (a:Agent) REQUIRE a.id IS UNIQUE;
CREATE CONSTRAINT ko_owner_id IF NOT EXISTS FOR (o:Owner) REQUIRE o.id IS UNIQUE;
CREATE CONSTRAINT ko_referral_id IF NOT EXISTS FOR (r:ReferralPartner) REQUIRE r.id IS UNIQUE;
CREATE CONSTRAINT ko_campaign_id IF NOT EXISTS FOR (c:Campaign) REQUIRE c.id IS UNIQUE;
CREATE CONSTRAINT ko_quote_id IF NOT EXISTS FOR (q:Quote) REQUIRE q.id IS UNIQUE;
CREATE CONSTRAINT ko_call_id IF NOT EXISTS FOR (c:Call) REQUIRE c.id IS UNIQUE;
CREATE CONSTRAINT ko_installation_id IF NOT EXISTS FOR (i:Installation) REQUIRE i.id IS UNIQUE;
CREATE CONSTRAINT ko_payout_id IF NOT EXISTS FOR (p:Payout) REQUIRE p.id IS UNIQUE;

CREATE INDEX ko_lead_status IF NOT EXISTS FOR (l:Lead) ON (l.status);
CREATE INDEX ko_lead_created_at IF NOT EXISTS FOR (l:Lead) ON (l.created_at);
CREATE INDEX ko_campaign_channel IF NOT EXISTS FOR (c:Campaign) ON (c.channel);

6.5 Projection worker pseudo-Cypher

UNWIND $rows AS row
MERGE (l:Lead {id: row.id})
SET l.status = row.status,
    l.source_channel = row.source_channel,
    l.display_channel = row.display_channel,
    l.created_at = datetime(row.created_at)

WITH l, row
MATCH (s:Store {store_id: row.atlas_store_id})
MERGE (l)-[:AT]->(s)

WITH l, row
OPTIONAL MATCH (submitted:Agent {id: row.agent_submitted_id})
FOREACH (_ IN CASE WHEN submitted IS NULL THEN [] ELSE [1] END |
  MERGE (submitted)-[:SUBMITTED]->(l)
)

WITH l, row
OPTIONAL MATCH (attributed:Agent {id: row.agent_attributed_id})
FOREACH (_ IN CASE WHEN attributed IS NULL THEN [] ELSE [1] END |
  MERGE (attributed)-[:ATTRIBUTED]->(l)
);

Projection policy:


Part 7. Tech Insights and Risks

7.1 Current implementation risks

Severity Finding Evidence Fix
High Admin mutation auth is inconsistent admin/leads/{id}/dispatch, tm-call, pending routes do not validate session server-side Central requirePermission() middleware
High PATCH /api/keeper-out/lead/{id} has no auth Lead status/agent assignment can be changed by route access Require admin session + audit
High NPay dispatch approval identity is client-supplied approved_by accepted from request body Derive from session server-side
High Giftishow readiness checks wrong env layer diagnose route checks GIFTISHOW_*, while actual send uses balerion proxy key Check BALERION_API_KEY and /sms/balance health instead
High Vhagar Neo4j currently not running container exit 3 on restart Fix before any graph dependency
Medium /diagnose and /keeper-out/diagnose flows diverge active /diagnose/DiagnoseFlow2 differs from legacy keeper-out flow Deprecate legacy route or unify component
Medium Schema drift scheduled_sms_at used in code, missing in migrations migration + schema test
Medium Hardcoded admin phones admin-auth.ts constant map DB-backed RBAC
Medium KPI loads up to 10k rows into app memory fetchKpi() client-style aggregation SQL/materialized daily stats
Medium Vhagar PG subscription broken publisher 100.113.6.15:5433 unreachable update publication host or disable subscription
Low Feature docs inconsistent 121 vs 126 v2 작성 중 matrix/supplemental 보정 완료

7.2 Graph DB feasibility

Scale is feasible if Neo4j is read model only.

Dataset Count Feasibility
Store nodes 4.54M Already loaded historically; disk 20G
Lead nodes 10K-1M near term Fine
Agent nodes <1K Fine
Calls/events 100K-5M Fine if summarized and indexed
Relationships 5M-30M Fine on 25GiB RAM with tuned heap/pagecache, but not for arbitrary deep traversals

Memory guidance for Vhagar:

7.3 Neo4j Community vs Enterprise

Enterprise is not required for MVP/Phase 1-5.

Need Community Enterprise need?
Single-node Store/Lead graph Yes No
Constraints/indexes Yes No
APOC core Yes No
RBAC inside Neo4j Limited Avoid by API-layer RBAC
HA clustering No Not now
Fabric/multi-db federation No Not now
Online backups Limited Use dumps/snapshots

Decision: keep Neo4j Community. Put RBAC and tenancy in /api/v2.

7.4 Security layer

Required baseline:


Part 8. Implementation Roadmap v2

Phase 0 - Spec freeze and infra repair (W0-W1)

  1. Keep source doc count drift fixed: 126 = Codex 26 + 김푸른 5 + original 95.
  2. Add DB migrations for device_age="0년", attribution split, referral partners, lead edit audit, scheduled SMS columns, RBAC.
  3. Fix Vhagar Neo4j restart failure and run verify:
    • Store≈4.54M
    • Brand=6
    • USES>=10K
    • LOCATED_IN>=4M
  4. Repair or intentionally disable Vhagar PG atlas_sub.
  5. Decide /api/v2 runtime: extend existing balerion unified-api or create new Hono service.
  6. Create /api/v2/health, /api/v2/auth/session, /api/v2/kpi/summary skeleton.

Exit criteria:

Phase 1 - Core API and security (W1-W3)

  1. Move admin auth to DB-backed RBAC.
  2. Protect all current admin mutation routes.
  3. Implement v2 leads CRUD and timeline.
  4. Implement NPay dispatch permission: superadmin + approved TM.
  5. Implement lead field edit UI/API with audit.
  6. Add device_age="0년" in active /diagnose UI.
  7. Keep current Next APIs as compatibility layer only.

Exit criteria:

Phase 2 - Store/field workflow (W3-W5)

  1. Wire Atlas /stores/search, /nearby, /claim through v2.
  2. Field PWA: assigned stores, claim lock, visit log, offline queue.
  3. QR/virtual number attribution.
  4. Real estate referral partner flow.
  5. Tablet UI polish.

Phase 3 - Graph projection and KPI (W5-W7)

  1. Outbox table + projection worker.
  2. Graph constraints for Lead/Agent/Referral/Campaign.
  3. D07 agent 1/2/3 funnel.
  4. D08 Hanwha masked KPI.
  5. Call-time recommendation from historical lead/call events.

Phase 4 - Calls, campaigns, and health (W7-W9)

  1. Tiro/recording import.
  2. STT valid-call scoring.
  3. ChannelTalk sync.
  4. PPurio/Giftishow dispatch health panel.
  5. SMS campaign automation and do-not-contact filter.

Phase 5 - Installation and payout automation (W9-W11)

  1. Installation job sheet.
  2. Exceptions/additional-cost flags.
  3. Schedule and routing.
  4. SKU/serial mapping.
  5. Payroll and STT-quality-based payout.

Phase 6 - Marketing/landing/packages (W11-W12)

  1. Quote link tracking.
  2. Store report/QR landing generator.
  3. Review program.
  4. Package/pricing manager.
  5. PMF tracking.

Phase 7 - Long-term platform (W12+)

Verkada-style single pane, ONVIF/POS, hybrid video metadata, plug-and-play tunnel, subscription model, cross-sell expansions.


Part 9. Verification Checklist

Before implementation

[ ] FEATURE-MATRIX total script returns total=126 done=16 todo=110
[ ] Supabase live schema checked against migrations
[ ] Vhagar Neo4j starts and `verify_phase1.sh` passes
[ ] Vhagar PG subscription fixed or disabled
[ ] balerion.cloud SSH/API ownership confirmed
[ ] `/api/v2/health` deployed
[ ] Admin permissions seeded in DB

Per phase

[ ] API contract tests
[ ] Role/permission tests
[ ] Payout dispatch audit tests
[ ] Graph projection idempotency tests
[ ] Hanwha masking tests
[ ] Browser smoke test for admin dashboard
[ ] Migration rollback note

Production SLOs

Metric Target
Diagnose submit p95 < 800ms excluding external payout
KPI summary p95 < 500ms
Graph funnel p95 < 1500ms with warm cache
NPay dispatch audit write 100%
SMS/NPay health refresh every 1-5 min
Outbox projection lag < 30s normal

Part 10. Cost Estimate

Item Current/target Monthly
Vercel keeper-direct frontend existing plan
balerion.cloud unified API current Lightsail/EC2 class about $24-40 if Lightsail medium
Atlas AWS outbound API + 4.54M Postgres existing EC2/EBS cost
Supabase keeper-direct current OLTP Free/Pro depending usage
Vhagar self-host power + network electricity only
Neo4j Community $0 license
Tailscale personal/team current likely $0/current
Storage backups S3/R2/B2 for dumps small until video metadata grows

Cost decision:


Part 11. Final Decisions

  1. The master feature count is 126, not 121.
  2. Graph DB layer is feasible and valuable, but only as projection/read model.
  3. Atlas remains Store SSOT. Keeper lead/payout/install/call state needs its own OLTP.
  4. balerion.cloud /api/v2 becomes the only public backend contract for new frontend work.
  5. /keeper-out/admin remains the current operations admin. New dogfooding admin work happens under /admin.
  6. Vhagar must be repaired before any graph-dependent feature is scheduled as required.
  7. Security/RBAC must be Phase 1, not a later cleanup.
  8. 김푸른 통화 requirements are first-class:
    • device_age="0년"
    • submitted vs attributed agent split
    • TM NPay permission
    • real-estate referral channel
    • superadmin field edit with audit

v2 SSOT: this document + FEATURE-MATRIX.md. Implementation should not start until Phase 0 checklist is complete.