Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@
{
"name": "product-development",
"source": "./plugins/product-development",
"description": "Product development skills for Glific — tech design docs, implementation specs, and ticket breakdowns"
"description": "Product development skills for Glific - tech design docs, implementation specs, and ticket breakdowns"
},
{
"name": "reports-and-presentations",
"source": "./plugins/reports-and-presentations",
"description": "Report generation skills for Glific - monthly and quarterly review decks"
}
]
}
8 changes: 8 additions & 0 deletions plugins/reports-and-presentations/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "reports-and-presentations",
"version": "0.1.0",
"description": "Report generation skills for Glific — monthly and quarterly review decks",
"author": {
"name": "Radhika Bhagwat"
}
}
12 changes: 12 additions & 0 deletions plugins/reports-and-presentations/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Reports and Presentations

Skills for generating Glific's internal review decks.

| Skill | What it does |
|-------|-------------|
| `glific-monthly-review` | Generates the monthly review PPTX for any given month, pulling live data from Google Sheets and GitHub |
| `glific-quarterly-review` | Generates the quarterly review PPTX for any quarter, aggregating KPIs from the KPI tracker spreadsheet across the quarter's months |

## Setup

These skills require the Google Drive MCP to be connected.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Glific Monthly Review — Data Sources Reference

## Google Sheets

### KPI Master Sheet
- **File ID**: `1K6gkFSU2TGf9bNRE0gFGiDXqKQhiXKHIoJjzABcDeFg` *(replace with actual)*
- **Sheet**: "KPI Dashboard" (or the tab containing all KPI rows)
- MCP tool: `read_file_content` with the file ID

| Section | KPI Labels to Find | Notes |
|---|---|---|
| Overall | Orgs on Glific, WhatsApp messages sent/month, Active orgs (last 30d), Active contacts, CSP MRR, Consulting Revenue YTD, Other Revenue YTD | Row labels in column A, values in the current month column |
| Biz Dev | Leads in pipeline, SQLs, Demos done, Proposals sent, Deals closed, MRR new this month | |
| CS Consulting | Consulting Revenue (month), Consulting Revenue (YTD), Churn Rate, Consulting Org Names | Org names may be a comma-separated list in one cell |
| CS Support | P0 issues (count + avg resolution hrs), P1 issues (count + avg resolution hrs), # of Incidents | # of Incidents cell should have a hyperlink to the Drive incidents folder |
| Platform | Deployments/week, Uptime %, Critical bugs open | |

---

### Biz Dev Tracker Sheet
- **File ID**: `1BizDevTrackerFileIdHere` *(replace with actual)*
- **Sheet tab**: "Monthly Tracker" or similar
- **Column layout**: Column A = category/label; monthly columns across the top (e.g., "Apr 2026", "May 2026")
- **What to extract**: Rows under "This Month Updates" and "Next Month Plan" for the target month column

> ⚠️ **Size warning**: This sheet is large (~230K chars). After reading, save to a temp file and use Python to filter:
> ```bash
> python3 -c "
> import sys, json
> data = open('/tmp/bizdev_raw.txt').read()
> lines = [l for l in data.splitlines() if 'month' in l.lower() or 'update' in l.lower() or 'plan' in l.lower()]
> print('\n'.join(lines[:100]))
> "
> ```

---

### CS / Product Tracker Sheet
- **File ID**: `1CSProductTrackerFileIdHere` *(replace with actual)*
- **Sheet tab**: "CS Updates" or "Monthly" or similar
- **Column layout**: Column A = section (CS Consulting / Platform); subsequent columns = months
- **What to extract**:
- CS Consulting rows → `csConsulting.thisMonth` and `csConsulting.nextMonth`
- Blog/release notes row → `platformOps.monthlyUpdates` (append to GitHub PR list)
- **Previous month column**: For CS Consulting updates, read the **previous month's** column (the month before the target month)

Comment on lines +43 to +46

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clarify CS/Product Tracker month extraction: "previous month" vs. "target month".

Line 45 states: "For CS Consulting updates, read the previous month's column (the month before the target month)."

However, SKILL.md Step 2c (lines 148–155) states: "Find the target month column and extract per-org updates."

