diff --git a/docs/generate-pptx.cjs b/docs/generate-pptx.cjs new file mode 100644 index 0000000..3977483 --- /dev/null +++ b/docs/generate-pptx.cjs @@ -0,0 +1,680 @@ +/** + * Helix Engage — Weekly Update (Mar 18–25, 2026) + * Light Mode PowerPoint Generator via PptxGenJS + */ +const PptxGenJS = require("pptxgenjs"); + +// ── Design Tokens (Light Mode) ───────────────────────────────────────── +const C = { + bg: "FFFFFF", + bgSubtle: "F8FAFC", + bgCard: "F1F5F9", + bgCardAlt: "E2E8F0", + text: "1E293B", + textSec: "475569", + textMuted: "94A3B8", + accent1: "0EA5E9", // Sky blue (telephony) + accent2: "8B5CF6", // Violet (server/backend) + accent3: "10B981", // Emerald (UX) + accent4: "F59E0B", // Amber (features) + accent5: "EF4444", // Rose (ops) + accent6: "6366F1", // Indigo (timeline) + white: "FFFFFF", + border: "CBD5E1", +}; + +const FONT = { + heading: "Arial", + body: "Arial", +}; + +// ── Helpers ────────────────────────────────────────────────────────────── +function addSlideNumber(slide, num, total) { + slide.addText(`${num} / ${total}`, { + x: 8.8, y: 5.2, w: 1.2, h: 0.3, + fontSize: 8, color: C.textMuted, + fontFace: FONT.body, + align: "right", + }); +} + +function addAccentBar(slide, color) { + slide.addShape("rect", { + x: 0, y: 0, w: 10, h: 0.06, + fill: { color }, + }); +} + +function addLabel(slide, text, color, x, y) { + slide.addShape("roundRect", { + x, y, w: text.length * 0.09 + 0.4, h: 0.3, + fill: { color, transparency: 88 }, + rectRadius: 0.15, + }); + slide.addText(text.toUpperCase(), { + x, y, w: text.length * 0.09 + 0.4, h: 0.3, + fontSize: 7, fontFace: FONT.heading, bold: true, + color, align: "center", valign: "middle", + letterSpacing: 2, + }); +} + +function addCard(slide, opts) { + const { x, y, w, h, title, titleColor, items, badge } = opts; + // Card background + slide.addShape("roundRect", { + x, y, w, h, + fill: { color: C.bgCard }, + line: { color: C.border, width: 0.5 }, + rectRadius: 0.1, + }); + // Title + const titleText = badge + ? [{ text: title + " ", options: { bold: true, color: titleColor, fontSize: 11 } }, + { text: badge, options: { bold: true, color: C.white, fontSize: 7, highlight: titleColor } }] + : title; + slide.addText(titleText, { + x: x + 0.2, y: y + 0.08, w: w - 0.4, h: 0.35, + fontSize: 11, fontFace: FONT.heading, bold: true, + color: titleColor, + }); + // Items as bullet list + if (items && items.length > 0) { + slide.addText( + items.map(item => ({ + text: item, + options: { + fontSize: 8.5, fontFace: FONT.body, color: C.textSec, + bullet: { type: "bullet", style: "arabicPeriod" }, + paraSpaceAfter: 2, + breakLine: true, + }, + })), + { + x: x + 0.2, y: y + 0.4, w: w - 0.4, h: h - 0.5, + valign: "top", + bullet: { type: "bullet" }, + lineSpacingMultiple: 1.1, + } + ); + } +} + +// ── Build Presentation ────────────────────────────────────────────────── +async function build() { + const pptx = new PptxGenJS(); + pptx.layout = "LAYOUT_16x9"; + pptx.author = "Satya Suman Sari"; + pptx.company = "FortyTwo Platform"; + pptx.title = "Helix Engage — Weekly Update (Mar 18–25, 2026)"; + pptx.subject = "Engineering Progress Report"; + + const TOTAL = 9; + + // ═══════════════════════════════════════════════════════════════════════ + // SLIDE 1 — Title + // ═══════════════════════════════════════════════════════════════════════ + { + const slide = pptx.addSlide(); + slide.background = { color: C.bg }; + + // Accent bar top + addAccentBar(slide, C.accent1); + + // Decorative side stripe + slide.addShape("rect", { + x: 0, y: 0, w: 0.12, h: 5.63, + fill: { color: C.accent1 }, + }); + + // Label + addLabel(slide, "Weekly Engineering Update", C.accent1, 3.0, 1.2); + + // Title + slide.addText("Helix Engage", { + x: 1.0, y: 1.8, w: 8, h: 1.2, + fontSize: 44, fontFace: FONT.heading, bold: true, + color: C.accent1, align: "center", + }); + + // Subtitle + slide.addText("Contact Center CRM · Real-time Telephony · AI Copilot", { + x: 1.5, y: 2.9, w: 7, h: 0.5, + fontSize: 14, fontFace: FONT.body, + color: C.textSec, align: "center", + }); + + // Date + slide.addText("March 18 – 25, 2026", { + x: 3, y: 3.6, w: 4, h: 0.4, + fontSize: 12, fontFace: FONT.heading, bold: true, + color: C.textMuted, align: "center", + letterSpacing: 3, + }); + + // Bottom decoration + slide.addShape("rect", { + x: 3.5, y: 4.2, w: 3, h: 0.04, + fill: { color: C.accent2 }, + }); + + // Author + slide.addText("Satya Suman Sari · FortyTwo Platform", { + x: 2, y: 4.5, w: 6, h: 0.35, + fontSize: 9, fontFace: FONT.body, + color: C.textMuted, align: "center", + }); + + addSlideNumber(slide, 1, TOTAL); + } + + // ═══════════════════════════════════════════════════════════════════════ + // SLIDE 2 — At a Glance + // ═══════════════════════════════════════════════════════════════════════ + { + const slide = pptx.addSlide(); + slide.background = { color: C.bg }; + addAccentBar(slide, C.accent2); + + addLabel(slide, "At a Glance", C.accent2, 0.5, 0.3); + + slide.addText("Week in Numbers", { + x: 0.5, y: 0.65, w: 5, h: 0.5, + fontSize: 24, fontFace: FONT.heading, bold: true, + color: C.text, + }); + + // Stat cards + const stats = [ + { value: "78", label: "Total Commits", color: C.accent1 }, + { value: "3", label: "Repositories", color: C.accent2 }, + { value: "8", label: "Days Active", color: C.accent3 }, + { value: "50", label: "Frontend Commits", color: C.accent4 }, + ]; + + stats.forEach((s, i) => { + const x = 0.5 + i * 2.35; + // Card bg + slide.addShape("roundRect", { + x, y: 1.3, w: 2.1, h: 1.7, + fill: { color: C.bgCard }, + line: { color: C.border, width: 0.5 }, + rectRadius: 0.12, + }); + // Accent top line + slide.addShape("rect", { + x: x + 0.2, y: 1.35, w: 1.7, h: 0.035, + fill: { color: s.color }, + }); + // Number + slide.addText(s.value, { + x, y: 1.5, w: 2.1, h: 0.9, + fontSize: 36, fontFace: FONT.heading, bold: true, + color: s.color, align: "center", valign: "middle", + }); + // Label + slide.addText(s.label, { + x, y: 2.4, w: 2.1, h: 0.4, + fontSize: 9, fontFace: FONT.body, + color: C.textSec, align: "center", + }); + }); + + // Repo breakdown pills + const repos = [ + { name: "helix-engage", count: "50", clr: C.accent1 }, + { name: "helix-engage-server", count: "27", clr: C.accent2 }, + { name: "FortyTwoApps/SDK", count: "1", clr: C.accent3 }, + ]; + repos.forEach((r, i) => { + const x = 1.5 + i * 2.8; + slide.addShape("roundRect", { + x, y: 3.4, w: 2.5, h: 0.4, + fill: { color: C.bgCard }, + line: { color: r.clr, width: 1 }, + rectRadius: 0.2, + }); + slide.addText(`${r.name} ${r.count}`, { + x, y: 3.4, w: 2.5, h: 0.4, + fontSize: 9, fontFace: FONT.heading, bold: true, + color: r.clr, align: "center", valign: "middle", + }); + }); + + // Summary text + slide.addText("3 repos · 7 working days · 78 commits shipped to production", { + x: 1, y: 4.2, w: 8, h: 0.35, + fontSize: 10, fontFace: FONT.body, italic: true, + color: C.textMuted, align: "center", + }); + + addSlideNumber(slide, 2, TOTAL); + } + + // ═══════════════════════════════════════════════════════════════════════ + // SLIDE 3 — Telephony & SIP + // ═══════════════════════════════════════════════════════════════════════ + { + const slide = pptx.addSlide(); + slide.background = { color: C.bg }; + addAccentBar(slide, C.accent1); + + addLabel(slide, "Core Infrastructure", C.accent1, 0.5, 0.3); + + slide.addText([ + { text: "☎ ", options: { fontSize: 22 } }, + { text: "Telephony & SIP Overhaul", options: { fontSize: 22, bold: true, color: C.text } }, + ], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading }); + + addCard(slide, { + x: 0.3, y: 1.35, w: 4.5, h: 2.0, + title: "Outbound Calling", titleColor: C.accent1, badge: "Frontend", + items: [ + "Direct SIP call from browser — no Kookoo bridge", + "Immediate call card UI with auto-answer SIP bridge", + "End Call label fix, force active state after auto-answer", + "Reset outboundPending on call end", + ], + }); + + addCard(slide, { + x: 5.2, y: 1.35, w: 4.5, h: 2.0, + title: "Ozonetel Integration", titleColor: C.accent2, badge: "Server", + items: [ + "Ozonetel V3 dial endpoint + webhook handler", + "Set Disposition API for ACW release", + "Force Ready endpoint for agent state mgmt", + "Token: 10-min cache, 401 invalidation, refresh on login", + ], + }); + + addCard(slide, { + x: 0.3, y: 3.55, w: 4.5, h: 1.8, + title: "SIP & Agent State", titleColor: C.accent1, badge: "Frontend", + items: [ + "SIP driven by Agent entity with token refresh", + "Centralised outbound dial into useSip().dialOutbound()", + "UCID tracking from SIP headers for disposition", + "Network indicator for connection health", + ], + }); + + addCard(slide, { + x: 5.2, y: 3.55, w: 4.5, h: 1.8, + title: "Multi-Agent & Sessions", titleColor: C.accent2, badge: "Server", + items: [ + "Multi-agent SIP with Redis session lockout", + "Strict duplicate login — one device per agent", + "Session lock stores IP + timestamp for debugging", + "SSE agent state broadcast for supervisor view", + ], + }); + + addSlideNumber(slide, 3, TOTAL); + } + + // ═══════════════════════════════════════════════════════════════════════ + // SLIDE 4 — Call Desk & Agent UX + // ═══════════════════════════════════════════════════════════════════════ + { + const slide = pptx.addSlide(); + slide.background = { color: C.bg }; + addAccentBar(slide, C.accent3); + + addLabel(slide, "User Experience", C.accent3, 0.5, 0.3); + + slide.addText([ + { text: "🖥 ", options: { fontSize: 22 } }, + { text: "Call Desk & Agent UX", options: { fontSize: 22, bold: true, color: C.text } }, + ], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading }); + + addCard(slide, { + x: 0.3, y: 1.35, w: 3.05, h: 2.6, + title: "Call Desk Redesign", titleColor: C.accent3, + items: [ + "2-panel layout with collapsible sidebar & inline AI", + "Collapsible context panel, worklist/calls tabs", + "Pinned header & chat input, numpad dialler", + "Ringtone support for incoming calls", + ], + }); + + addCard(slide, { + x: 3.55, y: 1.35, w: 3.05, h: 2.6, + title: "Post-Call Workflow", titleColor: C.accent3, + items: [ + "Disposition → appointment booking → follow-up", + "Disposition returns straight to worklist", + "Send disposition to sidecar with UCID for ACW", + "Enquiry in post-call, appointment skip button", + ], + }); + + addCard(slide, { + x: 6.8, y: 1.35, w: 2.9, h: 2.6, + title: "UI Polish", titleColor: C.accent3, + items: [ + "FontAwesome Pro Duotone icon migration", + "Tooltips, sticky headers, roles, search", + "Fix React error #520 in prod tables", + "AI scroll containment, brand tokens refresh", + ], + }); + + addSlideNumber(slide, 4, TOTAL); + } + + // ═══════════════════════════════════════════════════════════════════════ + // SLIDE 5 — Features Shipped + // ═══════════════════════════════════════════════════════════════════════ + { + const slide = pptx.addSlide(); + slide.background = { color: C.bg }; + addAccentBar(slide, C.accent4); + + addLabel(slide, "Features Shipped", C.accent4, 0.5, 0.3); + + slide.addText([ + { text: "🚀 ", options: { fontSize: 22 } }, + { text: "Major Features", options: { fontSize: 22, bold: true, color: C.text } }, + ], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading }); + + addCard(slide, { + x: 0.3, y: 1.35, w: 4.5, h: 2.0, + title: "Supervisor Module", titleColor: C.accent4, + items: [ + "Team performance analytics page", + "Live monitor with active calls visibility", + "Master data management pages", + "Server: team perf + active calls endpoints", + ], + }); + + addCard(slide, { + x: 5.2, y: 1.35, w: 4.5, h: 2.0, + title: "Missed Call Queue (Phase 2)", titleColor: C.accent4, + items: [ + "Missed call queue ingestion & worklist", + "Auto-assignment engine for agents", + "Login redesign with role-based routing", + "Lead lookup for missed callers", + ], + }); + + addCard(slide, { + x: 0.3, y: 3.55, w: 4.5, h: 1.8, + title: "Agent Features (Phase 1)", titleColor: C.accent4, + items: [ + "Agent status toggle (Ready / Not Ready / Break)", + "Global search across patients, leads, calls", + "Enquiry form for new patient intake", + "My Performance page + logout modal", + ], + }); + + addCard(slide, { + x: 5.2, y: 3.55, w: 4.5, h: 1.8, + title: "Recording Analysis", titleColor: C.accent4, + items: [ + "Deepgram diarization + AI insights", + "Redis caching layer for analysis results", + "Full-stack: frontend player + server module", + ], + }); + + addSlideNumber(slide, 5, TOTAL); + } + + // ═══════════════════════════════════════════════════════════════════════ + // SLIDE 6 — Backend & Data + // ═══════════════════════════════════════════════════════════════════════ + { + const slide = pptx.addSlide(); + slide.background = { color: C.bg }; + addAccentBar(slide, C.accent2); + + addLabel(slide, "Backend & Data", C.accent2, 0.5, 0.3); + + slide.addText([ + { text: "⚙ ", options: { fontSize: 22 } }, + { text: "Backend & Data Layer", options: { fontSize: 22, bold: true, color: C.text } }, + ], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading }); + + addCard(slide, { + x: 0.3, y: 1.35, w: 4.5, h: 2.0, + title: "Platform Data Wiring", titleColor: C.accent2, + items: [ + "Migrated frontend to Jotai + Vercel AI SDK", + "Corrected all 7 GraphQL queries (fields, LINKS/PHONES)", + "Webhook handler for Ozonetel call records", + "Complete seeder: 5 doctors, appointments linked", + ], + }); + + addCard(slide, { + x: 5.2, y: 1.35, w: 4.5, h: 2.0, + title: "Server Endpoints", titleColor: C.accent2, + items: [ + "Call control, recording, CDR, missed calls, live assist", + "Agent summary, AHT, performance aggregation", + "Token refresh endpoint for auto-renewal", + "Search module with full-text capabilities", + ], + }); + + addCard(slide, { + x: 0.3, y: 3.55, w: 4.5, h: 1.8, + title: "Data Pages Built", titleColor: C.accent2, + items: [ + "Worklist table, call history, patients, dashboard", + "Reports, team dashboard, campaigns, settings", + "Agent detail page, campaign edit slideout", + "Appointments page with data refresh on login", + ], + }); + + addCard(slide, { + x: 5.2, y: 3.55, w: 4.5, h: 1.8, + title: "SDK App", titleColor: C.accent3, badge: "FortyTwoApps", + items: [ + "Helix Engage SDK app entity definitions", + "Call center CRM object model for platform", + "Foundation for platform-native data integration", + ], + }); + + addSlideNumber(slide, 6, TOTAL); + } + + // ═══════════════════════════════════════════════════════════════════════ + // SLIDE 7 — Deployment & Ops + // ═══════════════════════════════════════════════════════════════════════ + { + const slide = pptx.addSlide(); + slide.background = { color: C.bg }; + addAccentBar(slide, C.accent5); + + addLabel(slide, "Operations", C.accent5, 0.5, 0.3); + + slide.addText([ + { text: "🛠 ", options: { fontSize: 22 } }, + { text: "Deployment & DevOps", options: { fontSize: 22, bold: true, color: C.text } }, + ], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading }); + + addCard(slide, { + x: 0.3, y: 1.35, w: 3.05, h: 2.2, + title: "Deployment", titleColor: C.accent5, + items: [ + "Deployed to Hostinger VPS with Docker", + "Switched to global_healthx Ozonetel account", + "Dockerfile for server-side containerization", + ], + }); + + addCard(slide, { + x: 3.55, y: 1.35, w: 3.05, h: 2.2, + title: "AI & Testing", titleColor: C.accent5, + items: [ + "Migrated AI to Vercel AI SDK + OpenAI provider", + "AI flow test script — validates full pipeline", + "Live call assist integration", + ], + }); + + addCard(slide, { + x: 6.8, y: 1.35, w: 2.9, h: 2.2, + title: "Documentation", titleColor: C.accent5, + items: [ + "Team onboarding README with arch guide", + "Supervisor module spec + plan", + "Multi-agent spec + plan", + "Next session plans in commits", + ], + }); + + addSlideNumber(slide, 7, TOTAL); + } + + // ═══════════════════════════════════════════════════════════════════════ + // SLIDE 8 — Timeline + // ═══════════════════════════════════════════════════════════════════════ + { + const slide = pptx.addSlide(); + slide.background = { color: C.bg }; + addAccentBar(slide, C.accent6); + + addLabel(slide, "Day by Day", C.accent6, 0.5, 0.3); + + slide.addText([ + { text: "📅 ", options: { fontSize: 22 } }, + { text: "Development Timeline", options: { fontSize: 22, bold: true, color: C.text } }, + ], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading }); + + const timeline = [ + { date: "MAR 18 (Tue)", title: "Foundation Day", desc: "Call desk redesign, Jotai + AI SDK migration, seeder, AI flow test, VPS deploy" }, + { date: "MAR 19 (Wed)", title: "Data Layer Sprint", desc: "All data pages, post-call workflow, GraphQL fixes, Kookoo IVR, outbound UI" }, + { date: "MAR 20 (Thu)", title: "Telephony Breakthrough", desc: "Direct SIP replacing Kookoo, UCID tracking, Force Ready, Set Disposition" }, + { date: "MAR 21 (Fri)", title: "Agent Experience", desc: "Phase 1: status toggle, search, enquiry form, My Performance, FA icons, AHT" }, + { date: "MAR 23 (Sun)", title: "Scale & Reliability", desc: "Phase 2: missed call queue, auto-assign, Redis lockout, Patient 360, SDK defs" }, + { date: "MAR 24 (Mon)", title: "Supervisor Module", desc: "Team perf, live monitor, master data, SSE, UUID fix, maintenance, QA sweep" }, + { date: "MAR 25 (Tue)", title: "Intelligence Layer", desc: "Deepgram diarization, AI insights, SIP via Agent entity, token refresh, network" }, + ]; + + // Vertical line + slide.addShape("rect", { + x: 1.4, y: 1.3, w: 0.025, h: 4.0, + fill: { color: C.accent6, transparency: 60 }, + }); + + timeline.forEach((entry, i) => { + const y = 1.3 + i * 0.56; + + // Dot + slide.addShape("ellipse", { + x: 1.32, y: y + 0.08, w: 0.18, h: 0.18, + fill: { color: C.accent6 }, + line: { color: C.bg, width: 2 }, + }); + + // Date + slide.addText(entry.date, { + x: 1.7, y: y, w: 1.6, h: 0.22, + fontSize: 7, fontFace: FONT.heading, bold: true, + color: C.accent6, + }); + + // Title + slide.addText(entry.title, { + x: 3.3, y: y, w: 2.0, h: 0.22, + fontSize: 9, fontFace: FONT.heading, bold: true, + color: C.text, + }); + + // Description + slide.addText(entry.desc, { + x: 5.3, y: y, w: 4.2, h: 0.45, + fontSize: 8, fontFace: FONT.body, + color: C.textSec, + valign: "top", + }); + }); + + addSlideNumber(slide, 8, TOTAL); + } + + // ═══════════════════════════════════════════════════════════════════════ + // SLIDE 9 — Closing + // ═══════════════════════════════════════════════════════════════════════ + { + const slide = pptx.addSlide(); + slide.background = { color: C.bg }; + addAccentBar(slide, C.accent3); + + // Big headline + slide.addText("78 commits. 8 days. Ship mode.", { + x: 0.5, y: 1.4, w: 9, h: 0.8, + fontSize: 32, fontFace: FONT.heading, bold: true, + color: C.accent3, align: "center", + }); + + // Ship emoji + slide.addText("🚢", { + x: 4.2, y: 2.3, w: 1.6, h: 0.6, + fontSize: 28, align: "center", + }); + + // Description + slide.addText( + "From browser-native SIP calling to AI-powered recording analysis — Helix Engage is becoming a production contact center platform.", + { + x: 1.5, y: 3.0, w: 7, h: 0.6, + fontSize: 11, fontFace: FONT.body, + color: C.textSec, align: "center", + lineSpacingMultiple: 1.3, + } + ); + + // Achievement pills + const achievements = [ + { text: "SIP Calling ✓", color: C.accent1 }, + { text: "Multi-Agent ✓", color: C.accent2 }, + { text: "Supervisor ✓", color: C.accent3 }, + { text: "AI Copilot ✓", color: C.accent4 }, + { text: "Recording Analysis ✓", color: C.accent5 }, + ]; + + achievements.forEach((a, i) => { + const x = 0.8 + i * 1.8; + slide.addShape("roundRect", { + x, y: 3.9, w: 1.6, h: 0.35, + fill: { color: C.bgCard }, + line: { color: a.color, width: 1 }, + rectRadius: 0.17, + }); + slide.addText(a.text, { + x, y: 3.9, w: 1.6, h: 0.35, + fontSize: 8, fontFace: FONT.heading, bold: true, + color: a.color, align: "center", valign: "middle", + }); + }); + + // Author + slide.addText("Satya Suman Sari · FortyTwo Platform", { + x: 2, y: 4.7, w: 6, h: 0.3, + fontSize: 9, fontFace: FONT.body, + color: C.textMuted, align: "center", + }); + + addSlideNumber(slide, 9, TOTAL); + } + + // ── Save ────────────────────────────────────────────────────────────── + const outPath = "weekly-update-mar18-25.pptx"; + await pptx.writeFile({ fileName: outPath }); + console.log(`✅ Presentation saved: ${outPath}`); +} + +build().catch(err => { + console.error("❌ Failed:", err.message); + process.exit(1); +}); diff --git a/docs/generate-pptx.js b/docs/generate-pptx.js new file mode 100644 index 0000000..3977483 --- /dev/null +++ b/docs/generate-pptx.js @@ -0,0 +1,680 @@ +/** + * Helix Engage — Weekly Update (Mar 18–25, 2026) + * Light Mode PowerPoint Generator via PptxGenJS + */ +const PptxGenJS = require("pptxgenjs"); + +// ── Design Tokens (Light Mode) ───────────────────────────────────────── +const C = { + bg: "FFFFFF", + bgSubtle: "F8FAFC", + bgCard: "F1F5F9", + bgCardAlt: "E2E8F0", + text: "1E293B", + textSec: "475569", + textMuted: "94A3B8", + accent1: "0EA5E9", // Sky blue (telephony) + accent2: "8B5CF6", // Violet (server/backend) + accent3: "10B981", // Emerald (UX) + accent4: "F59E0B", // Amber (features) + accent5: "EF4444", // Rose (ops) + accent6: "6366F1", // Indigo (timeline) + white: "FFFFFF", + border: "CBD5E1", +}; + +const FONT = { + heading: "Arial", + body: "Arial", +}; + +// ── Helpers ────────────────────────────────────────────────────────────── +function addSlideNumber(slide, num, total) { + slide.addText(`${num} / ${total}`, { + x: 8.8, y: 5.2, w: 1.2, h: 0.3, + fontSize: 8, color: C.textMuted, + fontFace: FONT.body, + align: "right", + }); +} + +function addAccentBar(slide, color) { + slide.addShape("rect", { + x: 0, y: 0, w: 10, h: 0.06, + fill: { color }, + }); +} + +function addLabel(slide, text, color, x, y) { + slide.addShape("roundRect", { + x, y, w: text.length * 0.09 + 0.4, h: 0.3, + fill: { color, transparency: 88 }, + rectRadius: 0.15, + }); + slide.addText(text.toUpperCase(), { + x, y, w: text.length * 0.09 + 0.4, h: 0.3, + fontSize: 7, fontFace: FONT.heading, bold: true, + color, align: "center", valign: "middle", + letterSpacing: 2, + }); +} + +function addCard(slide, opts) { + const { x, y, w, h, title, titleColor, items, badge } = opts; + // Card background + slide.addShape("roundRect", { + x, y, w, h, + fill: { color: C.bgCard }, + line: { color: C.border, width: 0.5 }, + rectRadius: 0.1, + }); + // Title + const titleText = badge + ? [{ text: title + " ", options: { bold: true, color: titleColor, fontSize: 11 } }, + { text: badge, options: { bold: true, color: C.white, fontSize: 7, highlight: titleColor } }] + : title; + slide.addText(titleText, { + x: x + 0.2, y: y + 0.08, w: w - 0.4, h: 0.35, + fontSize: 11, fontFace: FONT.heading, bold: true, + color: titleColor, + }); + // Items as bullet list + if (items && items.length > 0) { + slide.addText( + items.map(item => ({ + text: item, + options: { + fontSize: 8.5, fontFace: FONT.body, color: C.textSec, + bullet: { type: "bullet", style: "arabicPeriod" }, + paraSpaceAfter: 2, + breakLine: true, + }, + })), + { + x: x + 0.2, y: y + 0.4, w: w - 0.4, h: h - 0.5, + valign: "top", + bullet: { type: "bullet" }, + lineSpacingMultiple: 1.1, + } + ); + } +} + +// ── Build Presentation ────────────────────────────────────────────────── +async function build() { + const pptx = new PptxGenJS(); + pptx.layout = "LAYOUT_16x9"; + pptx.author = "Satya Suman Sari"; + pptx.company = "FortyTwo Platform"; + pptx.title = "Helix Engage — Weekly Update (Mar 18–25, 2026)"; + pptx.subject = "Engineering Progress Report"; + + const TOTAL = 9; + + // ═══════════════════════════════════════════════════════════════════════ + // SLIDE 1 — Title + // ═══════════════════════════════════════════════════════════════════════ + { + const slide = pptx.addSlide(); + slide.background = { color: C.bg }; + + // Accent bar top + addAccentBar(slide, C.accent1); + + // Decorative side stripe + slide.addShape("rect", { + x: 0, y: 0, w: 0.12, h: 5.63, + fill: { color: C.accent1 }, + }); + + // Label + addLabel(slide, "Weekly Engineering Update", C.accent1, 3.0, 1.2); + + // Title + slide.addText("Helix Engage", { + x: 1.0, y: 1.8, w: 8, h: 1.2, + fontSize: 44, fontFace: FONT.heading, bold: true, + color: C.accent1, align: "center", + }); + + // Subtitle + slide.addText("Contact Center CRM · Real-time Telephony · AI Copilot", { + x: 1.5, y: 2.9, w: 7, h: 0.5, + fontSize: 14, fontFace: FONT.body, + color: C.textSec, align: "center", + }); + + // Date + slide.addText("March 18 – 25, 2026", { + x: 3, y: 3.6, w: 4, h: 0.4, + fontSize: 12, fontFace: FONT.heading, bold: true, + color: C.textMuted, align: "center", + letterSpacing: 3, + }); + + // Bottom decoration + slide.addShape("rect", { + x: 3.5, y: 4.2, w: 3, h: 0.04, + fill: { color: C.accent2 }, + }); + + // Author + slide.addText("Satya Suman Sari · FortyTwo Platform", { + x: 2, y: 4.5, w: 6, h: 0.35, + fontSize: 9, fontFace: FONT.body, + color: C.textMuted, align: "center", + }); + + addSlideNumber(slide, 1, TOTAL); + } + + // ═══════════════════════════════════════════════════════════════════════ + // SLIDE 2 — At a Glance + // ═══════════════════════════════════════════════════════════════════════ + { + const slide = pptx.addSlide(); + slide.background = { color: C.bg }; + addAccentBar(slide, C.accent2); + + addLabel(slide, "At a Glance", C.accent2, 0.5, 0.3); + + slide.addText("Week in Numbers", { + x: 0.5, y: 0.65, w: 5, h: 0.5, + fontSize: 24, fontFace: FONT.heading, bold: true, + color: C.text, + }); + + // Stat cards + const stats = [ + { value: "78", label: "Total Commits", color: C.accent1 }, + { value: "3", label: "Repositories", color: C.accent2 }, + { value: "8", label: "Days Active", color: C.accent3 }, + { value: "50", label: "Frontend Commits", color: C.accent4 }, + ]; + + stats.forEach((s, i) => { + const x = 0.5 + i * 2.35; + // Card bg + slide.addShape("roundRect", { + x, y: 1.3, w: 2.1, h: 1.7, + fill: { color: C.bgCard }, + line: { color: C.border, width: 0.5 }, + rectRadius: 0.12, + }); + // Accent top line + slide.addShape("rect", { + x: x + 0.2, y: 1.35, w: 1.7, h: 0.035, + fill: { color: s.color }, + }); + // Number + slide.addText(s.value, { + x, y: 1.5, w: 2.1, h: 0.9, + fontSize: 36, fontFace: FONT.heading, bold: true, + color: s.color, align: "center", valign: "middle", + }); + // Label + slide.addText(s.label, { + x, y: 2.4, w: 2.1, h: 0.4, + fontSize: 9, fontFace: FONT.body, + color: C.textSec, align: "center", + }); + }); + + // Repo breakdown pills + const repos = [ + { name: "helix-engage", count: "50", clr: C.accent1 }, + { name: "helix-engage-server", count: "27", clr: C.accent2 }, + { name: "FortyTwoApps/SDK", count: "1", clr: C.accent3 }, + ]; + repos.forEach((r, i) => { + const x = 1.5 + i * 2.8; + slide.addShape("roundRect", { + x, y: 3.4, w: 2.5, h: 0.4, + fill: { color: C.bgCard }, + line: { color: r.clr, width: 1 }, + rectRadius: 0.2, + }); + slide.addText(`${r.name} ${r.count}`, { + x, y: 3.4, w: 2.5, h: 0.4, + fontSize: 9, fontFace: FONT.heading, bold: true, + color: r.clr, align: "center", valign: "middle", + }); + }); + + // Summary text + slide.addText("3 repos · 7 working days · 78 commits shipped to production", { + x: 1, y: 4.2, w: 8, h: 0.35, + fontSize: 10, fontFace: FONT.body, italic: true, + color: C.textMuted, align: "center", + }); + + addSlideNumber(slide, 2, TOTAL); + } + + // ═══════════════════════════════════════════════════════════════════════ + // SLIDE 3 — Telephony & SIP + // ═══════════════════════════════════════════════════════════════════════ + { + const slide = pptx.addSlide(); + slide.background = { color: C.bg }; + addAccentBar(slide, C.accent1); + + addLabel(slide, "Core Infrastructure", C.accent1, 0.5, 0.3); + + slide.addText([ + { text: "☎ ", options: { fontSize: 22 } }, + { text: "Telephony & SIP Overhaul", options: { fontSize: 22, bold: true, color: C.text } }, + ], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading }); + + addCard(slide, { + x: 0.3, y: 1.35, w: 4.5, h: 2.0, + title: "Outbound Calling", titleColor: C.accent1, badge: "Frontend", + items: [ + "Direct SIP call from browser — no Kookoo bridge", + "Immediate call card UI with auto-answer SIP bridge", + "End Call label fix, force active state after auto-answer", + "Reset outboundPending on call end", + ], + }); + + addCard(slide, { + x: 5.2, y: 1.35, w: 4.5, h: 2.0, + title: "Ozonetel Integration", titleColor: C.accent2, badge: "Server", + items: [ + "Ozonetel V3 dial endpoint + webhook handler", + "Set Disposition API for ACW release", + "Force Ready endpoint for agent state mgmt", + "Token: 10-min cache, 401 invalidation, refresh on login", + ], + }); + + addCard(slide, { + x: 0.3, y: 3.55, w: 4.5, h: 1.8, + title: "SIP & Agent State", titleColor: C.accent1, badge: "Frontend", + items: [ + "SIP driven by Agent entity with token refresh", + "Centralised outbound dial into useSip().dialOutbound()", + "UCID tracking from SIP headers for disposition", + "Network indicator for connection health", + ], + }); + + addCard(slide, { + x: 5.2, y: 3.55, w: 4.5, h: 1.8, + title: "Multi-Agent & Sessions", titleColor: C.accent2, badge: "Server", + items: [ + "Multi-agent SIP with Redis session lockout", + "Strict duplicate login — one device per agent", + "Session lock stores IP + timestamp for debugging", + "SSE agent state broadcast for supervisor view", + ], + }); + + addSlideNumber(slide, 3, TOTAL); + } + + // ═══════════════════════════════════════════════════════════════════════ + // SLIDE 4 — Call Desk & Agent UX + // ═══════════════════════════════════════════════════════════════════════ + { + const slide = pptx.addSlide(); + slide.background = { color: C.bg }; + addAccentBar(slide, C.accent3); + + addLabel(slide, "User Experience", C.accent3, 0.5, 0.3); + + slide.addText([ + { text: "🖥 ", options: { fontSize: 22 } }, + { text: "Call Desk & Agent UX", options: { fontSize: 22, bold: true, color: C.text } }, + ], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading }); + + addCard(slide, { + x: 0.3, y: 1.35, w: 3.05, h: 2.6, + title: "Call Desk Redesign", titleColor: C.accent3, + items: [ + "2-panel layout with collapsible sidebar & inline AI", + "Collapsible context panel, worklist/calls tabs", + "Pinned header & chat input, numpad dialler", + "Ringtone support for incoming calls", + ], + }); + + addCard(slide, { + x: 3.55, y: 1.35, w: 3.05, h: 2.6, + title: "Post-Call Workflow", titleColor: C.accent3, + items: [ + "Disposition → appointment booking → follow-up", + "Disposition returns straight to worklist", + "Send disposition to sidecar with UCID for ACW", + "Enquiry in post-call, appointment skip button", + ], + }); + + addCard(slide, { + x: 6.8, y: 1.35, w: 2.9, h: 2.6, + title: "UI Polish", titleColor: C.accent3, + items: [ + "FontAwesome Pro Duotone icon migration", + "Tooltips, sticky headers, roles, search", + "Fix React error #520 in prod tables", + "AI scroll containment, brand tokens refresh", + ], + }); + + addSlideNumber(slide, 4, TOTAL); + } + + // ═══════════════════════════════════════════════════════════════════════ + // SLIDE 5 — Features Shipped + // ═══════════════════════════════════════════════════════════════════════ + { + const slide = pptx.addSlide(); + slide.background = { color: C.bg }; + addAccentBar(slide, C.accent4); + + addLabel(slide, "Features Shipped", C.accent4, 0.5, 0.3); + + slide.addText([ + { text: "🚀 ", options: { fontSize: 22 } }, + { text: "Major Features", options: { fontSize: 22, bold: true, color: C.text } }, + ], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading }); + + addCard(slide, { + x: 0.3, y: 1.35, w: 4.5, h: 2.0, + title: "Supervisor Module", titleColor: C.accent4, + items: [ + "Team performance analytics page", + "Live monitor with active calls visibility", + "Master data management pages", + "Server: team perf + active calls endpoints", + ], + }); + + addCard(slide, { + x: 5.2, y: 1.35, w: 4.5, h: 2.0, + title: "Missed Call Queue (Phase 2)", titleColor: C.accent4, + items: [ + "Missed call queue ingestion & worklist", + "Auto-assignment engine for agents", + "Login redesign with role-based routing", + "Lead lookup for missed callers", + ], + }); + + addCard(slide, { + x: 0.3, y: 3.55, w: 4.5, h: 1.8, + title: "Agent Features (Phase 1)", titleColor: C.accent4, + items: [ + "Agent status toggle (Ready / Not Ready / Break)", + "Global search across patients, leads, calls", + "Enquiry form for new patient intake", + "My Performance page + logout modal", + ], + }); + + addCard(slide, { + x: 5.2, y: 3.55, w: 4.5, h: 1.8, + title: "Recording Analysis", titleColor: C.accent4, + items: [ + "Deepgram diarization + AI insights", + "Redis caching layer for analysis results", + "Full-stack: frontend player + server module", + ], + }); + + addSlideNumber(slide, 5, TOTAL); + } + + // ═══════════════════════════════════════════════════════════════════════ + // SLIDE 6 — Backend & Data + // ═══════════════════════════════════════════════════════════════════════ + { + const slide = pptx.addSlide(); + slide.background = { color: C.bg }; + addAccentBar(slide, C.accent2); + + addLabel(slide, "Backend & Data", C.accent2, 0.5, 0.3); + + slide.addText([ + { text: "⚙ ", options: { fontSize: 22 } }, + { text: "Backend & Data Layer", options: { fontSize: 22, bold: true, color: C.text } }, + ], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading }); + + addCard(slide, { + x: 0.3, y: 1.35, w: 4.5, h: 2.0, + title: "Platform Data Wiring", titleColor: C.accent2, + items: [ + "Migrated frontend to Jotai + Vercel AI SDK", + "Corrected all 7 GraphQL queries (fields, LINKS/PHONES)", + "Webhook handler for Ozonetel call records", + "Complete seeder: 5 doctors, appointments linked", + ], + }); + + addCard(slide, { + x: 5.2, y: 1.35, w: 4.5, h: 2.0, + title: "Server Endpoints", titleColor: C.accent2, + items: [ + "Call control, recording, CDR, missed calls, live assist", + "Agent summary, AHT, performance aggregation", + "Token refresh endpoint for auto-renewal", + "Search module with full-text capabilities", + ], + }); + + addCard(slide, { + x: 0.3, y: 3.55, w: 4.5, h: 1.8, + title: "Data Pages Built", titleColor: C.accent2, + items: [ + "Worklist table, call history, patients, dashboard", + "Reports, team dashboard, campaigns, settings", + "Agent detail page, campaign edit slideout", + "Appointments page with data refresh on login", + ], + }); + + addCard(slide, { + x: 5.2, y: 3.55, w: 4.5, h: 1.8, + title: "SDK App", titleColor: C.accent3, badge: "FortyTwoApps", + items: [ + "Helix Engage SDK app entity definitions", + "Call center CRM object model for platform", + "Foundation for platform-native data integration", + ], + }); + + addSlideNumber(slide, 6, TOTAL); + } + + // ═══════════════════════════════════════════════════════════════════════ + // SLIDE 7 — Deployment & Ops + // ═══════════════════════════════════════════════════════════════════════ + { + const slide = pptx.addSlide(); + slide.background = { color: C.bg }; + addAccentBar(slide, C.accent5); + + addLabel(slide, "Operations", C.accent5, 0.5, 0.3); + + slide.addText([ + { text: "🛠 ", options: { fontSize: 22 } }, + { text: "Deployment & DevOps", options: { fontSize: 22, bold: true, color: C.text } }, + ], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading }); + + addCard(slide, { + x: 0.3, y: 1.35, w: 3.05, h: 2.2, + title: "Deployment", titleColor: C.accent5, + items: [ + "Deployed to Hostinger VPS with Docker", + "Switched to global_healthx Ozonetel account", + "Dockerfile for server-side containerization", + ], + }); + + addCard(slide, { + x: 3.55, y: 1.35, w: 3.05, h: 2.2, + title: "AI & Testing", titleColor: C.accent5, + items: [ + "Migrated AI to Vercel AI SDK + OpenAI provider", + "AI flow test script — validates full pipeline", + "Live call assist integration", + ], + }); + + addCard(slide, { + x: 6.8, y: 1.35, w: 2.9, h: 2.2, + title: "Documentation", titleColor: C.accent5, + items: [ + "Team onboarding README with arch guide", + "Supervisor module spec + plan", + "Multi-agent spec + plan", + "Next session plans in commits", + ], + }); + + addSlideNumber(slide, 7, TOTAL); + } + + // ═══════════════════════════════════════════════════════════════════════ + // SLIDE 8 — Timeline + // ═══════════════════════════════════════════════════════════════════════ + { + const slide = pptx.addSlide(); + slide.background = { color: C.bg }; + addAccentBar(slide, C.accent6); + + addLabel(slide, "Day by Day", C.accent6, 0.5, 0.3); + + slide.addText([ + { text: "📅 ", options: { fontSize: 22 } }, + { text: "Development Timeline", options: { fontSize: 22, bold: true, color: C.text } }, + ], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading }); + + const timeline = [ + { date: "MAR 18 (Tue)", title: "Foundation Day", desc: "Call desk redesign, Jotai + AI SDK migration, seeder, AI flow test, VPS deploy" }, + { date: "MAR 19 (Wed)", title: "Data Layer Sprint", desc: "All data pages, post-call workflow, GraphQL fixes, Kookoo IVR, outbound UI" }, + { date: "MAR 20 (Thu)", title: "Telephony Breakthrough", desc: "Direct SIP replacing Kookoo, UCID tracking, Force Ready, Set Disposition" }, + { date: "MAR 21 (Fri)", title: "Agent Experience", desc: "Phase 1: status toggle, search, enquiry form, My Performance, FA icons, AHT" }, + { date: "MAR 23 (Sun)", title: "Scale & Reliability", desc: "Phase 2: missed call queue, auto-assign, Redis lockout, Patient 360, SDK defs" }, + { date: "MAR 24 (Mon)", title: "Supervisor Module", desc: "Team perf, live monitor, master data, SSE, UUID fix, maintenance, QA sweep" }, + { date: "MAR 25 (Tue)", title: "Intelligence Layer", desc: "Deepgram diarization, AI insights, SIP via Agent entity, token refresh, network" }, + ]; + + // Vertical line + slide.addShape("rect", { + x: 1.4, y: 1.3, w: 0.025, h: 4.0, + fill: { color: C.accent6, transparency: 60 }, + }); + + timeline.forEach((entry, i) => { + const y = 1.3 + i * 0.56; + + // Dot + slide.addShape("ellipse", { + x: 1.32, y: y + 0.08, w: 0.18, h: 0.18, + fill: { color: C.accent6 }, + line: { color: C.bg, width: 2 }, + }); + + // Date + slide.addText(entry.date, { + x: 1.7, y: y, w: 1.6, h: 0.22, + fontSize: 7, fontFace: FONT.heading, bold: true, + color: C.accent6, + }); + + // Title + slide.addText(entry.title, { + x: 3.3, y: y, w: 2.0, h: 0.22, + fontSize: 9, fontFace: FONT.heading, bold: true, + color: C.text, + }); + + // Description + slide.addText(entry.desc, { + x: 5.3, y: y, w: 4.2, h: 0.45, + fontSize: 8, fontFace: FONT.body, + color: C.textSec, + valign: "top", + }); + }); + + addSlideNumber(slide, 8, TOTAL); + } + + // ═══════════════════════════════════════════════════════════════════════ + // SLIDE 9 — Closing + // ═══════════════════════════════════════════════════════════════════════ + { + const slide = pptx.addSlide(); + slide.background = { color: C.bg }; + addAccentBar(slide, C.accent3); + + // Big headline + slide.addText("78 commits. 8 days. Ship mode.", { + x: 0.5, y: 1.4, w: 9, h: 0.8, + fontSize: 32, fontFace: FONT.heading, bold: true, + color: C.accent3, align: "center", + }); + + // Ship emoji + slide.addText("🚢", { + x: 4.2, y: 2.3, w: 1.6, h: 0.6, + fontSize: 28, align: "center", + }); + + // Description + slide.addText( + "From browser-native SIP calling to AI-powered recording analysis — Helix Engage is becoming a production contact center platform.", + { + x: 1.5, y: 3.0, w: 7, h: 0.6, + fontSize: 11, fontFace: FONT.body, + color: C.textSec, align: "center", + lineSpacingMultiple: 1.3, + } + ); + + // Achievement pills + const achievements = [ + { text: "SIP Calling ✓", color: C.accent1 }, + { text: "Multi-Agent ✓", color: C.accent2 }, + { text: "Supervisor ✓", color: C.accent3 }, + { text: "AI Copilot ✓", color: C.accent4 }, + { text: "Recording Analysis ✓", color: C.accent5 }, + ]; + + achievements.forEach((a, i) => { + const x = 0.8 + i * 1.8; + slide.addShape("roundRect", { + x, y: 3.9, w: 1.6, h: 0.35, + fill: { color: C.bgCard }, + line: { color: a.color, width: 1 }, + rectRadius: 0.17, + }); + slide.addText(a.text, { + x, y: 3.9, w: 1.6, h: 0.35, + fontSize: 8, fontFace: FONT.heading, bold: true, + color: a.color, align: "center", valign: "middle", + }); + }); + + // Author + slide.addText("Satya Suman Sari · FortyTwo Platform", { + x: 2, y: 4.7, w: 6, h: 0.3, + fontSize: 9, fontFace: FONT.body, + color: C.textMuted, align: "center", + }); + + addSlideNumber(slide, 9, TOTAL); + } + + // ── Save ────────────────────────────────────────────────────────────── + const outPath = "weekly-update-mar18-25.pptx"; + await pptx.writeFile({ fileName: outPath }); + console.log(`✅ Presentation saved: ${outPath}`); +} + +build().catch(err => { + console.error("❌ Failed:", err.message); + process.exit(1); +}); diff --git a/docs/weekly-update-mar18-25.html b/docs/weekly-update-mar18-25.html new file mode 100644 index 0000000..b9cc274 --- /dev/null +++ b/docs/weekly-update-mar18-25.html @@ -0,0 +1,886 @@ + + +
+ + +Select a lead from the worklist to see their full profile.
-{formatPhone(phone)}
} - {email &&{email}
} -Interested in: {lead.interestedService}
- )} -Loading patient details...
- )} - {isReturning && appointments.length > 0 && ( -- {appt.scheduledAt ? formatShortDate(appt.scheduledAt) : ''} - {appt.reasonForVisit ? ` — ${appt.reasonForVisit}` : ''} -
+ {/* Lead profile */} +{formatPhone(phone)}
} + {email &&{email}
} +Interested in: {lead.interestedService}
+ )} + + {/* AI Insight — live from platform */} + {(lead.aiSummary || lead.aiSuggestedAction) && ( +{lead.aiSummary}
} + {lead.aiSuggestedAction && ( +{lead.aiSuggestedAction}
+ )} +{lead.aiSummary}
} - {lead.aiSuggestedAction && ( -{lead.aiSuggestedAction}
+ {loadingPatient &&Loading patient details...
} + + {/* Recent activity */} + {leadActivities.length > 0 && ( +{a.summary}
++ {a.activityType?.replace(/_/g, ' ')}{a.occurredAt ? ` · ${formatShortDate(a.occurredAt)}` : ''} +
+{a.summary}
-- {a.activityType}{a.occurredAt ? ` · ${formatShortDate(a.occurredAt)}` : ''} -
-Select a lead from the worklist to see context