mcpField
A
aa-top-movers-watchlist

aa-top-movers-watchlist

github.com/adobe数据与数据库316331 次安装今天更新
代码仓库

关于 aa-top-movers-watchlist

这是一项 Adobe Analytics 技能,可比较两个时段的指标,并按维度找出增幅和降幅最大的项目。它生成 HTML 报告和文字摘要,并规范日期窗口及低流量项目筛选。

主要功能

  1. 01比较两个时段的维度项目
  2. 02按绝对变化排列增幅与降幅
  3. 03标记新增和消失项目
  4. 04应用重要性阈值
  5. 05生成 HTML 报告和文字摘要

适用场景

  1. 01查找周环比变化最大的页面
  2. 02比较不同时段的营销活动或渠道表现
  3. 03筛选低流量项目后检查产品或地区变化

MCP 相关

使用 Adobe Analytics MCP 工具选择报表套件、解析指标和维度,并获取各时段的报表。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/adobe/skills --skill aa-top-movers-watchlist

安装后在 Claude Code 中输入/aa-top-movers-watchlist确认已加载。

附带文件evals/evals.json

详细介绍

Top Movers Watchlist(Adobe Analytics)

识别两个时间段之间关键指标增幅和降幅最大的维度项。呈现页面、渠道、营销活动、产品或任何其他细分维度中的涨幅最高项和跌幅最高项。


使用的 AA MCP 工具

  • findReportSuites — 选择报表包
  • setSessionDefaults — 设置会话上下文(reportSuiteId + globalCompanyId)
  • describeAa(REPORT_SUITE_CONTEXT_GUIDE) — 加载日历/时区上下文
  • findMetrics — 查找关键指标 ID
  • findDimensions — 查找可用的细分维度
  • runReport — 为每个维度分别运行时间段 A 和时间段 B 的报表
  • searchDimensionItems — 按需验证特定项

阶段 0 — 设置

  1. 使用 findReportSuites / setSessionDefaults 确认报表包。

  2. 调用 describeAa(REPORT_SUITE_CONTEXT_GUIDE) 加载报表包上下文。记录:

    • WEEK_START_DOW — 上下文指南中规定的一周首日。如果上下文指南未返回值,则使用星期一(ISO 8601)作为明确且确定的默认值。这不是会在不同运行之间变化的回退值:针对同一报表包的每次运行,都会根据上下文指南或固定的星期一默认值解析出相同的 WEEK_START_DOW。
    • TIMEZONE — 报表包时区。
    • WEEK_START_DOW_SOURCE — 如果值来自 describeAa,则设为 "context guide";如果上下文指南未提供值而使用了星期一,则设为 "default"。在产物页脚中显示此项,以便审计来源。

    在阶段 1 中定义时间段 A 和时间段 B 时,将使用这些值。

findReportSuites(globalCompanyId: "<gcid>")
setSessionDefaults(globalCompanyId: "<gcid>", reportSuiteId: "<rsid>")
describeAa(guideType: "REPORT_SUITE_CONTEXT_GUIDE")

阶段 1 — 确认参数

询问用户:

  1. 指标 — “我应该按哪个指标对变动项进行排名?(访问量、收入、转化次数、页面浏览量等)” 如果未指定,默认使用 metrics/visits。

  2. 维度 — “你想按哪个维度细分?

    • 页面
    • 营销渠道
    • 营销活动
    • 产品
    • 流量来源
    • 地理区域
    • 入口页面” 如果未指定,提供以上列表。
  3. 时间段 — “你想比较哪两个时间段?” 常用默认值:

    • 本周与上周
    • 本月与上月
    • 最近 7 天与之前的 7 天
    • 最近 30 天与之前的 30 天

    日历规则(必须遵守): 时间段 A 和时间段 B 必须使用阶段 0 中相同的 WEEK_START_DOW — 两个时间段的 startDate 必须落在相同的星期几,两者长度必须完全相等,并且时间段 B 必须在时间段 A 开始前一天结束。同一次运行中绝不能混用日期规则(例如,时间段 A 为周一至周日,而时间段 B 为周日至周六)。对于自定义日期范围,将时间段 B 计算为紧接在时间段 A 开始前结束、且长度相同的时间窗口。

    调用 runReport 前的合理性检查: 确认 periodA.startDate 和 periodB.startDate 是同一个星期几,并且 periodA.startDate - periodB.endDate == 1 day。如果不满足,则重新计算。

  4. 项目数量上限 — 要显示多少个涨幅最高项和跌幅最高项(默认值:各 10 个)。

  5. 重要性阈值 — 纳入分析所需的时间段 A 或时间段 B 中的最低绝对值(过滤低流量项目产生的噪声)。默认情况下,排除两个时间段内访问量(或等效指标)均不足 100 的项目。

