Skip to content

리드 캡처 유입구 전수 조사 — themodernblinds.com

조사일 2026-08-14 · 대상 라이브 Flask 사이트 (landing-page-modernblinds/)

이 문서는 **지금 프로덕션에 떠 있는 것**만 다룹니다. 신규 Next.js 사이트 (mb_homepage_dev)는 범위 밖입니다.

이 문서를 만든 이유

유입구가 오랫동안 늘어나기만 하고 정리된 적이 없었습니다. 조사 결과 약 108개의 form_source 코드가 있지만 프로덕션에서 한 건도 저장되지 않고, 마그넷 파이프라인은 3개월째 동작하지 않고 있습니다. 상세는 §5.


1. 한 장 요약

구분 수량
캡처 표면 종류 5종 (+ LP v2 별도 체계)
폼이 달린 페이지 인라인 30 · L2 팝업 25 · L4 마그넷 팝업 4 · 인라인 마그넷 5 · L3 전화 폴백 전 페이지
작성된 form_source 코드 83개 (템플릿 하드코딩 59 + LP v2 파생 24)
런타임에 실제로 생기는 문자열 약 208개 (팝업 25개가 트리거별로 ×5 분화)
서버 엔드포인트 3개 (+ Meta CAPI 브리지 1)
프로덕션에 저장되는 유입구 정보 🔴 없음 (태그 9종 + 콜백 태그 1종이 전부)

2. 캡처 표면 5종

2-1. 인라인 견적 폼 (상시 노출)

  • 파일 app/templates/components/lp_form.html
  • 게시 30개 페이지 — 제품 6, 지역 17, gallery, faq, LP v1 4, Meta LP 1
  • 전송 POST /api/estimate
  • form_source 호출부가 Jinja 로 넘김. 미지정 시 lp_form_legacy (lp_form.html:18)
product-{제품}_inline      예: product-shutters_inline
local-{전체슬러그}_inline   예: local-window-treatments-kyle_inline
gallery_inline · faq_inline

2-2. L2 팝업 CTA — 전체 폼 (참여 트리거)

  • 파일 components/popup_cta.html + static/js/popup-cta.js
  • 게시 25개 페이지 — index, 제품 6, faq, 지역 17
  • 전송 POST /api/estimate (인라인 폼과 같은 파이프라인)

트리거 — 먼저 걸리는 것 하나 (popup-cta.js:23-28)

조건 임계값
스크롤 깊이 40%
체류 시간 10초
Exit intent 데스크톱만 (min-width:768px + hover:hover)
아티클 푸터 진입 .bp-back, .be-back, .article-footer

코드와 주석이 다릅니다

popup-cta.js 상단 주석은 "스크롤 60% / 60초" 라고 적혀 있지만 실제 TRIGGERS 값은 40% / 10초 입니다 (4-6행 vs 23-28행). 임계값을 조정하려는 사람이 주석을 믿으면 틀립니다.

🔴 제출 시점에 트리거 접미사가 붙습니다 (popup-cta.js:58-71). 서버에 도착하는 값은 템플릿에 적힌 것과 다릅니다.

템플릿    : blog-post_popup
실제 전송 : blog-post_popup_scroll   (또는 _time / _exit / _footer / _force)

이 때문에 팝업 25개 코드가 런타임에 **최대 125개**로 분화합니다.

2-3. L3 전화 폴백 — 전화 1필드

  • 파일 components/popup_phone_fallback.html + static/js/popup-phone-fallback.js
  • 게시 전 페이지 (base.html, base_lp.html 에 포함)
  • 전송 POST /api/estimate

트리거 — 기기별로 다릅니다 (popup-phone-fallback.js:35-36)

기기 조건
모바일 (≤768px) 스크롤 50% 또는 체류 30초
데스크톱 (≥768px hover) 스크롤 70% 또는 exit-intent

데스크톱이 더 보수적인 이유는 **이미 L2 전체 폼을 한 번 거절한 사용자**이기 때문입니다.

form_source 는 런타임에 L2 오버레이 값에서 파생합니다.

home_popup → home_phone1 · product-blinds_popup → product-blinds_phone1

영업은 _phone1 접미사로 콜백 대기열을 식별합니다. 프로덕션에서 유일하게 살아 있는 form_source 용도입니다 (§5-② 참조).

2-4. L4 마그넷 팝업 — 이메일 1필드

  • 파일 components/popup_magnet.html + static/js/popup-magnet.js
  • 게시 4개 — blog, blog_post, gallery, about
  • 전송 POST /api/magnet-signup 🔴 프로덕션 장애 중 (§5-①)
  • 트리거 스크롤 40% / 체류 20초 / exit-intent

