diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 24362c7..e75352b 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -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" } ] } diff --git a/plugins/reports-and-presentations/.claude-plugin/plugin.json b/plugins/reports-and-presentations/.claude-plugin/plugin.json new file mode 100644 index 0000000..8e470cc --- /dev/null +++ b/plugins/reports-and-presentations/.claude-plugin/plugin.json @@ -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" + } +} diff --git a/plugins/reports-and-presentations/README.md b/plugins/reports-and-presentations/README.md new file mode 100644 index 0000000..d43f438 --- /dev/null +++ b/plugins/reports-and-presentations/README.md @@ -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. diff --git a/plugins/reports-and-presentations/skills/glific-monthly-review/SKILL.md b/plugins/reports-and-presentations/skills/glific-monthly-review/SKILL.md new file mode 100644 index 0000000..93105c5 --- /dev/null +++ b/plugins/reports-and-presentations/skills/glific-monthly-review/SKILL.md @@ -0,0 +1,381 @@ +--- +name: glific-monthly-review +description: > + Creates the Glific Monthly Review slide deck (.pptx) for a given month. + Use this skill whenever the user asks to create, generate, build, or prepare + a Glific monthly review, monthly deck, monthly slides, or monthly report - + even if they just say "make the deck for March" or "generate last month's review". + The skill pulls live data from Google Sheets (KPIs, CS consulting, Biz Dev) + and GitHub, then generates a fully formatted 6-slide PPTX (title + 5 content slides). + Trigger whenever the user mentions: Glific deck, monthly review, monthly slides, + Glific report, or any combination of Glific + a month name. +--- + +# Glific Monthly Review Skill + +Generates the Glific Monthly Review deck (title slide + 5 content slides) for any given +month, pulling live data from Google Sheets, the Glific blog, and GitHub. + +## Slide Structure + +| # | Title | Left Panel | Right Panel | +|---|-------|------------|-------------| +| T | Title slide | - | "Glific Monthly Review / [Month Year]" | +| 1 | Glific Overall Update | Goal KPIs | Monthly Updates (0-3 bullets synthesised from other slides) | +| 2 | Biz Dev & Marketing | Biz Dev KPIs (no churn) | Monthly Updates + Next Month | +| 3 | Customer Success - Consulting | CS KPIs incl. Churn | Monthly Updates only (no Next Month) | +| 4 | Customer Success - Support | CS Support KPIs | Documentation Updates | +| 5 | Platform | Platform KPIs | Monthly Updates + Next Month (Epic-level, not tickets) | + +## Data Sources Quick Reference + +| Slide | Source | File ID / URL | +|-------|--------|---------------| +| All KPIs (left panels) | KPI Tracker sheet | `1qMr_lDsn00CYp9-iHsT5-7Jr-ZRek2p4RBSJdlJUveQ` gid=1733984597 | +| Slide 2 KPIs - new bots, onboardings | Biz Dev / Sales Tracker | `1vRH8T_1FPWec9nyhzILYBgypjEoATaugsIcnKlN18jU` | +| Slide 2 right - this month bullets | Glific Sales 2026-27 (current quarter tab) | `1JwTCsh5r_DhQlTj4EAxxkSNN2V6SqfVNoizqRc20B0E` | +| Slide 2 right - next month bullets | Glific Sales 2026-27 (next quarter tab) | same file | +| Slide 3 right | CS/Product Tracker sheet | `1hNk_495eGBlBrlLSm4mGDpYRqsIj1aKAM7OuYFULMQk` gid=625816466 | +| Slide 4 KPI - Incidents count + dates | Google Drive folder | `https://drive.google.com/drive/folders/1OKOHucGLae9FMFbifHLZveCHlKer_trv` | +| Slide 4 right | GitHub Docs PRs | `https://github.com/glific/docs/pulls` | +| Slide 5 right (updates) - PRIMARY | Glific blog post | `https://glific.org/{month-slug}-{year}-bug-fixes-enhancements-documentation-updates/` | +| Slide 5 right (updates) - gap-fill | GitHub backend + frontend PRs | `github.com/glific/glific/pulls` + `github.com/glific/glific-frontend/pulls` | +| Slide 5 right (next month) | GitHub Project Board - Roadmap view | `https://github.com/orgs/glific/projects/8/views/16` | + +Read `references/data-sources.md` for exact column names and extraction details. + +--- + +## Step-by-Step Workflow + +### Step 0 - Parse the month + +Extract **month name** (e.g. "May") and **year** (e.g. "2026") from the user's request. +If the year is omitted, infer from today's date. The target month is usually the *previous* +calendar month - confirm if ambiguous. + +Determine: +- **target_month**: the month being reported on (e.g. "May") +- **next_month**: the following calendar month (e.g. "June") +- **current_quarter_tab**: the sheet tab name covering that month + - Apr-Jun 2026 -> "Quarter 1 (April to June 2026)" + - Jul-Sep 2026 -> "Quarter 2 (July to Sept 2026)" + - Oct-Dec 2026 -> "Quarter 3 ..." (and so on) +- **next_quarter_tab**: the tab one quarter ahead + +### Step 1 - Set up working directory + +```bash +mkdir -p /tmp/glific-deck-work +cd /tmp/glific-deck-work +[ -d node_modules/pptxgenjs ] || (npm init -y && npm install pptxgenjs) +``` + +### Step 2 - Gather all data + +Read `references/data-sources.md` before starting for exact column names and row labels. + +#### 2a. KPI Tracker - all left-panel KPIs (Slides 1-5) +- Tool: `mcp__4f39b92b-af20-4540-a74e-39576272b2ba__read_file_content` +- fileId: `1qMr_lDsn00CYp9-iHsT5-7Jr-ZRek2p4RBSJdlJUveQ` +- Find the column whose header matches the target month name (case-insensitive) +- Extract values for every KPI listed in data-sources.md sections 1-5 +- Churn rate actual goes to `csConsulting.kpis[Churn (Month)]` (not bizdev) + +#### 2b. Biz Dev / Sales Tracker - new bots, pipeline (Slide 2 KPIs) + +fileId: `1vRH8T_1FPWec9nyhzILYBgypjEoATaugsIcnKlN18jU` + +This file is large (230K+ chars) and will auto-save to a temp file on read. Use bash to parse: + +```bash +python3 -c " +import json +path = '/path/from/error/message.txt' +with open(path) as f: data = json.loads(f.read()) +content = data['fileContent'] +lines = [l for l in content.split('\n') if any(k in l.lower() for k in + ['onboard','trial','mql','sql','mdql','new bot','converted','handed over', + 'active','suspended','may','june','launchpad','regular saas','quarterly','annual','monthly'])] +print('\n'.join(lines[:300])) +" +``` + +**What to extract:** + +From the **"Annual Onboardings (April 26 to March 27)"** tab: +- Find rows where the Month column = target month +- Extract: org names, subscription type (M/Q/A), onboarding route (Launchpad / Regular SaaS), status +- Count = `bizdev.kpis[New Bots (Month)]`; running YTD total = `bizdev.kpis[New Bots YTD]` +- Do NOT extract churn here - churn belongs on Slide 3 (CS Consulting) + +From the **"Trial Accounts Leads"** tab: +- Find rows whose Date column falls in the target month (e.g. "1st May", "9th May", etc.) +- Count by Lead Status: MQL, SQL, MDQL +- Extract org names for SQLs (most progressed leads) +- `bizdev.kpis[MQLs]`, `bizdev.kpis[SQLs]` + +#### 2b2. Glific Sales 2026-27 - Biz Dev narrative (Slide 2 right panel) + +fileId: `1JwTCsh5r_DhQlTj4EAxxkSNN2V6SqfVNoizqRc20B0E` +Tool: `mcp__4f39b92b-af20-4540-a74e-39576272b2ba__read_file_content` + +This file is small (~10KB) and should return in full. + +Read the **current quarter tab** (e.g. "Quarter 1 (April to June 2026)" for Apr-Jun months). +Each row has: Area, Initiative, Description, Owner, Priority, Deadline, Key monthly milestone, +KPI/Success Metric, Status. + +**For "This Month" bullets** (`bizdev.thisMonth`): +- Filter rows where Status = "Closed" or "In-progress" +- For Closed: note what completed this month (use Key monthly milestone + description) +- For In-progress: note what is actively happening +- Organise into 2-4 bullets covering Acquisition, Marketing, and Process initiatives +- Also include concrete onboarding facts from step 2b (e.g. "3 new bots onboarded", + "Launchpad cohort ran", "win-back campaign launched") + +**For "Next Month" bullets** (`bizdev.nextMonth`): +- From the current quarter tab: filter rows where Status = "To Do" - these are coming up +- From the **next quarter tab**: look at P0/P1 initiatives marked for the next month +- Write 2-3 bullets: what campaigns, webinars, or acquisition initiatives are planned + +**Tab name logic:** +- Month in Apr/May/Jun -> current tab = "Quarter 1 (April to June 2026)" + next tab = "Quarter 2 (July to Sept 2026)" +- Month in Jul/Aug/Sep -> current tab = "Quarter 2 (July to Sept 2026)" + next tab = next quarter tab if it exists + +#### 2c. CS/Product Tracker - Slide 3 right panel (Monthly Updates ONLY) + +- Tool: `mcp__4f39b92b-af20-4540-a74e-39576272b2ba__read_file_content` +- fileId: `1hNk_495eGBlBrlLSm4mGDpYRqsIj1aKAM7OuYFULMQk` +- Find the target month column and extract per-org updates (org name and key work) +- Format as: **"OrgName: brief description of work"** - do NOT include hours +- Slide 3 has no "Next Month" section - do not extract or generate next month content +- Save as `csConsulting.thisMonth` only + +#### 2d. Incidents - Slide 4 left panel (count + named list) + +- Use `mcp__4f39b92b-af20-4540-a74e-39576272b2ba__search_files` with query: + `parentId = '1OKOHucGLae9FMFbifHLZveCHlKer_trv'` +- For each document, extract the **date from the file title** (not createdTime). + File titles follow the pattern `YYYYMMDD-Description`, e.g. `20260511-TTS Failures`. + Parse the leading 8 digits: YYYY=year, MM=month, DD=day. +- Exclude: the blank template doc ("Template" in title) and any .xlsx error-log files +- Count documents whose **title date** (YYYYMMDD prefix) falls in the target month (YYYY-MM) + -> `csSupport.kpis[# of Incidents]` +- List each incident as: `"Description — [Month Day]"` using the date from the title + e.g. `20260511-TTS Failures` -> `"TTS Failures — May 11"` + -> `csSupport.incidents` (used for the bullet list in the left panel) + +Note: A document may be *created* in the target month but have an earlier title date +(e.g. an April incident reported late). Use the title date for both the count and the display. + +#### 2e. GitHub Docs PRs - Slide 4 right panel +- Use Claude in Chrome (web_fetch returns empty for GitHub) +- Navigate to: + `https://github.com/glific/docs/pulls?q=is%3Apr+is%3Amerged+merged%3AYYYY-MM-01..YYYY-MM-31` + (substitute actual year/month, e.g. 2026-05-01..2026-05-31) +- Extract PR titles; write 2-4 bullets clubbing related changes -> `csSupport.docUpdates` + +#### 2f. Glific Blog Post - Slide 5 Monthly Updates (PRIMARY source) + +The Glific team publishes a monthly release blog that groups all platform changes into +plain, user-facing themes. Always start here for Slide 5. + +Construct the URL: + `https://glific.org/{month-lower}-{year}-bug-fixes-enhancements-documentation-updates/` + e.g. May 2026 -> `https://glific.org/may-2026-bug-fixes-enhancements-documentation-updates/` + +- Try `mcp__workspace__web_fetch` first; if it returns a JS-only shell (no article body), + fall back to Claude in Chrome (navigate + get_page_text) +- Extract "Bugs Fixed", "Enhancements Done", and "Documentation Update" sections +- Condense into 3-5 theme-level bullets (not one bullet per sub-point) +- Save as `platformOps.monthlyUpdates` + +If the blog returns 404: post not yet published. Fall back to step 2g for all content. + +#### 2g. GitHub Backend + Frontend PRs - Slide 5 Monthly Updates (GAP-FILL only) + +Only fetch GitHub PRs if the blog returned 404 or is missing a significant feature. + +Use Claude in Chrome: +- `https://github.com/glific/glific/pulls?q=is%3Apr+is%3Amerged+merged%3AYYYY-MM-01..YYYY-MM-31` +- `https://github.com/glific/glific-frontend/pulls?q=is%3Apr+is%3Amerged+merged%3AYYYY-MM-01..YYYY-MM-31` + +Write bullets only for themes NOT already covered by the blog. Keep total to 3-5 bullets. + +#### 2h. GitHub Project Board - Slide 5 Next Month (ROADMAP EPICS, not individual tickets) + +Slide 5 next month content must be presented at the **Epic level** (roadmap view), not as +individual tickets. Individual ticket details are used only to add specifics to each Epic bullet. + +**Step 1 - Get active Epics from the Roadmap view:** +- Use Claude in Chrome; navigate to `https://github.com/orgs/glific/projects/8/views/16` + (Roadmap view - shows Epics on a timeline) +- Identify Epics whose timeline bar extends into or starts in next_month +- Note each Epic's title and issue number + +**Step 2 - Get sprint details from the Weekly plan:** +- Navigate to the "Weekly plan" tab (views/8) +- The current sprint shows individual tickets grouped by Epic +- For each active Epic, note the P0/P1 tickets currently In Progress or To Do +- Use these ticket titles to understand *what specifically* is happening within the Epic + +**Step 3 - Write Epic-level bullets for `platformOps.nextMonth`:** +- One bullet per relevant Epic (3-5 bullets total) +- Format: `"[Epic Name]: — <1-2 specific things from the sprint tickets>"` +- Keep it outcome-focused, not ticket-list style +- Example: "[Multi-phone WA Groups]: Building full multi-phone architecture — non-destructive + contact sync, inbound phone lookup, and Maytapi sender with failover (Phases 2-4 in sprint)" + +### Step 3 - Compile deck_data.json + +Create `/tmp/glific-deck-work/deck_data.json`. Use `null` for missing values (renders as "-"). +Set `"highlight": true` on the most important KPI per section. + +```json +{ + "month": "May", + "year": "2026", + "overall": { + "kpis": [ + { "label": "Sustainability YTD", "value": null }, + { "label": "Total Revenue YTD", "value": null }, + { "label": "Total Expense YTD", "value": null }, + { "label": "Total Active Bots YTD", "value": null }, + { "label": "Total Active Contacts YTD", "value": null, "highlight": true }, + { "label": "Total Messages Exchanged YTD", "value": null }, + { "label": "Grants Applied to YTD", "value": null } + ], + "updates": [ + "0-3 bullets: most impactful highlights synthesised from slides 2-5" + ] + }, + "bizdev": { + "kpis": [ + { "label": "SaaS Revenue (Month)", "value": null }, + { "label": "New Bots (Month)", "value": null, "highlight": true }, + { "label": "New Bots YTD", "value": null }, + { "label": "MQLs", "value": null }, + { "label": "SQLs", "value": null } + ], + "thisMonth": [ + "Acquisition/onboarding bullet from step 2b + 2b2 Closed/In-progress initiatives", + "Marketing activity bullet (webinar, campaign) from step 2b2", + "Pipeline/sales activity bullet from trial leads data" + ], + "nextMonth": [ + "Upcoming initiative from Sales 2026-27 To Do items or next quarter tab" + ] + }, + "csConsulting": { + "kpis": [ + { "label": "Consulting Revenue (Month)", "value": null, "highlight": true }, + { "label": "Consulting Revenue YTD", "value": null }, + { "label": "Churn (Month)", "value": null }, + { "label": "Consulting Org Names", "value": null } + ], + "thisMonth": [ + "OrgA: brief description of work (no hours)", + "OrgB: brief description of work (no hours)" + ] + }, + "csSupport": { + "kpis": [ + { "label": "# of P0 Issues", "value": "0" }, + { "label": "P0 Resolution Time", "value": null }, + { "label": "# of P1 Issues", "value": null }, + { "label": "P1 Resolution Time", "value": null }, + { "label": "# of Incidents", "value": null, + "labelHyperlink": "https://drive.google.com/drive/folders/1OKOHucGLae9FMFbifHLZveCHlKer_trv" } + ], + "incidents": [ + "Description - Month Day (from file title date)" + ], + "docUpdates": [ + "2-4 bullets summarising merged GitHub docs PRs, clubbing related changes" + ] + }, + "platformOps": { + "kpis": [ + { "label": "Deployments / Week", "value": null }, + { "label": "Uptime", "value": null }, + { "label": "Critical Bugs", "value": null } + ], + "monthlyUpdates": [ + "from Glific blog (primary): theme-grouped bullet", + "from GitHub PRs only if blog is missing something significant" + ], + "nextMonth": [ + "[Epic Name]: what the epic delivers - specific sprint details from tickets" + ] + } +} +``` + +### Step 4 - Generate the PPTX + +```bash +SKILL_DIR="" +cp "$SKILL_DIR/scripts/create_deck.js" /tmp/glific-deck-work/ +cd /tmp/glific-deck-work +node create_deck.js deck_data.json "glific_monthly_review_${MONTH}_${YEAR}.pptx" +``` + +### Step 5 - QA + +```bash +python -m markitdown /tmp/glific-deck-work/glific_monthly_review_*.pptx +``` + +Verify: 6 slides present, correct month/year on every slide, no "TODO" text, +values on correct slides, no hours in Slide 3 org updates, no Next Month on Slide 3. + +### Step 6 - Deliver + +Copy to the user's outputs folder and share via `mcp__cowork__present_files`. +Remind them to drag the `.pptx` into Google Drive and open with Google Slides to edit. + +--- + +## Known Issues & Gotchas + +**Churn KPI placement**: Churn belongs on Slide 3 (CS Consulting), NOT Slide 2 (Biz Dev). +Extract churn from the KPI tracker and place it in `csConsulting.kpis`, not `bizdev.kpis`. + +**Slide 3 format rules**: +- Org update bullets must NOT include hours (no "(N hrs)" in the text) +- Slide 3 has NO "Next Month" section - do not generate `csConsulting.nextMonth` + +**Incident dates from file titles**: The incidents Drive folder uses filenames with a YYYYMMDD +prefix (e.g. `20260511-TTS Failures`). Always parse the date from the title, not from +`createdTime`. A document created in May may have an April title date if the incident +happened in April but was documented late - use the title date for both counting and display. + +**Slide 5 next month - Epics not tickets**: Write one bullet per Epic, not per ticket. +Use the Roadmap view (views/16) to identify which Epics are targeting next month, then +use the Weekly plan (views/8) sprint tickets to add specifics within each Epic bullet. + +**Biz Dev sheet size**: File `1vRH8T...` exceeds token limit and auto-saves to a temp file. +The error message contains the path. Use the bash/Python snippet in step 2b to read it. + +**Glific Sales 2026-27 sheet**: Small file (~10KB), reads in full without issues. +Tab names follow the pattern "Quarter N (Month to Month Year)" - match against target month. + +**Blog URL pattern**: `{month-lower}-{year}-bug-fixes-enhancements-documentation-updates` +Full lowercase month name: `may-2026`, `april-2026`, etc. +If 404, post not yet published - fall back to GitHub PRs (step 2g) for all Slide 5 content. + +**GitHub pages are client-rendered**: `web_fetch` returns empty for all GitHub URLs. +Always use Claude in Chrome for GitHub data. + +**GitHub project board**: "Current Iteration" view no longer exists. Use "Weekly plan" +tab (views/8) for sprint details and Roadmap view (views/16) for Epic-level next month. + +**hex colors**: Never use `#` prefix in pptxgenjs hex strings (`"028090"` not `"#028090"`). + +**Slide 1 updates**: Synthesise 0-3 bullets from the other slides - most impactful outcomes. +Do not fetch a separate data source for this. + +**Onboarding tab name**: For 2026-27, the tab is "Annual Onboardings (April 26 to March 27)". diff --git a/plugins/reports-and-presentations/skills/glific-monthly-review/references/data-sources.md b/plugins/reports-and-presentations/skills/glific-monthly-review/references/data-sources.md new file mode 100644 index 0000000..ffe4186 --- /dev/null +++ b/plugins/reports-and-presentations/skills/glific-monthly-review/references/data-sources.md @@ -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) + +--- + +## 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. diff --git a/plugins/reports-and-presentations/skills/glific-monthly-review/scripts/create_deck.js b/plugins/reports-and-presentations/skills/glific-monthly-review/scripts/create_deck.js new file mode 100644 index 0000000..165d441 --- /dev/null +++ b/plugins/reports-and-presentations/skills/glific-monthly-review/scripts/create_deck.js @@ -0,0 +1,268 @@ +/** + * Glific Monthly Review — PPTX Generator + * Usage: node create_deck.js [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 [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); + +// ══════════════════════════════════════════════════════════ +// 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); }); diff --git a/plugins/reports-and-presentations/skills/glific-quarterly-review/SKILL.md b/plugins/reports-and-presentations/skills/glific-quarterly-review/SKILL.md new file mode 100644 index 0000000..673fa69 --- /dev/null +++ b/plugins/reports-and-presentations/skills/glific-quarterly-review/SKILL.md @@ -0,0 +1,393 @@ +--- +name: glific-quarterly-review +description: > + Creates the Glific Quarterly Review slide deck (.pptx) for a given quarter. + Use this skill whenever the user asks to create, generate, build, or prepare + a Glific quarterly review, quarterly deck, quarterly slides, or quarterly report — + even if they just say "make the Q1 deck" or "generate the quarterly review for Q2". + The skill maps the quarter to its constituent months (e.g. Q1 2026-27 = April, May, June), + confirms this with the user, pulls KPI data directly from the KPI tracker spreadsheet + for each month and aggregates them as YTD, and generates a fully formatted 6-slide PPTX + using Glific brand colors (green/yellow palette, Heebo font). + Trigger whenever the user mentions: quarterly review, Glific quarterly, Q1/Q2/Q3/Q4 deck, + or any combination of Glific + a quarter label or fiscal year. +--- + +# Glific Quarterly Review Skill + +Generates the Glific Quarterly Review deck (title slide + 5 content slides) for any given +quarter of the Glific fiscal year (April–March). KPIs are pulled **directly from the KPI +tracker spreadsheet** for each available month in the quarter and aggregated as YTD. +Narrative bullet content is pulled from the monthly PPTX review decks in Google Drive. + +## Fiscal Year & Quarter Mapping + +Glific's fiscal year runs April → March: + +| Quarter | Months | FY Example | +|---------|--------|------------| +| Q1 | April, May, June | Q1 2026-27 = Apr–Jun 2026 | +| Q2 | July, August, September | Q2 2026-27 = Jul–Sep 2026 | +| Q3 | October, November, December | Q3 2026-27 = Oct–Dec 2026 | +| Q4 | January, February, March | Q4 2026-27 = Jan–Mar 2027 | + +## Slide Structure + +| # | Title | Left Panel | Right Panel | +|---|-------|------------|-------------| +| T | Title slide | — | "Glific Quarterly Review / Q[N] YYYY-YY" | +| 1 | Glific Overall Update | Goal KPIs (YTD) | Highlights + Lowlights | +| 2 | Biz Dev & Marketing | Biz Dev KPIs (YTD) | This Quarter + Next Quarter | +| 3 | CS - Consulting | CS Consulting KPIs (YTD) | Q Updates (top 4 engagements only, no Next Quarter) | +| 4 | CS - Support | CS Support KPIs (Q total) | Documentation Updates + Next Quarter | +| 5 | Platform | Platform KPIs (Q total) | This Quarter (summarised) + Next Quarter | + +## Data Sources + +| Data | Source | File ID | +|------|--------|---------| +| All left-panel KPIs | KPI Tracker spreadsheet | `1qMr_lDsn00CYp9-iHsT5-7Jr-ZRek2p4RBSJdlJUveQ` gid=1733984597 | +| Narrative bullets (right panels) | Monthly PPTX decks in Drive | folder `1MFXFiek0HVq6NEqwfGyaT72gJITNl4e8` | +| CS Consulting org updates | CS/Product Tracker sheet | `1hNk_495eGBlBrlLSm4mGDpYRqsIj1aKAM7OuYFULMQk` gid=625816466 | +| Incidents count | Drive incidents folder | `1OKOHucGLae9FMFbifHLZveCHlKer_trv` | + +--- + +## Step-by-Step Workflow + +### Step 0 — Parse & confirm the quarter + +Extract the **quarter** (Q1–Q4) and **fiscal year** (e.g. 2026-27) from the user's request. +Derive the three calendar months and the calendar year(s) involved. If the fiscal year is +omitted, infer it from today's date (Glific FY starts in April). + +**Confirm with the user before doing any data fetching:** +> "This is Q[N] [FY], covering [Month1], [Month2], and [Month3] [Year]. Shall I proceed?" + +### Step 1 — Set up working directory + +```bash +mkdir -p /tmp/glific-q-deck +cd /tmp/glific-q-deck +[ -d node_modules/pptxgenjs ] || (npm init -y && npm install pptxgenjs) +``` + +### Step 2 — Pull KPIs from the KPI Tracker spreadsheet + +This is the **primary KPI source** for all left-panel values. Read it once and extract +data for all three quarter months in a single pass. + +``` +mcp__4f39b92b-af20-4540-a74e-39576272b2ba__read_file_content + fileId: 1qMr_lDsn00CYp9-iHsT5-7Jr-ZRek2p4RBSJdlJUveQ +``` + +The sheet has KPI row labels in the leftmost column and month names as column headers +(e.g. "April", "May", "June"). For each of the three quarter months: +- Find the column whose header matches the month name (case-insensitive) +- Extract the values for every KPI row listed below + +**KPIs to extract per month:** + +*Slide 1 — Overall:* +- Sustainability % (or Revenue/Expense ratio — whichever row is labelled) +- Total Revenue +- Total Expense +- Total Active Bots (end-of-month snapshot) +- New Bot Accounts (month count) +- Active Contacts +- Messages Exchanged +- Grants Applied + +*Slide 2 — Biz Dev:* +- SaaS Revenue +- New Bots (month) +- MQLs +- SQLs + +*Slide 3 — CS Consulting:* +- Consulting Revenue +- % of Annual Target (or calculate: cumulative consulting ÷ annual target) +- Churn count +- Active Consulting Org Names (comma-separated list) + +*Slide 4 — CS Support:* +- # of P0 Issues +- P0 Average Resolution Time +- # of P1 Issues +- P1 Average Resolution Time +- # of Incidents + +*Slide 5 — Platform:* +- Deployments / Week +- Uptime % +- Escaped Bugs (label may be "Critical Bugs" or "Escaped Bugs") + +Save the raw per-month values in a working dict before aggregation. + +### Step 3 — Aggregate KPIs across available months + +For each KPI, apply the correct aggregation rule: + +**Cumulative (SUM) — add monthly values:** +| KPI | Notes | +|-----|-------| +| Total Revenue | Sum all months | +| SaaS Revenue | Sum all months | +| Consulting Revenue | Sum all months | +| Total Expense | Sum all months | +| New Bot Accounts | Sum all months | +| MQLs | Sum all months | +| SQLs | Sum all months | +| # of P0 Issues | Sum; format as "N (X Apr, Y May, Z Jun)" if breakdown useful | +| # of P1 Issues | Sum; same format | +| # of Incidents | Sum; format with per-month breakdown | +| Escaped Bugs | Sum; format with per-month breakdown | +| Deployments/Week | Average across available months (sum ÷ count), then annotate: "2.1 avg (Apr: 2, May: 2.2)" | +| Messages Exchanged | Sum (or use latest if only cumulative YTD is tracked) | + +**Snapshot (LATEST) — use the most recent available month's value:** +| KPI | Notes | +|-----|-------| +| Sustainability % | Most recent month | +| Total Active Bots | Most recent month; mark highlight:true | +| Active Contacts | Most recent month | +| Active Consulting Org Names | Most recent month (current active list) | +| Uptime % | Most recent month | +| P0 / P1 Resolution Time | Most recent month's average | +| % of Annual Target | Recalculate: (cumulative consulting revenue ÷ annual target) × 100 | + +**Missing months**: Never fabricate values. If a month's data is blank or the month's deck +doesn't exist, use only the available months and note the gap: +- On cumulative KPIs: label as "Q1 YTD (Apr + May only)" if June is missing +- On the title slide: "Data covers April & May 2026 — June pending" + +**Formatting rules:** +- Revenue: `Rs XX,XX,XXX` (Indian comma format, e.g. `Rs 23,73,332`) +- Percentages: one decimal place (e.g. `65.5%`) +- Missing/unavailable: `"—"` (en-dash, not hyphen) + +### Step 4 — Pull narrative content from monthly PPTX decks + +The right-panel bullet content (Highlights, Lowlights, This Quarter bullets) comes from +the existing monthly review decks in Drive, not from the spreadsheet. + +**Locate the decks:** +``` +mcp__4f39b92b-af20-4540-a74e-39576272b2ba__search_files + query: name contains 'glific_monthly_review_' and '1MFXFiek0HVq6NEqwfGyaT72gJITNl4e8' in parents +``` +Match filenames case-insensitively against the quarter's months. Skip missing months. + +**Download and parse each deck:** +``` +mcp__4f39b92b-af20-4540-a74e-39576272b2ba__download_file_content fileId: +``` +```bash +pip install python-pptx --break-system-packages -q +python3 << 'EOF' +from pptx import Presentation, json +path = "/tmp/monthly_deck.pptx" +prs = Presentation(path) +slides = [] +for i, slide in enumerate(prs.slides): + texts = [] + for shape in slide.shapes: + if shape.has_text_frame: + for para in shape.text_frame.paragraphs: + t = para.text.strip() + if t: + texts.append(t) + slides.append({"slide": i, "texts": texts}) +print(json.dumps(slides, indent=2)) +EOF +``` + +**What to extract per slide from each monthly deck:** +- Slide 1: highlights + lowlights bullets (or "Updates" if older format) +- Slide 2: "This Month" bullets; "Next Month" bullets +- Slide 3: consulting org update bullets +- Slide 4: documentation update bullets +- Slide 5: platform "Monthly Updates" bullets; "Next Month" bullets + +**Consulting updates fallback**: If monthly decks don't have consulting org detail, read the +CS/Product Tracker sheet (`1hNk_495eGBlBrlLSm4mGDpYRqsIj1aKAM7OuYFULMQk`) for the quarter +months' columns. + +### Step 5 — Synthesise quarterly narrative content + +Build the right-panel bullets for each slide: + +**Slide 1 — Highlights / Lowlights:** +- Distil from all available months' updates into the **4 most significant** highlights and + **4 most significant** lowlights for the quarter overall. Avoid repetition across months. + +**Slide 2 — Biz Dev This Quarter (max 5 bullets):** +- Merge "This Month" bullets from all available months into one coherent quarterly narrative. +- Do NOT list month-by-month — synthesise into themes (Onboarding, Marketing, Pipeline, Process). +- Next Quarter: set to `null` unless the user provides content. + +**Slide 3 — CS Consulting Quarterly Updates (top 4 only):** +- Highlight the 4 most impactful consulting engagements across the quarter. +- Format: `"OrgName: key work done — key outcome"` +- Do NOT include hours, do NOT include all orgs, do NOT add a Next Quarter section. + +**Slide 4 — CS Support Documentation Updates:** +- Merge all doc update bullets from available months; club related changes. +- Next Quarter: set to `null` unless user provides content. + +**Slide 5 — Platform This Quarter (max 5 bullets):** +- Synthesise across months into theme-level bullets (not month-by-month). +- Next Quarter: set to `null` unless user provides content. + +### Step 6 — Compile deck_data.json + +Write `/tmp/glific-q-deck/deck_data.json` using all gathered data: + +```json +{ + "quarter": "Q1", + "fiscalYear": "2026-27", + "months": ["April", "May"], + "missingMonths": ["June"], + "coverageNote": "Data covers April & May 2026 — June pending", + + "overall": { + "kpis": [ + { "label": "Sustainability Q1 YTD", "value": "65.5%" }, + { "label": "Total Revenue Q1 YTD", "value": "Rs 37,22,512" }, + { "label": "Total Expense Q1 YTD", "value": "Rs 56,81,094" }, + { "label": "Total Active Bots", "value": "147", "highlight": true }, + { "label": "New Bot Accounts Q1", "value": "5" }, + { "label": "Active Contacts (FY)", "value": "56,307" }, + { "label": "Messages Exchanged YTD", "value": null }, + { "label": "Grants Applied to YTD", "value": "$0" } + ], + "highlights": ["bullet 1", "bullet 2", "bullet 3", "bullet 4"], + "lowlights": ["bullet 1", "bullet 2", "bullet 3", "bullet 4"] + }, + + "bizdev": { + "kpis": [ + { "label": "SaaS Revenue Q1 YTD", "value": "Rs 23,73,332", "highlight": true }, + { "label": "New Bots Q1", "value": "5" }, + { "label": "MQLs", "value": "4+" }, + { "label": "SQLs", "value": "2+" } + ], + "thisQuarter": ["bullet 1", "..."], + "nextQuarter": null + }, + + "csConsulting": { + "kpis": [ + { "label": "Consulting Revenue Q1 YTD", "value": "Rs 12,78,547", "highlight": true }, + { "label": "% of Annual Target", "value": "20.5% of Rs 62.5L" }, + { "label": "Churn Q1", "value": "16 (9 Apr, 7 May)" }, + { "label": "Active Consulting Orgs", "value": "ATECF, FMCH, Antara, ..." } + ], + "quarterUpdates": ["OrgA: work done", "OrgB: work done", "OrgC: work done", "OrgD: work done"] + }, + + "csSupport": { + "kpis": [ + { "label": "# of P0 Issues", "value": "2 (0 Apr, 2 May)" }, + { "label": "P0 Resolution Time", "value": "4.3 days (May avg)" }, + { "label": "# of P1 Issues", "value": "14 (1 Apr, 13 May)" }, + { "label": "P1 Resolution Time", "value": "1-2 days" }, + { "label": "# of Incidents", "value": "2 (May; 0 in April)" } + ], + "docUpdates": ["bullet 1", "..."], + "nextQuarter": null + }, + + "platformOps": { + "kpis": [ + { "label": "Deployments / Week", "value": "2.1 avg (Apr: 2, May: 2.2)" }, + { "label": "Uptime", "value": null }, + { "label": "Escaped Bugs", "value": "10 (4 Apr + 6 May)" } + ], + "thisQuarter": ["bullet 1", "..."], + "nextQuarter": null + } +} +``` + +### Step 7 — Generate the PPTX + +```bash +SKILL_DIR="" +cp "$SKILL_DIR/scripts/create_quarterly_deck.js" /tmp/glific-q-deck/ +cd /tmp/glific-q-deck +node create_quarterly_deck.js deck_data.json "glific_quarterly_review_Q1_2026-27.pptx" +``` + +Output filename pattern: `glific_quarterly_review_Q[N]_[FY].pptx` + +### Step 8 — QA + +```bash +python3 -c " +from pptx import Presentation +prs = Presentation('/tmp/glific-q-deck/glific_quarterly_review_Q1_2026-27.pptx') +print(f'Slides: {len(prs.slides)}') +for i, s in enumerate(prs.slides): + texts = [sh.text_frame.paragraphs[0].text for sh in s.shapes if sh.has_text_frame] + print(f'Slide {i}: {texts[:5]}') +" +``` + +Confirm: 6 slides, quarter label on title, coverage note visible, no "TODO" text, +grey italic placeholder on all null `nextQuarter` sections. + +### Step 9 — Deliver + +Copy to the outputs folder and share via `mcp__cowork__present_files`. +Remind the user to drag the `.pptx` into Google Drive and open with Google Slides to edit. + +--- + +## Aggregation Quick Reference + +| KPI | Rule | Notes | +|-----|------|-------| +| Revenue (all types) | SUM | | +| Expenses | SUM | | +| New Bots | SUM | | +| MQLs / SQLs | SUM | | +| P0 / P1 counts | SUM | Show per-month breakdown: "14 (1 Apr, 13 May)" | +| Incidents | SUM | Show per-month breakdown | +| Escaped Bugs | SUM | Show per-month breakdown | +| Deployments/Week | AVG | `(sum of monthly avgs) ÷ months available` + per-month note | +| Active Bots | LATEST | End-of-quarter snapshot | +| Active Contacts | LATEST | End-of-quarter snapshot | +| Sustainability % | LATEST | Most recent month | +| Uptime | LATEST | Most recent month | +| P0/P1 Avg Resolution | LATEST | Most recent month | +| Active Consulting Orgs | LATEST | Current active list | +| % of Annual Target | CALCULATED | `(cumulative consulting revenue ÷ Rs 62.5L) × 100` | + +--- + +## Known Issues & Gotchas + +**Quarter vs month confusion**: Glific FY starts April. Always confirm the month mapping +before fetching. A user saying "Q1" might mean calendar Q1 (Jan–Mar) — clarify. + +**Missing months**: Aggregate only available months. Never invent data. Label cumulative +KPIs as "Q1 YTD (Apr + May only)" and note the gap on the title slide. + +**Consulting slide (Slide 3)**: +- Include only the top 4 most notable engagements, not all orgs +- No "Next Quarter" section on this slide +- No hours in org update bullets + +**Slide 1 layout**: Uses Highlights + Lowlights (4 each) instead of a single updates list. + +**KPI label naming**: Use "Q[N] YTD" on cumulative KPIs (e.g. "SaaS Revenue Q1 YTD"). + +**"Next Quarter" placeholder**: When `nextQuarter` is `null`, the script renders +"To be updated once [next quarter] data is available" in grey italic. + +**Sustainability %**: Snapshot — use the most recent available month's value, never sum. + +**hex colors in pptxgenjs**: Never use `#` prefix (`"063F24"` not `"#063F24"`). diff --git a/plugins/reports-and-presentations/skills/glific-quarterly-review/scripts/create_quarterly_deck.js b/plugins/reports-and-presentations/skills/glific-quarterly-review/scripts/create_quarterly_deck.js new file mode 100644 index 0000000..874deb4 --- /dev/null +++ b/plugins/reports-and-presentations/skills/glific-quarterly-review/scripts/create_quarterly_deck.js @@ -0,0 +1,281 @@ +/** + * Glific Quarterly Review — PPTX Generator + * Usage: node create_quarterly_deck.js [output.pptx] + * + * deck_data.json must match the schema in SKILL.md Step 5. + */ +const fs = require("fs"); +const pptxgen = require("pptxgenjs"); + +// ── CLI args ────────────────────────────────────────────────────────────────── +const dataPath = process.argv[2]; +const outName = process.argv[3] || "glific_quarterly_review.pptx"; + +if (!dataPath) { + console.error("Usage: node create_quarterly_deck.js [output.pptx]"); + process.exit(1); +} + +const D = JSON.parse(fs.readFileSync(dataPath, "utf8")); +const QUARTER_LABEL = `${D.quarter} ${D.fiscalYear}`; + +// ── COLOR PALETTE (Glific brand — approved) ─────────────────────────────────── +const C = { + darkGreen: "063F24", // body text, section labels, title bg + midDark: "11452B", // header strip + midGreen: "23995B", // left panel bg + rowAlt: "1D7A49", // alternating KPI row + mint: "CCE6D0", // KPI label text on green + yellow: "FFCE00", // accent bar, KPI panel label, highlights + yellowMid: "FFD633", // right panel header divider + cream: "FFF6E6", // right panel bg + white: "FFFFFF", + grey: "AAAAAA", // placeholder text for TBD sections +}; + +// ── 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; +const FONT = "Heebo"; + +const pres = new pptxgen(); +pres.layout = "LAYOUT_16x9"; +pres.title = `Glific Quarterly Review - ${QUARTER_LABEL}`; + +// ── HELPERS ─────────────────────────────────────────────────────────────────── + +function header(s, title) { + s.addShape(pres.shapes.RECTANGLE, { + x: 0, y: 0, w: W, h: HEADER_H, + fill: { color: C.midDark }, line: { color: C.midDark } + }); + s.addText(title, { + x: PAD, y: 0, w: W - PAD * 2, h: HEADER_H, + fontSize: 20, bold: true, color: C.white, + fontFace: FONT, valign: "middle", align: "left", margin: 0 + }); +} + +function leftPanel(s, label) { + s.addShape(pres.shapes.RECTANGLE, { + x: 0, y: PANEL_Y, w: LEFT_W, h: PANEL_H, + fill: { color: C.midGreen }, line: { color: C.midGreen } + }); + s.addText(label, { + x: PAD, y: PANEL_Y + 0.12, w: LEFT_W - PAD * 2, h: 0.38, + fontSize: 10, bold: true, color: C.yellow, + fontFace: FONT, valign: "middle", align: "left", charSpacing: 1.5, margin: 0 + }); + s.addShape(pres.shapes.RECTANGLE, { + x: PAD, y: PANEL_Y + 0.52, w: LEFT_W - PAD * 2, h: 0.018, + fill: { color: C.mint }, line: { color: C.mint } + }); +} + +function rightPanel(s, label) { + s.addShape(pres.shapes.RECTANGLE, { + x: RIGHT_X, y: PANEL_Y, w: RIGHT_W, h: PANEL_H, + fill: { color: C.cream }, line: { color: C.cream } + }); + s.addShape(pres.shapes.RECTANGLE, { + x: RIGHT_X, y: PANEL_Y, w: 0.04, h: PANEL_H, + fill: { color: C.yellow }, line: { color: C.yellow } + }); + s.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.darkGreen, + fontFace: FONT, valign: "middle", align: "left", charSpacing: 1.5, margin: 0 + }); + s.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.yellowMid }, line: { color: C.yellowMid } + }); +} + +function kpiRows(s, kpis, startY) { + const ROW_H = 0.52; + kpis.forEach((k, i) => { + const y = startY + i * ROW_H; + if (i % 2 === 0) { + s.addShape(pres.shapes.RECTANGLE, { + x: 0.08, y, w: LEFT_W - 0.1, h: ROW_H - 0.04, + fill: { color: C.rowAlt }, line: { color: C.rowAlt } + }); + } + const labelOpts = { + x: PAD, y: y + 0.03, w: LEFT_W - PAD * 2, h: 0.22, + fontSize: 8, color: C.mint, + fontFace: FONT, valign: "top", align: "left", margin: 0 + }; + if (k.labelHyperlink) labelOpts.hyperlink = { url: k.labelHyperlink }; + s.addText(k.label, labelOpts); + const val = k.value != null ? String(k.value) : "—"; + s.addText(val, { + x: PAD, y: y + 0.24, w: LEFT_W - PAD * 2, h: 0.24, + fontSize: k.highlight ? 13 : 11, bold: !!k.highlight, + color: k.highlight ? C.yellow : C.white, + fontFace: FONT, valign: "top", align: "left", margin: 0 + }); + }); +} + +function sectionLabel(s, text, y) { + s.addText(text, { + x: RIGHT_X + 0.15, y, w: RIGHT_W - 0.3, h: 0.27, + fontSize: 11, bold: true, color: C.darkGreen, + fontFace: FONT, valign: "top", align: "left", margin: 0 + }); +} + +function bullets(s, items, y, h, fontSize) { + if (!items || !items.length) return; + s.addText( + items.map((t, i) => ({ + text: t, + options: { + bullet: true, + breakLine: i < items.length - 1, + fontSize: fontSize || 11, + color: C.darkGreen, + fontFace: FONT, + paraSpaceAfter: 4 + } + })), + { x: RIGHT_X + 0.12, y, w: RIGHT_W - 0.25, h, valign: "top", margin: 0 } + ); +} + +// Render a grey italic placeholder when nextQuarter data is null +function tbdPlaceholder(s, y, nextQLabel) { + const label = nextQLabel || "next quarter"; + s.addText(`To be updated once ${label} data is available`, { + x: RIGHT_X + 0.18, y: y + 0.05, w: RIGHT_W - 0.35, h: 0.3, + fontSize: 10, color: C.grey, fontFace: FONT, + italic: true, valign: "top", align: "left", margin: 0 + }); +} + +// ══ SLIDE 0 — TITLE ══════════════════════════════════════════════════════════ +const nextQ = D.quarter === "Q1" ? "Q2" : + D.quarter === "Q2" ? "Q3" : + D.quarter === "Q3" ? "Q4" : "next quarter"; + +const s0 = pres.addSlide(); +s0.background = { color: C.darkGreen }; +s0.addShape(pres.shapes.RECTANGLE, { + x: 0, y: 3.65, w: W, h: 0.07, + fill: { color: C.yellow }, line: { color: C.yellow } +}); +s0.addShape(pres.shapes.RECTANGLE, { + x: 0, y: 4.88, w: W, h: 0.745, + fill: { color: C.midDark }, line: { color: C.midDark } +}); +s0.addShape(pres.shapes.RECTANGLE, { + x: 0, y: 0, w: 0.18, h: H, + fill: { color: C.midGreen }, line: { color: C.midGreen } +}); +s0.addText("Glific Quarterly Review", { + x: 0.5, y: 1.0, w: 9, h: 1.2, + fontSize: 44, bold: true, color: C.white, + fontFace: FONT, align: "center", valign: "middle", margin: 0 +}); +s0.addText(QUARTER_LABEL, { + x: 0.5, y: 2.35, w: 9, h: 0.7, + fontSize: 28, color: C.yellow, + fontFace: FONT, align: "center", valign: "middle", margin: 0 +}); +if (D.coverageNote) { + s0.addText(D.coverageNote, { + x: 0.5, y: 3.1, w: 9, h: 0.4, + fontSize: 13, color: C.mint, fontFace: FONT, + align: "center", valign: "middle", margin: 0, italic: true + }); +} +s0.addText("Project Tech4Dev · Glific Platform", { + x: 0.5, y: 4.93, w: 9, h: 0.38, + fontSize: 11, color: C.mint, + fontFace: FONT, align: "center", valign: "middle", margin: 0 +}); + +// ══ SLIDE 1 — OVERALL UPDATE ═════════════════════════════════════════════════ +const s1 = pres.addSlide(); +header(s1, `Glific Overall Update - ${QUARTER_LABEL}`); +leftPanel(s1, "GOAL KPIs"); +rightPanel(s1, "QUARTERLY UPDATES"); +kpiRows(s1, D.overall.kpis, PANEL_Y + 0.6); + +const HIGHLIGHTS = D.overall.highlights || []; +const LOWLIGHTS = D.overall.lowlights || []; + +sectionLabel(s1, "Highlights", PANEL_Y + 0.62); +bullets(s1, HIGHLIGHTS, PANEL_Y + 0.88, 1.55); +sectionLabel(s1, "Lowlights", PANEL_Y + 2.56); +bullets(s1, LOWLIGHTS, PANEL_Y + 2.82, 1.55); + +// ══ SLIDE 2 — BIZ DEV & MARKETING ═══════════════════════════════════════════ +const s2 = pres.addSlide(); +header(s2, `Biz Dev & Marketing - ${QUARTER_LABEL}`); +leftPanel(s2, "BIZ DEV KPIs"); +rightPanel(s2, "QUARTERLY UPDATES"); +kpiRows(s2, D.bizdev.kpis, PANEL_Y + 0.6); + +sectionLabel(s2, "This Quarter", PANEL_Y + 0.62); +bullets(s2, D.bizdev.thisQuarter, PANEL_Y + 0.88, 2.2); +sectionLabel(s2, "Next Quarter", PANEL_Y + 3.18); +if (D.bizdev.nextQuarter) { + bullets(s2, D.bizdev.nextQuarter, PANEL_Y + 3.44, 1.0); +} else { + tbdPlaceholder(s2, PANEL_Y + 3.18, nextQ); +} + +// ══ SLIDE 3 — CS CONSULTING ══════════════════════════════════════════════════ +const s3 = pres.addSlide(); +header(s3, `Customer Success - Consulting - ${QUARTER_LABEL}`); +leftPanel(s3, "CS CONSULTING KPIs"); +rightPanel(s3, `${D.quarter} CONSULTING UPDATES`); +kpiRows(s3, D.csConsulting.kpis, PANEL_Y + 0.6); +bullets(s3, D.csConsulting.quarterUpdates, PANEL_Y + 0.65, PANEL_H - 0.75); + +// ══ SLIDE 4 — CS SUPPORT ═════════════════════════════════════════════════════ +const s4 = pres.addSlide(); +header(s4, `Customer Success - Support - ${QUARTER_LABEL}`); +leftPanel(s4, "CS SUPPORT KPIs"); +rightPanel(s4, "DOCUMENTATION UPDATES"); +kpiRows(s4, D.csSupport.kpis, PANEL_Y + 0.6); + +sectionLabel(s4, "This Quarter", PANEL_Y + 0.62); +bullets(s4, D.csSupport.docUpdates, PANEL_Y + 0.88, 1.85); +sectionLabel(s4, "Next Quarter", PANEL_Y + 2.85); +if (D.csSupport.nextQuarter) { + bullets(s4, D.csSupport.nextQuarter, PANEL_Y + 3.1, 1.2); +} else { + tbdPlaceholder(s4, PANEL_Y + 2.85, nextQ); +} + +// ══ SLIDE 5 — PLATFORM ═══════════════════════════════════════════════════════ +const s5 = pres.addSlide(); +header(s5, `Platform - ${QUARTER_LABEL}`); +leftPanel(s5, "PLATFORM KPIs"); +rightPanel(s5, "QUARTERLY UPDATES"); +kpiRows(s5, D.platformOps.kpis, PANEL_Y + 0.6); + +sectionLabel(s5, "This Quarter", PANEL_Y + 0.62); +bullets(s5, D.platformOps.thisQuarter, PANEL_Y + 0.88, 2.2); +sectionLabel(s5, "Next Quarter", PANEL_Y + 3.18); +if (D.platformOps.nextQuarter) { + bullets(s5, D.platformOps.nextQuarter, PANEL_Y + 3.44, 1.0); +} else { + tbdPlaceholder(s5, PANEL_Y + 3.18, nextQ); +} + +// ── WRITE ───────────────────────────────────────────────────────────────────── +pres.writeFile({ fileName: outName }) + .then(() => console.log("OK: " + outName)) + .catch(err => { console.error("Error:", err); process.exit(1); });