This is a direct contradiction. Which month's column should be read? Please align these documents and clarify the intent in both files.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@plugins/reports-and-presentations/skills/glific-monthly-review/references/data-sources.md`
around lines 43 - 46, The data-sources.md file (line 45) and SKILL.md file
(lines 148-155) contain contradictory instructions regarding which month's
column should be read for CS Consulting updates. In data-sources.md it states to
read the "previous month's column (the month before the target month)" while
SKILL.md Step 2c states to read the "target month column". Determine the correct
intended month to read, then update both files to use consistent and clear
terminology. Ensure both files unambiguously specify whether CS Consulting data
should be extracted from the previous month's column or the target month's
column.

---

## GitHub

### Repositories to check

| Repo | Purpose | Endpoint |
|---|---|---|
| `glific/docs` | Documentation PRs → CS Support docUpdates | `https://api.github.com/repos/glific/docs/pulls?state=closed&per_page=20` |
| `glific/glific` | Backend PRs → Platform monthly updates | `https://api.github.com/repos/glific/glific/pulls?state=closed&per_page=30` |
| `glific/glific-frontend` | Frontend PRs → Platform monthly updates | `https://api.github.com/repos/glific/glific-frontend/pulls?state=closed&per_page=30` |

### Filtering PRs by month
After fetching, filter by `merged_at` date falling within the target month:
```python
import json, sys
from datetime import datetime

month_str = "2026-04" # change to target month YYYY-MM
prs = json.loads(open('/tmp/prs_raw.json').read())
filtered = [
pr['title'] for pr in prs
if pr.get('merged_at') and pr['merged_at'].startswith(month_str)
]
print(json.dumps(filtered, indent=2))
```

### Platform GitHub Project Board (Next Month)
- **URL**: `https://github.com/orgs/glific/projects/8/views/16`
- **Note**: Page is client-rendered (JavaScript). `web_fetch` returns an empty shell.
- **Fallback**: Use Claude in Chrome MCP:
```
mcp__Claude_in_Chrome__navigate → URL above
mcp__Claude_in_Chrome__get_page_text → extract "In Progress" / "Next Sprint" items
```

---

## Data Compilation Notes

- **Overall slide updates** (right panel): Synthesise 0–3 bullets from the other 4 slides — pick the most impactful highlights, not raw data.
- **CS Consulting org names**: If the KPI cell is a comma-separated list, split into individual org names for the KPI row value.
- **Platform monthly updates deduplication**: Merge CS/Product sheet blog bullets + glific/glific PRs + glific-frontend PRs, then deduplicate any that mention the same feature.
- **# of Incidents hyperlink**: When building the `csSupport.kpis` array, set `labelHyperlink` to the Google Drive incidents folder URL found in the KPI sheet cell's hyperlink.
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
/**
* Glific Monthly Review — PPTX Generator
* Usage: node create_deck.js <data.json> [output.pptx]
*
* data.json must match the schema documented in SKILL.md Step 3.
*/

const fs = require("fs");
const path = require("path");
const pptxgen = require("pptxgenjs");

// ─── CLI args ────────────────────────────────────────────────────────────────
const dataPath = process.argv[2];
const outName = process.argv[3] || "glific_monthly_review.pptx";

if (!dataPath) {
console.error("Usage: node create_deck.js <data.json> [output.pptx]");
process.exit(1);
}

const D = JSON.parse(fs.readFileSync(dataPath, "utf8"));
const MONTH_YEAR = `${D.month} ${D.year}`;

// ─── COLOR PALETTE ───────────────────────────────────────────────────────────
const C = {
teal: "028090",
darkTeal: "01535D",
deepTeal: "00404A",
white: "FFFFFF",
offWhite: "F4FAFB",
lightTeal:"D0EBEE",
accent: "02C39A",
textDark: "1A2E32",
textMid: "374649",
textLight:"6B8F93",
headerBg: "013C45",
};

// ─── LAYOUT CONSTANTS ────────────────────────────────────────────────────────
const W = 10;
const H = 5.625;
const HEADER_H = 0.65;
const PANEL_Y = HEADER_H;
const PANEL_H = H - HEADER_H;
const LEFT_W = 3.2;
const RIGHT_X = LEFT_W;
const RIGHT_W = W - LEFT_W;
const PAD = 0.22;

