diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 14d89a5..d5551ec 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,6 +9,15 @@ "name": "headset", "source": "./", "description": "Connects Claude Code to Headset's hosted MCP server" + }, + { + "name": "headset-bridge-toolkit", + "source": "./plugins/headset-bridge-toolkit", + "description": "Buyer-ready reports from your Bridge sell-through data: account reviews, restock risk, and a weekly pulse.", + "version": "1.0.0", + "author": { + "name": "Headset" + } } ] } diff --git a/README.md b/README.md index 302de6c..078bc9c 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,26 @@ Bring your [Headset](https://headset.io) retailer data into Claude Code. Ask abo ## Installation +Add the marketplace once, then install the plugin that fits how you use Headset: + ``` /plugin marketplace add Headset/claude-code /plugin install headset@Headset ``` +| Plugin | For | What you get | +|---|---|---| +| `headset` | Anyone with a Headset account | Connects Claude Code to Headset's hosted MCP server — ask about your data in plain language | +| `headset-bridge-toolkit` | Brands and vendors using Headset Bridge | Buyer-ready report skills on top of the MCP connection: account reviews, restock risk, and a weekly pulse | + +To install the Bridge toolkit instead of (or alongside) the base plugin: + +``` +/plugin install headset-bridge-toolkit@Headset +``` + +Update anytime with `/plugin marketplace update Headset`. + ## Setup On first use, Claude Code will prompt you to authenticate with Headset via OAuth. No API keys to manage — once you approve access, you're connected. diff --git a/plugins/headset-bridge-toolkit/.claude-plugin/plugin.json b/plugins/headset-bridge-toolkit/.claude-plugin/plugin.json new file mode 100644 index 0000000..6234844 --- /dev/null +++ b/plugins/headset-bridge-toolkit/.claude-plugin/plugin.json @@ -0,0 +1,21 @@ +{ + "name": "headset-bridge-toolkit", + "version": "1.0.0", + "description": "Turn Headset Bridge sell-through data into buyer-ready reports: retailer account reviews, restock risk, and a weekly sell-through pulse.", + "author": { + "name": "Headset", + "email": "support@headset.io" + }, + "homepage": "https://www.headset.io", + "keywords": ["headset", "bridge", "cannabis", "sell-through", "retail-analytics"], + "mcpServers": { + "headset": { + "type": "http", + "url": "https://mcp.headset.io", + "oauth": { + "clientId": "tsZq8k6XeiZiXMPCTQT9pt0Ia9zzMqDN", + "callbackPort": 5420 + } + } + } +} diff --git a/plugins/headset-bridge-toolkit/README.md b/plugins/headset-bridge-toolkit/README.md new file mode 100644 index 0000000..4fc7df0 --- /dev/null +++ b/plugins/headset-bridge-toolkit/README.md @@ -0,0 +1,33 @@ +# Headset Bridge Toolkit + +Turn your Headset Bridge connection into buyer-ready deliverables. This plugin gives Claude three skills built on live Bridge sell-through and inventory data from your connected retail partners. + +## What's included + +| Skill | Who it's for | What it does | +|---|---|---| +| **bridge-account-review** | Sales reps / account managers | A polished one-pager for a buyer meeting at a specific store: performance, category mix vs your other doors, top SKUs, shopper demographics, restock asks, and talking points. | +| **bridge-restock-risk** | Sales reps / account managers | Velocity-based restock report: what's out or nearly out while still selling, ranked by estimated revenue at risk, grouped per store for easy forwarding. | +| **bridge-weekly-pulse** | Brand / ops leadership | A two-minute Monday brief: week-over-week revenue with the swing explained, movers and decliners, discount watch, and risk carried into the new week. Offers to schedule itself every Monday. | + +The skills share the plugin's `references/` files for the Bridge data conventions and Headset report style, keeping every skill honest about Bridge data quirks (raw POS taxonomy, per-store product IDs, partial periods, real stockout logic) and consistent with Headset's visual identity in every report. + +## Setup + +1. **Connect the Headset MCP.** In Claude Code, the plugin registers the Headset MCP server (`https://mcp.headset.io`) automatically via its manifest; complete authentication when prompted. On claude.ai and Claude Desktop, if the Headset connector does not appear after installing the plugin, add it manually (Settings → Connectors → `https://mcp.headset.io`); on organization installs, an admin can add the Headset MCP as a connection in the same Access bundle or scope as the plugin. +2. **Bridge must be enabled on your Headset account.** The Headset MCP exposes tools based on your account's products. This plugin uses the Bridge tool set; if your account is Insights- or Retailer-only, the skills will let you know rather than running on the wrong data. +3. That's it. The skills discover your stores through your Bridge connections; nothing else to configure. + +## Usage + +Just ask naturally: + +- "Prep an account review for my meeting at Star Buds Riverside" +- "What's running low across my stores?" +- "Run my weekly pulse" + +## Notes + +- Bridge data covers **your products at your connected stores**. It is not market-level data. +- Reports are self-contained HTML files styled to Headset's brand. +- All figures involving margin are quality-checked against cost coverage before they're shown. diff --git a/plugins/headset-bridge-toolkit/references/bridge-data-conventions.md b/plugins/headset-bridge-toolkit/references/bridge-data-conventions.md new file mode 100644 index 0000000..c1095fd --- /dev/null +++ b/plugins/headset-bridge-toolkit/references/bridge-data-conventions.md @@ -0,0 +1,74 @@ +# Bridge Data Conventions + +Shared rules for every skill in this plugin. Follow these before and while querying the Headset Bridge tools (`bridge_find_connections`, `bridge_sales_totals`, `bridge_sales_by_dimension`, `bridge_sales_trend`, `bridge_get_inventory`, `bridge_search_dimension_values`). Getting these right is the difference between numbers a customer trusts and numbers that look broken. + +## 1. Scope: this is a vendor-side view + +A Bridge account sees only its own products' sales and inventory at the retailer stores it is connected to. It does NOT see the store's full menu, other vendors' products, or market share. Frame every output as "your brands at your connected stores." Never imply market-level coverage. + +Always start by calling `bridge_find_connections` to get the active connections. Every `storeIds` filter must use the `retailerStoreId` values it returns. When the user names a store, resolve it with the `query` parameter; if more than one connection matches, ask which one they mean. + +## 2. Taxonomy is raw retailer POS data. Normalize it. + +Categories and units come straight from each retailer's point of sale, so the same concept appears under many spellings, and the mix differs per store. Real examples: "Edibles", "Edible", "Edibles Gummies", "Gummies" all coexist; pre-rolls appear as "Pre-Roll", "Pre-Rolls", "Preroll", "Pre-Roll Pack Full Flower", and more; eighths appear as "3.5 Grams", "3.5", and "3.5g". + +Rules: + +- Before filtering on any brand, category, vendor, product, or unit the user names, call `bridge_search_dimension_values` and use ALL matching canonical values, OR-joined in the filter. +- When presenting category rollups, aggregate raw categories into canonical groups before showing them. Use these case-insensitive substring rules, applied in this exact order, first match wins: + 1. Excluded: category contains "employee", "promo", or "sample". Drop these rows entirely. + 2. Pre-Rolls: contains "pre-roll" or "preroll". + 3. Vapor: contains "vape", "vapes", "cartridge", or "disposable". + 4. Edibles: contains "edible", "gummies", "gummy", "mints", "chocolate", or "tincture". + 5. Concentrates: contains "concentrate", "extract", "rosin", "resin", "budder", "sugar", or "rso". + 6. Flower: contains "flower", "popcorn", or "shake" (catches Infused Flower, Flower Popcorn, Flower Shake, Shake, Infused Shake). + 7. Other: anything left, including Capsules. + + The order is load-bearing: "Infused Pre-Rolls" must land in Pre-Rolls, not Flower, and "Live Resin Cartridge" must land in Vapor, not Concentrates. +- Exclude "Employee/Promo Samples" categories (and any item priced under $1) from average-price, margin, and top-seller calculations. Include them nowhere unless the user asks about samples. +- Never show a customer a raw category list with obvious duplicates. That reads as broken data. + +## 3. Product IDs are per-store + +The same SKU has a different `product_id` and often a slightly different name at each retailer ("Cl - Cresco - Pineapple Express - Live Resin Cartridge - 1g" vs "Cresco - Live Resin Cartridge Pineapple Express - 1g"). `store_count` on product rows is almost always 1 for this reason. + +For any cross-store product comparison, build a match key instead of relying on `product_id`: brand + normalized product name + unit group. Normalize names by lowercasing, stripping vendor prefixes (such as "Cl - "), punctuation, pack-size text in parentheses, and reordering-insensitive token comparison. Match conservatively; when unsure whether two rows are the same SKU, treat them as different and say so. + +## 4. Partial periods will lie to you + +Trend queries include the in-progress day, week, and month. Any week-over-week or month-over-month comparison must use complete periods only. Either exclude the current partial period or clearly annotate it ("week in progress"). Never present a partial period as a decline. + +## 5. Stockout and reorder logic + +`stockout_count` in grouped inventory counts every zero-on-hand SKU, including long-discontinued ones, so it wildly overstates risk (a store can show 1,100+ "stockouts"). Never quote it as reorder risk. + +Real signals, from per-product inventory rows: + +- Active stockout that matters: `on_hand_units` = 0 AND `avg_daily_units` >= 0.25 (it was selling within the trailing 28 days). +- Critical low stock: in stock, `weeks_of_supply` <= 2, `avg_daily_units` >= 0.25. +- Revenue at risk: `avg_daily_units` x `price` x 14 days is a fair two-week estimate. Label it as an estimate. +- Overstock: `minWeeksOfSupply` >= 12 with meaningful on-hand value. + +Bridge data is descriptive. Do not present computed reorder quantities as recommendations; present velocity and days of supply and let the customer decide. + +## 6. Measures worth knowing + +- `total_revenue` is net of discounts; `total_gross_sales` is pre-discount. Discount rate = `total_discounts` / `total_gross_sales`. Discount rates of 30%+ are common in some markets and are a story, not an error. +- Check `pct_revenue_with_cost` before quoting margin or profit. Below ~0.9, caveat that cost coverage is incomplete. +- `avg_item_price` is per item sold, not per transaction. + +## 7. Demographics and channels + +`customer_age_group` (generations), `customer_gender`, `order_source` (Walk In, Dutchie, IHeartJane, kiosk, e-commerce, etc.), and `is_medical` are available as dimensions and filters. Null and "Unknown" buckets exist (typically a few percent of revenue); group them as "Unknown" and compute share-of-known percentages. Only one dimension per call: to cross two (say category by age group), loop the dimension call with a filter per value of the other. + +## 8. Practical querying + +- Date windows use Looker expressions: "last 30 days", "this month", "2026-06-01 to 2026-07-01", "last 13 weeks". +- Explicit ranges of the form "YYYY-MM-DD to YYYY-MM-DD" are END-EXCLUSIVE: the second date is not included. A Monday-to-Sunday week is "monday_date to next_monday_date" (the week of Jul 13 to 19, 2026 is "2026-07-13 to 2026-07-20"). After pulling totals for an explicit range, validate against the matching bucket of a `bridge_sales_trend` pull at the same grain; a mismatch means the range is wrong. +- Transient MCP errors (connection lost) happen occasionally; retry the call once before reporting a problem. +- Independent queries should be issued in parallel to keep reports fast. +- Avoid `current_user_accounts`; it can return an enormous payload and is not needed for these workflows. + +## 9. Entitlements + +The Headset MCP exposes different tool sets depending on what the authenticated account has enabled. This plugin requires the Bridge tool set (`bridge_*` tools). If those tools are not available after the Headset connector is authenticated, do not retry or improvise with other Headset tools; tell the user plainly that the connected Headset account does not have Bridge enabled and that they should reconnect with a Bridge-enabled account or contact Headset. If the account also exposes Insights or Retailer tools, those are separate products; use them only where a skill explicitly says so. diff --git a/plugins/headset-bridge-toolkit/references/headset-report-style.md b/plugins/headset-bridge-toolkit/references/headset-report-style.md new file mode 100644 index 0000000..7fb4a67 --- /dev/null +++ b/plugins/headset-bridge-toolkit/references/headset-report-style.md @@ -0,0 +1,60 @@ +# Headset Report Style + +Shared visual and voice rules for every HTML deliverable this plugin produces. These reports carry the Headset brand into a customer's buyer meetings; they must look and sound the part. + +## Deliverable format + +Produce a single self-contained HTML file: all CSS and JS inline, images as data URLs. Chart.js from cdnjs.cloudflare.com is allowed for charts. Never use localStorage or sessionStorage. Deliver the file using the platform's file output/presentation mechanism (for example, writing to the outputs directory and presenting the file). For reports the user will revisit or update (the weekly pulse, a standing account review), also persist it in the platform's persistent/recurring artifact store when that capability is available. + +## Typography + +Body and headings: Poppins, loaded from Google Fonts, with fallback stack `'Poppins', 'Inter', 'Montserrat', system-ui, sans-serif`. Never a serif or display font. Do not attempt to recreate the Headset logo or wordmark; a plain-text footer line is the correct brand presence (see Footer). + +## Color + +Anchor on the Insights palette for data, use orange only as a spotlight, keep everything else quiet. + +Core UI: +- Text / headers: `#14172E` (dark navy) +- Secondary text: `#666666` +- Page background: `#F8FBFB` or white; card backgrounds white with `#CCCCCC` hairline borders or a subtle shadow +- Data-section card tint: `#F0F0FA` + +Charts and data: +- Primary series: `#232E83` (deep blue), secondary: `#6862C7` +- Additional series, in order: `#C2BEFF`, `#258682`, `#61C2BC`, `#B4E8E4` +- Comparison / context data: grays `#848383`, `#B4B4B4`, `#D9D9D9` +- Highlight (the one thing the reader must notice): `#F15D22` orange. One or two elements per report, no more. If every bar is orange, nothing is. + +Accents: +- This is a Bridge product: `#87005B` (deep magenta) may be used sparingly for the report masthead accent or section dividers. Not for chart series. +- Positive deltas / success states: `#3EB280`. Negative deltas: use `#F15D22` sparingly or `#87005B`; never invent a red. + +Aesthetic: rounded corners (8-12px), generous whitespace, subtle shadows over hard edges, one accent per view. The report should still look right in three years. + +## Layout pattern + +1. Masthead: report title, store or scope, date range, generated date. Thin magenta or blue accent rule. +2. KPI row: 3-5 stat tiles (big number, small label, delta vs prior period with arrow and color). +3. Charts and tables in cards, one idea per card, with a one-line takeaway headline above each ("High Supply drove the week's growth", not "Revenue by brand"). +4. Callout card (orange-tinted background `#FEF3EC`) for the single most important action item. +5. Footer. + +## Voice + +Confident, trusted, friendly. Reassuring first, factual always. Rules that are non-negotiable in generated report copy: + +- Never use em dashes. Use commas, periods, parentheses, or colons instead. +- Lead with what it means, then the number that proves it. Every chart gets a takeaway headline in plain language. +- Specifics over adjectives: "up 14% at Star Buds Riverside" beats "strong growth." +- No hype vocabulary (revolutionary, game-changing, best-in-class), no hedges on things the data proves ("we think", "it seems"), no corporate filler ("leverage", "move the needle"). +- Respect the reader's expertise; do not explain industry basics. Do explain a metric definition when misreading it would mislead (for example, that revenue is net of discounts). +- Numbers: currency with thousands separators, no decimals above $1,000; percentages to one decimal; units as whole numbers. + +## Footer + +Every report ends with a quiet footer line in `#666666`, Poppins: + +"Powered by Headset Bridge data · Generated [date] · [date range covered]" + +Add, where relevant: "Sales and inventory reflect your products at your connected retail partners." diff --git a/plugins/headset-bridge-toolkit/skills/bridge-account-review/SKILL.md b/plugins/headset-bridge-toolkit/skills/bridge-account-review/SKILL.md new file mode 100644 index 0000000..abb6e57 --- /dev/null +++ b/plugins/headset-bridge-toolkit/skills/bridge-account-review/SKILL.md @@ -0,0 +1,68 @@ +--- +name: bridge-account-review +description: > + This skill should be used when the user asks for an "account review", "store review", + "buyer meeting prep", "one-pager for [store]", "how are we doing at [store]", "prep for my + meeting with [retailer]", or wants a presentable summary of their brands' performance at one + connected retail store using Headset Bridge data. Produces a polished, buyer-ready HTML + one-pager for a specific retailer door. +metadata: + version: "1.0.0" + author: "Headset" +--- + +# Retailer Account Review One-Pager + +Build a polished single-store review a sales rep can walk into a buyer meeting with: how the vendor's brands are performing at that door, what is selling, who is buying, what needs restocking, and what to ask for. + +Before querying, read `${CLAUDE_PLUGIN_ROOT}/references/bridge-data-conventions.md`. Before building the HTML, read `${CLAUDE_PLUGIN_ROOT}/references/headset-report-style.md`. Both are mandatory. + +## Step 1: Resolve the store + +Call `bridge_find_connections` with `query` set to the store name the user gave. If multiple connections match, ask the user which one. If none match, list the active connections and ask. Capture the `retailerStoreId`; use it as `storeIds: [id]` on every subsequent call. + +If the user did not name a store, ask which store the review is for, listing active connections as options. + +## Step 2: Choose the period + +Default to the last 90 complete days, with the prior 90 days as the comparison window. If the user names a period, honor it and compare to the equivalent preceding period. Exclude partial current periods from comparisons. + +Seasonality: the default prior-90-day comparison spans different seasons; state that in the report footnote. When the connection has 15+ months of history, prefer the same window one year prior for the delta instead. + +## Step 3: Pull the data + +Issue these in parallel where possible (all scoped to the store's `storeIds`): + +1. `bridge_sales_totals` for the review period and separately for the comparison period, measures: revenue, units, discounts, `total_gross_sales`, `transaction_count`, `avg_item_price`, `gross_margin_pct`, `pct_revenue_with_cost`. Use these schema names exactly; validation rejects `transactions` and `gross sales`. Compute discounts as a share of gross (`total_discounts` / `total_gross_sales`) for both periods. +2. `bridge_sales_trend`, grain week, spanning both periods, for the revenue trend chart. +3. `bridge_sales_by_dimension`, dimension category, review period, twice: once scoped to this store and once across ALL connections (no store filter) for the rest-of-network mix. Each door's POS taxonomy differs, so map BOTH lists to canonical groups with the ordered substring rules in the conventions BEFORE computing the mix comparison. +4. `bridge_sales_by_dimension`, dimension brand, review period. +5. `bridge_sales_by_dimension`, dimension product, review period, limit 15, for the top-SKU table. Apply the sample exclusion: drop rows where price < $1, OR category contains "employee", "promo", or "sample", OR product_name contains "sample" or "birthday item" (all case-insensitive). +6. `bridge_sales_by_dimension`, dimension customer_age_group, and separately customer_gender, review period. +7. `bridge_sales_by_dimension`, dimension order_source, review period. +8. `bridge_get_inventory` per-product at this store, default sort. The response is large and may arrive as a stored/encoded result; process it with code rather than reading it inline. Collapse rows on (`product_id`, `store_id`) first (the feed can return duplicates), apply the same sample exclusion as the top-SKU pull, then collect active stockouts (zero on hand, `avg_daily_units` >= 0.25) and critical low stock (`weeks_of_supply` <= 2, in stock, velocity >= 0.25). +9. For network context: `bridge_sales_totals` for the review period across ALL connections (no store filter). Compute this store's share of the vendor's connected-network revenue and its rank among doors (via `bridge_sales_by_dimension`, dimension store). +10. For the expansion ask: `bridge_sales_by_dimension`, dimension product, review period, no store filter, limit 25, for the network's top products. Compare against this store's top products at the brand-line and format level, and choose the strongest line that is present at other doors but absent or small here. Keep the comparison anonymized per the guardrail below. + +## Step 4: Build the one-pager + +Single self-contained HTML file following the report style reference. Structure: + +- Masthead: "[Store name] Account Review", vendor's brand names, date range. +- KPI row: revenue (with vs-prior delta), units, transactions, avg item price, share of the vendor's connected-network revenue at this door. Phrase the share stat as share of the vendor's connected-door revenue, never as market share; the report is buyer-facing and the distinction matters. +- Weekly revenue trend chart (highlight the most recent complete week only if it is the story). +- Category mix (normalized groups) with the store's mix side by side with the rest-of-network mix, so over- and under-indexed categories are visible. Spotlight the one biggest gap in orange. +- Top SKUs table: product, category group, units, revenue, avg price. +- Buyer snapshot: age-generation and gender split, order-source split, one line each on what stands out vs intuition. +- Inventory callout card: active stockouts and critical-low SKUs with velocity and estimated two-week revenue at risk. This is the rep's restock ask, so make it concrete. +- Talking points: 3 to 5 auto-drafted bullets a rep can say out loud. Pattern: one win to open with, one data-backed insight about this store's shoppers, one restock ask, one expansion ask (the strongest line from Step 3.10, strong elsewhere in the network but small or absent here), one closing commitment. If `avg_item_price` moved more than 10% versus the comparison window, one talking point MUST name the price/promo tradeoff explicitly, using the discount-share-of-gross figures from Step 3.1, positioned so the rep raises it before the buyer does. Keep each under 25 words, confident, specific, no em dashes. + +## Step 5: Deliver + +Deliver the HTML file using the platform's file output/presentation mechanism (for example, writing to the outputs directory and presenting the file), with a one-line summary. If the user reviews this store regularly and the environment supports persistent artifacts, offer to persist it as an artifact they can refresh. Mention the two or three numbers most worth leading the meeting with in the chat message, nothing more; the report carries the detail. + +## Guardrails + +- If margin data quality is weak (`pct_revenue_with_cost` < 0.9), omit margin from the one-pager rather than caveating it in a buyer-facing document. +- Never include other retailers' store names in a document intended for a buyer; the rest-of-network comparison is always aggregated and anonymous ("your other connected doors"). +- If the store has under 30 days of data, say so and shorten the comparison rather than fabricating a delta. diff --git a/plugins/headset-bridge-toolkit/skills/bridge-restock-risk/SKILL.md b/plugins/headset-bridge-toolkit/skills/bridge-restock-risk/SKILL.md new file mode 100644 index 0000000..de6968e --- /dev/null +++ b/plugins/headset-bridge-toolkit/skills/bridge-restock-risk/SKILL.md @@ -0,0 +1,52 @@ +--- +name: bridge-restock-risk +description: > + This skill should be used when the user asks "what's running low", "what's out of stock", + "restock report", "reorder risk", "stockout risk", "where am I losing sales to stockouts", + or wants to know which of their products need restocking at connected retail stores using + Headset Bridge inventory data. Produces a revenue-at-risk ranked restock report grouped by store. +metadata: + version: "1.0.0" + author: "Headset" +--- + +# Restock Risk Report + +Find the products that are about to cost the vendor money: SKUs that are out of stock or nearly out at connected stores while still selling. Rank everything by estimated revenue at risk so the biggest problems come first. + +Before querying, read `${CLAUDE_PLUGIN_ROOT}/references/bridge-data-conventions.md`. For HTML output, read `${CLAUDE_PLUGIN_ROOT}/references/headset-report-style.md`. + +## Step 1: Scope + +Call `bridge_find_connections`. Default to all active connections; if the user named stores, brands, or categories, resolve them (stores via connections, brands/categories via `bridge_search_dimension_values`) and filter accordingly. + +## Step 2: Pull inventory + +Call `bridge_get_inventory` in per-product mode (no `groupBy`), default sort (weeks_of_supply ascending), limit 500, scoped as above. Retry once on a transient error. + +The limit-500 response is large and may arrive as a stored/encoded result; process it with code rather than reading it inline. If exactly 500 rows return, the result is truncated, but the default weeks_of_supply ascending sort guarantees all at-risk rows are included, so proceed without a second pull. + +## Step 3: Classify + +Never use `stockout_count` for this; it counts dead SKUs. First collapse rows on (`product_id`, `store_id`) before computing anything; the feed can return duplicates that double-count revenue at risk. Then apply the sample-exclusion guardrail below. From what remains, keep only items with `avg_daily_units` >= 0.25 (selling within the trailing 28 days), then bucket: + +- **Out now**: `on_hand_units` = 0. These are actively losing sales. +- **Days away**: in stock, `weeks_of_supply` <= 1. +- **This week's watch list**: in stock, 1 < `weeks_of_supply` <= 2. + +For every item compute estimated 14-day revenue at risk: `avg_daily_units` x `price` x 14. Label it clearly as an estimate based on trailing velocity. + +## Step 4: Report + +Group by store, order stores by their total revenue at risk. For each store show a table: product, brand, category group (mapped with the ordered substring rules in `bridge-data-conventions.md`), on-hand units, daily velocity, weeks of supply, price, and 14-day revenue at risk, with the "Out now" bucket on top. + +Open with a summary: total revenue at risk across all doors, the single worst store, and the single worst SKU, stated plainly ("Riff Tree eighths at Greenlight Park City sell 3 a day and there is 1 left"). The single worst SKU is the highest 14-day revenue at risk WITHIN the "Out now" bucket, not overall; a high-priced in-stock watch item can carry a larger raw number without being the story. + +Format: for a quick check, a concise chat response with tables is fine. If the user wants something to forward to stores or reps, or asks for a report, produce a self-contained HTML file per the style reference, with each store's section designed to stand alone so a rep can screenshot or forward just their door. Deliver the HTML file using the platform's file output/presentation mechanism (for example, writing to the outputs directory and presenting the file). + +## Guardrails + +- Present velocity and supply, not prescriptive reorder quantities. If asked "how much should they order", frame it as arithmetic the customer owns: velocity times their lead time plus safety stock, using the numbers in the report. +- Sample exclusion: exclude any item where price < $1, OR category contains "employee", "promo", or "sample", OR product_name contains "sample" or "birthday item" (all case-insensitive). Rows priced at $0.01 are usually data-entry junk even when they look like real SKUs. +- Note the data reflects the vendor's products at connected doors only. +- If the user runs this regularly, offer to set it up as a recurring scheduled report. diff --git a/plugins/headset-bridge-toolkit/skills/bridge-weekly-pulse/SKILL.md b/plugins/headset-bridge-toolkit/skills/bridge-weekly-pulse/SKILL.md new file mode 100644 index 0000000..c92e04f --- /dev/null +++ b/plugins/headset-bridge-toolkit/skills/bridge-weekly-pulse/SKILL.md @@ -0,0 +1,71 @@ +--- +name: bridge-weekly-pulse +description: > + This skill should be used when the user asks for a "weekly pulse", "sell-through pulse", + "Monday brief", "weekly recap", "how did last week go", "weekly sales summary", or wants a + recurring leadership-level summary of their brands' sell-through across connected retail + stores from Headset Bridge data. Produces a week-over-week HTML brief and offers to schedule + it as a recurring Monday report. +metadata: + version: "1.0.0" + author: "Headset" +--- + +# Weekly Sell-Through Pulse + +A leadership brief covering the last complete week across every connected door: what moved, what stalled, and what needs attention this week. Written for a brand or ops leader scanning it in two minutes. + +Before querying, read `${CLAUDE_PLUGIN_ROOT}/references/bridge-data-conventions.md`. Before building the HTML, read `${CLAUDE_PLUGIN_ROOT}/references/headset-report-style.md`. If either file is missing from the installed plugin, keep going: the run-critical rules (date ranges, category mapping, style essentials) are inlined below. + +## Step 1: Define the weeks + +The report week is the most recent COMPLETE Monday-to-Sunday week; the comparison week is the one before it. Never include the in-progress week in deltas. State both date ranges in the masthead. + +**The date range trap.** `soldDate` expressions of the form "YYYY-MM-DD to YYYY-MM-DD" are END-EXCLUSIVE: the second date is not included. A Monday-to-Sunday week must be written as "monday_date to next_monday_date". Example: the week of Jul 13 to 19, 2026 is "2026-07-13 to 2026-07-20". Writing "2026-07-13 to 2026-07-19" silently drops Sunday and understates the week. + +**Mandatory validation.** After the Step 2 pulls, before any analysis: + +1. Using the week-grain trend from Step 2.5 (it must cover both weeks), confirm each week's `total_revenue` from `bridge_sales_totals` matches its trend bucket exactly. If they do not match, the date ranges are wrong; fix them and re-pull before proceeding. +2. Confirm the sum of the per-store rows from Step 2.2 equals that week's totals row. + +## Step 2: Pull the data + +Call `bridge_find_connections` for scope, then in parallel: + +1. `bridge_sales_totals` for each of the two weeks: revenue, gross sales, units, discounts, transactions, avg_item_price. +2. `bridge_sales_by_dimension`, dimension store, for each week (per-store WoW). +3. `bridge_sales_by_dimension`, dimension brand, for each week (brand WoW). +4. `bridge_sales_by_dimension`, dimension category, for each week. Connected stores use inconsistent POS taxonomies, so map raw categories into canonical groups with these case-insensitive substring rules, applied in this exact order, first match wins: + 1. Excluded: category contains "employee", "promo", or "sample". Drop these rows entirely. + 2. Pre-Rolls: contains "pre-roll" or "preroll". + 3. Vapor: contains "vape", "vapes", "cartridge", or "disposable". + 4. Edibles: contains "edible", "gummies", "gummy", "mints", "chocolate", or "tincture". + 5. Concentrates: contains "concentrate", "extract", "rosin", "resin", "budder", "sugar", or "rso". + 6. Flower: contains "flower", "popcorn", or "shake" (catches Infused Flower, Flower Popcorn, Flower Shake, Shake, Infused Shake). + 7. Other: anything left, including Capsules. + The order is load-bearing: "Infused Pre-Rolls" must land in Pre-Rolls, not Flower, and "Live Resin Cartridge" must land in Vapor, not Concentrates. Compute category deltas on the mapped groups, never on raw categories. +5. `bridge_sales_trend`, grain week, last 8 weeks, for the trend chart and the Step 1 validation. +6. `bridge_sales_by_dimension`, dimension product, report week only. Product rows come back network-aggregated: there is no store column. Pull the top 15 products overall for the report week, drop promo/sample items and any row with price under $2, and use them to name top movers in prose only. +7. `bridge_get_inventory` per-product, `maxWeeksOfSupply` 1, to flag stockout risk carried into this week. The velocity threshold cannot be applied server-side: pull first, then keep only rows with `avg_daily_units` >= 0.25. Apply the same exclusions as movers (drop employee/promo/sample SKUs and SKUs priced under $2). Revenue at risk = `avg_daily_units` x `price` x 7. + +## Step 3: Find the story + +Do not dump every number. Identify, in order of priority: + +- The headline: total revenue WoW, and the one factor that most explains it (a store, a brand, a category). Attribute the swing with numbers. +- Movers: top 3 gainers and top 3 decliners at the store and brand level, with WoW deltas. +- Discount watch: discount rate (`total_discounts` / `total_gross_sales`) WoW overall and any store whose rate jumped more than 5 points. +- Risk carried into this week: the stockout-risk items from Step 2.7. Show the top 8 by revenue at risk in the table; put the total exposure figure across all flagged SKUs in the takeaway line. + +## Step 4: Build the brief + +Single self-contained HTML file per the style reference: masthead with both week ranges, KPI row with WoW deltas, 8-week trend chart, movers and decliners (spotlight the single biggest swing in orange), discount watch, and an orange callout card titled "This week" with 2 to 4 action-oriented bullets. Style essentials: Poppins type, Insights blues #232E83 and #6862C7 as the chart anchors, orange #F15D22 reserved for a single spotlight, peach #FEF3EC callout backgrounds, dark navy #14172E text. Voice: confident, specific, no em dashes anywhere in copy. Deliver the HTML file using the platform's file output/presentation mechanism (for example, writing to the outputs directory and presenting the file). If the environment supports persistent or recurring artifacts, persist it there as well; this is a recurring report by nature. + +## Step 5: Offer scheduling + +After delivering, offer once, in one sentence, to run this automatically every Monday morning. If the user accepts, create a scheduled task with the platform's scheduled-task tools (never local cron tools) firing Monday mornings in the user's timezone, with a standalone prompt such as: "Run the bridge-weekly-pulse skill from the headset-bridge-toolkit plugin: build the weekly sell-through pulse for the most recent complete week across all active Bridge connections and deliver the HTML brief." Confirm the schedule back in plain language. + +## Guardrails + +- A week with a data gap (a store reporting zero mid-week) is more likely a feed issue than a collapse; flag it as "worth checking" rather than reporting a crash. +- Keep the brief scannable: no table longer than 10 rows, every chart gets a one-line takeaway headline.