确认:“我将按 [指标] 对 [维度] 进行细分,显示涨幅最高和跌幅最高的各 10 项,并比较 [时间段 A] 与 [时间段 B]。”


阶段 2 — 解析组件

findMetrics(expansions: "componentType")
findDimensions(page: 1, limit: 100)

注意: findMetrics 需要 expansions 参数(使用 "componentType" 或 "categories")。findDimensions 需要 page 和 limit。

记录指标和维度的 id。


阶段 3 — 运行时段报表

在所选维度上,分别为两个时间段运行该指标的报表:

runReport(
  metricIds: "<metricId>",
  dimensionId: "<dimensionId>",
  startDate: "<period A start>T00:00",
  endDate: "<period A end>T23:59",
  limit: 200
)

runReport(
  metricIds: "<metricId>",
  dimensionId: "<dimensionId>",
  startDate: "<period B start>T00:00",
  endDate: "<period B end>T23:59",
  limit: 200
)

注意: runReport 使用 metricIds(而非 metricId),日期采用 ISO 8601 格式(YYYY-MM-DDTHH:mm)的 startDate/endDate,而非 dateRange 参数。

使用 limit: 200,以捕获足够的项目进行有意义的变动分析。


阶段 4 — 计算变化量并排序

对于任一时期中出现的每个维度项:

  1. 时期 A 的值(如果结果中不存在,则为 0)
  2. 时期 B 的值(如果结果中不存在,则为 0)
  3. 绝对变化量:时期 A - 时期 B
  4. 百分比变化:变化量 / 时期 B × 100(如果时期 B = 0,则为“+100% new”)
  5. 应用重要性筛选:排除 max(时期 A,时期 B)小于 重要性阈值的项

按绝对变化量降序排列增长项(正变化量)。 按绝对变化量升序排列下降项(负变化量)。

分别取两个列表中的前 N 项。

特殊情况
  • 新增项(仅在时期 A 中出现):标记为“新增 — 没有上一时期的数据。”达到重要性阈值时纳入。
  • 消失项(仅在时期 B 中出现,时期 A 为 0):标记为“已移除 — 没有当前时期的数据。”
  • 接近零的项:占指标总值 <1% 的项——除非其百分比变化非常大,否则考虑将其从监控列表中排除。

阶段 5 — 可选:多维度监控列表

如果用户希望查看多个维度的变化项(例如,同时查看页面和渠道),则对每个附加维度重复阶段 3–4。每个附加维度会增加 2 次 runReport 调用。

runReport(metricIds: "<metricId>", dimensionId: "variables/marketingchannel",
          startDate: "<A>T00:00", endDate: "<A>T23:59", ...)
runReport(metricIds: "<metricId>", dimensionId: "variables/page",
          startDate: "<A>T00:00", endDate: "<A>T23:59", ...)

在 HTML 报告中按维度将结果分别归入不同的表格。


阶段 6 — 生成 HTML 报告

在行内构建变化项报告,并写入 /tmp/aa_top_movers_report_<YYYY-MM-DD_HHMMSS>.html。

渲染规则 — 每次运行都一致应用

针对同一报告套件、指标/维度和时期运行此技能两次,渲染结果必须完全一致(生成时间戳除外)。以下规则用于固定格式选择,避免 AI 的输出出现偏差。

数字格式
  • KPI 值(每个摘要卡片中的大号数字,以及每行变化项的值)——使用完整数字并添加千位分隔符(8,160、77,584、1,250,000)。不要使用 K 或 M 等 SI 后缀,即使数值很大。利益相关者需要准确数字,而非缩写。
  • 百分比变化(胶囊标签和叙述性要点中)——始终保留一位小数,按远离零的方向舍入。例如,−23.55% 显示为 −23.6%,绝不显示为 −23.5%。使用完整精度的值进行计算;仅在显示时舍入。
  • 百分点变化(适用于转换率或跳出率等本身为百分比的指标)——采用相同的舍入方式,后缀为 pp。例如:+0.40 pp。
  • 货币——使用 $ 前缀和千位分隔符,金额 ≥ $100 时不显示小数位($1,240,000);金额 < $100 时保留美分($45.20)。