// ─── HELPERS ─────────────────────────────────────────────────────────────────

function addHeader(slide, title) {
slide.addShape(pres.shapes.RECTANGLE, {
x: 0, y: 0, w: W, h: HEADER_H,
fill: { color: C.headerBg }, line: { color: C.headerBg }
});
slide.addText(title, {
x: PAD, y: 0, w: W - PAD * 2, h: HEADER_H,
fontSize: 20, bold: true, color: C.white,
fontFace: "Calibri", valign: "middle", align: "left", margin: 0
});
}

function addLeftPanel(slide, label) {
slide.addShape(pres.shapes.RECTANGLE, {
x: 0, y: PANEL_Y, w: LEFT_W, h: PANEL_H,
fill: { color: C.darkTeal }, line: { color: C.darkTeal }
});
slide.addText(label, {
x: PAD, y: PANEL_Y + 0.12, w: LEFT_W - PAD * 2, h: 0.38,
fontSize: 10, bold: true, color: C.accent,
fontFace: "Calibri", valign: "middle", align: "left",
charSpacing: 1.5, margin: 0
});
slide.addShape(pres.shapes.RECTANGLE, {
x: PAD, y: PANEL_Y + 0.52, w: LEFT_W - PAD * 2, h: 0.018,
fill: { color: C.teal }, line: { color: C.teal }
});
}

function addRightPanel(slide, label) {
slide.addShape(pres.shapes.RECTANGLE, {
x: RIGHT_X, y: PANEL_Y, w: RIGHT_W, h: PANEL_H,
fill: { color: C.offWhite }, line: { color: C.offWhite }
});
slide.addShape(pres.shapes.RECTANGLE, {
x: RIGHT_X, y: PANEL_Y, w: 0.04, h: PANEL_H,
fill: { color: C.teal }, line: { color: C.teal }
});
slide.addText(label, {
x: RIGHT_X + 0.12, y: PANEL_Y + 0.12, w: RIGHT_W - 0.2, h: 0.38,
fontSize: 12, bold: true, color: C.teal,
fontFace: "Calibri", valign: "middle", align: "left",
charSpacing: 1.5, margin: 0
});
slide.addShape(pres.shapes.RECTANGLE, {
x: RIGHT_X + 0.12, y: PANEL_Y + 0.52, w: RIGHT_W - 0.24, h: 0.018,
fill: { color: C.lightTeal }, line: { color: C.lightTeal }
});
}

function addKpiRows(slide, kpis, startY) {
const ROW_H = 0.52;
const labelX = PAD;
const labelW = LEFT_W - PAD * 2;
kpis.forEach((kpi, i) => {
const y = startY + i * ROW_H;
if (i % 2 === 0) {
slide.addShape(pres.shapes.RECTANGLE, {
x: 0.08, y: y, w: LEFT_W - 0.1, h: ROW_H - 0.04,
fill: { color: "013843" }, line: { color: "013843" }
});
}
const labelOpts = {
x: labelX, y: y + 0.03, w: labelW, h: 0.22,
fontSize: 8, color: C.lightTeal,
fontFace: "Calibri", valign: "top", align: "left", margin: 0
};
if (kpi.labelHyperlink) labelOpts.hyperlink = { url: kpi.labelHyperlink };
slide.addText(kpi.label, labelOpts);
const val = kpi.value != null ? String(kpi.value) : "—";
const valColor = kpi.highlight ? C.accent : C.white;
slide.addText(val, {
x: labelX, y: y + 0.24, w: labelW, h: 0.24,
fontSize: kpi.highlight ? 13 : 11, bold: !!kpi.highlight,
color: valColor, fontFace: "Calibri",
valign: "top", align: "left", margin: 0
});
});
}

function bulletRuns(items, fontSize) {
return items.map((text, i) => ({
text,
options: {
bullet: true,
breakLine: i < items.length - 1,
fontSize: fontSize || 12,
color: C.textDark,
fontFace: "Calibri",
paraSpaceAfter: 3
}
}));
}

function addSectionLabel(slide, text, y) {
slide.addText(text, {
x: RIGHT_X + 0.15, y, w: RIGHT_W - 0.3, h: 0.27,
fontSize: 12, bold: true, color: C.teal,
fontFace: "Calibri", valign: "top", align: "left", margin: 0
});
}

