Plans006 2026 02 20 Present Service
Set Up @services/present
Overview
Build the @services/present PPTX + PDF generator. Implements a PresentAdapter interface making the present layer backend-agnostic. The default implementation uses pptxgenjs to produce editable PPTX files. LibreOffice CLI converts PPTX to PDF. All 6 slide types from the reference PDF are implemented with per-unit brand colors (WLC: blue, TM: dark red).
Result: bun run present --entity IONS --period 2026-02 reads report.json and produces output/monthly/2026-02.pptx and output/monthly/2026-02.pdf.
Goals
- Define
PresentAdapterinterface + output types for backend-agnostic rendering - Implement
PptxAdapter(default) usingpptxgenjs - Implement all 6 slide types matching the reference PDF layout and style
- Wire PDF conversion via LibreOffice CLI (
libreoffice --headless --convert-to pdf) - Wire CLI:
--reportfor direct JSON input, or--entity + --periodto invoke Format then Present - Verify: real PPTX + PDF produced from 2026-02 data matching the reference PDF structure
Non-Goals
- Google Slides adapter (future — interface stub only)
- NotebookLM adapter via
teng-lin/notebooklm-py(future — interface stub only) - Component reuse with
@services/dashboard— deferred (see Open Questions in architecture.md) - Interactive or animated slides
- CI/CD or automated deployment
- Cloudflare Workers deployment (present is a local CLI, not a web service)
Phases
- Phase 1: Package scaffold + PresentAdapter interface
- Phase 2: PptxAdapter implementation
- Phase 3: Slide implementations (all 6 types)
- Phase 4: Generator orchestration + LibreOffice PDF
- Phase 5: CLI wiring + end-to-end verification
Success
-
@services/presentscaffolded as Bun workspace package -
PresentAdapterinterface defined with stubGoogleSlidesAdapterandNotebookLMAdapterplaceholders -
PptxAdapterimplementsPresentAdapterusingpptxgenjs - All 6 slide types implemented and producing output
- Slide colors match reference: WLC units blue (
#1565c0), TM unit dark red (#7b1111), others default blue -
bun run present --entity IONS --period 2026-02exits 0 -
output/monthly/2026-02.pptxexists and opens in PowerPoint/LibreOffice -
output/monthly/2026-02.pdfexists (requires LibreOffice installed) -
bun run test:type --filter @services/presentpasses
Requirements
- Plan 004 completed:
report.jsonexists atoutput/monthly/2026-02-report.json pptxgenjsavailable (add to workspace catalogs)- LibreOffice installed locally (
libreoffice --versionworks) for PDF conversion @repo/typescript/libtsconfig preset@repo/lint/baseESLint config- Reference PDF:
source/Contoh punya TM WLC.pdfas visual spec
Context
Why This Approach
pptxgenjs: produces native PPTX that users can open and edit in PowerPoint/LibreOffice — required per user requirementPresentAdapterinterface: decouples slide generation logic from the rendering backend; future adapters (Google Slides, etc.) plug in without changing the generator or CLI- LibreOffice CLI for PDF: simple, reliable, no additional dependencies beyond what's likely already installed;
--headless --convert-to pdfis well-tested - Adapter stubs: Google Slides and NotebookLM adapters are stubbed (throw NotImplemented) so the interface contract is established before implementation
- Slide colors per unit: WLC=blue, TM=dark red matches the reference PDF exactly; other units use a default blue
Key Constraints
pptxgenjsuses inches for positioning/sizing, not CSS pixels — all layout values are in inches- Slide dimensions: 13.33" × 7.5" (standard widescreen 16:9)
- Cover slide: gradient background (dark blue → teal), bold title, two brand logos
- Revenue comparison slide: bar chart (pptxgenjs
addChart) + summary table above - Key comparison slide: 5-column table (Parameter, Last Year, Target, Actuals, Gap%, Status) with color-coded Status column
- Program progress slide: 4-column table (Program, Last Year, Target, Actuals) with Status
- School progress slide: dense matrix table — organizations as rows, years as columns; highlight cells with data
- Channel marketing slide: 6-column table (Channel, Leads, Follow-ups, Closings, Contribution%, Status/Strategy)
- Status badge colors: Completed=green, On Track=light blue, Needs Attention=salmon/red, High Performer=green, Solid=blue, Potensial=yellow
- Each unit gets its own set of slides (cover is shared); slide order: cover → [revenue, key, program, school per unit] → [channel marketing combined]
Edge Cases
- LibreOffice not installed: PDF step fails gracefully with a clear error message; PPTX is still produced
--reportflag with a path that doesn't exist: clear error- Unit in report with no data for a section: slide rendered with empty table/chart (no crash)
- Long organization names in school progress slide: truncate to fit cell width
Tradeoffs
- pptxgenjs coordinate system (inches): more verbose layout code vs CSS, but no alternative for native PPTX
- Monochrome charts (pptxgenjs bar charts): matching exact reference colors requires explicit
fillconfig per bar; defaulting to solid unit color per chart is acceptable - LibreOffice PDF: output quality is slightly lower than native PDF renderer, but avoids heavy dependencies
Skills
pptx— pptxgenjs patterns and slide layout conventionsplan— plan file format and conventions
Boundaries
- Always: Update Progress.md after each task completion
- Always: Consult reference PDF (
source/Contoh punya TM WLC.pdf) for layout decisions - Always: Slide dimensions in inches (pptxgenjs convention)
- Ask first: Any layout deviation from the reference PDF
- Ask first: Adding a new adapter implementation (stubs only in this plan)
- Never: Apply business logic in the Present layer — consume report.json as-is
- Never: Modify report.json — present is read-only on the format output
Questions
- PPTX required? → Yes, users need to edit slides
- PDF via LibreOffice? → Yes
- Adapter pattern? → Yes — PresentAdapter interface with pptxgenjs as default; Google Slides and NotebookLM as future stubs
- Component reuse with dashboard? → Deferred to architecture.md Open Questions
- Should the CLI skip PDF generation if LibreOffice is not installed (warn only), or hard-fail?
- Should Google Slides and NotebookLM adapter stubs live in this package or in separate packages?