[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6XCXZGGXA2YFFEVAC4B7PD3":3,"skill-same:01M6XCXZGGXA2YFFEVAC4B7PD3":137},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":31,"publishedAt":51,"linkOnly":52,"markdownContent":53,"markdownBody":54,"frontMatter":55,"refName":56,"commitSha":57,"skillDirectory":58,"forksCount":14,"repositoryHtmlUrl":59,"repositoryHomepageUrl":6,"ownerHtmlUrl":60,"ownerBlogUrl":61,"ownerDescription":6,"updatedAt":62,"install":63,"resources":76},"01M6XCXZGGXA2YFFEVAC4B7PD3","bitmovin-observability-ad-report",null,"Snapshot-style ad operations report for a Bitmovin Analytics license. Use whenever someone asks for an \"ad ops report\", \"ad health check\", \"ad funnel\", \"weekly ad review\", \"VAST error breakdown\", \"advertiser performance\", \"ad system comparison\", \"ad startup-time breakdown\", or \"how are our ads doing\" — even without the word \"report\". Builds the impression→start→quartile→ completion funnel, ranks advertisers and ad systems, flags VAST\u002Fad-server errors, evaluates pod-position cohorts, and summarizes ad startup latency as structured anomaly flags (AdOpsReport object). Does NOT explain why a metric moved between two windows — use the QoE RCA skill for that.","bitmovin\u002Fskills","bitmovin","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F3273006?v=4","01M6XCX24ZDCBTPHWAAM18VJQY","Bitmovin","01M6XCX29H5E446MM0E5XAVQTC",1,"MIT","https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-observability-ad-report\u002FSKILL.md",[18,23,28],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000013","分析与可视化","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000033","数据分析师","PROFESSION","适用职业",{"categoryNo":29,"categoryName":30,"taxonomyType":26,"taxonomyTypeDesc":27},"01JBSEEDCAT000000000000034","行政与办公人员",[32,9,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50],"bitmovin observability ad report","report","observability","ad","snapshot","metric","weekly","errors","error","time","flags","review","ads","why","how","start","server","analytics","performance","2026-11-10 10:12:30",false,"---\nname: bitmovin-observability-ad-report\ndescription: >-\n  Snapshot-style ad operations report for a Bitmovin Analytics license. Use\n  whenever someone asks for an \"ad ops report\", \"ad health check\", \"ad funnel\",\n  \"weekly ad review\", \"VAST error breakdown\", \"advertiser performance\", \"ad\n  system comparison\", \"ad startup-time breakdown\", or \"how are our ads doing\" —\n  even without the word \"report\". Builds the impression→start→quartile→\n  completion funnel, ranks advertisers and ad systems, flags VAST\u002Fad-server\n  errors, evaluates pod-position cohorts, and summarizes ad startup latency as\n  structured anomaly flags (AdOpsReport object). Does NOT explain why a metric\n  moved between two windows — use the QoE RCA skill for that.\n---\n\n# Ad Operations Snapshot Report\n\nProduce a structured, single-window report for an ad-operations audience: how\nthe funnel looks, where errors concentrate, which advertisers \u002F ad systems are\ncarrying or dragging the inventory, how pod position affects completion, and\nwhether ad startup time is within bounds. The output is a structured\n`AdOpsReport` object (`references\u002Freport-contract.md`) plus the prose\nnarrative the agent renders from it.\n\n## Operating principle\n\n**The agent decides; the scripts compute.** Funnel drop-offs across stages,\nconcentration ranking, and threshold checks all go to scripts under `scripts\u002F`\n— silent arithmetic slips poison a report the user cannot easily audit, and\nthe scripts are tested where mental math is not. Scripts return numbers and\nclassifications; they **never** return a verdict like \"this advertiser is\nunderperforming.\" Query construction, framing, and the final narrative stay\nwith the agent.\n\n## Setup (once)\n\nScripts run under Node via `tsx`.\n\n1. Check the runtime: `node --version` and `npm --version`. **If either is\n   missing, stop and ask the user for approval before installing anything** —\n   installing a runtime is a system change you must not make unattended. State\n   the platform command (macOS: `brew install node`) and wait for approval.\n2. Install deps once from `scripts\u002F`: `npm install` (pulls `tsx` + `vitest`).\n   Afterwards `npx tsx scripts\u002Fcli.ts \u003Ccmd>` uses the local `tsx`, no network.\n\nIf no Node is available and no one can approve an install, report that as a\nblocking limitation — do **not** fall back to in-head arithmetic for the\nfunnel decomposition or anomaly thresholds.\n\n## Scope of this skill\n\n**In scope.** Single-window snapshot reports for ad-ops audiences. The window\ncan be anything the user asks for (last 7 days, yesterday, last 30 days, a\ncustom range). Always assumes the license has the ads dataset wired up.\n\n**Not in scope.** Window-comparison or \"why did this change\" RCA — that work\nbelongs in the QoE RCA skill (`bitmovin-observability-root-cause-analysis`).\nIf the user's question is \"why did ad completions drop on Tuesday\", redirect\nthem. This skill does not run mix-vs-rate decomposition or onset detection.\n\n## Tools (ad analytics)\n\nThe ad query tools route any metric prefixed with `ad_` to the Ads Queries API:\n\n- **`query`** — time series of a single ad metric, optional filter, optional\n  group-by.\n- **`queryGroupBy`** — grouped aggregation; this is the workhorse for\n  concentration, error code breakdowns, and pod position cohorts.\n- **`queryTotal`** — one aggregate value over the window. Used for each funnel\n  stage and the latency percentiles.\n- **Schema helpers** — `searchMetrics` to resolve the actual ad-metric keyword\n  (\"ads\" → `ad_impressions`, \"CTR\" → `ad_clicks`), `searchFilters` \u002F\n  `getAvailableFilters` to confirm filter attributes (`AD_SYSTEM`,\n  `ADVERTISER_NAME`, `AD_POSITION`, `AD_POD_POSITION`, `AD_TAG_SERVER`,\n  `ERROR_CODE`, `CUSTOM_DATA_*`), `peekAllLicenses` when the user hasn't\n  supplied a license key.\n\nThe ad filter catalog is disjoint from the playback filter catalog. The ads\ndataset only accepts `AdAnalyticsAttribute` names — don't reuse playback\nattribute names in ad queries.\n\n## The four phases\n\nRun **Scope → Pull → Flag → Report** strictly in order. Each phase has a job;\nmixing them produces sloppy reports.\n\n- **Scope** decides what to ask. No queries.\n- **Pull** only pulls. No interpretation.\n- **Flag** only interprets the pulled data through the scripts. No new queries.\n- **Report** assembles the structured object and renders the narrative.\n\n### 1 — Scope\n\nPin the question and the schema before touching data.\n\n1. Resolve the license key (`peekAllLicenses` if not supplied) and the window\n   (`start`, `end`). For \"weekly review\" with no explicit dates, default to\n   the last 7 full days (UTC).\n2. Resolve schema via `searchMetrics` and `searchFilters` — never guess\n   keywords. The canonical ad metric names are `ad_impressions`, `ad_starts`,\n   `ad_quartile_1`, `ad_midpoint`, `ad_quartile_3`, `ad_completions`,\n   `ad_clicks`, `ad_skips`, `ad_error_sessions`, `ad_error_percentage`,\n   `ad_startup_time`, `ad_play_percentage`, `ad_unique_users`.\n3. **Confirm the license has ad data.** Run one `queryTotal` on\n   `ad_impressions` over the window. If it returns 0 (or near-zero), stop and\n   report \"license has no ad data in this window\" — do not attempt the rest of\n   the report.\n4. Capture any human-supplied scope (filter to a specific advertiser, ad\n   system, country, app version) and remember it as a filter to apply to every\n   pull. See HITL — context is enrichment, never required.\n5. Decide which sections to produce. The default report includes all five\n   sections; the agent may skip a section when the user's ask is narrow\n   (\"just show me the error breakdown\").\n\n### 2 — Pull\n\nFive independent data batches. Run them as a fan-out (see subagent boundary).\n\n- **Funnel batch.** Six `queryTotal` calls — one per stage:\n  `ad_impressions`, `ad_starts` (sum), `ad_quartile_1` (sum), `ad_midpoint`\n  (sum), `ad_quartile_3` (sum), `ad_completions` (sum). Same window, same\n  cohort filter on every call. Optionally pull `ad_clicks` (sum) and\n  `ad_skips` (sum) as side-branch totals.\n- **Errors batch.** `queryTotal` on `ad_error_sessions` + `queryGroupBy` on\n  `ERROR_CODE` (top 10 by count) + `queryGroupBy` on `AD_SYSTEM` for\n  `ad_error_sessions`. Optionally `AD_TAG_SERVER` if the user cares about ad\n  server attribution.\n- **Concentration batch.** `queryGroupBy` on `ADVERTISER_NAME` for both\n  `ad_completions` (sum) and `ad_starts` (sum). Optionally `AD_SYSTEM` for the\n  same metrics. Limit to top 20 entries — pareto only cares about the head.\n- **Pod position batch.** `queryGroupBy` on `AD_POSITION` for `ad_starts`\n  (sum), `ad_completions` (sum), and `ad_skips` (sum). Optionally\n  `AD_POD_POSITION` for the in-pod slot effect.\n- **Latency batch.** `queryTotal` on `ad_startup_time` with `average`, plus\n  `median` and `percentile` (p95). `queryGroupBy` on `AD_SYSTEM` for the\n  median to spot one ad system anchoring the latency.\n\nApply the cohort filter from Scope (if any) to every call. Do not interpret\nresults in this phase — write them to a structured payload and move on.\n\n### 3 — Flag\n\nRun the scripts. No new queries.\n\n1. **`funnel`** — assemble the six stage counts (plus optional clicks\u002Fskips)\n   and pipe to the script. Returns per-stage drop-off, completion rate, start\n   rate, **and a `quartileBeaconsMissing` flag** (true when Q1\u002FQ2\u002FQ3 are near\n   zero but completions are non-zero — classic SSAI signature). When that flag\n   is true, treat the funnel as start→completion only and note it in\n   `notDeterminable`.\n2. **`pareto`** — feed the concentration entries (advertisers, ad systems).\n   Returns rank, cumulative share, top-N share, and how many entries cover the\n   threshold (default 80%). A small `headCount` means one or two players carry\n   the inventory; a large one means it's diffuse.\n3. **`sample-gate`** — before declaring any advertiser \u002F ad system \u002F error\n   code \"an offender\" in the narrative, gate it: thin slices swing wildly and\n   should not be named. The gate is **per slice**, not on the aggregate.\n4. **`anomaly-flags`** — feed the funnel, top error code share, top advertiser\n   share, and the latency p95. The script applies ad-ops thresholds (see\n   below) and returns an `AnomalyFlag[]`. The script never says \"this is bad\";\n   it says \"metric X crossed threshold Y.\"\n\n### 4 — Report\n\nAssemble the `AdOpsReport` (`references\u002Freport-contract.md`):\n\n- the funnel section, with the SSAI degradation noted if it fired,\n- the errors section, top codes + top offending ad systems \u002F servers (gated),\n- the concentration section, top advertisers + top ad systems (gated),\n- the pod position section, with the **survivor-bias note attached** to any\n  comparison across positions,\n- the latency section, p50 \u002F p95 + top offending ad system,\n- the `anomalies[]` list,\n- `notDeterminable[]`: anything the report could not establish (e.g. \"clicks\n  not tracked — `ad_clicks` was 0 across the window\", \"SSAI suspected — Q1\u002FQ2\u002F\n  Q3 missing\", \"advertiser names absent — likely ad system not surfacing\n  VAST\"),\n- a single `recommendedAction` — the most actionable item (e.g. \"Investigate\n  error code 30021 on ad system X — 42% of all ad errors\"). May be null when\n  nothing is actionable.\n\nThen render the narrative from the object — never from in-head re-derivation.\n\n## Default anomaly thresholds\n\nThese are the conventions baked into `anomaly-flags`. Override per-customer\nwhen you have reason to.\n\n| Flag id                       | Default       | Why it matters                                                  |\n| ----------------------------- | ------------- | --------------------------------------------------------------- |\n| `impression-to-start-drop`    | > 8%          | Ad-server timeouts, VAST parse failures, player issues          |\n| `start-to-q1-drop`            | > 5%          | Early in-ad abandonment or quartile beacon issue                |\n| `q3-to-completion-drop`       | > 5%          | Suspicious — Q3 viewers almost always complete; usually beacons |\n| `low-completion-rate`         | \u003C 50%         | Inventory is leaking value                                      |\n| `top-error-code-share`        | > 30%         | One error code dominates → usually a single fixable cause       |\n| `top-advertiser-share`        | > 50%         | Over-concentration risk                                         |\n| `ad-startup-p95-high`         | > 2000 ms     | Slow ad load drags start rate                                   |\n\nThe thresholds are *flags*, not failures. The narrative should acknowledge a\nflagged condition and propose investigation — never declare it a regression.\n\n## Guards\n\n- **SSAI \u002F quartile reliability.** Server-side ad insertion does not fire\n  quartile beacons consistently. If Q1\u002FQ2\u002FQ3 are near zero but completions are\n  non-zero, the `funnel` script sets `quartileBeaconsMissing = true`; never\n  report quartile drop-offs in that case, and add the limitation to\n  `notDeterminable`.\n- **Mid-roll survivor bias.** Later pod positions are pre-filtered to users\n  who already retained through earlier ads — comparing completion rate at\n  pod-position-3 to pod-position-0 without saying so is misleading. Every\n  pod-position comparison in the narrative MUST mention this.\n- **Sample-size at advertiser level.** Tiny advertisers swing wildly; gate\n  every named advertiser through `sample-gate` on its `ad_starts` volume\n  (default minN = 1000 starts). Per-slice ad impressions cannot be pulled, so\n  starts are the gate input. Aggregate the long tail as \"Other (N\n  advertisers)\" rather than list low-volume ones.\n- **Click tracking availability.** CTR is meaningless when `ad_clicks` is zero\n  across the window — record \"clicks not tracked\" in `notDeterminable` instead\n  of reporting a 0% CTR.\n- **Custom ad systems may not surface VAST attributes.** When `ADVERTISER_NAME`\n  comes back empty for most rows, note it in `notDeterminable`; do not blame\n  any single ad system without evidence.\n- **Don't suggest a \"regression\" from one window of data.** This skill\n  produces a snapshot, not a comparison. If the user wants to know whether\n  something got worse, redirect to the QoE RCA skill.\n\n## Human-in-the-loop\n\nHITL is enrichment, never a dependency — every touchpoint must degrade to a\nstated assumption when no human is present.\n\n- **Front-loaded context (primary).** Let the human optionally seed Scope\n  with: a target cohort (advertiser \u002F ad system \u002F region), known recent\n  changes (ad-server vendor swap, new VAST tag, campaign launch), the\n  audience for the report (engineering vs sales vs leadership). Non-blocking.\n- **Section selection (fallback).** When the user's ask is narrow (\"just the\n  error breakdown\"), confirm before producing the full five-section report.\n- **Report approval** for any side effect (paging on a flag, posting to a\n  channel, flagging an ad system as an offender to a customer). Get explicit\n  human approval before any external action.\n\n## Subagent boundary\n\nIsolate exactly one chunk: the **five data-pull batches** in Pull. They are\nindependent, produce many grouped rows you don't want in the main thread, and\ndistill to compact contracts (funnel counts, top-N entries, etc.). Fan out the\nfive batches and bring back structured payloads. Do **not** subagent the Flag\nphase — the script outputs drive the narrative in the main thread, and\nsplitting them adds coordination cost for no parallelism gain.\n\n## Scripts\n\n| Command         | Purpose                                                            |\n| --------------- | ------------------------------------------------------------------ |\n| `funnel`        | Stage drop-offs + SSAI \u002F quartile-beacon detection                 |\n| `pareto`        | Concentration ranking + cumulative share + threshold split        |\n| `anomaly-flags` | Apply ad-ops thresholds to funnel + side metrics → AnomalyFlag[]  |\n| `sample-gate`   | Minimum-volume gate per slice — vendored from QoE RCA              |\n\nRun from the skill root: `npx tsx scripts\u002Fcli.ts \u003Ccommand>`. Each command takes\nJSON on stdin, returns JSON on stdout. Signatures are at the top of\n`scripts\u002Fcli.ts`. Tests cover the math: `npm test` in `scripts\u002F`.\n","# Ad Operations Snapshot Report\n\nProduce a structured, single-window report for an ad-operations audience: how\nthe funnel looks, where errors concentrate, which advertisers \u002F ad systems are\ncarrying or dragging the inventory, how pod position affects completion, and\nwhether ad startup time is within bounds. The output is a structured\n`AdOpsReport` object (`references\u002Freport-contract.md`) plus the prose\nnarrative the agent renders from it.\n\n## Operating principle\n\n**The agent decides; the scripts compute.** Funnel drop-offs across stages,\nconcentration ranking, and threshold checks all go to scripts under `scripts\u002F`\n— silent arithmetic slips poison a report the user cannot easily audit, and\nthe scripts are tested where mental math is not. Scripts return numbers and\nclassifications; they **never** return a verdict like \"this advertiser is\nunderperforming.\" Query construction, framing, and the final narrative stay\nwith the agent.\n\n## Setup (once)\n\nScripts run under Node via `tsx`.\n\n1. Check the runtime: `node --version` and `npm --version`. **If either is\n   missing, stop and ask the user for approval before installing anything** —\n   installing a runtime is a system change you must not make unattended. State\n   the platform command (macOS: `brew install node`) and wait for approval.\n2. Install deps once from `scripts\u002F`: `npm install` (pulls `tsx` + `vitest`).\n   Afterwards `npx tsx scripts\u002Fcli.ts \u003Ccmd>` uses the local `tsx`, no network.\n\nIf no Node is available and no one can approve an install, report that as a\nblocking limitation — do **not** fall back to in-head arithmetic for the\nfunnel decomposition or anomaly thresholds.\n\n## Scope of this skill\n\n**In scope.** Single-window snapshot reports for ad-ops audiences. The window\ncan be anything the user asks for (last 7 days, yesterday, last 30 days, a\ncustom range). Always assumes the license has the ads dataset wired up.\n\n**Not in scope.** Window-comparison or \"why did this change\" RCA — that work\nbelongs in the QoE RCA skill (`bitmovin-observability-root-cause-analysis`).\nIf the user's question is \"why did ad completions drop on Tuesday\", redirect\nthem. This skill does not run mix-vs-rate decomposition or onset detection.\n\n## Tools (ad analytics)\n\nThe ad query tools route any metric prefixed with `ad_` to the Ads Queries API:\n\n- **`query`** — time series of a single ad metric, optional filter, optional\n  group-by.\n- **`queryGroupBy`** — grouped aggregation; this is the workhorse for\n  concentration, error code breakdowns, and pod position cohorts.\n- **`queryTotal`** — one aggregate value over the window. Used for each funnel\n  stage and the latency percentiles.\n- **Schema helpers** — `searchMetrics` to resolve the actual ad-metric keyword\n  (\"ads\" → `ad_impressions`, \"CTR\" → `ad_clicks`), `searchFilters` \u002F\n  `getAvailableFilters` to confirm filter attributes (`AD_SYSTEM`,\n  `ADVERTISER_NAME`, `AD_POSITION`, `AD_POD_POSITION`, `AD_TAG_SERVER`,\n  `ERROR_CODE`, `CUSTOM_DATA_*`), `peekAllLicenses` when the user hasn't\n  supplied a license key.\n\nThe ad filter catalog is disjoint from the playback filter catalog. The ads\ndataset only accepts `AdAnalyticsAttribute` names — don't reuse playback\nattribute names in ad queries.\n\n## The four phases\n\nRun **Scope → Pull → Flag → Report** strictly in order. Each phase has a job;\nmixing them produces sloppy reports.\n\n- **Scope** decides what to ask. No queries.\n- **Pull** only pulls. No interpretation.\n- **Flag** only interprets the pulled data through the scripts. No new queries.\n- **Report** assembles the structured object and renders the narrative.\n\n### 1 — Scope\n\nPin the question and the schema before touching data.\n\n1. Resolve the license key (`peekAllLicenses` if not supplied) and the window\n   (`start`, `end`). For \"weekly review\" with no explicit dates, default to\n   the last 7 full days (UTC).\n2. Resolve schema via `searchMetrics` and `searchFilters` — never guess\n   keywords. The canonical ad metric names are `ad_impressions`, `ad_starts`,\n   `ad_quartile_1`, `ad_midpoint`, `ad_quartile_3`, `ad_completions`,\n   `ad_clicks`, `ad_skips`, `ad_error_sessions`, `ad_error_percentage`,\n   `ad_startup_time`, `ad_play_percentage`, `ad_unique_users`.\n3. **Confirm the license has ad data.** Run one `queryTotal` on\n   `ad_impressions` over the window. If it returns 0 (or near-zero), stop and\n   report \"license has no ad data in this window\" — do not attempt the rest of\n   the report.\n4. Capture any human-supplied scope (filter to a specific advertiser, ad\n   system, country, app version) and remember it as a filter to apply to every\n   pull. See HITL — context is enrichment, never required.\n5. Decide which sections to produce. The default report includes all five\n   sections; the agent may skip a section when the user's ask is narrow\n   (\"just show me the error breakdown\").\n\n### 2 — Pull\n\nFive independent data batches. Run them as a fan-out (see subagent boundary).\n\n- **Funnel batch.** Six `queryTotal` calls — one per stage:\n  `ad_impressions`, `ad_starts` (sum), `ad_quartile_1` (sum), `ad_midpoint`\n  (sum), `ad_quartile_3` (sum), `ad_completions` (sum). Same window, same\n  cohort filter on every call. Optionally pull `ad_clicks` (sum) and\n  `ad_skips` (sum) as side-branch totals.\n- **Errors batch.** `queryTotal` on `ad_error_sessions` + `queryGroupBy` on\n  `ERROR_CODE` (top 10 by count) + `queryGroupBy` on `AD_SYSTEM` for\n  `ad_error_sessions`. Optionally `AD_TAG_SERVER` if the user cares about ad\n  server attribution.\n- **Concentration batch.** `queryGroupBy` on `ADVERTISER_NAME` for both\n  `ad_completions` (sum) and `ad_starts` (sum). Optionally `AD_SYSTEM` for the\n  same metrics. Limit to top 20 entries — pareto only cares about the head.\n- **Pod position batch.** `queryGroupBy` on `AD_POSITION` for `ad_starts`\n  (sum), `ad_completions` (sum), and `ad_skips` (sum). Optionally\n  `AD_POD_POSITION` for the in-pod slot effect.\n- **Latency batch.** `queryTotal` on `ad_startup_time` with `average`, plus\n  `median` and `percentile` (p95). `queryGroupBy` on `AD_SYSTEM` for the\n  median to spot one ad system anchoring the latency.\n\nApply the cohort filter from Scope (if any) to every call. Do not interpret\nresults in this phase — write them to a structured payload and move on.\n\n### 3 — Flag\n\nRun the scripts. No new queries.\n\n1. **`funnel`** — assemble the six stage counts (plus optional clicks\u002Fskips)\n   and pipe to the script. Returns per-stage drop-off, completion rate, start\n   rate, **and a `quartileBeaconsMissing` flag** (true when Q1\u002FQ2\u002FQ3 are near\n   zero but completions are non-zero — classic SSAI signature). When that flag\n   is true, treat the funnel as start→completion only and note it in\n   `notDeterminable`.\n2. **`pareto`** — feed the concentration entries (advertisers, ad systems).\n   Returns rank, cumulative share, top-N share, and how many entries cover the\n   threshold (default 80%). A small `headCount` means one or two players carry\n   the inventory; a large one means it's diffuse.\n3. **`sample-gate`** — before declaring any advertiser \u002F ad system \u002F error\n   code \"an offender\" in the narrative, gate it: thin slices swing wildly and\n   should not be named. The gate is **per slice**, not on the aggregate.\n4. **`anomaly-flags`** — feed the funnel, top error code share, top advertiser\n   share, and the latency p95. The script applies ad-ops thresholds (see\n   below) and returns an `AnomalyFlag[]`. The script never says \"this is bad\";\n   it says \"metric X crossed threshold Y.\"\n\n### 4 — Report\n\nAssemble the `AdOpsReport` (`references\u002Freport-contract.md`):\n\n- the funnel section, with the SSAI degradation noted if it fired,\n- the errors section, top codes + top offending ad systems \u002F servers (gated),\n- the concentration section, top advertisers + top ad systems (gated),\n- the pod position section, with the **survivor-bias note attached** to any\n  comparison across positions,\n- the latency section, p50 \u002F p95 + top offending ad system,\n- the `anomalies[]` list,\n- `notDeterminable[]`: anything the report could not establish (e.g. \"clicks\n  not tracked — `ad_clicks` was 0 across the window\", \"SSAI suspected — Q1\u002FQ2\u002F\n  Q3 missing\", \"advertiser names absent — likely ad system not surfacing\n  VAST\"),\n- a single `recommendedAction` — the most actionable item (e.g. \"Investigate\n  error code 30021 on ad system X — 42% of all ad errors\"). May be null when\n  nothing is actionable.\n\nThen render the narrative from the object — never from in-head re-derivation.\n\n## Default anomaly thresholds\n\nThese are the conventions baked into `anomaly-flags`. Override per-customer\nwhen you have reason to.\n\n| Flag id                       | Default       | Why it matters                                                  |\n| ----------------------------- | ------------- | --------------------------------------------------------------- |\n| `impression-to-start-drop`    | > 8%          | Ad-server timeouts, VAST parse failures, player issues          |\n| `start-to-q1-drop`            | > 5%          | Early in-ad abandonment or quartile beacon issue                |\n| `q3-to-completion-drop`       | > 5%          | Suspicious — Q3 viewers almost always complete; usually beacons |\n| `low-completion-rate`         | \u003C 50%         | Inventory is leaking value                                      |\n| `top-error-code-share`        | > 30%         | One error code dominates → usually a single fixable cause       |\n| `top-advertiser-share`        | > 50%         | Over-concentration risk                                         |\n| `ad-startup-p95-high`         | > 2000 ms     | Slow ad load drags start rate                                   |\n\nThe thresholds are *flags*, not failures. The narrative should acknowledge a\nflagged condition and propose investigation — never declare it a regression.\n\n## Guards\n\n- **SSAI \u002F quartile reliability.** Server-side ad insertion does not fire\n  quartile beacons consistently. If Q1\u002FQ2\u002FQ3 are near zero but completions are\n  non-zero, the `funnel` script sets `quartileBeaconsMissing = true`; never\n  report quartile drop-offs in that case, and add the limitation to\n  `notDeterminable`.\n- **Mid-roll survivor bias.** Later pod positions are pre-filtered to users\n  who already retained through earlier ads — comparing completion rate at\n  pod-position-3 to pod-position-0 without saying so is misleading. Every\n  pod-position comparison in the narrative MUST mention this.\n- **Sample-size at advertiser level.** Tiny advertisers swing wildly; gate\n  every named advertiser through `sample-gate` on its `ad_starts` volume\n  (default minN = 1000 starts). Per-slice ad impressions cannot be pulled, so\n  starts are the gate input. Aggregate the long tail as \"Other (N\n  advertisers)\" rather than list low-volume ones.\n- **Click tracking availability.** CTR is meaningless when `ad_clicks` is zero\n  across the window — record \"clicks not tracked\" in `notDeterminable` instead\n  of reporting a 0% CTR.\n- **Custom ad systems may not surface VAST attributes.** When `ADVERTISER_NAME`\n  comes back empty for most rows, note it in `notDeterminable`; do not blame\n  any single ad system without evidence.\n- **Don't suggest a \"regression\" from one window of data.** This skill\n  produces a snapshot, not a comparison. If the user wants to know whether\n  something got worse, redirect to the QoE RCA skill.\n\n## Human-in-the-loop\n\nHITL is enrichment, never a dependency — every touchpoint must degrade to a\nstated assumption when no human is present.\n\n- **Front-loaded context (primary).** Let the human optionally seed Scope\n  with: a target cohort (advertiser \u002F ad system \u002F region), known recent\n  changes (ad-server vendor swap, new VAST tag, campaign launch), the\n  audience for the report (engineering vs sales vs leadership). Non-blocking.\n- **Section selection (fallback).** When the user's ask is narrow (\"just the\n  error breakdown\"), confirm before producing the full five-section report.\n- **Report approval** for any side effect (paging on a flag, posting to a\n  channel, flagging an ad system as an offender to a customer). Get explicit\n  human approval before any external action.\n\n## Subagent boundary\n\nIsolate exactly one chunk: the **five data-pull batches** in Pull. They are\nindependent, produce many grouped rows you don't want in the main thread, and\ndistill to compact contracts (funnel counts, top-N entries, etc.). Fan out the\nfive batches and bring back structured payloads. Do **not** subagent the Flag\nphase — the script outputs drive the narrative in the main thread, and\nsplitting them adds coordination cost for no parallelism gain.\n\n## Scripts\n\n| Command         | Purpose                                                            |\n| --------------- | ------------------------------------------------------------------ |\n| `funnel`        | Stage drop-offs + SSAI \u002F quartile-beacon detection                 |\n| `pareto`        | Concentration ranking + cumulative share + threshold split        |\n| `anomaly-flags` | Apply ad-ops thresholds to funnel + side metrics → AnomalyFlag[]  |\n| `sample-gate`   | Minimum-volume gate per slice — vendored from QoE RCA              |\n\nRun from the skill root: `npx tsx scripts\u002Fcli.ts \u003Ccommand>`. Each command takes\nJSON on stdin, returns JSON on stdout. Signatures are at the top of\n`scripts\u002Fcli.ts`. Tests cover the math: `npm test` in `scripts\u002F`.\n",{"name":5,"description":7},"main","14b211bb07d3e4057b85fb9140369fc73b1eb33b","skills\u002Fbitmovin-observability-ad-report","https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills","https:\u002F\u002Fgithub.com\u002Fbitmovin","https:\u002F\u002Fbitmovin.com","2026-09-11 10:36:33",{"source":8,"skillName":5,"commands":64,"prompt":74,"zipAvailable":75},[65,68,71],{"tool":66,"command":67},"npx","npx skills add bitmovin\u002Fskills --skill bitmovin-observability-ad-report",{"tool":69,"command":70},"bunx","bunx skills add bitmovin\u002Fskills --skill bitmovin-observability-ad-report",{"tool":72,"command":73},"pnpm","pnpm dlx skills add bitmovin\u002Fskills --skill bitmovin-observability-ad-report","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-observability-ad-report\u002FSKILL.md",true,[77,83,89,93,97,101,105,109,113,117,121,125,129,133],{"path":78,"resourceType":79,"resourceTypeDesc":80,"sizeBytes":81,"sourceHtmlUrl":82},"references\u002Freport-contract.md","REFERENCE","参考文档",10115,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-observability-ad-report\u002Freferences\u002Freport-contract.md",{"path":84,"resourceType":85,"resourceTypeDesc":86,"sizeBytes":87,"sourceHtmlUrl":88},"scripts\u002Fanomaly-flags.test.ts","SCRIPT","脚本",4795,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-observability-ad-report\u002Fscripts\u002Fanomaly-flags.test.ts",{"path":90,"resourceType":85,"resourceTypeDesc":86,"sizeBytes":91,"sourceHtmlUrl":92},"scripts\u002Fanomaly-flags.ts",6435,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-observability-ad-report\u002Fscripts\u002Fanomaly-flags.ts",{"path":94,"resourceType":85,"resourceTypeDesc":86,"sizeBytes":95,"sourceHtmlUrl":96},"scripts\u002Fcli.ts",2927,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-observability-ad-report\u002Fscripts\u002Fcli.ts",{"path":98,"resourceType":85,"resourceTypeDesc":86,"sizeBytes":99,"sourceHtmlUrl":100},"scripts\u002Ffunnel.test.ts",4741,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-observability-ad-report\u002Fscripts\u002Ffunnel.test.ts",{"path":102,"resourceType":85,"resourceTypeDesc":86,"sizeBytes":103,"sourceHtmlUrl":104},"scripts\u002Ffunnel.ts",3642,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-observability-ad-report\u002Fscripts\u002Ffunnel.ts",{"path":106,"resourceType":85,"resourceTypeDesc":86,"sizeBytes":107,"sourceHtmlUrl":108},"scripts\u002Fpackage-lock.json",58476,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-observability-ad-report\u002Fscripts\u002Fpackage-lock.json",{"path":110,"resourceType":85,"resourceTypeDesc":86,"sizeBytes":111,"sourceHtmlUrl":112},"scripts\u002Fpackage.json",459,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-observability-ad-report\u002Fscripts\u002Fpackage.json",{"path":114,"resourceType":85,"resourceTypeDesc":86,"sizeBytes":115,"sourceHtmlUrl":116},"scripts\u002Fpareto.test.ts",3325,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-observability-ad-report\u002Fscripts\u002Fpareto.test.ts",{"path":118,"resourceType":85,"resourceTypeDesc":86,"sizeBytes":119,"sourceHtmlUrl":120},"scripts\u002Fpareto.ts",2525,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-observability-ad-report\u002Fscripts\u002Fpareto.ts",{"path":122,"resourceType":85,"resourceTypeDesc":86,"sizeBytes":123,"sourceHtmlUrl":124},"scripts\u002Fsample-gate.test.ts",1239,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-observability-ad-report\u002Fscripts\u002Fsample-gate.test.ts",{"path":126,"resourceType":85,"resourceTypeDesc":86,"sizeBytes":127,"sourceHtmlUrl":128},"scripts\u002Fsample-gate.ts",1173,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-observability-ad-report\u002Fscripts\u002Fsample-gate.ts",{"path":130,"resourceType":85,"resourceTypeDesc":86,"sizeBytes":131,"sourceHtmlUrl":132},"scripts\u002Ftypes.ts",8633,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-observability-ad-report\u002Fscripts\u002Ftypes.ts",{"path":134,"resourceType":85,"resourceTypeDesc":86,"sizeBytes":135,"sourceHtmlUrl":136},"scripts\u002Fvitest.config.ts",183,"https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-observability-ad-report\u002Fscripts\u002Fvitest.config.ts",[138,167,191,213,233,250],{"skillNo":139,"name":9,"nameZh":6,"description":140,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":141,"categories":142,"keywords":149,"publishedAt":51},"01M6XCZ365TGD9DCJG6Z45S0T4","Hub skill for Bitmovin video infrastructure. Use when the user mentions Bitmovin, asks about video playback, encoding, transcoding, streaming, player integration, video analytics, content delivery optimization, or video AI. Routes to the right Bitmovin product (Player, VOD\u002FLive Encoding, Observability, AI Scene Analysis, Streams, Stream Lab) and walks through MCP\u002FCLI\u002FSDK setup.","https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin\u002FSKILL.md",[143,146],{"categoryNo":144,"categoryName":145,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000016","图像与多媒体",{"categoryNo":147,"categoryName":148,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[9,150,151,49,152,153,34,154,155,156,157,158,159,160,161,162,163,164,165,166],"MCP","cli","infrastructure","product","streaming","Optimization","analysis","Video","use","sdk setup","delivery","live","streams","lab","player","for","when",{"skillNo":168,"name":169,"nameZh":6,"description":170,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":171,"categories":172,"keywords":176,"publishedAt":51},"01M6XCYXP57QRYXCW7QN1HXNVC","bitmovin-player-web","Integrate the Bitmovin Web Player SDK into a web app. Use when the user asks to add video playback, embed a player, play HLS\u002FDASH\u002FMP4\u002FWHEP\u002FMOQ, set up DRM (Widevine\u002FPlayReady\u002FFairPlay), integrate ads, customize the Player UI, or work with the Bitmovin Player in any way. Covers both Player v8 (stable) and Player Web X \u002F PWX (next-gen).","https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-player-web\u002FSKILL.md",[173],{"categoryNo":174,"categoryName":175,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发",[177,164,178,9,179,180,181,157,44,182,183,158,184,185,186,187,188,189,190,166],"web","bitmovin player web","play","ui","SET","mp4","work","integrate","up","next","app","add","dash","hls",{"skillNo":192,"name":193,"nameZh":6,"description":194,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":195,"categories":196,"keywords":197,"publishedAt":51},"01M6XCYRHQ60DC0YXWRCASET1X","bitmovin-player-ios","Integrate and troubleshoot the Bitmovin Player SDK for iOS, tvOS, and visionOS in Swift, SwiftUI, UIKit, and existing Objective-C apps, including playback setup, player lifecycle, source loading, DRM, ads, AirPlay, Google Cast \u002F Chromecast, PiP, platform-specific UX, logs, network diagnostics, and stream validation.","https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-player-ios\u002FSKILL.md",[],[198,164,199,9,200,201,202,203,204,44,205,206,184,207,208,209,165,210,211,212],"ios","bitmovin player ios","google","swift","validation","logs","lifecycle","swiftui","uikit","platform","apps","pip","Existing","stream","objective",{"skillNo":214,"name":215,"nameZh":6,"description":216,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":217,"categories":218,"keywords":219,"publishedAt":51},"01M6XCYM297KGT0QKD7J9DNC4G","bitmovin-player-android","Integrate and troubleshoot the Bitmovin Android Player SDK in Kotlin\u002FCompose Android apps, including playback setup, Web UI, PiP\u002Ffullscreen handlers, media session, cast, TV variants, and source-loading robustness.","https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-player-android\u002FSKILL.md",[],[220,164,221,9,222,177,180,223,224,225,184,226,227,208,228,229,209,230,231,232],"android","bitmovin player android","troubleshoot","Media","kotlin","session","compose","cast","web ui","loading","source","robustness","variants",{"skillNo":234,"name":235,"nameZh":6,"description":236,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":237,"categories":238,"keywords":240,"publishedAt":51},"01M6XCYCHP945BKSJ37BMDR0KR","bitmovin-observability-root-cause-analysis","Root-cause analysis for a video QoE metric that moved — startup time, rebuffer ratio, error rate, bitrate, video-start-failures — using the Bitmovin Observability MCP. Use whenever someone asks WHY a QoE metric changed, spiked, dropped, regressed, or \"looks off\", whether a release or CDN change hurt quality, or what drives a delta between two windows or cohorts. Trigger even without \"RCA\" — \"rebuffering is up in APAC\", \"did v11 regress startup?\", \"what happened to error rate Tuesday\" all belong here. Drives a disciplined Define→Gather→Reflect→Conclude investigation with mix-vs-rate (Simpson's paradox) protection and a falsification step, producing a structured findings object.","https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-observability-root-cause-analysis\u002FSKILL.md",[239],{"categoryNo":147,"categoryName":148,"taxonomyType":21,"taxonomyTypeDesc":22},[241,34,156,242,243,9,244,150,245,246,247,47,157,45,185,41,40,248,37,249],"root cause analysis","root","root cause","bitmovin observability root cause analysis","change","reflect","release","mix","rate",{"skillNo":251,"name":252,"nameZh":6,"description":253,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":254,"categories":255,"keywords":258,"publishedAt":51},"01M6XCY63KD9ZQ5S5P4KSV881A","bitmovin-observability-collector-integration","Integrate the Bitmovin Observability (Analytics) collector into any video player on any platform. Use WHENEVER a developer wants to add Bitmovin Analytics \u002F Observability tracking, \"set up the collector\", \"enable analytics\", \"track playback \u002F QoE \u002F impressions\", wire up a player to the Bitmovin dashboard, or asks why their analytics data is missing, empty, or wrong after integrating. Covers Web, Android\u002FFire TV, iOS\u002FtvOS\u002FvisionOS, and Roku, for both the Bitmovin Player and third-party engines (HLS.js, Shaka, Video.js, HTML5, ExoPlayer, Media3, AVPlayer, THEOplayer, CAFv3). Use it even when the user only names their player (\"add analytics to my Shaka player\") without saying \"Bitmovin collector\". Knows that the Bitmovin Player has analytics pre-integrated while third-party players need a separate collector with a managed attach\u002Fdetach lifecycle.","https:\u002F\u002Fgithub.com\u002Fbitmovin\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fbitmovin-observability-collector-integration\u002FSKILL.md",[256,257],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":24,"categoryName":25,"taxonomyType":26,"taxonomyTypeDesc":27},[34,259,260,9,261,262,263,181,157,45,264,184,265,185,266,164,267,268,269,166],"integration","collector","bitmovin observability collector integration","dashboard","data","video player","only","attach","my","track","managed"]