Files
MosaicIQ/packages/shared/src/export/pptx.ts

436 lines
9.1 KiB
TypeScript

/**
* PowerPoint export functionality
* Generates presentation decks from memos and models
*/
import PptxGenJS from "pptxgenjs";
import type { Memo, Company } from "@mosaiciq/contracts/rpc";
export interface PPTXOptions {
includeCharts?: boolean;
includeModel?: boolean;
maxBulletPoints?: number;
}
/**
* Export memo as PowerPoint presentation
*/
export async function exportPresentation(
memo: Memo,
company: Company,
options: PPTXOptions = {}
): Promise<Buffer> {
const {
includeCharts = true,
includeModel = true,
maxBulletPoints = 5,
} = options;
const pptx = new (PptxGenJS as any)();
pptx.author = "MosaicIQ";
pptx.company = "MosaicIQ Research";
pptx.title = `${company.name} Investment Committee Presentation`;
pptx.subject = "Investment Research";
const COLORS = {
primary: "4472C4",
secondary: "6C757D",
accent: "28A745",
warning: "FFC107",
danger: "DC3545",
dark: "363636",
light: "F8F9FA",
};
// Slide 1: Title slide
addTitleSlide(pptx, company, COLORS);
// Slide 2: Investment Thesis
const thesisSection = memo.sections.find((s) => s.id === "thesis");
if (thesisSection) {
addThesisSlide(pptx, thesisSection, COLORS);
}
// Slide 3: Key Drivers
const driversSection = memo.sections.find((s) => s.id === "drivers");
if (driversSection) {
addDriversSlide(pptx, driversSection, COLORS, maxBulletPoints);
}
// Slide 4: Business Quality
const qualitySection = memo.sections.find((s) => s.id === "quality");
if (qualitySection) {
addContentSlide(pptx, "Business Quality", qualitySection.content, COLORS);
}
// Slide 5: Financial Summary
const financialsSection = memo.sections.find((s) => s.id === "financials");
if (financialsSection) {
addContentSlide(pptx, "Financial Summary", financialsSection.content, COLORS);
}
// Slide 6: Valuation
const valuationSection = memo.sections.find((s) => s.id === "valuation");
if (valuationSection) {
addValuationSlide(pptx, valuationSection, COLORS);
}
// Slide 7: Key Risks
const risksSection = memo.sections.find((s) => s.id === "risks");
if (risksSection) {
addRisksSlide(pptx, risksSection.content, COLORS, maxBulletPoints);
}
// Slide 8: Catalysts
const catalystsSection = memo.sections.find((s) => s.id === "catalysts");
if (catalystsSection) {
addCatalystsSlide(pptx, catalystsSection.content, COLORS, maxBulletPoints);
}
// Slide 9: Conclusion/Recommendation
addConclusionSlide(pptx, company, COLORS);
// Generate buffer
const buffer = await pptx.write({ outputType: "nodebuffer", compression: false }) as Buffer;
return buffer;
}
function addTitleSlide(pptx: any, company: Company, COLORS: any) {
const slide = pptx.addSlide();
// Background color
slide.background = { color: COLORS.light };
// Company name and ticker
slide.addText(company.name, {
x: 0.5,
y: 1.5,
w: 9,
h: 0.8,
fontSize: 44,
bold: true,
color: COLORS.dark,
fontFace: "Arial",
});
slide.addText(`(${company.ticker})`, {
x: 0.5,
y: 2.2,
w: 9,
h: 0.4,
fontSize: 24,
color: COLORS.secondary,
fontFace: "Arial",
});
// Subtitle
slide.addText("Investment Committee Presentation", {
x: 0.5,
y: 3.0,
w: 9,
h: 0.5,
fontSize: 20,
color: COLORS.primary,
fontFace: "Arial",
});
// Date
slide.addText(new Date().toLocaleDateString(), {
x: 0.5,
y: 3.6,
w: 9,
h: 0.4,
fontSize: 16,
color: COLORS.secondary,
fontFace: "Arial",
});
// Divider line
slide.addShape("line", {
x: 0.5,
y: 4.2,
w: 9,
h: 0,
line: { color: COLORS.primary, width: 2 },
});
// Sector info
slide.addText(`Sector: ${company.sector}`, {
x: 0.5,
y: 4.5,
w: 9,
h: 0.4,
fontSize: 14,
color: COLORS.secondary,
fontFace: "Arial",
});
}
function addThesisSlide(pptx: any, section: any, COLORS: any) {
const slide = pptx.addSlide();
// Title
slide.addText("Investment Thesis", {
x: 0.5,
y: 0.5,
w: 9,
h: 0.5,
fontSize: 32,
bold: true,
color: COLORS.primary,
fontFace: "Arial",
});
// Content (truncated if too long)
const content = section.content.slice(0, 600) + (section.content.length > 600 ? "..." : "");
slide.addText(content, {
x: 0.5,
y: 1.2,
w: 9,
h: 4,
fontSize: 18,
color: COLORS.dark,
fontFace: "Arial",
align: "justify",
});
}
function addDriversSlide(pptx: any, section: any, COLORS: any, maxBullets: number) {
const slide = pptx.addSlide();
// Title
slide.addText("Key Value Drivers", {
x: 0.5,
y: 0.5,
w: 9,
h: 0.5,
fontSize: 32,
bold: true,
color: COLORS.primary,
fontFace: "Arial",
});
// Parse bullets (simplified - assumes line breaks separate bullets)
const lines = section.content.split("\n").filter((l: string) => l.trim().length > 0);
const bullets = lines.slice(0, maxBullets);
bullets.forEach((bullet: string, index: number) => {
slide.addText(bullet.trim(), {
x: 0.5,
y: 1.2 + index * 0.5,
w: 9,
h: 0.4,
fontSize: 18,
color: COLORS.dark,
fontFace: "Arial",
bullet: true,
});
});
}
function addValuationSlide(pptx: any, section: any, COLORS: any) {
const slide = pptx.addSlide();
// Title
slide.addText("Valuation", {
x: 0.5,
y: 0.5,
w: 9,
h: 0.5,
fontSize: 32,
bold: true,
color: COLORS.primary,
fontFace: "Arial",
});
// Content
const content = section.content.slice(0, 500);
slide.addText(content, {
x: 0.5,
y: 1.2,
w: 9,
h: 3,
fontSize: 16,
color: COLORS.dark,
fontFace: "Arial",
align: "justify",
});
// Valuation summary box
slide.addShape("rect", {
x: 0.5,
y: 4.5,
w: 9,
h: 1.5,
fill: { color: COLORS.light },
line: { color: COLORS.primary, width: 1 },
});
slide.addText("Valuation Summary", {
x: 0.7,
y: 4.7,
w: 8.6,
h: 0.3,
fontSize: 14,
bold: true,
color: COLORS.primary,
fontFace: "Arial",
});
}
function addRisksSlide(pptx: any, content: string, COLORS: any, maxBullets: number) {
const slide = pptx.addSlide();
// Title
slide.addText("Key Risks", {
x: 0.5,
y: 0.5,
w: 9,
h: 0.5,
fontSize: 32,
bold: true,
color: COLORS.danger,
fontFace: "Arial",
});
// Parse bullets
const lines = content.split("\n").filter((l) => l.trim().length > 0);
const bullets = lines.slice(0, maxBullets);
bullets.forEach((bullet, index) => {
slide.addText(bullet.trim(), {
x: 0.5,
y: 1.2 + index * 0.5,
w: 9,
h: 0.4,
fontSize: 16,
color: COLORS.dark,
fontFace: "Arial",
bullet: true,
});
});
}
function addCatalystsSlide(pptx: any, content: string, COLORS: any, maxBullets: number) {
const slide = pptx.addSlide();
// Title
slide.addText("Upcoming Catalysts", {
x: 0.5,
y: 0.5,
w: 9,
h: 0.5,
fontSize: 32,
bold: true,
color: COLORS.accent,
fontFace: "Arial",
});
// Parse bullets
const lines = content.split("\n").filter((l) => l.trim().length > 0);
const bullets = lines.slice(0, maxBullets);
bullets.forEach((bullet, index) => {
slide.addText(bullet.trim(), {
x: 0.5,
y: 1.2 + index * 0.5,
w: 9,
h: 0.4,
fontSize: 16,
color: COLORS.dark,
fontFace: "Arial",
bullet: true,
});
});
}
function addContentSlide(pptx: any, title: string, content: string, COLORS: any) {
const slide = pptx.addSlide();
// Title
slide.addText(title, {
x: 0.5,
y: 0.5,
w: 9,
h: 0.5,
fontSize: 32,
bold: true,
color: COLORS.primary,
fontFace: "Arial",
});
// Content
const truncated = content.slice(0, 700);
slide.addText(truncated, {
x: 0.5,
y: 1.2,
w: 9,
h: 5,
fontSize: 16,
color: COLORS.dark,
fontFace: "Arial",
align: "justify",
});
}
function addConclusionSlide(pptx: any, company: Company, COLORS: any) {
const slide = pptx.addSlide();
// Title
slide.addText("Conclusion & Recommendation", {
x: 0.5,
y: 0.5,
w: 9,
h: 0.5,
fontSize: 32,
bold: true,
color: COLORS.primary,
fontFace: "Arial",
});
// Recommendation box
slide.addShape("rect", {
x: 0.5,
y: 1.2,
w: 9,
h: 2,
fill: { color: "E7F3FF" },
line: { color: COLORS.primary, width: 2 },
});
slide.addText("Investment Recommendation", {
x: 0.7,
y: 1.4,
w: 8.6,
h: 0.4,
fontSize: 18,
bold: true,
color: COLORS.primary,
fontFace: "Arial",
});
// Disclaimer
slide.addText(
"This presentation is for informational purposes only and does not constitute investment advice. " +
"Please refer to the full investment memo for detailed analysis and disclosures.",
{
x: 0.5,
y: 5.5,
w: 9,
h: 1,
fontSize: 10,
color: COLORS.secondary,
fontFace: "Arial",
align: "center",
}
);
}
/**
* Generate filename for presentation export
*/
export function getPresentationFilename(company: Company): string {
const date = new Date().toISOString().split("T")[0];
return `${company.ticker}_Presentation_${date}.pptx`;
}