function addBullets(slide, items, y, h) {
if (!items || items.length === 0) return;
slide.addText(bulletRuns(items), {
x: RIGHT_X + 0.12, y, w: RIGHT_W - 0.25, h,
valign: "top", margin: 0
});
}

// ─── PRESENTATION ────────────────────────────────────────────────────────────
const pres = new pptxgen();
pres.layout = "LAYOUT_16x9";
pres.title = `Glific Monthly Review - ${MONTH_YEAR}`;

// ══════════════════════════════════════════════════════════
// SLIDE 1 — TITLE
// ══════════════════════════════════════════════════════════
const s0 = pres.addSlide();
s0.background = { color: C.deepTeal };

s0.addShape(pres.shapes.RECTANGLE, {
x: 0, y: 3.7, w: W, h: 0.06,
fill: { color: C.accent }, line: { color: C.accent }
});
s0.addShape(pres.shapes.RECTANGLE, {
x: 0, y: 4.9, w: W, h: 0.725,
fill: { color: C.headerBg }, line: { color: C.headerBg }
});
s0.addShape(pres.shapes.RECTANGLE, {
x: 0, y: 0, w: 0.18, h: H,
fill: { color: C.teal }, line: { color: C.teal }
});
s0.addText("Glific Monthly Review", {
x: 0.5, y: 1.1, w: 9, h: 1.2,
fontSize: 44, bold: true, color: C.white,
fontFace: "Calibri", align: "center", valign: "middle", margin: 0
});
s0.addText(MONTH_YEAR, {
x: 0.5, y: 2.5, w: 9, h: 0.7,
fontSize: 28, bold: false, color: C.accent,
fontFace: "Calibri", align: "center", valign: "middle", margin: 0
});
s0.addText("Project Tech4Dev · Glific Platform", {
x: 0.5, y: 4.95, w: 9, h: 0.38,
fontSize: 11, color: C.lightTeal,
fontFace: "Calibri", align: "center", valign: "middle", margin: 0
});

// ══════════════════════════════════════════════════════════
// SLIDE 2 — OVERALL UPDATE
// ══════════════════════════════════════════════════════════
const s1 = pres.addSlide();
addHeader(s1, `Glific Overall Update — ${MONTH_YEAR}`);
addLeftPanel(s1, "GOAL KPIs");
addRightPanel(s1, "MONTHLY UPDATES");
addKpiRows(s1, D.overall.kpis, PANEL_Y + 0.6);
addBullets(s1, D.overall.updates, PANEL_Y + 0.62,
PANEL_H - 0.62 - 0.1);

// ══════════════════════════════════════════════════════════
// SLIDE 3 — BIZ DEV & MARKETING
// ══════════════════════════════════════════════════════════
const s2 = pres.addSlide();
addHeader(s2, `Biz Dev & Marketing — ${MONTH_YEAR}`);
addLeftPanel(s2, "BIZ DEV KPIs");
addRightPanel(s2, "MONTHLY UPDATES");
addKpiRows(s2, D.bizdev.kpis, PANEL_Y + 0.6);

addSectionLabel(s2, "This Month", PANEL_Y + 0.62);
addBullets(s2, D.bizdev.thisMonth, PANEL_Y + 0.89, 1.7);
addSectionLabel(s2, "Next Month", PANEL_Y + 2.7);
addBullets(s2, D.bizdev.nextMonth, PANEL_Y + 2.97, 1.7);

// ══════════════════════════════════════════════════════════
// SLIDE 4 — CUSTOMER SUCCESS - CONSULTING
// ══════════════════════════════════════════════════════════
const s3 = pres.addSlide();
addHeader(s3, `Customer Success - Consulting — ${MONTH_YEAR}`);
addLeftPanel(s3, "CS CONSULTING KPIs");
addRightPanel(s3, "MONTHLY UPDATES");
addKpiRows(s3, D.csConsulting.kpis, PANEL_Y + 0.6);

addSectionLabel(s3, "This Month", PANEL_Y + 0.62);
addBullets(s3, D.csConsulting.thisMonth, PANEL_Y + 0.89, 1.7);
addSectionLabel(s3, "Next Month", PANEL_Y + 2.7);
addBullets(s3, D.csConsulting.nextMonth, PANEL_Y + 2.97, 1.7);
Comment on lines +229 to +238

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

