Loading...
Loading...
Compare original and translation side by side
borders: noneshading: noneparagraph.borders.bottom = { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" }doc.addParagraph()thematicBreakborders: noneshading: noneparagraph.borders.bottom = { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" }doc.addParagraph()thematicBreakconst docx = require("docx");
const {
Document, Paragraph, TextRun, Table, TableRow, TableCell,
WidthType, AlignmentType, BorderStyle, ShadingType,
Header, Footer, PageNumber, HeadingLevel, TableLayoutType,
convertInchesToTwip
} = docx;
// ── Color constants ──
const COLORS = {
PRIMARY: "1F3864",
ACCENT: "2E75B6",
TABLE_HEADER_FILL: "D6E4F0",
TABLE_ALT_ROW: "F2F2F2",
TABLE_BORDER: "CCCCCC",
HEADER_TEXT: "FFFFFF",
FOOTER_TEXT: "666666",
};
const FONT = "Arial";
// ── 1. createHeaderBanner ──
// Returns an array of docx elements: [banner paragraph, key-value table]
function createHeaderBanner(companyName, leftFields, rightFields) {
// leftFields / rightFields: arrays of { label: string, value: string }
const banner = new Paragraph({
children: [
new TextRun({
text: companyName,
bold: true,
size: 36, // 18pt
color: COLORS.HEADER_TEXT,
font: FONT,
}),
],
shading: { type: ShadingType.CLEAR, color: "auto", fill: COLORS.PRIMARY },
spacing: { after: 0 },
alignment: AlignmentType.LEFT,
});
function buildCellParagraphs(fields) {
return fields.map(
(f) =>
new Paragraph({
children: [
new TextRun({ text: f.label + " ", bold: true, size: 18, font: FONT }),
new TextRun({ text: f.value, size: 18, font: FONT }),
],
spacing: { after: 40 },
})
);
}
const noBorder = { style: BorderStyle.NONE, size: 0, color: "FFFFFF" };
const noBorders = { top: noBorder, bottom: noBorder, left: noBorder, right: noBorder };
const noShading = { type: ShadingType.CLEAR, color: "auto", fill: "FFFFFF" };
const kvTable = new Table({
rows: [
new TableRow({
children: [
new TableCell({
children: buildCellParagraphs(leftFields),
width: { size: 50, type: WidthType.PERCENTAGE },
borders: noBorders,
shading: noShading,
}),
new TableCell({
children: buildCellParagraphs(rightFields),
width: { size: 50, type: WidthType.PERCENTAGE },
borders: noBorders,
shading: noShading,
}),
],
}),
],
width: { size: 100, type: WidthType.PERCENTAGE },
});
return [banner, kvTable];
}
// ── 2. createSectionHeader ──
// Returns a single Paragraph with bottom border rule
function createSectionHeader(text) {
return new Paragraph({
children: [
new TextRun({
text: text,
bold: true,
size: 22, // 11pt
color: COLORS.PRIMARY,
font: FONT,
}),
],
spacing: { before: 240, after: 0 }, // 12pt before, 0pt after
border: {
bottom: { style: BorderStyle.SINGLE, size: 1, color: COLORS.TABLE_BORDER },
},
});
}
// ── 3. createTable ──
// headers: string[], rows: string[][], options: { accentHeader?, fontSize? }
function createTable(headers, rows, options = {}) {
const fontSize = options.fontSize || 17; // 8.5pt default
const headerFill = options.accentHeader ? COLORS.ACCENT : COLORS.TABLE_HEADER_FILL;
const headerTextColor = options.accentHeader ? COLORS.HEADER_TEXT : "000000";
const cellBorders = {
top: { style: BorderStyle.SINGLE, size: 1, color: COLORS.TABLE_BORDER },
bottom: { style: BorderStyle.SINGLE, size: 1, color: COLORS.TABLE_BORDER },
left: { style: BorderStyle.SINGLE, size: 1, color: COLORS.TABLE_BORDER },
right: { style: BorderStyle.SINGLE, size: 1, color: COLORS.TABLE_BORDER },
};
const cellMargins = { top: 40, bottom: 40, left: 80, right: 80 };
function isNumeric(val) {
if (typeof val !== "string") return false;
const cleaned = val.replace(/[,$%()]/g, "").trim();
return cleaned !== "" && !isNaN(cleaned);
}
// Header row
const headerRow = new TableRow({
children: headers.map(
(h) =>
new TableCell({
children: [
new Paragraph({
children: [
new TextRun({
text: h,
bold: true,
size: fontSize,
color: headerTextColor,
font: FONT,
}),
],
}),
],
shading: { type: ShadingType.CLEAR, color: "auto", fill: headerFill },
borders: cellBorders,
margins: cellMargins,
})
),
});
// Data rows with alternating shading
const dataRows = rows.map((row, rowIdx) => {
const fill = rowIdx % 2 === 1 ? COLORS.TABLE_ALT_ROW : "FFFFFF";
return new TableRow({
children: row.map((cell, colIdx) => {
const align = colIdx > 0 && isNumeric(cell)
? AlignmentType.RIGHT
: AlignmentType.LEFT;
return new TableCell({
children: [
new Paragraph({
children: [
new TextRun({ text: cell, size: fontSize, font: FONT }),
],
alignment: align,
}),
],
shading: { type: ShadingType.CLEAR, color: "auto", fill: fill },
borders: cellBorders,
margins: cellMargins,
});
}),
});
});
return new Table({
rows: [headerRow, ...dataRows],
width: { size: 100, type: WidthType.PERCENTAGE },
});
}
// ── 4. createBulletList ──
// items: string[], style: "synthesis" | "informational"
function createBulletList(items, style = "synthesis") {
const indent =
style === "synthesis"
? { left: 360, hanging: 180 } // 360 DXA left, hanging indent for bullet
: { left: 180 }; // 180 DXA, no hanging
return items.map(
(item) =>
new Paragraph({
children: [
new TextRun({ text: "• ", font: FONT, size: 18 }),
new TextRun({ text: item, font: FONT, size: 18 }),
],
indent: indent,
spacing: { after: 60 },
})
);
}
// ── 5. createFooter ──
// date: string (e.g., "February 23, 2026")
function createFooter(date) {
return new Footer({
children: [
new Paragraph({
children: [
new TextRun({
text: `Data: S&P Capital IQ via Kensho | Analysis: AI-generated | ${date}`,
italics: true,
size: 14, // 7pt
color: COLORS.FOOTER_TEXT,
font: FONT,
}),
],
alignment: AlignmentType.CENTER,
}),
new Paragraph({
children: [
new TextRun({
text: "For informational purposes only. Not investment advice.",
italics: true,
size: 14,
color: COLORS.FOOTER_TEXT,
font: FONT,
}),
],
alignment: AlignmentType.CENTER,
}),
],
});
}createHeaderBanner(...)createSectionHeader(...)createTable(...){ accentHeader: true }createBulletList(items, "synthesis")createBulletList(items, "informational")createFooter(date)footers.defaultShadingType.CLEARborder.bottomborders: none•const docx = require("docx");
const {
Document, Paragraph, TextRun, Table, TableRow, TableCell,
WidthType, AlignmentType, BorderStyle, ShadingType,
Header, Footer, PageNumber, HeadingLevel, TableLayoutType,
convertInchesToTwip
} = docx;
// ── Color constants ──
const COLORS = {
PRIMARY: "1F3864",
ACCENT: "2E75B6",
TABLE_HEADER_FILL: "D6E4F0",
TABLE_ALT_ROW: "F2F2F2",
TABLE_BORDER: "CCCCCC",
HEADER_TEXT: "FFFFFF",
FOOTER_TEXT: "666666",
};
const FONT = "Arial";
// ── 1. createHeaderBanner ──
// Returns an array of docx elements: [banner paragraph, key-value table]
function createHeaderBanner(companyName, leftFields, rightFields) {
// leftFields / rightFields: arrays of { label: string, value: string }
const banner = new Paragraph({
children: [
new TextRun({
text: companyName,
bold: true,
size: 36, // 18pt
color: COLORS.HEADER_TEXT,
font: FONT,
}),
],
shading: { type: ShadingType.CLEAR, color: "auto", fill: COLORS.PRIMARY },
spacing: { after: 0 },
alignment: AlignmentType.LEFT,
});
function buildCellParagraphs(fields) {
return fields.map(
(f) =>
new Paragraph({
children: [
new TextRun({ text: f.label + " ", bold: true, size: 18, font: FONT }),
new TextRun({ text: f.value, size: 18, font: FONT }),
],
spacing: { after: 40 },
})
);
}
const noBorder = { style: BorderStyle.NONE, size: 0, color: "FFFFFF" };
const noBorders = { top: noBorder, bottom: noBorder, left: noBorder, right: noBorder };
const noShading = { type: ShadingType.CLEAR, color: "auto", fill: "FFFFFF" };
const kvTable = new Table({
rows: [
new TableRow({
children: [
new TableCell({
children: buildCellParagraphs(leftFields),
width: { size: 50, type: WidthType.PERCENTAGE },
borders: noBorders,
shading: noShading,
}),
new TableCell({
children: buildCellParagraphs(rightFields),
width: { size: 50, type: WidthType.PERCENTAGE },
borders: noBorders,
shading: noShading,
}),
],
}),
],
width: { size: 100, type: WidthType.PERCENTAGE },
});
return [banner, kvTable];
}
// ── 2. createSectionHeader ──
// Returns a single Paragraph with bottom border rule
function createSectionHeader(text) {
return new Paragraph({
children: [
new TextRun({
text: text,
bold: true,
size: 22, // 11pt
color: COLORS.PRIMARY,
font: FONT,
}),
],
spacing: { before: 240, after: 0 }, // 12pt before, 0pt after
border: {
bottom: { style: BorderStyle.SINGLE, size: 1, color: COLORS.TABLE_BORDER },
},
});
}
// ── 3. createTable ──
// headers: string[], rows: string[][], options: { accentHeader?, fontSize? }
function createTable(headers, rows, options = {}) {
const fontSize = options.fontSize || 17; // 8.5pt default
const headerFill = options.accentHeader ? COLORS.ACCENT : COLORS.TABLE_HEADER_FILL;
const headerTextColor = options.accentHeader ? COLORS.HEADER_TEXT : "000000";
const cellBorders = {
top: { style: BorderStyle.SINGLE, size: 1, color: COLORS.TABLE_BORDER },
bottom: { style: BorderStyle.SINGLE, size: 1, color: COLORS.TABLE_BORDER },
left: { style: BorderStyle.SINGLE, size: 1, color: COLORS.TABLE_BORDER },
right: { style: BorderStyle.SINGLE, size: 1, color: COLORS.TABLE_BORDER },
};
const cellMargins = { top: 40, bottom: 40, left: 80, right: 80 };
function isNumeric(val) {
if (typeof val !== "string") return false;
const cleaned = val.replace(/[,$%()]/g, "").trim();
return cleaned !== "" && !isNaN(cleaned);
}
// Header row
const headerRow = new TableRow({
children: headers.map(
(h) =>
new TableCell({
children: [
new Paragraph({
children: [
new TextRun({
text: h,
bold: true,
size: fontSize,
color: headerTextColor,
font: FONT,
}),
],
}),
],
shading: { type: ShadingType.CLEAR, color: "auto", fill: headerFill },
borders: cellBorders,
margins: cellMargins,
})
),
});
// Data rows with alternating shading
const dataRows = rows.map((row, rowIdx) => {
const fill = rowIdx % 2 === 1 ? COLORS.TABLE_ALT_ROW : "FFFFFF";
return new TableRow({
children: row.map((cell, colIdx) => {
const align = colIdx > 0 && isNumeric(cell)
? AlignmentType.RIGHT
: AlignmentType.LEFT;
return new TableCell({
children: [
new Paragraph({
children: [
new TextRun({ text: cell, size: fontSize, font: FONT }),
],
alignment: align,
}),
],
shading: { type: ShadingType.CLEAR, color: "auto", fill: fill },
borders: cellBorders,
margins: cellMargins,
});
}),
});
});
return new Table({
rows: [headerRow, ...dataRows],
width: { size: 100, type: WidthType.PERCENTAGE },
});
}
// ── 4. createBulletList ──
// items: string[], style: "synthesis" | "informational"
function createBulletList(items, style = "synthesis") {
const indent =
style === "synthesis"
? { left: 360, hanging: 180 } // 360 DXA left, hanging indent for bullet
: { left: 180 }; // 180 DXA, no hanging
return items.map(
(item) =>
new Paragraph({
children: [
new TextRun({ text: "• ", font: FONT, size: 18 }),
new TextRun({ text: item, font: FONT, size: 18 }),
],
indent: indent,
spacing: { after: 60 },
})
);
}
// ── 5. createFooter ──
// date: string (e.g., "February 23, 2026")
function createFooter(date) {
return new Footer({
children: [
new Paragraph({
children: [
new TextRun({
text: `Data: S&P Capital IQ via Kensho | Analysis: AI-generated | ${date}`,
italics: true,
size: 14, // 7pt
color: COLORS.FOOTER_TEXT,
font: FONT,
}),
],
alignment: AlignmentType.CENTER,
}),
new Paragraph({
children: [
new TextRun({
text: "For informational purposes only. Not investment advice.",
italics: true,
size: 14,
color: COLORS.FOOTER_TEXT,
font: FONT,
}),
],
alignment: AlignmentType.CENTER,
}),
],
});
}createHeaderBanner(...)createSectionHeader(...)createTable(...){ accentHeader: true }createBulletList(items, "synthesis")createBulletList(items, "informational")createFooter(date)footers.defaultShadingType.CLEARborder.bottomborders: none•references/equity-research.mdreferences/ib-ma.mdreferences/corp-dev.mdreferences/sales-bd.mdreferences/equity-research.mdreferences/ib-ma.mdreferences/corp-dev.mdreferences/sales-bd.mdmkdir -p /tmp/tear-sheet/mkdir -p /tmp/tear-sheet//tmp/tear-sheet/calculations.csvmetric,value,formula,componentsmetric,value,formula,components
gross_margin_fy2024,72.4%,gross_profit/revenue,"9524/13159"
revenue_growth_fy2024,12.3%,(current-prior)/prior,"13159/11716"
net_debt_fy2024,2150,total_debt-cash,"4200-2050"/tmp/tear-sheet/calculations.csvmetric,value,formula,componentsmetric,value,formula,components
gross_margin_fy2024,72.4%,gross_profit/revenue,"9524/13159"
revenue_growth_fy2024,12.3%,(current-prior)/prior,"13159/11716"
net_debt_fy2024,2150,total_debt-cash,"4200-2050"=== Tear Sheet Data Verification ===
company-profile.txt: ✓ (12 fields)
financials.csv: ✓ (36 rows)
segments.csv: ✓ (8 rows)
valuation.csv: ✓ (5 rows)
calculations.csv: ✓ (18 rows)
earnings.txt: ✓ (populated)
relationships.txt: ⚠ MISSING
peer-comps.csv: ✓ (12 rows)
=================================== 财务摘要文档数据验证 ===
company-profile.txt: ✓ (12个字段)
financials.csv: ✓ (36行)
segments.csv: ✓ (8行)
valuation.csv: ✓ (5行)
calculations.csv: ✓ (18行)
earnings.txt: ✓ (已填充)
relationships.txt: ⚠ 缺失
peer-comps.csv: ✓ (12行)
================================/mnt/skills/public/docx/SKILL.md[CompanyName]_TearSheet_[Audience]_[YYYYMMDD].docxNvidia_TearSheet_CorpDev_20260220.docx/mnt/user-data/outputs//mnt/skills/public/docx/SKILL.md[CompanyName]_TearSheet_[Audience]_[YYYYMMDD].docxNvidia_TearSheet_CorpDev_20260220.docx/mnt/user-data/outputs/mkdir -p /tmp/tear-sheet/| File | Format | Columns / Structure | Used By |
|---|---|---|---|
| Key-value text | name, ticker, exchange, HQ, sector, industry, founded, employees, market_cap, enterprise_value, stock_price, 52wk_high, 52wk_low, shares_outstanding, beta, ownership | All |
| CSV | | All |
| CSV | | ER, IB, CD |
| CSV | | ER, IB, CD |
| CSV | | ER |
| Structured text | Quarter, date, key quotes, guidance, key drivers | ER, IB, Sales |
| Structured text | Customers, suppliers, partners, competitors — each with descriptors | IB, CD, Sales |
| CSV | | ER, IB, CD |
| CSV | | IB, CD |
| CSV | | All (written in Step 3b) |
mkdir -p /tmp/tear-sheet/| 文件 | 格式 | 列 / 结构 | 使用者 |
|---|---|---|---|
| 键值文本 | name, ticker, exchange, HQ, sector, industry, founded, employees, market_cap, enterprise_value, stock_price, 52wk_high, 52wk_low, shares_outstanding, beta, ownership | 所有受众 |
| CSV | | 所有受众 |
| CSV | | ER, IB, CD |
| CSV | | ER, IB, CD |
| CSV | | ER |
| 结构化文本 | Quarter, date, key quotes, guidance, key drivers | ER, IB, Sales |
| 结构化文本 | Customers, suppliers, partners, competitors — 每个都有描述符 | IB, CD, Sales |
| CSV | | ER, IB, CD |
| CSV | | IB, CD |
| CSV | | 所有受众(步骤3b中写入) |