空值 / 缺失数据处理

KPI 卡片或变化项行必须如实反映报告套件返回的内容。AI 不得擅自替换为其他指标,也不得为了让报告更整洁而隐藏卡片。

  • 两个时期返回的跟踪指标均为 0 或 NULL:在摘要卡片中,将 kpi-value 渲染为 Data unavailable,胶囊标签类设为 flat,胶囊标签文本设为 ⚠ N/A,prior 文本设为 Both periods returned no data — validate instrumentation。卡片必须保留在网格中;不要省略。
  • 一个时期返回有效数据,另一个时期返回 0 / NULL:将有效值作为 kpi-value 渲染,胶囊标签类设为 flat,胶囊标签文本设为 ⚠ N/A,prior 文本设为 Prior {period_noun}: no data。
  • 绝不替换为其他指标(例如,Revenue 返回 $0 时改用 Orders)。正在分析的指标必须就是渲染的指标。
HTML 模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Top Movers &mdash; {ORG_NAME} &mdash; {METRIC_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">
<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: 600px;
                 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; }

  /* === Period strip === */
  .period-bar { background: var(--surface); border-radius: 8px;
                padding: 14px 22px; margin-bottom: 28px;
                font-size: 13px; color: var(--ink-muted);
                box-shadow: 0 1px 3px rgba(0,0,0,.04);
                border-left: 3px solid var(--accent-red); }
  .period-bar strong { color: var(--ink); font-weight: 600; }

  /* === Dimension section === */
  .dim-section { margin-top: 32px; }
  .dim-section > h2 { font-family: "Playfair Display", Georgia, serif;
                      font-size: 22px; font-weight: 700; color: var(--ink);
                      margin-bottom: 16px; padding-bottom: 10px;
                      border-bottom: 1px solid var(--border); }

  /* === Gainers / Losers dual panels === */
  .movers-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 22px;
                 margin-bottom: 32px; }
  .movers-panel { background: var(--surface); border-radius: 8px;
                  box-shadow: 0 1px 3px rgba(0,0,0,.05); overflow: hidden; }
  .movers-panel-header { padding: 18px 24px; border-bottom: 1px solid var(--border); }
  .movers-panel-header.gainers { border-top: 3px solid var(--accent-green); }
  .movers-panel-header.losers  { border-top: 3px solid var(--accent-red); }
  .movers-panel-header h2 { font-family: "Playfair Display", Georgia, serif;
                            font-size: 17px; font-weight: 700; }
  .movers-panel-header.gainers h2 { color: var(--accent-green); }
  .movers-panel-header.losers  h2 { color: var(--accent-red); }

  /* === Tables === */
  table { width: 100%; border-collapse: collapse; font-size: 13px; }
  thead th { background: #faf8f4; padding: 10px 18px;
             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: 10px 18px; border-bottom: 1px solid #f4f1eb; }
  tbody tr:last-child td { border-bottom: none; }
  .delta-pos { color: var(--accent-green); font-weight: 700; }
  .delta-neg { color: var(--accent-red); font-weight: 700; }

  /* === Rank badges (circular) === */
  .rank-badge { display: inline-flex; align-items: center; justify-content: center;
                width: 24px; height: 24px;
                border-radius: 50%;
                background: var(--accent-red); color: #fff;
                font-size: 11px; font-weight: 700;
                margin-right: 8px; vertical-align: middle; }
  .rank-badge.gain { background: var(--accent-green); }

  .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,.35); }
  footer { text-align: center; padding: 32px 24px;
           font-size: 12px; color: var(--ink-muted); }
  @media (max-width: 700px) { .movers-grid { grid-template-columns: 1fr; } }

  /* === Print === */
  @media print {
    nav { display: none; position: static; }
    header { padding: 36px 32px 28px; }
    header h1 { font-size: 42px; }
    .back-top { display: none; }
    .movers-grid { page-break-inside: avoid; }
    .movers-panel, .period-bar {
      box-shadow: none; border: 1px solid var(--border);
    }
  }
