| name | reaction-analytics |
| description | Analytics on #feed-mentions reactions and responder activity. Shows reaction distribution (✅ replied, ❌ skip, ❤️ like, ⏭️ agent-skipped), who responds most, daily breakdowns, and trends over time. Generates charts and sends a DM summary. Use when the user asks for "reaction analytics", "who's responding to mentions", "mention reaction stats", "responder breakdown", or wants to understand team engagement patterns in #feed-mentions. Also runs as a scheduled daily report.
|
Reaction Analytics
Analyze team reactions and responder patterns in #feed-mentions. Produces a text summary + charts, delivered as a Slack DM.
Prerequisites
Chart generation uses matplotlib and numpy. Install them into a virtualenv
once from the repo root (see requirements.txt):
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
The commands below use .venv/bin/python for this reason. Data collection
(collect_data.py) is standard-library only; only chart rendering needs these
packages.
Parameters
- Days — number of days to analyze. Default: 14. Override via user request (e.g. "last 7 days", "last 30 days").
- DM recipient — Slack user ID to DM, from
$DM_USER_ID.
- Feed mentions channel — channel ID from
$FEED_MENTIONS_CHANNEL_ID.
Workflow
Step 1: Collect data
Run the bundled script from the repo root:
.venv/bin/python .agents/skills/reaction-analytics/scripts/collect_data.py <DAYS> > /tmp/reaction_data.json
Outputs JSON to stdout with: overview, daily, responders (top 20 with per-day breakdown), emoji_distribution, trends. Progress logs go to stderr.
Step 2: Generate charts
.venv/bin/python .agents/skills/reaction-analytics/scripts/generate_charts.py /tmp/reaction_data.json /tmp/reaction_report.png
Produces a single image with four panels:
- Daily Reaction % — line chart of replied/skip/like/agent-skipped/no-reaction rates over time (full width)
- Top Responders — stacked horizontal bar chart by reaction type (left, spanning bottom two rows)
- Responder Activity by Day — heatmap showing who's active which days (right-top)
- Responder Activity by Day (line) — line chart of daily reaction counts per responder with >1% share (right-bottom)
Step 3: Format summary
Read /tmp/reaction_data.json and produce Slack mrkdwn:
📊 *Reaction Analytics — <start> → <end> (<N>d)*
*Overview*
<total> mentions | ✅ <replied> (<pct>%) · ❌ <not_replying> (<pct>%) · ❤️ <liked> (<pct>%) · ⏭️ <agent_skipped> (<pct>%) · ⚪ <no_reaction> (<pct>%)
*Trends*
• Reply rate: <first_half>% → <second_half>% (↑/↓)
• Skip rate: <first_half>% → <second_half>% (↑/↓)
• Avg <avg>/day, peak <peak_count> on <peak_day>
*Top Responders*
1. <name> — <total> reactions (✅×<n>, ❌×<n>, ❤️×<n>) — <pct>%
2. ...
3. ...
4. ...
5. ...
*Responder Split*
<name>: <pct>% · <name>: <pct>% · <name>: <pct>% · ...
Show top 5 in the numbered list. The split line lists everyone with ≥5% share.
Step 4: Send DM with chart
Only send if the user explicitly asks ("send it", "DM me") or when running in scheduled mode. Otherwise just display the summary and chart path.
Use Python to open a DM and upload the chart:
import json, os, urllib.request, urllib.parse
TOKEN = os.environ["BUZZ_SLACK_TOKEN"]
DM_USER = os.environ["DM_USER_ID"]
dm_resp = json.loads(urllib.request.urlopen(urllib.request.Request(
"https://slack.com/api/conversations.open",
data=json.dumps({"users": DM_USER}).encode(),
headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
)).read())
assert dm_resp["ok"], f"conversations.open failed: {dm_resp}"
channel = dm_resp["channel"]["id"]
summary = "<formatted mrkdwn from Step 3>"
chart_path = "/tmp/reaction_report.png"
with open(chart_path, "rb") as f:
file_data = f.read()
url_resp = json.loads(urllib.request.urlopen(urllib.request.Request(
"https://slack.com/api/files.getUploadURLExternal",
data=urllib.parse.urlencode({"filename": "reaction_report.png", "length": len(file_data)}).encode(),
headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/x-www-form-urlencoded"}
)).read())
assert url_resp["ok"], f"getUploadURL failed: {url_resp}"
urllib.request.urlopen(urllib.request.Request(
url_resp["upload_url"], data=file_data, method="POST",
headers={"Content-Type": "application/octet-stream"}
))
complete_resp = json.loads(urllib.request.urlopen(urllib.request.Request(
"https://slack.com/api/files.completeUploadExternal",
data=json.dumps({
"files": [{"id": url_resp["file_id"], "title": "Reaction Analytics"}],
"channel_id": channel,
"initial_comment": summary,
}).encode(),
headers={"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}
)).read())
assert complete_resp["ok"], f"completeUpload failed: {complete_resp}"
Scheduled / Daily Mode
When running on a schedule (e.g. daily via <CLOUD_PRODUCT>):
- Default to 14 days (rolling window)
- Always send the DM — no confirmation needed
BUZZ_SLACK_TOKEN, FEED_MENTIONS_CHANNEL_ID, and DM_USER_ID must be set as environment variables
Scheduled prompt: "Run reaction analytics for the last 14 days and DM the results."
Constraints
- Do NOT hardcode or log the Slack token — use
$BUZZ_SLACK_TOKEN via os.environ
- Do NOT fabricate data — only report what's actually in Slack
- Chart style: dark navy background (
#0f172a), matching existing Buzz charts
- Keep the DM summary under 500 words
- If zero messages found, report that — don't generate empty charts