CRITICAL: Slide 3 (CS Consulting) should NOT render "Next Month" section.

Lines 237–238 render a "Next Month" section on Slide 3:

addSectionLabel(s3, "Next Month", PANEL_Y + 2.7);
addBullets(s3, D.csConsulting.nextMonth, PANEL_Y + 2.97, 1.7);

However, SKILL.md explicitly forbids this:

  • Line 26: "Customer Success - Consulting | ... | Monthly Updates only (no Next Month)"
  • Line 349: "Slide 3 has NO 'Next Month' section - do not generate csConsulting.nextMonth"
  • Line 154: "Slide 3 has no 'Next Month' section - do not extract or generate next month content"

Additionally, the deck_data.json schema (SKILL.md lines 272–282) does not include a csConsulting.nextMonth field, so this code will crash with "Cannot read property 'nextMonth' of undefined" at runtime.

Remove lines 237–238 entirely.

🔧 Proposed fix
 const s3 = pres.addSlide();
 addHeader(s3, `Customer Success - Consulting — ${MONTH_YEAR}`);
 addLeftPanel(s3, "CS CONSULTING KPIs");
 addRightPanel(s3, "MONTHLY UPDATES");
 addKpiRows(s3, D.csConsulting.kpis, PANEL_Y + 0.6);

 addSectionLabel(s3, "This Month", PANEL_Y + 0.62);
 addBullets(s3, D.csConsulting.thisMonth, PANEL_Y + 0.89, 1.7);
-addSectionLabel(s3, "Next Month", PANEL_Y + 2.7);
-addBullets(s3, D.csConsulting.nextMonth, PANEL_Y + 2.97, 1.7);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@plugins/reports-and-presentations/skills/glific-monthly-review/scripts/create_deck.js`
around lines 229 - 238, The Slide 3 (CS Consulting) code is rendering a "Next
Month" section using addSectionLabel and addBullets with
D.csConsulting.nextMonth, but per SKILL.md requirements, Slide 3 should only
display Monthly Updates without a Next Month section, and the deck_data.json
schema does not include a csConsulting.nextMonth field which will cause a
runtime crash. Remove the two lines that add the Next Month section: the call to
addSectionLabel(s3, "Next Month", PANEL_Y + 2.7) and the corresponding
addBullets(s3, D.csConsulting.nextMonth, PANEL_Y + 2.97, 1.7) call.


// ══════════════════════════════════════════════════════════
// SLIDE 5 — CUSTOMER SUCCESS - SUPPORT
// ══════════════════════════════════════════════════════════
const s4 = pres.addSlide();
addHeader(s4, `Customer Success - Support — ${MONTH_YEAR}`);
addLeftPanel(s4, "CS SUPPORT KPIs");
addRightPanel(s4, "DOCUMENTATION UPDATES");
addKpiRows(s4, D.csSupport.kpis, PANEL_Y + 0.6);

addBullets(s4, D.csSupport.docUpdates, PANEL_Y + 0.62, PANEL_H - 0.72);

// ══════════════════════════════════════════════════════════
// SLIDE 6 — PLATFORM
// ══════════════════════════════════════════════════════════
const s5 = pres.addSlide();
addHeader(s5, `Platform — ${MONTH_YEAR}`);
addLeftPanel(s5, "PLATFORM KPIs");
addRightPanel(s5, "MONTHLY UPDATES");
addKpiRows(s5, D.platformOps.kpis, PANEL_Y + 0.6);

addSectionLabel(s5, "Monthly Updates", PANEL_Y + 0.62);
addBullets(s5, D.platformOps.monthlyUpdates, PANEL_Y + 0.89, 1.9);
addSectionLabel(s5, "Next Month", PANEL_Y + 2.9);
addBullets(s5, D.platformOps.nextMonth, PANEL_Y + 3.17, 1.7);

// ─── WRITE FILE ──────────────────────────────────────────────────────────────
pres.writeFile({ fileName: outName })
.then(() => console.log(`✅ Created: ${outName}`))
.catch(err => { console.error("❌ Error:", err); process.exit(1); });
Loading