</style>
</head>
<body>
<header>
  <div class="header-inner">
    <div class="eyebrow">Top Movers Watchlist</div>
    <h1>{ORG_NAME} Top Movers</h1>
    <p class="lede">Biggest gainers and decliners for {METRIC_NAME} between {PERIOD_A} and {PERIOD_B}.</p>
    <div class="meta">
      <span><span class="icon">&#128197;</span> {PERIOD_A} vs {PERIOD_B}</span>
      <span><span class="icon">&#128202;</span> {REPORT_SUITE}</span>
      <span><span class="icon">&#128340;</span> Prepared {GENERATED_DATE}</span>
    </div>
  </div>
</header>
<nav>
  <a href="#summary">Summary</a>
  
</nav>
<div class="container">
  <div id="summary" class="period-bar">
    <strong>Metric:</strong> {METRIC_NAME} &nbsp;|&nbsp;
    <strong>Period A:</strong> {PERIOD_A} &nbsp;|&nbsp;
    <strong>Period B:</strong> {PERIOD_B} &nbsp;|&nbsp;
    <strong>Materiality:</strong> &ge;{THRESHOLD} {METRIC_UNIT}
  </div>
  
  <div id="dim-{DIM_ID}" class="dim-section">
    <h2>{DIMENSION_NAME}</h2>
    <div class="movers-grid">
      <div class="movers-panel">
        <div class="movers-panel-header gainers"><h2>&#9650; Top Gainers</h2></div>
        <table>
          <thead><tr>
            <th>#</th><th>Item</th><th>Period A</th><th>Period B</th>
            <th>Delta</th><th>% Change</th>
          </tr></thead>
          <tbody></tbody>
        </table>
      </div>
      <div class="movers-panel">
        <div class="movers-panel-header losers"><h2>&#9660; Top Losers</h2></div>
        <table>
          <thead><tr>
            <th>#</th><th>Item</th><th>Period A</th><th>Period B</th>
            <th>Delta</th><th>% Change</th>
          </tr></thead>
          <tbody></tbody>
        </table>
      </div>
    </div>
  </div>
</div>
<button class="back-top" onclick="window.scrollTo({top:0,behavior:'smooth'})">
  &#8679;
</button>
<footer>Top Movers Watchlist &mdash; {ORG_NAME} &mdash; Generated {GENERATED_DATE}</footer>
</body>
</html>

章节标题 — 不带阶段前缀:HTML 报告中的章节标题不得包含阶段编号。仅使用纯章节名称(例如,“Top Movers”而非“Phase 3 — Top Movers”,“Dimension Breakdown”而非“Phase 4 — Dimension Breakdown”)。

写入 /tmp/aa_top_movers_report_<YYYY-MM-DD_HHMMSS>.html 并打开:

open /tmp/aa_top_movers_report_<YYYY-MM-DD_HHMMSS>.html

行内摘要(始终提供)

HTML 报告后始终附上简短的文字说明:

Top Movers: [Metric] — [Period A] vs [Period B]

GAINERS (by [Dimension])
1. [Item A]  +12,400 visits  (+34%)
2. [Item B]  +8,200 visits   (+21%)
3. [Item C]  +5,100 visits   (+18%)

LOSERS
1. [Item X]  -9,800 visits   (-41%)
2. [Item Y]  -4,200 visits   (-18%)
3. [Item Z]  -2,900 visits   (-12%)

Key takeaway: [1 sentence explanation of the story]

防护措施

  • 始终应用重要性阈值,过滤掉数量可以忽略不计的项目,避免产生噪声。
  • 明确标记新增项目(上一时段不存在)和消失项目(当前时段不存在)——它们往往透露出重要信息。
  • 对百分比变化进行排名时,优先按绝对变化量排序(而非百分比),以避免数量较小的项目因百分比虚高而排在前面。
  • 为提高调用效率,维度列表最多包含 6 项。可应要求再运行其他维度。

示例交互

“上周哪些页面的增幅和降幅最大?”

  1. 确认报告套件。
  2. 确认指标:页面浏览量。维度:页面名称。时段:最近 7 天与之前 7 天。数量限制:各取前 10 项。
  3. 针对页面 × 访问量运行 2 份报告。
  4. 计算变化量,并应用重要性标准(任一时段的页面浏览量至少为 500)。
  5. 生成 HTML 报告并提供内联摘要。
  6. “增幅最大:/products/new-feature,页面浏览量增加 18,200(+312%)。降幅最大:/promo/expired-campaign,页面浏览量减少 9,400(-88%)。”

评论

支持 Markdown