L2 와 달리 트리거 접미사를 붙이지 않습니다. 메인 Lead 전환도 발화하지 않고 magnet_signup GA4 이벤트만 냅니다 (콜드 티어).

페이지 magnet_type form_source
about price_guide about_magnet_pricing_popup
blog price_guide blog-index_magnet_pricing_popup
blog_post price_guide blog-post_magnet_pricing_popup
gallery lookbook gallery_magnet_lookbook_popup

2-5. 인라인 마그넷 위젯 (상시 노출)

  • 파일 components/magnet_optin_widget.html
  • 게시 5개 — index, about, gallery, blog, blog_post
  • 전송 POST /api/magnet-signup 🔴 프로덕션 장애 중
페이지 magnet_type form_source
index price_guide home_magnet_pricing
about price_guide about_magnet_pricing
blog price_guide blog-index_magnet_pricing
blog_post price_guide blog-post_magnet_pricing
gallery lookbook gallery_magnet_lookbook

2-6. 폼이 아닌 유입구

컴포넌트 성격
mobile_phone_bar.html tel: 링크. 전 페이지. 폼 아님
cta_banner.html 연락처 페이지 링크. base.html. 폼 아님

3. 🔴 3계층 팝업 상호 억제 규칙

가장 문서화가 안 돼 있던 부분입니다. 팝업이 안 뜬다는 문의가 반복되는 이유가 여기 있습니다. 네 개의 스토리지 키가 세 팝업에 걸쳐 얽혀 있습니다.

키 저장소 수명 설정 주체 효과
lp_popup_submitted session 탭 종료까지 L2·L4 공유 어느 팝업이든 제출하면 전부 정지
lp_popup_dismissed local 24h L2 L2 정지 + L3 자격의 기준점
lp_phone_fallback_dismissed local 24h L3 L3 정지
lp_magnet_popup_dismissed local 24h L4 L4 정지

L3 는 조건이 하나 더 있습니다 (popup-phone-fallback.js:32).

L3 가 뜨려면 L2 를 최소 12시간 전에 닫았어야 한다
(L3_ELIGIBILITY_DELAY_MS = 12h)

즉 L2 를 방금 닫은 사용자에게는 L3 가 절대 뜨지 않습니다. 같은 세션에서 연달아 테스트하면 "L3 가 고장났다"고 오인하게 됩니다.

공통 억제: GPC(Global Privacy Control) 가 켜져 있으면 세 팝업 모두 뜨지 않습니다.

QA 강제 노출

브라우저 저장소를 지우지 않고 즉시 띄우는 방법입니다.

?popup=now      → L2 팝업 CTA
?popup=phone1   → L3 전화 폴백
?popup=magnet   → L4 마그넷 팝업

강제 노출로 제출하면 form_source 에 _force 가 붙습니다(L2 한정).


4. LP v2 — 별도 체계

pages/lp/google/v2/ 8개 페이지는 위 컴포넌트를 쓰지 않고 자체 lp2/ 컴포넌트를 씁니다. **페이지당 유입구가 4개**입니다.

form_source 는 하드코딩이 아니라 슬러그에서 파생됩니다 (app/lp/schema.py:365-375).

form_source_hero       → f"lp-{self.slug}_hero"
form_source_second_cta → f"lp-{self.slug}_second-cta"
form_source_bottom_bar → f"lp-{self.slug}_bottom-bar"
컴포넌트 form_source
lp2/hero.html lp-{slug}_hero
lp2/second_cta.html lp-{slug}_second-cta
lp2/bottom_bar.html lp-{slug}_bottom-bar
lp2/popup_form.html hero 값을 재사용 (popup_form.html:124)

8개 슬러그: blinds-near-me-austin, custom-blinds-austin, energy-efficient-blinds-austin, faux-wood-blinds-austin, motorized-blinds-austin, plantation-shutters-austin, roller-shades-austin, zebra-shades-austin

→ 8 × 3 = 24개 파생 코드. LP v2 는 자동 트리거를 의도적으로 쓰지 않습니다 (popup-cta.js:337).


5. 현황 진단 — 실측 4건

① 🔴 마그넷 신청이 프로덕션에서 전부 실패한다

lead_magnet_subscribers 테이블이 프로덕션에 존재하지 않습니다.

