关于 cja-funnel-health-check
这是一项 Customer Journey Analytics 技能,可将漏斗描述转化为逐步转化率和流失分析。它能找出流失最多的环节,并按受众或渠道细分结果,最后生成 HTML 报告。
主要功能
- 01将漏斗描述转为可测量步骤
- 02统计各步骤的访客或会话
- 03计算转化率和流失率
- 04找出流失最多的环节
- 05按维度或细分进行拆解
- 06生成 HTML 漏斗报告
适用场景
- 01查找结账访客在哪个环节流失
- 02衡量注册或引导流程的完成情况
- 03比较不同设备或渠道的漏斗流失
MCP 相关
使用 Customer Journey Analytics MCP 工具解析维度和指标、搜索维度值,并通过临时细分运行报表。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ adobe/ skills --skill cja-funnel-health-check安装后在 Claude Code 中输入/cja-funnel-health-check确认已加载。
附带文件evals/evals.json
详细介绍
漏斗健康检查(客户旅程分析)
将通俗易懂的漏斗描述转化为量化的逐步转化分析。输出会找出漏斗中流失最严重的环节,并按维度细分数据,以展示哪个受众群体或渠道的流失率最高。
当团队怀疑某个特定环节存在问题,但尚未量化时,漏斗健康检查最有价值。此技能可在一次对话中完成量化分析。
使用的 CJA MCP 工具
findDimensions— 解析用于筛选步骤的页面名称、事件或其他维度findMetrics— 获取要衡量的基础指标(会话、访客、事件)searchDimensionItems— 查找步骤名称对应的确切维度值runReport(搭配adhocSegments)— 衡量每个漏斗步骤的访客数findSegments— 适用时,使用现有区段作为漏斗步骤筛选条件describeSegment— 在将区段用作漏斗步骤之前,了解其逻辑
阶段 0 — 设置
- 调用
findDataViews列出可用的数据视图。 - 如果用户未指定数据视图,则展示列表并询问要使用哪一个。
- 使用所选 ID 调用
setDefaultSessionDataViewId。 - 如果用户尚未指定漏斗阶段,请让用户定义阶段(例如:“您想分析的漏斗步骤有哪些?”)。
阶段 1 — 定义漏斗步骤
1.1 解析用户的漏斗描述
从用户的通俗描述中提取步骤顺序:
- “首页 → 产品页 → 购物车 → 购买”
- “注册 → 引导步骤 1 → 引导步骤 2 → 已激活”
- “着陆页 → 潜在客户表单 → 致谢页”
每个步骤都必须对应 CJA 中可衡量的条件。步骤可以是:
- 页面浏览:用户浏览了特定页面(通过页面名称维度解析)
- 事件/指标:用户触发了特定操作(例如“加入购物车”)
- 区段成员资格:用户符合预先构建的区段条件
1.2 澄清含义不明确的步骤
如果某个步骤表述含糊(例如只说“结账”,未提供页面名称),请提出一个问题:
“对于‘结账’这一步,我应该查找访问过 URL 中包含‘checkout’的页面的用户,还是触发了‘Cart Add’等特定事件的用户?”
每次最多只提出一个澄清问题。
1.3 将页面名称解析为维度值
对于基于页面的步骤,调用 searchDimensionItems 查找与步骤名称匹配的确切维度值。首先使用类似 "page name url" 的语义搜索调用 findDimensions,以确认正确的维度 ID — 在大多数 CJA 数据视图中,此 ID 为 variables/web.webPageDetails.name,而不是 variables/page:
searchDimensionItems(
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’吗?”
阶段 2 — 衡量每个步骤
对于每个漏斗步骤,构建一个临时区段,用于筛选到达该步骤的访客/会话。然后运行报表,衡量应用该区段后的基础指标(通常为独立访客数或会话数)。
2.1 为每个步骤构建临时区段
“到达第 N 步”的临时区段包括:
- 容器:访问或人员(会话级漏斗使用访问,跨访问旅程使用人员)
- 条件:页面名称等于“<步骤页面值>”或事件已发生
2.2 为每个步骤运行报告
为每个步骤运行一次 runReport,并应用临时细分。adhocSegments
参数需要完整的 CJA 细分定义对象,而不是简化的简写形式。正确结构如下:
runReport(
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 阶段通过
searchDimensionItems 查到的内容一致——请使用返回的确切 value 字符串。
重要提示:每个步骤都使用累积筛选器——衡量“在此期间曾到达此步骤的访客”,而不是“只访问过此页面的访客”。这样会生成经典的漏斗瀑布图。
为每个步骤 i(从 1 到 N)记录 stepCount[i]。
第 3 阶段 — 计算漏斗指标
对于每个步骤间的转化:
stepConversionRate[i→i+1]=stepCount[i+1]/stepCount[i]× 100dropOff[i→i+1]=stepCount[i]−stepCount[i+1]dropOffRate[i→i+1]= 100 −stepConversionRate[i→i+1]
整体漏斗:
overallConversionRate=stepCount[N]/stepCount[1]× 100biggestDropOffStep= argmax(dropOff[i→i+1])——流失访客人数最多的步骤
第 4 阶段 — 可选:细分漏斗
如果用户想比较不同受众或渠道的漏斗表现,请按某个维度或细分筛选,并运行相同的步骤报告:
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 */ }]
)
注意:使用 findDimensions 和 searchQuery: "device type mobile desktop",确认此数据视图的设备维度 ID(通常为 variables/device_type)。
这会按设备类型(或渠道、国家/地区)细分显示第 N 步的访客人数。如果某个细分在流失最严重的步骤中转化率显著偏低,那么这就是需要优化的目标。
建议比较的常见维度:
- 设备类型(移动设备与桌面设备的转化率通常差异显著)
- 营销渠道(付费用户与自然流量用户的转化率可能不同)
- 新访客与回访访客
第 5 阶段 — 生成 HTML 漏斗报告
在页面中生成漏斗报告,并写入
/tmp/cja_funnel_health_check_report_<YYYY-MM-DD_HHMMSS>.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/[email protected]/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">
<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>
<div id="chart" class="chart-wrap">
<h2>Funnel Visualization</h2>
<canvas id="funnelChart" height="100"></canvas>
</div>
<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>
</tbody>
</table>
</div>
</div>
<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>
</tbody>
</table>
</div>
</div>
<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">
</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>
工作流摘要
- 根据用户描述解析漏斗步骤。
- 澄清任何含糊不清的步骤(每次只问一个问题)。
- 使用
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 倍。”
重要注意事项
- 只读分析。 切勿自动修改细分、计算指标或项目定义。
- 运行前确认漏斗阶段。 含糊的阶段定义会导致结果产生误导——请先向用户澄清。
- 注明归因模型。 漏斗转化率取决于数据视图中使用的归因模型;请在报告中说明。
- 标记不完整数据。 如果任何阶段的计数为零或低得可疑,请在得出结论前指出可能存在跟踪缺口。
- 限制日期范围。 超过 90 天的超长日期范围可能导致漏斗分析变慢;建议默认使用 30 天窗口。
- 切勿假定阶段顺序。 计算流失率前,请确认这些阶段按顺序排列且互不重叠。
示例交互
“检查一下我们的结账漏斗表现——我想看看用户在哪些环节流失。”
- 设置: 调用
findDataViews,用户选择其电商数据视图。调用setDefaultSessionDataViewId。 - 定义漏斗: 询问“结账流程包含哪些阶段?”用户回答:“商品浏览 → 加入购物车 → 开始结账 → 购买。”
- 分析: 对过去 30 天内的每个阶段转化运行
runReport。计算流失率:商品浏览→购物车 22%,购物车→开始结账 58%,开始结账→购买 71%。 - 发现: 确认购物车→开始结账是流失率最高的环节(78% 的用户流失)。按设备类型细分后发现,移动端的转化率比桌面端低 40%。
- 报告: 展示漏斗可视化图表,列出各阶段的流失率、主要退出细分群体,以及 3 条优先级建议。
建议逻辑
- 最差环节流失率 > 60%:“严重流失——此环节可能存在问题,或用户预期不匹配。应优先开展 UX 调查。”
- 移动端流失率 > 桌面端的 2 倍:“此环节的移动端 UX 需要改进——考虑设计专用的移动端流程或简化表单。”
- 特定渠道的流失率比平均值高出 > 40%:“来自此渠道的用户可能意图不匹配——请检查着陆页是否与用户意图一致。”
- 第 1 步人数 < 1,000:“漏斗入口量过低,无法获得统计置信度。请检查步骤定义或扩大日期范围。”