{
  "id": "86e6e085-eebe-47a4-ae4e-c236cd84231e",
  "slug": "ref/khub/geocare-서브방-핸드오프-som-분석-설정-모달-v2",
  "doc_type": "ref",
  "title": "🔀 서브방 핸드오프: SoM 분석 설정 모달 v2",
  "title_en": null,
  "one_liner": "# 🔀 서브방 핸드오프: SoM 분석 설정 모달 > 메인방 작성: 2026-02-17 (v2 업데이트) > 서브방 프로토콜: ①작업설명+승인대기 ②승인 후 실행 ③결과보고 --- ## 전체 진행 상태 | Phase | 작업 | 상태 | 비고 | |-------|------|:-",
  "summary_bullets": [],
  "body_md": "# 🔀 서브방 핸드오프: SoM 분석 설정 모달\n\n> 메인방 작성: 2026-02-17 (v2 업데이트)\n> 서브방 프로토콜: ①작업설명+승인대기 ②승인 후 실행 ③결과보고\n\n---\n\n## 전체 진행 상태\n\n| Phase | 작업 | 상태 | 비고 |\n|-------|------|:----:|------|\n| A1 | gp_geoex_som_usage 테이블 | ✅ 완료 | DDL 실행 |\n| A2 | check_som_quota() RPC | ✅ 완료 | 어드민 이중확인 + RLS |\n| A3 | increment_som_usage() RPC | ✅ 완료 | UPSERT + overage |\n| A4 | Realtime Publication | ✅ 완료 | gp_geoex_som_results 등록 |\n| A5 | Premium-2 WF 병렬화 | ✅ 완료 | v9.3→v9.4 (B안) |\n| B1 | queriesReviewed UX | ✅ 완료 | 커밋 cf78b2b |\n| B2 | SoM 라이브 피드 | ✅ 완료 | 커밋 cf78b2b |\n| B3 | SomAnalysisModal 컴포넌트 | 🔲 Phase B 잔여 | 서브방 자율 |\n| B4 | QueryListSubModal | 🔲 Phase B 잔여 | 서브방 자율 |\n| B5 | SoM 페이지 연동 | 🔲 Phase B 잔여 | 서브방 자율 |\n| B6 | TrialModal 체이닝 | 🔲 Phase B 잔여 | 서브방 자율 |\n| **C1** | **SoM Orchestrator WF 수정** | **⏳ 대기** | **메인방 승인 필요** |\n| **C2** | **Quota 체크 WF 추가** | **⏳ 대기** | **메인방 승인 필요** |\n\n---\n\n## 배경\n\nSoM 분석 실행/재실행을 위한 **공용 설정 모달** 신규 개발.\n\n### 해결하는 문제\n1. **업종(industry) null → 쿼리 품질 저하**: 모달에서 업종 필수 입력\n2. **재분석 불가**: SoM 페이지에서 직접 실행 가능\n3. **어드민 대행 불가**: 어드민 패널에서 고객사 대신 실행 가능\n\n---\n\n## ✅ 메인방 완료 사항\n\n### A1~A3. Quota 백엔드\n\n- `gp_geoex_som_usage` 테이블 — RLS, UNIQUE(user_id, site_domain, period_start)\n- `check_som_quota(p_user_id, p_site_domain, p_is_admin)` → allowed, remaining, limit, used\n- `increment_som_usage(p_user_id, p_site_domain, p_executed_by)` → UPSERT\n\n### A4. Realtime Publication\n\n```sql\nALTER PUBLICATION supabase_realtime ADD TABLE gp_geoex_som_results;\n```\n\n### A5. Premium-2 WF 병렬화 (v9.4)\n\nWF: https://bawee.app.n8n.cloud/workflow/4ZSWwyYmqVqEOVVjyEWSM\n\n```\nSplitInBatches → ┬ OpenAI → Save → Merge[0] ─┐\n                 ├ Anthropic → Save → Merge[1] ─┤→ Loop\n                 ├ Perplexity → Save → Merge[2] ─┤\n                 └ Gemini → Save → Merge[3] ─┘\n```\n\nLLM별 즉시 개별 저장 → Realtime 3~8초 간격 1건씩 수신\n\n---\n\n## ✅ 작업 1+2 (커밋 cf78b2b)\n\n### queriesReviewed UX\n\n| 상황 | 라디오 | force_regenerate |\n|------|:---:|:---:|\n| 쿼리 0개 | 숨김 | true |\n| 쿼리 있음 + 미확인 | 숨김 | true |\n| 쿼리 있음 + 확인함 + 기존 유지 | 표시 | false |\n| 쿼리 있음 + 확인함 + 재생성 | 표시 | true |\n\n### SoM 라이브 피드\n\nRealtime 구독:\n```typescript\nconst channel = baweeSupabase\n  .channel(`som-results-${siteDomain}`)\n  .on('postgres_changes', {\n    event: 'INSERT', schema: 'public',\n    table: 'gp_geoex_som_results',\n    filter: `site_domain=eq.${siteDomain}`\n  }, (payload) => { /* payload.new = LLM 결과 1건 */ })\n  .subscribe();\n```\n\n---\n\n## ⏳ Phase C (메인방 승인 필요)\n\n### C1. SoM Orchestrator 파라미터\n\nWF: SXdgnGyUFMAP9oTB\nhttps://bawee.app.n8n.cloud/workflow/SXdgnGyUFMAP9oTB\n\n```json\n{\n  \"force_regenerate_queries\": false,\n  \"skip_report\": true,\n  \"executed_by\": \"user\"\n}\n```\n\n### C2. Quota 체크\n\nCONFIG 직후 → check_som_quota → If allowed=false → 에러 → 완료 후 increment_som_usage\n\n---\n\n## Phase B 잔여 (서브방 자율)\n\n### B3. SomAnalysisModal Props\n\n```typescript\ninterface SomAnalysisModalProps {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  siteDomain: string;\n  userId: string;\n  mode: 'user' | 'admin';\n  adminTargetUserId?: string;\n  onAnalysisStarted?: () => void;\n}\n```\n\n### Submit 핸들러\n\n```typescript\nawait fetch('https://bawee.app.n8n.cloud/webhook/geo/som/pipeline', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    user_id, site_domain,\n    force_regenerate_queries: queryMode === 'regenerate',\n    skip_report: true,\n    executed_by: mode === 'admin' ? `admin:${userId}` : 'user'\n  })\n});\n```\n\n### B4. QueryListSubModal\n\n쿼리 목록/수정/삭제(soft)/추가, 5~30개 제한\n\n### 참조 코드\n\n| 파일 | 참조 이유 |\n|------|----------|\n| TrialModal.tsx | AliasTagInput, 경쟁사 카드 |\n| useCompetitorManagement.ts | 경쟁사 CRUD |\n| useSettingsData.ts | brand_settings 로드 |\n| save_trial_competitors RPC | 경쟁사 저장 |\n\n---\n\n## 테스트 시나리오\n\n- T1: dentistestore.kr (20쿼리) → 모달 → 간편재분석 → force_regenerate:false\n- T2: 새 사이트 (쿼리 0개) → 자동 생성 → force_regenerate:true\n- T3: 업종 변경 → 재생성 → force_regenerate:true\n- T4: 어드민 모드 → quota 무제한 → increment 미호출\n- T5: 라이브 피드 → 3~8초 간격 → 80건 → 3초 후 자동 종료",
  "related_slugs": [],
  "faq": [],
  "jsonld": null,
  "keywords": [],
  "source_id": "eabb8fb1-d8a8-425a-ba90-7154c25781b5",
  "video_url": null,
  "duration_sec": null,
  "thumbnail_url": null,
  "image_url": null,
  "caption": null,
  "solution_slug": null,
  "captured_at": null,
  "width": null,
  "height": null,
  "created_at": "2026-08-24T11:53:44.5731+00:00",
  "updated_at": "2026-08-24T11:53:44.5731+00:00",
  "visibility": "public",
  "source_stage": "1",
  "anonymized": true,
  "source_key": "legacy-khub",
  "origin": "ingest",
  "locked": false,
  "locked_by": null,
  "locked_at": null,
  "gate_state": {
    "g1": {
      "hits": [],
      "pass": true
    },
    "g2": {
      "pass": true,
      "total": 43,
      "issues": [
        "숫자·규격 같은 검증 가능한 구체가 부족합니다",
        "핵심 요약 불릿이 3개 미만입니다",
        "Q&A 쌍이 3개 미만입니다"
      ],
      "applies": true
    },
    "g3": {
      "pass": true,
      "required": false
    },
    "stage": "1",
    "decided_at": "2026-08-24T11:53:44.334Z"
  },
  "raw_id": "03e55da7-a5cd-4d62-8b80-411062fccf68",
  "search_text": "🔀 서브방 핸드오프: SoM 분석 설정 모달 v2  # 🔀 서브방 핸드오프: SoM 분석 설정 모달 > 메인방 작성: 2026-02-17 (v2 업데이트) > 서브방 프로토콜: ①작업설명+승인대기 ②승인 후 실행 ③결과보고 --- ## 전체 진행 상태 | Phase | 작업 | 상태 | 비고 | |-------|------|:-  # 🔀 서브방 핸드오프: SoM 분석 설정 모달\n\n> 메인방 작성: 2026-02-17 (v2 업데이트)\n> 서브방 프로토콜: ①작업설명+승인대기 ②승인 후 실행 ③결과보고\n\n---\n\n## 전체 진행 상태\n\n| Phase | 작업 | 상태 | 비고 |\n|-------|------|:----:|------|\n| A1 | gp_geoex_som_usage 테이블 | ✅ 완료 | DDL 실행 |\n| A2 | check_som_quota() RPC | ✅ 완료 | 어드민 이중확인 + RLS |\n| A3 | increment_som_usage() RPC | ✅ 완료 | UPSERT + overage |\n| A4 | Realtime Publication | ✅ 완료 | gp_geoex_som_results 등록 |\n| A5 | Premium-2 WF 병렬화 | ✅ 완료 | v9.3→v9.4 (B안) |\n| B1 | queriesReviewed UX | ✅ 완료 | 커밋 cf78b2b |\n| B2 | SoM 라이브 피드 | ✅ 완료 | 커밋 cf78b2b |\n| B3 | SomAnalysisModal 컴포넌트 | 🔲 Phase B 잔여 | 서브방 자율 |\n| B4 | QueryListSubModal | 🔲 Phase B 잔여 | 서브방 자율 |\n| B5 | SoM 페이지 연동 | 🔲 Phase B 잔여 | 서브방 자율 |\n| B6 | TrialModal 체이닝 | 🔲 Phase B 잔여 | 서브방 자율 |\n| **C1** | **SoM Orchestrator WF 수정** | **⏳ 대기** | **메인방 승인 필요** |\n| **C2** | **Quota 체크 WF 추가** | **⏳ 대기** | **메인방 승인 필요** |\n\n---\n\n## 배경\n\nSoM 분석 실행/재실행을 위한 **공용 설정 모달** 신규 개발.\n\n### 해결하는 문제\n1. **업종(industry) null → 쿼리 품질 저하**: 모달에서 업종 필수 입력\n2. **재분석 불가**: SoM 페이지에서 직접 실행 가능\n3. **어드민 대행 불가**: 어드민 패널에서 고객사 대신 실행 가능\n\n---\n\n## ✅ 메인방 완료 사항\n\n### A1~A3. Quota 백엔드\n\n- `gp_geoex_som_usage` 테이블 — RLS, UNIQUE(user_id, site_domain, period_start)\n- `check_som_quota(p_user_id, p_site_domain, p_is_admin)` → allowed, remaining, limit, used\n- `increment_som_usage(p_user_id, p_site_domain, p_executed_by)` → UPSERT\n\n### A4. Realtime Publication\n\n```sql\nALTER PUBLICATION supabase_realtime ADD TABLE gp_geoex_som_results;\n```\n\n### A5. Premium-2 WF 병렬화 (v9.4)\n\nWF: https://bawee.app.n8n.cloud/workflow/4ZSWwyYmqVqEOVVjyEWSM\n\n```\nSplitInBatches → ┬ OpenAI → Save → Merge[0] ─┐\n                 ├ Anthropic → Save → Merge[1] ─┤→ Loop\n                 ├ Perplexity → Save → Merge[2] ─┤\n                 └ Gemini → Save → Merge[3] ─┘\n```\n\nLLM별 즉시 개별 저장 → Realtime 3~8초 간격 1건씩 수신\n\n---\n\n## ✅ 작업 1+2 (커밋 cf78b2b)\n\n### queriesReviewed UX\n\n| 상황 | 라디오 | force_regenerate |\n|------|:---:|:---:|\n| 쿼리 0개 | 숨김 | true |\n| 쿼리 있음 + 미확인 | 숨김 | true |\n| 쿼리 있음 + 확인함 + 기존 유지 | 표시 | false |\n| 쿼리 있음 + 확인함 + 재생성 | 표시 | true |\n\n### SoM 라이브 피드\n\nRealtime 구독:\n```typescript\nconst channel = baweeSupabase\n  .channel(`som-results-${siteDomain}`)\n  .on('postgres_changes', {\n    event: 'INSERT', schema: 'public',\n    table: 'gp_geoex_som_results',\n    filter: `site_domain=eq.${siteDomain}`\n  }, (payload) => { /* payload.new = LLM 결과 1건 */ })\n  .subscribe();\n```\n\n---\n\n## ⏳ Phase C (메인방 승인 필요)\n\n### C1. SoM Orchestrator 파라미터\n\nWF: SXdgnGyUFMAP9oTB\nhttps://bawee.app.n8n.cloud/workflow/SXdgnGyUFMAP9oTB\n\n```json\n{\n  \"force_regenerate_queries\": false,\n  \"skip_report\": true,\n  \"executed_by\": \"user\"\n}\n```\n\n### C2. Quota 체크\n\nCONFIG 직후 → check_som_quota → If allowed=false → 에러 → 완료 후 increment_som_usage\n\n---\n\n## Phase B 잔여 (서브방 자율)\n\n### B3. SomAnalysisModal Props\n\n```typescript\ninterface SomAnalysisModalProps {\n  open: boolean;\n  onOpenChange: (open: boolean) => void;\n  siteDomain: string;\n  userId: string;\n  mode: 'user' | 'admin';\n  adminTargetUserId?: string;\n  onAnalysisStarted?: () => void;\n}\n```\n\n### Submit 핸들러\n\n```typescript\nawait fetch('https://bawee.app.n8n.cloud/webhook/geo/som/pipeline', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({\n    user_id, site_domain,\n    force_regenerate_queries: queryMode === 'regenerate',\n    skip_report: true,\n    executed_by: mode === 'admin' ? `admin:${userId}` : 'user'\n  })\n});\n```\n\n### B4. QueryListSubModal\n\n쿼리 목록/수정/삭제(soft)/추가, 5~30개 제한\n\n### 참조 코드\n\n| 파일 | 참조 이유 |\n|------|----------|\n| TrialModal.tsx | AliasTagInput, 경쟁사 카드 |\n| useCompetitorManagement.ts | 경쟁사 CRUD |\n| useSettingsData.ts | brand_settings 로드 |\n| save_trial_competitors RPC | 경쟁사 저장 |\n\n---\n\n## 테스트 시나리오\n\n- T1: dentistestore.kr (20쿼리) → 모달 → 간편재분석 → force_regenerate:false\n- T2: 새 사이트 (쿼리 0개) → 자동 생성 → force_regenerate:true\n- T3: 업종 변경 → 재생성 → force_regenerate:true\n- T4: 어드민 모드 → quota 무제한 → increment 미호출\n- T5: 라이브 피드 → 3~8초 간격 → 80건 → 3초 후 자동 종료",
  "embedding": null,
  "embedded_at": null,
  "embedding_hash": null,
  "backlinks": [],
  "html_url": "https://bizspring.ai/kb/ref/khub/geocare-서브방-핸드오프-som-분석-설정-모달-v2",
  "markdown_url": "https://bizspring.ai/kb/ref/khub/geocare-서브방-핸드오프-som-분석-설정-모달-v2.md"
}