cja-funnel-health-check
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFunnel Health Check (Customer Journey Analytics)
漏斗健康检查(Customer Journey Analytics)
Turn a plain-English funnel description into a quantified step-by-step
conversion analysis. The output identifies the biggest leakage point in the
funnel and provides dimension-based breakdowns to show which audience or channel
has the worst drop-off.
Funnel health checks are most valuable when a team suspects a specific step is
broken but hasn't quantified it. This skill does the quantification in a single
conversation turn.
将用自然语言描述的漏斗转化为量化的分步转化分析。输出结果会识别漏斗中泄漏最严重的节点,并提供基于维度的细分数据,展示哪类受众或渠道的用户流失情况最严重。
当团队怀疑某个特定步骤存在问题但尚未量化时,漏斗健康检查的价值最大。此技能可在单次对话中完成量化分析。
CJA MCP Tools Used
使用的CJA MCP工具
- — resolve page name, event, or other dimensions for step filtering
findDimensions - — get the base metric to measure (sessions, visitors, events)
findMetrics - — find exact dimension values for step names
searchDimensionItems - (with
runReport) — measure visitor counts at each funnel stepadhocSegments - — use existing segments as funnel step filters if applicable
findSegments - — understand segment logic before applying as a funnel step
describeSegment
- — 解析页面名称、事件或其他维度,用于步骤筛选
findDimensions - — 获取用于衡量的基础指标(会话数、访客数、事件数)
findMetrics - — 查找与步骤名称匹配的精确维度值
searchDimensionItems - (搭配
runReport) — 衡量每个漏斗步骤的访客数量adhocSegments - — 若适用,使用现有细分作为漏斗步骤筛选条件
findSegments - — 在将细分作为漏斗步骤应用前,先了解其逻辑
describeSegment
Phase 0 — Setup
阶段0 — 设置
- Call to list available data views.
findDataViews - If the user hasn't specified a data view, present the list and ask which to use.
- Call with the chosen ID.
setDefaultSessionDataViewId - Ask the user to define the funnel stages if not already specified (e.g., "What are the steps in the funnel you want to analyze?").
- 调用列出可用的数据视图。
findDataViews - 如果用户未指定数据视图,列出选项并询问使用哪一个。
- 使用选定的ID调用。
setDefaultSessionDataViewId - 如果用户尚未指定漏斗阶段,请询问用户定义这些阶段(例如:“你想要分析的漏斗包含哪些步骤?”)。
Phase 1 — Define Funnel Steps
阶段1 — 定义漏斗步骤
1.1 Parse the user's funnel description
1.1 解析用户的漏斗描述
Extract the step sequence from the user's plain-English description:
- "Homepage → Product Page → Cart → Purchase"
- "Registration → Onboarding Step 1 → Onboarding Step 2 → Activated"
- "Landing Page → Lead Form → Thank You Page"
Each step must resolve to a measurable condition in CJA. A step can be:
- Page view: user viewed a specific page (resolved via page name dimension)
- Event/metric: user triggered a specific action (e.g., "Added to Cart")
- Segment membership: user meets a pre-built segment condition
从用户的自然语言描述中提取步骤序列:
- “首页 → 产品页 → 购物车 → 购买”
- “注册 → 新用户引导步骤1 → 新用户引导步骤2 → 激活”
- “着陆页 → 线索表单 → 感谢页”
每个步骤必须能解析为CJA中可衡量的条件。步骤可以是:
- 页面浏览:用户浏览了特定页面(通过页面名称维度解析)
- 事件/指标:用户触发了特定操作(例如“加入购物车”)
- 细分成员:用户符合预构建的细分条件
1.2 Clarify ambiguous steps
1.2 明确模糊的步骤
If any step is vague (e.g., "checkout" without a page name), ask one question:
"For the 'Checkout' step — should I look at people who visited a page containing 'checkout' in the URL, or those who triggered a specific event like 'Cart Add'?"
Do not ask more than one clarifying question at a time.
如果任何步骤描述模糊(例如未指定页面名称的“结账”),提出一个问题:
“对于‘结账’步骤 — 我应该查看URL中包含‘checkout’的页面访问者,还是触发了‘加入购物车’这类特定事件的用户?”
一次不要提出多个澄清问题。
1.3 Resolve page names to dimension values
1.3 将页面名称解析为维度值
For page-based steps, call to find the exact dimension
values that match the step name. First call with a semantic
search like to confirm the correct dimension ID — in most
CJA data views this is , not :
searchDimensionItemsfindDimensions"page name url"variables/web.webPageDetails.namevariables/pagesearchDimensionItems(
dimensionId: "variables/web.webPageDetails.name",
searchAnd: "<step page name>",
startDate: "<period start>",
endDate: "<period end>",
page: 0,
limit: 10
)Present matches to the user if there are multiple candidates:
"I found these pages matching 'checkout': /checkout/start, /checkout/payment, /checkout/review. Should I use '/checkout/start' as the entry to the checkout step?"
对于基于页面的步骤,调用查找与步骤名称匹配的精确维度值。首先调用并使用语义搜索,例如,确认正确的维度ID — 在大多数CJA数据视图中,这个ID是,而非:
searchDimensionItemsfindDimensions"page name url"variables/web.webPageDetails.namevariables/pagesearchDimensionItems(
dimensionId: "variables/web.webPageDetails.name",
searchAnd: "<step page name>",
startDate: "<period start>",
endDate: "<period end>",
page: 0,
limit: 10
)如果有多个候选结果,将匹配项展示给用户:
“我找到了这些匹配‘checkout’的页面:/checkout/start, /checkout/payment, /checkout/review。我应该将‘/checkout/start’作为结账步骤的入口吗?”
Phase 2 — Measure Each Step
阶段2 — 衡量每个步骤
For each funnel step, construct an ad hoc segment that filters to visitors/sessions
that reached that step. Then run a report measuring the base metric (usually
Unique Visitors or Sessions) with that segment applied.
对于每个漏斗步骤,构建一个临时细分(ad hoc segment),筛选出到达该步骤的访客/会话。然后运行报告,应用该细分来衡量基础指标(通常是独立访客数或会话数)。
2.1 Construct ad hoc segments for each step
2.1 为每个步骤构建临时细分
A "reached step N" ad hoc segment is:
- Container: Visit or Person (use Visit for session-level funnels, Person for cross-visit journeys)
- Condition: Page Name equals "<step page value>" OR Event occurred
“到达步骤N”的临时细分规则:
- 容器:访问(Visit)或用户(Person)(会话级漏斗使用Visit,跨访问旅程使用Person)
- 条件:页面名称等于“<步骤页面值>” 或 事件已触发
2.2 Run reports for each step
2.2 为每个步骤运行报告
Run one per step with the ad hoc segment applied. The
parameter takes fully-formed CJA segment definition objects — NOT a simplified
shorthand. The correct structure is:
runReportadhocSegmentsrunReport(
dimensionIds: "variables/web.webPageDetails.name",
metricIds: "metrics/visitors",
startDate: "<period start>",
endDate: "<period end>",
page: 0,
limit: 1,
adhocSegments: [{
"func": "segment",
"version": [1, 0, 0],
"container": {
"func": "container",
"context": "visitors",
"pred": {
"func": "streq",
"val": { "func": "attr", "name": "variables/web.webPageDetails.name" },
"str": "<step page value>"
}
}
}]
)Use (person-level) for cross-visit funnels. Use
(session-level) for within-session funnels. The metric
value comes from in the response.
context: "visitors"context: "visits"summaryData.filteredTotals[0]The dimension and value used in the predicate should match what was found via
in Phase 1 — use the exact string returned.
searchDimensionItemsvalueImportant: Each step uses a cumulative filter — measure "visitors who
EVER reached this step in the period," not "visitors who ONLY visited this
page." This produces the classic funnel waterfall.
Capture for each step i from 1 to N.
stepCount[i]为每个步骤运行一次并应用临时细分。参数需要完整的CJA细分定义对象 — 而非简化的简写形式。正确的结构如下:
runReportadhocSegmentsrunReport(
dimensionIds: "variables/web.webPageDetails.name",
metricIds: "metrics/visitors",
startDate: "<period start>",
endDate: "<period end>",
page: 0,
limit: 1,
adhocSegments: [{
"func": "segment",
"version": [1, 0, 0],
"container": {
"func": "container",
"context": "visitors",
"pred": {
"func": "streq",
"val": { "func": "attr", "name": "variables/web.webPageDetails.name" },
"str": "<step page value>"
}
}
}]
)跨访问漏斗使用(用户级)。会话内漏斗使用(会话级)。指标值来自响应中的。
context: "visitors"context: "visits"summaryData.filteredTotals[0]谓词中使用的维度和值应与阶段1中通过找到的内容匹配 — 使用返回的精确字符串。
searchDimensionItemsvalue重要提示:每个步骤使用累积筛选 — 衡量“在周期内曾到达此步骤的访客”,而非“仅访问此页面的访客”。这会生成经典的漏斗瀑布图。
记录每个步骤i(从1到N)的。
stepCount[i]Phase 3 — Compute Funnel Metrics
阶段3 — 计算漏斗指标
For each step-to-step transition:
- = stepCount[i+1] / stepCount[i] × 100
stepConversionRate[i→i+1] - = stepCount[i] − stepCount[i+1]
dropOff[i→i+1] - = 100 − stepConversionRate[i→i+1]
dropOffRate[i→i+1]
Overall funnel:
- = stepCount[N] / stepCount[1] × 100
overallConversionRate - = argmax(dropOff[i→i+1]) — the step with the most visitors lost
biggestDropOffStep
对于每个步骤到步骤的转换:
- = stepCount[i+1] / stepCount[i] × 100
stepConversionRate[i→i+1] - = stepCount[i] − stepCount[i+1]
dropOff[i→i+1] - = 100 − stepConversionRate[i→i+1]
dropOffRate[i→i+1]
整体漏斗:
- = stepCount[N] / stepCount[1] × 100
overallConversionRate - = argmax(dropOff[i→i+1]) — 流失访客数最多的步骤
biggestDropOffStep
Phase 4 — Optional: Segment the Funnel
阶段4 — 可选:细分漏斗
If the user wants to compare funnel performance across audiences or channels,
run the same step reports filtered by a dimension or segment:
runReport(
dimensionIds: "variables/device_type",
metricIds: "metrics/visitors",
startDate: "<range start>",
endDate: "<range end>",
page: 0,
limit: 10,
adhocSegments: [{ /* step N filter — same structure as Phase 2 */ }]
)Note: use with to
confirm the device dimension ID for the data view (often ).
findDimensionssearchQuery: "device type mobile desktop"variables/device_typeThis shows step-N visitor counts broken down by device type (or channel,
or country). If one segment has a dramatically lower conversion through the
worst step, that's the target for optimization.
Common comparisons to suggest:
- Device type (mobile vs desktop conversion often differs significantly)
- Marketing channel (paid vs organic users may convert differently)
- New vs returning visitors
如果用户想要比较不同受众或渠道的漏斗表现,运行相同的步骤报告并按维度或细分筛选:
runReport(
dimensionIds: "variables/device_type",
metricIds: "metrics/visitors",
startDate: "<range start>",
endDate: "<range end>",
page: 0,
limit: 10,
adhocSegments: [{ /* step N filter — same structure as Phase 2 */ }]
)注意:调用并使用确认数据视图中的设备维度ID(通常是)。
findDimensionssearchQuery: "device type mobile desktop"variables/device_type这会展示按设备类型(或渠道、国家)细分的步骤N访客数。如果某个细分群体在最糟糕的步骤中的转化率明显更低,那就是优化的目标。
建议的常见对比:
- 设备类型(移动端与桌面端的转化率通常差异显著)
- 营销渠道(付费用户与自然流量用户的转化率可能不同)
- 新访客 vs 回访客
Phase 5 — Generate HTML Funnel Report
阶段5 — 生成HTML漏斗报告
Generate the funnel report inline and write to
.
/tmp/cja_funnel_health_check_report_<YYYY-MM-DD_HHMMSS>.html在线生成漏斗报告,并写入。
/tmp/cja_funnel_health_check_report_<YYYY-MM-DD_HHMMSS>.htmlHTML Template
HTML模板
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Funnel Health Check — {ORG_NAME} — {FUNNEL_NAME}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700;900&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #f5f4f1;
--surface: #ffffff;
--ink: #1a1a1a;
--ink-muted: #6b6b6b;
--border: #e5e2dc;
--header-bg: #0e0e10;
--header-warm: #3a1010;
--accent-red: #c8312f;
--accent-red-bright: #ff6b68;
--accent-red-soft: #fdecea;
--accent-green: #1f7a4d;
--accent-yellow: #d4a017;
}
body { font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: var(--bg); color: var(--ink); line-height: 1.5;
-webkit-font-smoothing: antialiased; }
/* === Header === */
header { background: linear-gradient(120deg, var(--header-bg) 0%, #1a0d0d 55%, var(--header-warm) 100%);
color: #fff; padding: 56px 56px 44px; position: relative; overflow: hidden; }
header::after { content: ""; position: absolute; right: -140px; top: -140px;
width: 460px; height: 460px;
background: radial-gradient(circle, rgba(200,49,47,.35) 0%, transparent 70%);
pointer-events: none; }
.header-inner { max-width: 1080px; margin: 0 auto; position: relative; z-index: 1; }
.eyebrow { display: inline-flex; align-items: center; gap: 8px;
padding: 6px 14px; border: 1px solid rgba(255,107,104,.55);
border-radius: 999px; color: var(--accent-red-bright);
font-size: 11px; font-weight: 600; letter-spacing: 1.2px;
text-transform: uppercase; margin-bottom: 24px;
background: rgba(200,49,47,.10); }
.eyebrow::before { content: ""; width: 6px; height: 6px;
background: var(--accent-red-bright); border-radius: 50%; }
header h1 { font-family: "Playfair Display", Georgia, serif;
font-size: 56px; font-weight: 700; letter-spacing: -1.5px;
line-height: 1.05; margin-bottom: 14px; color: #fff; }
header .lede { font-size: 16px; max-width: 560px;
color: rgba(255,255,255,.80); margin-bottom: 24px;
line-height: 1.55; }
header .meta { display: flex; flex-wrap: wrap; gap: 22px;
font-size: 13px; color: rgba(255,255,255,.60); }
header .meta span { display: inline-flex; align-items: center; gap: 6px; }
header .meta .icon { opacity: .8; }
/* === Tabs === */
nav { background: var(--surface); border-bottom: 1px solid var(--border);
padding: 0 56px; display: flex; gap: 28px;
position: sticky; top: 0; z-index: 50; }
nav a { display: block; padding: 16px 0; font-size: 14px;
color: var(--ink); text-decoration: none;
border-bottom: 2px solid transparent;
transition: border-color .15s ease; }
nav a:hover { border-bottom-color: var(--accent-red); }
/* === Container === */
.container { max-width: 1080px; margin: 0 auto; padding: 36px 56px 60px; }
/* === Section label === */
.section-label { font-size: 11px; font-weight: 700;
text-transform: uppercase; letter-spacing: 1.4px;
color: var(--ink-muted); margin-bottom: 14px;
padding-bottom: 10px; border-bottom: 1px solid var(--border); }
/* === KPI grid === */
.kpi-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 14px; margin-bottom: 36px; }
.kpi-tile { background: var(--surface); border-radius: 8px;
padding: 22px 22px 20px;
border-top: 3px solid #b9b6ae;
box-shadow: 0 1px 3px rgba(0,0,0,.05); }
.kpi-tile.down { border-top-color: var(--accent-red); }
.kpi-tile.up { border-top-color: var(--accent-green); }
.kpi-tile.flat { border-top-color: #b9b6ae; }
.kpi-head { display: flex; justify-content: space-between;
align-items: center; margin-bottom: 10px; }
.kpi-label { font-size: 11px; font-weight: 700;
text-transform: uppercase;
color: var(--ink-muted); letter-spacing: 1px; }
.kpi-value { font-family: "Playfair Display", Georgia, serif;
font-weight: 700; font-size: 38px;
line-height: 1; color: var(--ink);
margin-bottom: 12px; }
.pill { display: inline-flex; align-items: center; gap: 4px;
padding: 3px 9px; border-radius: 4px;
font-size: 12px; font-weight: 600; line-height: 1.4; }
.pill.down { background: var(--accent-red-soft); color: var(--accent-red); }
.pill.up { background: #ebf5ef; color: var(--accent-green); }
.pill.flat { background: #f1efea; color: var(--ink-muted); }
.prior { display: block; margin-top: 10px;
font-size: 12px; color: var(--ink-muted); }
/* === Funnel chart wrap === */
.chart-wrap { background: var(--surface); border-radius: 8px;
padding: 28px 32px;
box-shadow: 0 1px 3px rgba(0,0,0,.04);
margin-bottom: 22px; }
.chart-wrap h2 { font-family: "Playfair Display", Georgia, serif;
font-size: 18px; font-weight: 700;
margin-bottom: 18px;
padding-bottom: 12px;
border-bottom: 1px solid var(--border); }
/* === Sections (collapsible tables) === */
.section { background: var(--surface); border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,.04);
margin-bottom: 22px; overflow: hidden; }
.section-header { padding: 18px 28px; border-bottom: 1px solid var(--border);
display: flex; justify-content: space-between;
align-items: center; cursor: pointer; }
.section-header h2 { font-family: "Playfair Display", Georgia, serif;
font-size: 18px; font-weight: 700; }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
thead th { background: #faf8f4; padding: 12px 22px;
text-align: left; font-weight: 600;
text-transform: uppercase; letter-spacing: .6px;
font-size: 11px; color: var(--ink-muted);
border-bottom: 1px solid var(--border); }
tbody td { padding: 12px 22px; border-bottom: 1px solid #f4f1eb; }
tbody tr:last-child td { border-bottom: none; }
.badge { display: inline-block; padding: 3px 9px; border-radius: 4px;
font-size: 11px; font-weight: 600; }
.badge.green { background: #ebf5ef; color: var(--accent-green); }
.badge.red { background: var(--accent-red-soft); color: var(--accent-red); }
.badge.yellow { background: #fef6e3; color: #b67a08; }
.badge.grey { background: #f1efea; color: var(--ink-muted); }
.highlight-row td { background: var(--accent-red-soft) !important;
font-weight: 700; }
.progress-bar-wrap { background: #f1efea; border-radius: 4px;
height: 8px; overflow: hidden;
width: 100%; min-width: 80px; }
.progress-bar { height: 100%; border-radius: 4px;
background: linear-gradient(90deg, var(--accent-red), #8a1d1c); }
.rec-item { display: flex; gap: 12px; align-items: flex-start;
padding: 14px 28px; border-bottom: 1px solid #f1eeea;
font-size: 14px; line-height: 1.55; }
.rec-item:last-child { border-bottom: none; }
.back-top { position: fixed; bottom: 24px; right: 24px;
background: var(--accent-red); color: #fff;
width: 44px; height: 44px; border-radius: 50%;
border: none; font-size: 20px; cursor: pointer;
box-shadow: 0 4px 12px rgba(200,49,47,0.30); }
footer { text-align: center; padding: 32px 24px;
font-size: 12px; color: var(--ink-muted); }
/* === Print === */
@media print {
nav { display: none; position: static; }
header { padding: 36px 32px 28px; }
header h1 { font-size: 42px; }
.section-header { cursor: default; }
.kpi-row { page-break-inside: avoid; }
.kpi-tile, .chart-wrap, .section {
box-shadow: none; border: 1px solid var(--border);
}
.back-top { display: none; }
}
</style>
</head>
<body>
<header>
<div class="header-inner">
<div class="eyebrow">Funnel Health Report</div>
<h1>{ORG_NAME} Funnel Health</h1>
<p class="lede">Step-by-step conversion for the {FUNNEL_NAME} journey across {DATE_RANGE}, with the biggest leakage point surfaced.</p>
<div class="meta">
<span><span class="icon">📅</span> {DATE_RANGE}</span>
<span><span class="icon">📊</span> {DATA_VIEW}</span>
<span><span class="icon">🕔</span> Prepared {GENERATED_DATE}</span>
</div>
</div>
</header>
<nav>
<a href="#overview">Overview</a>
<a href="#chart">Funnel Chart</a>
<a href="#steps">Step Detail</a>
<a href="#segments">Segment Breakdown</a>
<a href="#recs">Recommendations</a>
</nav>
<div class="container">
<!-- Summary KPI Tiles -->
<div class="section-label">Funnel Summary</div>
<div id="overview" class="kpi-row">
<div class="kpi-tile flat">
<div class="kpi-head"><div class="kpi-label">Entered Funnel</div></div>
<div class="kpi-value">{STEP_1_COUNT}</div>
<span class="prior">Step 1 visitors</span>
</div>
<div class="kpi-tile flat">
<div class="kpi-head"><div class="kpi-label">Completed Funnel</div></div>
<div class="kpi-value">{STEP_N_COUNT}</div>
<span class="prior">Reached final step</span>
</div>
<div class="kpi-tile flat">
<div class="kpi-head"><div class="kpi-label">Overall Conversion</div></div>
<div class="kpi-value">{OVERALL_CVR}%</div>
<span class="prior">End-to-end rate</span>
</div>
<div class="kpi-tile down">
<div class="kpi-head"><div class="kpi-label">Biggest Drop-Off Step</div></div>
<div class="kpi-value" style="font-size:22px;">{WORST_STEP_NAME}</div>
<span class="pill down">▼ Worst leakage</span>
</div>
<div class="kpi-tile down">
<div class="kpi-head"><div class="kpi-label">Worst Step Drop-Off</div></div>
<div class="kpi-value">{WORST_STEP_DROPOFF}%</div>
<span class="prior">Of visitors lost at this step</span>
</div>
</div>
<!-- Funnel Bar Chart -->
<div id="chart" class="chart-wrap">
<h2>Funnel Visualization</h2>
<canvas id="funnelChart" height="100"></canvas>
</div>
<!-- Step-by-Step Table -->
<div id="steps" class="section">
<div class="section-header" onclick="toggle('steps-body')">
<h2>Step-by-Step Analysis</h2>
<span id="steps-body-icon">▾</span>
</div>
<div id="steps-body">
<table>
<thead><tr>
<th>Step</th>
<th>Visitors</th>
<th>% of Step 1</th>
<th>Step Conversion</th>
<th>Drop-Off</th>
<th>Drop-Off Rate</th>
<th>Visual</th>
</tr></thead>
<tbody>
<!-- For each step i:
<tr class="{highlight-row if worst step}">
<td>{STEP_NAME}</td>
<td>{STEP_COUNT}</td>
<td>{PCT_OF_STEP_1}%</td>
<td>{STEP_CVR}% {badge}</td>
<td>{DROPOFF}</td>
<td>{DROPOFF_RATE}%</td>
<td>
<div class="progress-bar-wrap">
<div class="progress-bar" style="width:{PCT_OF_STEP_1}%"></div>
</div>
</td>
</tr>
-->
</tbody>
</table>
</div>
</div>
<!-- Segment Breakdown (optional) -->
<div id="segments" class="section">
<div class="section-header" onclick="toggle('seg-body')">
<h2>Segment Breakdown at Worst Step</h2>
<span id="seg-body-icon">▾</span>
</div>
<div id="seg-body">
<table>
<thead><tr>
<th>Segment / Dimension</th>
<th>Entered Worst Step</th>
<th>Passed Worst Step</th>
<th>Conversion</th>
<th>vs. Average</th>
</tr></thead>
<tbody>
<!-- Fill with dimension breakdown at the worst step -->
</tbody>
</table>
</div>
</div>
<!-- Recommendations -->
<div id="recs" class="section">
<div class="section-header" onclick="toggle('rec-body')">
<h2>Optimization Recommendations</h2>
<span id="rec-body-icon">▾</span>
</div>
<div id="rec-body">
<!-- 2-3 recommendation items as .rec-item -->
</div>
</div>
</div>
<button class="back-top" onclick="window.scrollTo({top:0,behavior:'smooth'})">↑</button>
<footer>Funnel Health Check — {ORG_NAME} — Generated {GENERATED_DATE}</footer>
<script>
// Funnel bar chart — recolor via design tokens.
// Stage colors fade from accent-red (worst leakage upstream) to a deeper red downstream.
const ctx = document.getElementById('funnelChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
labels: {STEP_NAMES_JSON},
datasets: [{
label: 'Visitors',
data: {STEP_COUNTS_JSON},
backgroundColor: [
'rgba(200,49,47,0.90)',
'rgba(200,49,47,0.72)',
'rgba(200,49,47,0.54)',
'rgba(200,49,47,0.38)',
'rgba(200,49,47,0.22)'
],
borderRadius: 6
}]
},
options: {
responsive: true,
plugins: { legend: { display: false } },
scales: {
y: { beginAtZero: true, grid: { color: '#eee9df' },
ticks: { color: '#6b6b6b' } },
x: { grid: { display: false },
ticks: { color: '#1a1a1a', font: { weight: '600' } } }
}
}
});
function toggle(id) {
var el = document.getElementById(id);
var ic = document.getElementById(id + '-icon');
if (el.style.display === 'none') { el.style.display=''; ic.textContent='\u25be'; }
else { el.style.display='none'; ic.textContent='\u25b8'; }
}
</script>
</body></html>html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Funnel Health Check — {ORG_NAME} — {FUNNEL_NAME}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700;900&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #f5f4f1;
--surface: #ffffff;
--ink: #1a1a1a;
--ink-muted: #6b6b6b;
--border: #e5e2dc;
--header-bg: #0e0e10;
--header-warm: #3a1010;
--accent-red: #c8312f;
--accent-red-bright: #ff6b68;
--accent-red-soft: #fdecea;
--accent-green: #1f7a4d;
--accent-yellow: #d4a017;
}
body { font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: var(--bg); color: var(--ink); line-height: 1.5;
-webkit-font-smoothing: antialiased; }
/* === Header === */
header { background: linear-gradient(120deg, var(--header-bg) 0%, #1a0d0d 55%, var(--header-warm) 100%);
color: #fff; padding: 56px 56px 44px; position: relative; overflow: hidden; }
header::after { content: ""; position: absolute; right: -140px; top: -140px;
width: 460px; height: 460px;
background: radial-gradient(circle, rgba(200,49,47,.35) 0%, transparent 70%);
pointer-events: none; }
.header-inner { max-width: 1080px; margin: 0 auto; position: relative; z-index: 1; }
.eyebrow { display: inline-flex; align-items: center; gap: 8px;
padding: 6px 14px; border: 1px solid rgba(255,107,104,.55);
border-radius: 999px; color: var(--accent-red-bright);
font-size: 11px; font-weight: 600; letter-spacing: 1.2px;
text-transform: uppercase; margin-bottom: 24px;
background: rgba(200,49,47,.10); }
.eyebrow::before { content: ""; width: 6px; height: 6px;
background: var(--accent-red-bright); border-radius: 50%; }
header h1 { font-family: "Playfair Display", Georgia, serif;
font-size: 56px; font-weight: 700; letter-spacing: -1.5px;
line-height: 1.05; margin-bottom: 14px; color: #fff; }
header .lede { font-size: 16px; max-width: 560px;
color: rgba(255,255,255,.80); margin-bottom: 24px;
line-height: 1.55; }
header .meta { display: flex; flex-wrap: wrap; gap: 22px;
font-size: 13px; color: rgba(255,255,255,.60); }
header .meta span { display: inline-flex; align-items: center; gap: 6px; }
header .meta .icon { opacity: .8; }
/* === Tabs === */
nav { background: var(--surface); border-bottom: 1px solid var(--border);
padding: 0 56px; display: flex; gap: 28px;
position: sticky; top: 0; z-index: 50; }
nav a { display: block; padding: 16px 0; font-size: 14px;
color: var(--ink); text-decoration: none;
border-bottom: 2px solid transparent;
transition: border-color .15s ease; }
nav a:hover { border-bottom-color: var(--accent-red); }
/* === Container === */
.container { max-width: 1080px; margin: 0 auto; padding: 36px 56px 60px; }
/* === Section label === */
.section-label { font-size: 11px; font-weight: 700;
text-transform: uppercase; letter-spacing: 1.4px;
color: var(--ink-muted); margin-bottom: 14px;
padding-bottom: 10px; border-bottom: 1px solid var(--border); }
/* === KPI grid === */
.kpi-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 14px; margin-bottom: 36px; }
.kpi-tile { background: var(--surface); border-radius: 8px;
padding: 22px 22px 20px;
border-top: 3px solid #b9b6ae;
box-shadow: 0 1px 3px rgba(0,0,0,.05); }
.kpi-tile.down { border-top-color: var(--accent-red); }
.kpi-tile.up { border-top-color: var(--accent-green); }
.kpi-tile.flat { border-top-color: #b9b6ae; }
.kpi-head { display: flex; justify-content: space-between;
align-items: center; margin-bottom: 10px; }
.kpi-label { font-size: 11px; font-weight: 700;
text-transform: uppercase;
color: var(--ink-muted); letter-spacing: 1px; }
.kpi-value { font-family: "Playfair Display", Georgia, serif;
font-weight: 700; font-size: 38px;
line-height: 1; color: var(--ink);
margin-bottom: 12px; }
.pill { display: inline-flex; align-items: center; gap: 4px;
padding: 3px 9px; border-radius: 4px;
font-size: 12px; font-weight: 600; line-height: 1.4; }
.pill.down { background: var(--accent-red-soft); color: var(--accent-red); }
.pill.up { background: #ebf5ef; color: var(--accent-green); }
.pill.flat { background: #f1efea; color: var(--ink-muted); }
.prior { display: block; margin-top: 10px;
font-size: 12px; color: var(--ink-muted); }
/* === Funnel chart wrap === */
.chart-wrap { background: var(--surface); border-radius: 8px;
padding: 28px 32px;
box-shadow: 0 1px 3px rgba(0,0,0,.04);
margin-bottom: 22px; }
.chart-wrap h2 { font-family: "Playfair Display", Georgia, serif;
font-size: 18px; font-weight: 700;
margin-bottom: 18px;
padding-bottom: 12px;
border-bottom: 1px solid var(--border); }
/* === Sections (collapsible tables) === */
.section { background: var(--surface); border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,.04);
margin-bottom: 22px; overflow: hidden; }
.section-header { padding: 18px 28px; border-bottom: 1px solid var(--border);
display: flex; justify-content: space-between;
align-items: center; cursor: pointer; }
.section-header h2 { font-family: "Playfair Display", Georgia, serif;
font-size: 18px; font-weight: 700; }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
thead th { background: #faf8f4; padding: 12px 22px;
text-align: left; font-weight: 600;
text-transform: uppercase; letter-spacing: .6px;
font-size: 11px; color: var(--ink-muted);
border-bottom: 1px solid var(--border); }
tbody td { padding: 12px 22px; border-bottom: 1px solid #f4f1eb; }
tbody tr:last-child td { border-bottom: none; }
.badge { display: inline-block; padding: 3px 9px; border-radius: 4px;
font-size: 11px; font-weight: 600; }
.badge.green { background: #ebf5ef; color: var(--accent-green); }
.badge.red { background: var(--accent-red-soft); color: var(--accent-red); }
.badge.yellow { background: #fef6e3; color: #b67a08; }
.badge.grey { background: #f1efea; color: var(--ink-muted); }
.highlight-row td { background: var(--accent-red-soft) !important;
font-weight: 700; }
.progress-bar-wrap { background: #f1efea; border-radius: 4px;
height: 8px; overflow: hidden;
width: 100%; min-width: 80px; }
.progress-bar { height: 100%; border-radius: 4px;
background: linear-gradient(90deg, var(--accent-red), #8a1d1c); }
.rec-item { display: flex; gap: 12px; align-items: flex-start;
padding: 14px 28px; border-bottom: 1px solid #f1eeea;
font-size: 14px; line-height: 1.55; }
.rec-item:last-child { border-bottom: none; }
.back-top { position: fixed; bottom: 24px; right: 24px;
background: var(--accent-red); color: #fff;
width: 44px; height: 44px; border-radius: 50%;
border: none; font-size: 20px; cursor: pointer;
box-shadow: 0 4px 12px rgba(200,49,47,0.30); }
footer { text-align: center; padding: 32px 24px;
font-size: 12px; color: var(--ink-muted); }
/* === Print === */
@media print {
nav { display: none; position: static; }
header { padding: 36px 32px 28px; }
header h1 { font-size: 42px; }
.section-header { cursor: default; }
.kpi-row { page-break-inside: avoid; }
.kpi-tile, .chart-wrap, .section {
box-shadow: none; border: 1px solid var(--border);
}
.back-top { display: none; }
}
</style>
</head>
<body>
<header>
<div class="header-inner">
<div class="eyebrow">Funnel Health Report</div>
<h1>{ORG_NAME} Funnel Health</h1>
<p class="lede">Step-by-step conversion for the {FUNNEL_NAME} journey across {DATE_RANGE}, with the biggest leakage point surfaced.</p>
<div class="meta">
<span><span class="icon">📅</span> {DATE_RANGE}</span>
<span><span class="icon">📊</span> {DATA_VIEW}</span>
<span><span class="icon">🕔</span> Prepared {GENERATED_DATE}</span>
</div>
</div>
</header>
<nav>
<a href="#overview">Overview</a>
<a href="#chart">Funnel Chart</a>
<a href="#steps">Step Detail</a>
<a href="#segments">Segment Breakdown</a>
<a href="#recs">Recommendations</a>
</nav>
<div class="container">
<!-- Summary KPI Tiles -->
<div class="section-label">Funnel Summary</div>
<div id="overview" class="kpi-row">
<div class="kpi-tile flat">
<div class="kpi-head"><div class="kpi-label">Entered Funnel</div></div>
<div class="kpi-value">{STEP_1_COUNT}</div>
<span class="prior">Step 1 visitors</span>
</div>
<div class="kpi-tile flat">
<div class="kpi-head"><div class="kpi-label">Completed Funnel</div></div>
<div class="kpi-value">{STEP_N_COUNT}</div>
<span class="prior">Reached final step</span>
</div>
<div class="kpi-tile flat">
<div class="kpi-head"><div class="kpi-label">Overall Conversion</div></div>
<div class="kpi-value">{OVERALL_CVR}%</div>
<span class="prior">End-to-end rate</span>
</div>
<div class="kpi-tile down">
<div class="kpi-head"><div class="kpi-label">Biggest Drop-Off Step</div></div>
<div class="kpi-value" style="font-size:22px;">{WORST_STEP_NAME}</div>
<span class="pill down">▼ Worst leakage</span>
</div>
<div class="kpi-tile down">
<div class="kpi-head"><div class="kpi-label">Worst Step Drop-Off</div></div>
<div class="kpi-value">{WORST_STEP_DROPOFF}%</div>
<span class="prior">Of visitors lost at this step</span>
</div>
</div>
<!-- Funnel Bar Chart -->
<div id="chart" class="chart-wrap">
<h2>Funnel Visualization</h2>
<canvas id="funnelChart" height="100"></canvas>
</div>
<!-- Step-by-Step Table -->
<div id="steps" class="section">
<div class="section-header" onclick="toggle('steps-body')">
<h2>Step-by-Step Analysis</h2>
<span id="steps-body-icon">▾</span>
</div>
<div id="steps-body">
<table>
<thead><tr>
<th>Step</th>
<th>Visitors</th>
<th>% of Step 1</th>
<th>Step Conversion</th>
<th>Drop-Off</th>
<th>Drop-Off Rate</th>
<th>Visual</th>
</tr></thead>
<tbody>
<!-- For each step i:
<tr class="{highlight-row if worst step}">
<td>{STEP_NAME}</td>
<td>{STEP_COUNT}</td>
<td>{PCT_OF_STEP_1}%</td>
<td>{STEP_CVR}% {badge}</td>
<td>{DROPOFF}</td>
<td>{DROPOFF_RATE}%</td>
<td>
<div class="progress-bar-wrap">
<div class="progress-bar" style="width:{PCT_OF_STEP_1}%"></div>
</div>
</td>
</tr>
-->
</tbody>
</table>
</div>
</div>
<!-- Segment Breakdown (optional) -->
<div id="segments" class="section">
<div class="section-header" onclick="toggle('seg-body')">
<h2>Segment Breakdown at Worst Step</h2>
<span id="seg-body-icon">▾</span>
</div>
<div id="seg-body">
<table>
<thead><tr>
<th>Segment / Dimension</th>
<th>Entered Worst Step</th>
<th>Passed Worst Step</th>
<th>Conversion</th>
<th>vs. Average</th>
</tr></thead>
<tbody>
<!-- Fill with dimension breakdown at the worst step -->
</tbody>
</table>
</div>
</div>
<!-- Recommendations -->
<div id="recs" class="section">
<div class="section-header" onclick="toggle('rec-body')">
<h2>Optimization Recommendations</h2>
<span id="rec-body-icon">▾</span>
</div>
<div id="rec-body">
<!-- 2-3 recommendation items as .rec-item -->
</div>
</div>
</div>
<button class="back-top" onclick="window.scrollTo({top:0,behavior:'smooth'})">↑</button>
<footer>Funnel Health Check — {ORG_NAME} — Generated {GENERATED_DATE}</footer>
<script>
// Funnel bar chart — recolor via design tokens.
// Stage colors fade from accent-red (worst leakage upstream) to a deeper red downstream.
const ctx = document.getElementById('funnelChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
labels: {STEP_NAMES_JSON},
datasets: [{
label: 'Visitors',
data: {STEP_COUNTS_JSON},
backgroundColor: [
'rgba(200,49,47,0.90)',
'rgba(200,49,47,0.72)',
'rgba(200,49,47,0.54)',
'rgba(200,49,47,0.38)',
'rgba(200,49,47,0.22)'
],
borderRadius: 6
}]
},
options: {
responsive: true,
plugins: { legend: { display: false } },
scales: {
y: { beginAtZero: true, grid: { color: '#eee9df' },
ticks: { color: '#6b6b6b' } },
x: { grid: { display: false },
ticks: { color: '#1a1a1a', font: { weight: '600' } } }
}
}
});
function toggle(id) {
var el = document.getElementById(id);
var ic = document.getElementById(id + '-icon');
if (el.style.display === 'none') { el.style.display=''; ic.textContent='\u25be'; }
else { el.style.display='none'; ic.textContent='\u25b8'; }
}
</script>
</body></html>Workflow Summary
工作流程总结
- Parse funnel steps from user description.
- Clarify any ambiguous steps (one question at a time).
- Resolve page names with .
searchDimensionItems - Run one per step with ad hoc segment filter; capture visitor counts.
runReport - Compute step conversion rates, drop-off counts, and rates.
- Identify the biggest drop-off step.
- Optionally run dimension breakdown at the worst step.
- Generate HTML report with Chart.js funnel visualization.
- Write to .
/tmp/cja_funnel_health_check_report_<YYYY-MM-DD_HHMMSS>.html - Open with .
open /tmp/cja_funnel_health_check_report_<YYYY-MM-DD_HHMMSS>.html - Summarize inline: "Overall conversion: X%. Biggest drop-off at [Step N]: Y% of users abandon. Mobile users drop off at a 2× higher rate than desktop."
- 从用户描述中解析漏斗步骤。
- 明确任何模糊的步骤(一次一个问题)。
- 使用解析页面名称。
searchDimensionItems - 为每个步骤运行一次带临时细分筛选的;捕获访客数量。
runReport - 计算步骤转化率、流失数量和流失率。
- 识别流失最严重的步骤。
- 可选:在最糟糕的步骤运行维度细分分析。
- 生成带Chart.js漏斗可视化的HTML报告。
- 写入。
/tmp/cja_funnel_health_check_report_<YYYY-MM-DD_HHMMSS>.html - 使用打开报告。
open /tmp/cja_funnel_health_check_report_<YYYY-MM-DD_HHMMSS>.html - 在线总结:“整体转化率:X%。最大流失点在[步骤N]:Y%的用户放弃。移动端用户的流失率是桌面端的2倍。”
Important Guardrails
重要约束
- Read-only analysis. Never modify segments, calculated metrics, or project definitions automatically.
- Confirm funnel stages before running. Ambiguous stage definitions produce misleading results — clarify with the user first.
- Note attribution model. Funnel conversion rates depend on the attribution model in the data view; mention it in the report.
- Flag incomplete data. If any stage returns zero or suspiciously low counts, note possible tracking gaps before drawing conclusions.
- Cap date range. Funnel analysis over very long date ranges (>90 days) can be slow; suggest 30-day windows as default.
- Never assume stage order. Confirm with the user that the stages are sequential and mutually exclusive before calculating drop-off rates.
- 只读分析。切勿自动修改细分、计算指标或项目定义。
- 运行前确认漏斗阶段。模糊的阶段定义会产生误导性结果 — 先与用户确认。
- 注意归因模型。漏斗转化率取决于数据视图中的归因模型;在报告中提及这一点。
- 标记不完整数据。如果任何阶段返回零或异常低的数量,在得出结论前先指出可能的跟踪缺口。
- 限制日期范围。过长日期范围(>90天)的漏斗分析可能很慢;建议默认使用30天窗口。
- 切勿假设阶段顺序。在计算流失率前,与用户确认阶段是连续且互斥的。
Example Interaction
示例交互
"Check the health of our checkout funnel — I want to see where people are dropping off."
- Setup: Call , user selects their e-commerce data view. Call
findDataViews.setDefaultSessionDataViewId - Define funnel: Ask "What are the checkout stages?" User replies: "Product View → Add to Cart → Checkout Start → Purchase."
- Analysis: Run for each stage transition over the last 30 days. Calculate drop-off rates: Product View→Cart 22%, Cart→Checkout 58%, Checkout→Purchase 71%.
runReport - Findings: Identify the Cart→Checkout step as the highest drop-off (78% fall off). Segment by device type to find mobile conversion is 40% lower than desktop.
- Report: Present a funnel visualization with drop-off rates per stage, top exit segments, and 3 prioritized recommendations.
“检查我们结账漏斗的健康状况 — 我想看看人们在哪里流失。”
- 设置:调用,用户选择其电商数据视图。调用
findDataViews。setDefaultSessionDataViewId - 定义漏斗:询问“结账阶段有哪些?”用户回复:“产品浏览 → 加入购物车 → 开始结账 → 购买。”
- 分析:在过去30天内运行每个阶段转换的。计算流失率:产品浏览→购物车22%,购物车→结账58%,结账→购买71%。
runReport - 发现:识别购物车→结账步骤为最高流失点(78%的用户脱落)。按设备类型细分发现移动端转化率比桌面端低40%。
- 报告:展示带各阶段流失率的漏斗可视化、顶级流失细分群体,以及3个优先优化建议。
Recommendations Logic
建议逻辑
- Worst step drop-off > 60%: "Critical leakage — this step is broken or the user expectation is misaligned. Prioritize UX investigation."
- Mobile drop-off > 2× desktop: "Mobile UX at this step needs attention — consider a dedicated mobile flow or simplified form."
- Specific channel drop-off > 40% worse than average: "Users from this channel may have mismatched intent — review landing page alignment."
- Step 1 count < 1,000: "Funnel entry volume is too low for statistical confidence. Check the step definition or expand the date range."
- 最糟糕步骤流失率 > 60%:“严重泄漏 — 此步骤存在问题或用户预期不符。优先开展UX调查。”
- 移动端流失率 > 桌面端2倍:“此步骤的移动端UX需要关注 — 考虑专用移动端流程或简化表单。”
- 特定渠道流失率比平均水平高 >40%:“该渠道的用户可能存在意图不匹配 — 检查着陆页的一致性。”
- 步骤1数量 < 1000:“漏斗入口量过低,统计置信度不足。检查步骤定义或扩大日期范围。”