프로덕션 alembic head : 20260326_0033
필요한 마이그레이션    : 20260510_0035_lead_magnet_subscribers.py   ← 미적용
(20260427_0034_add_ai_visualize_audit_action.py 도 함께 미적용)

Loki 30일 로그 — 매일 07:00 UTC 육성 잡이 같은 오류로 죽습니다.

UndefinedTableError: relation "lead_magnet_subscribers" does not exist
2026-07-17 ~ 2026-08-14 · 34건 (하루 1건)

영향 범위: §2-4 의 팝업 4개 페이지 + §2-5 의 인라인 5개 페이지.

완화 요인 — 그리고 그것이 더 나쁜 신호일 수 있습니다

같은 30일 동안 **실제 신청 시도 로그가 0건**입니다. 34건 전부 육성 잡의 자체 오류입니다. 지금 유실되는 리드는 없어 보입니다.

뒤집으면 **마그넷 유입구 9개가 3개월간 단 한 건도 만들지 못했다**는 뜻이기도 합니다. 장애 복구보다 존치 여부가 먼저인 이유입니다.

② 🔴 form_source 108개가 프로덕션에서 측정되지 않는다

프로덕션 FastAPI(main 브랜치)는 form_source 를 받기는 하지만 저장하지 않습니다. 유일한 용도가 _phone1 접미사로 콜백 태그를 붙이는 것뿐입니다 (public_form_service.py:238-240).

-- 프로덕션 crm_lead 에서 form_source 를 담은 행
SELECT count(*) FROM crm_lead WHERE description ILIKE '%form%source%';
-- → 0

x_form_source 등 8개 구조화 필드는 스테이징에만 있습니다 (WO-001, 2026-08-12 배포). 프로덕션 반영은 홈페이지 컷오버 시점입니다.

결과 — 웹 리드 110건에 대해 프로덕션이 구분할 수 있는 것은 태그뿐입니다.

SITE: MODERNBLINDS                62   (2026-03-18 ~ 08-12)
LP: BLINDS-AUSTIN                 16
LP: MOTORIZED-BLINDS-AUSTIN        8
LP: CUSTOM-BLINDS-AUSTIN           7
SITE: AUSTINJDX                    7
LP: PLANTATION-SHUTTERS-AUSTIN     6
LP: BLOG/... 외 3종                4
PHONE CALLBACK                     2

즉 "어느 폼이 계약으로 이어지는가"를 지금은 답할 수 없습니다. 표면·트리거·페이지 축이 전부 버려집니다.

③ LP v1·Meta 5개 페이지가 같은 값을 보낸다

form_source 를 지정하지 않아 전부 기본값 lp_form_legacy 로 떨어집니다 (lp_form.html:18).

lp/google/blinds-austin.html
lp/google/motorized-blinds-austin.html
lp/google/shutters-austin.html
lp/google/outdoor-screens-austin.html
lp/meta/free-consultation.html

5개 LP 가 서로 구분되지 않습니다. §2-2 의 태그(LP: BLINDS-AUSTIN 등)로는 페이지가 구분되지만, 그 페이지 안의 **어느 폼**인지는 알 수 없습니다.

④ 마그넷 이름이 두 체계로 갈려 있다

위치 값
서버 enum (app/routes/magnet.py:47) swatch · price_guide · lookbook
form_source 문자열 pricing · lookbook

home_magnet_pricing 의 magnet_type 은 price_guide 입니다. 두 축을 조인하려면 pricing ↔ price_guide 매핑이 필요한데 어디에도 적혀 있지 않습니다.

또 swatch 는 서버 enum 과 컴포넌트 주석에만 있고 실제로 쓰는 페이지가 없습니다. 스왓치 팩은 정책에는 있으나 배포된 적이 없습니다. (magnet.py:48 의 주소 필수 검증도 그래서 한 번도 실행되지 않았습니다)


6. 데이터 흐름

[브라우저]
  utm-capture.js  →  hidden 필드 채움 (gclid/gbraid/wbraid/utm_*/lp_slug)
        │
        ├─ 인라인 폼 · L2 팝업 · L3 전화 폴백 · LP v2 폼
        │     └→ POST /api/estimate ──→ FastAPI ──→ Odoo crm.lead   ✅
        │
        ├─ L4 마그넷 팝업 · 인라인 마그넷
        │     └→ POST /api/magnet-signup ─→ FastAPI ─→ lead_magnet_subscribers
        │                                                    🔴 테이블 없음
        └─ 헬프데스크 폼 (helpdesk 페이지 1개)
              └→ POST /api/helpdesk ────→ FastAPI ──→ Odoo 티켓        ✅
경로 파일 레이트 리밋
POST /api/estimate app/routes/estimate.py:56 —
POST /api/magnet-signup app/routes/magnet.py:53 5회 / 60초
POST /api/helpdesk app/routes/helpdesk.py:187 —
POST /api/lp/meta-capi app/routes/estimate.py:177 폼 아님 (CAPI 브리지)

모든 폼은 reCAPTCHA v3 토큰을 JSON 본문에 담아 보냅니다. FormData 가 아닙니다 — 새 폼을 만들 때 main.js 의 submitForm 을 따라야 합니다.


7. 미결 판단 (오너용)

조치를 제안하지 않고 선택지와 근거만 적습니다.

# 판단 대상 배경 시점
1 마그넷 파이프라인 — 복구 vs 철거 ✅ 2026-08-14 결정: 철거. 신규 사이트에 만들지 않음. 프로덕션 alembic 복구 불필요 종결
2 스왓치 팩 — 배포 vs 코드 제거 ✅ 종결 — 마그넷 철거에 포함. 신규 사이트로 이식하지 않음 종결
3 LP v1 5개 lp_form_legacy 개별화 컷오버로 사라질 페이지면 불필요 컷오버 전
4 마그넷 이름 2체계 통일 ✅ 종결 — 마그넷 철거로 소멸. 다만 **서버 파서의 마그넷 분해 로직은 유지**한다(레거시가 컷오버까지 계속 보냄) 종결
5 3계층 팝업을 신규 사이트로 이관할 것인가 ✅ 2026-08-14 결정: L2 만 승계. L3 전화 폴백 미승계(성과 2건), L4 는 마그넷 철거로 소멸. 억제 규칙 4키→2키, 12시간 자격 규칙 소멸 종결
6 popup-cta.js 주석/코드 불일치 주석 60%/60초, 실제 40%/10초. 둘 중 어느 쪽이 의도인지 확인 필요 아무 때나

8. 재확인 방법

이 문서의 수치를 다시 검증할 때 씁니다.

cd landing-page-modernblinds

# 게시 페이지 수
for c in popup_cta popup_magnet magnet_optin_widget lp_form; do
  echo -n "$c: "; grep -rl "components/$c.html" app/templates | wc -l
done

# form_source 하드코딩 값 개수 (59 이어야 함)
grep -rhoE "form_source[\"' ]*[:=][\"' ]*[a-z0-9_-]+" app/templates \
  | sed 's/.*[:=]//' | tr -d "\"' " | grep -v '^form_source$' | sort -u | wc -l

# 마그넷 장애가 여전한가 — 0033 이면 미해결
ssh infra-production "PGPASSWORD='<fastapi>' psql -h <rds> -U fastapi \
  -d fastapi_db -c 'SELECT version_num FROM alembic_version'"

# 프로덕션이 form_source 를 저장하는가 — 0 이면 미측정
ssh infra-production "PGPASSWORD='<odoo>' psql -h <rds> -U austinjdx -d production \
  -c \"SELECT count(*) FROM crm_lead WHERE description ILIKE '%form%source%'\""

자격 증명은 프로젝트 CLAUDE.md 의 Credentials 절을 참조하십시오.


9. 이 조사가 낳은 것

이 문서의 진단은 MB-WO-05 로 실행에 옮겨졌습니다. docs/handoff/MB-WO-05-lead-capture-architecture.md

신규 Next.js 사이트의 캡처 영역을 한 파일에서 관리하는 살아 있는 지시서이며, frontend → FastAPI → Odoo 체인을 계층별 소유와 함께 명시합니다. 위 §7 의 결정 4건이 거기에 반영돼 있습니다.

🔴 부수 효과 하나 — x_form_magnet_popup(2026-08-12 추가)은 마그넷 철거로 값을 가질 수 있는 리드가 없어졌습니다. 신규 사이트는 마그넷이 없고, 레거시 리드는 프로덕션이 form_source 를 저장하지 않아 못 채웁니다. 아직 스테이징에만 있어 제거 비용은 0이며, 판단은 열려 있습니다.


10. 관련 문서

  • docs/MB_Landingpage_status.md — 컷오버 의존 관계 중앙 상태판
  • docs/work-orders/WO-001-attribution-persistence.md — 스테이징에만 있는 구조화 필드 8개
  • docs/handoff/MB-REVIEW-2026-08-09-wo-realignment.md — 신규 사이트와의 계약 (§10 마그넷 팝업 필드)