mcpField
T
tear-sheet

tear-sheet

github.com/anthropics文档处理40.9k1.7k 次安装今天更新
代码仓库

关于 tear-sheet

通过 S&P Global MCP 工具获取实时数据,并生成面向不同受众的公司 Word 概览。技能规定了四类受众的数据收集流程和文档布局。

主要功能

  1. 01四类受众专用概览格式
  2. 02获取实时财务与公司数据
  3. 03生成 Word 文档
  4. 04可复用的布局与样式函数
  5. 05来源及免责声明页脚

适用场景

  1. 01准备股票研究公司简介
  2. 02制作 IB / M&A 交易概览
  3. 03供 Corp Dev 评估收购目标
  4. 04为销售会议准备公司资料

MCP 相关

使用 S&P Global MCP 工具检索结构化的公司、财务、市场及相关数据,用于生成概览。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/anthropics/financial-services --skill tear-sheet

安装后在 Claude Code 中输入/tear-sheet确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

LICENSE
references/
corp-dev.md
equity-research.md
ib-ma.md
sales-bd.md

详细介绍

财务概况表生成器

通过 S&P Global MCP 工具从 S&P Capital IQ 拉取实时数据,生成面向特定受众的公司概况表,并将结果格式化为专业的 Word 文档。

样式配置

以下为合理的默认值。如需按贵公司品牌进行定制,请修改本节内容 —— 常见修改包括更换配色方案、更改字体(Calibri 是许多银行的标准字体)以及更新免责声明文本。

颜色:

  • 主色(页眉横幅背景、章节标题文字):#1F3864
  • 强调色(签名区块高亮):#2E75B6
  • 表头行填充色:#D6E4F0
  • 表格交替行填充色:#F2F2F2
  • 表格边框:#CCCCCC
  • 页眉横幅文字:#FFFFFF

字体排版(字号以半磅为单位,用于 docx-js):

  • 字体族:Arial
  • 公司名称:18pt 粗体(size: 36)
  • 章节标题:11pt 粗体(size: 22),主色
  • 正文:9pt(size: 18)
  • 表格文字:8.5pt(size: 17)
  • 页脚/免责声明:7pt 斜体(size: 14)
  • 各模板的覆盖设置已在每个参考文件的“格式说明”中指定。

公司页眉横幅:

  • 页眉为一条横跨整个页面宽度的藏青色(#1F3864)横幅,公司名称以白色显示。
  • 横幅下方,键值对必须以一个横跨整个页面宽度的两列无边框表格呈现。 左列:公司标识信息(股票代码、总部、成立时间、员工人数、行业板块)。右列:财务标识信息(市值、企业价值、股价、流通股数)。每个单元格在同一行内包含一个粗体标签和一个常规字重的值(例如“市值 $124.7B”)。不要将所有字段左对齐放在单列中 —— 这会浪费水平空间,看起来也不专业。两列分布是区分专业概况表与默认文档的最重要视觉标志。
    • 实现方式: 创建一个 2 列表格,所有单元格设置 borders: none 和 shading: none。将列宽各设为 50%。将左列字段(股票代码、总部、成立时间、员工人数)作为独立段落放入左侧单元格。将右列字段(市值、企业价值、股价、流通股数)放入右侧单元格。每个字段为单个段落:标签使用粗体 run,值使用常规 run。
    • 各列中的具体字段因受众而异 —— 参见参考文件的页眉规范。原则始终是:横跨页面分布,而不是挤在左侧。
  • 页眉键值块不要使用带边框的表格。 带边框的表格仅保留用于财务数据。
  • 页眉中的关键指标(市值、企业价值、股价)应以内联键值对形式显示,而不是放在单独的带边框表格中。

章节标题:

  • 每个章节标题正下方有一条水平分隔线(细线,#CCCCCC,0.5pt),用于在各章节之间形成清晰的视觉分隔。
  • 将分隔线渲染为标题段落本身的底部边框 —— 不要为分隔线插入单独的段落元素。单独段落会带来自身的段前/段后间距,导致章节标题下方出现过多空白。
  • 实现方式: 在 docx-js 中,通过 paragraph.borders.bottom = { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" } 为章节标题段落应用底部边框。不要使用带单独水平分隔线元素的 doc.addParagraph()。不要使用 thematicBreak。边框必须应用在标题段落本身,且段后间距为 0pt,这样分隔线才会紧贴标题文字。
  • 间距:标题段落前 12pt,标题段落后 0pt,下一个内容元素前 4pt。 项目符号格式:
  • 在所有 tear sheet 类型中,所有项目符号内容均使用单一项目符号字符(•)。不要在同一 tear sheet 内或不同 tear sheet 之间混用 •、-、▸ 或编号列表。
  • 综合分析类项目符号(Earnings Highlights、Strategic Fit、Integration Considerations、Conversation Starters):采用缩进块式格式,左缩进 360 DXA(0.25"),并为项目符号字符设置悬挂缩进。这些内容应在视觉上与正文文本错开——它们属于解读性内容,应与数据表格和叙述性段落有不同观感。
  • 关系部分中的信息类项目符号:使用标准正文缩进(180 DXA),无悬挂缩进。
  • 不要对任何项目符号部分应用左边框强调样式。 左边框样式在 docx-js 中渲染不一致,并会产生视觉伪影。请改用缩进和字号差异来区分标志性部分。

表格(仅限财务数据):

  • 表头行:Table Header Fill(#D6E4F0),搭配加粗深色文本
  • 正文行:白色 / Table Alternating Fill(#F2F2F2)交替
  • 边框:Table Border 颜色(#CCCCCC),细线(BorderStyle.SINGLE,size 1)
  • 单元格内边距:上下 40 DXA,左右 80 DXA
  • 所有数值列右对齐
  • 始终使用 ShadingType.CLEAR(切勿使用 SOLID——SOLID 会导致黑色背景)

布局:

  • US Letter 纵向,0.75" 页边距(四边均为 1080 DXA)

数字格式:

  • 货币:USD。除非公司营收 > $50B,否则使用百万为单位(若超过则以十亿为单位,保留一位小数)。单位标注在列标题中(例如 "Revenue ($M)"),而不是标注在各个单元格中。
  • 表格单元格:纯数字加逗号,不带美元符号。 示例:营收单元格显示 "4,916" 而非 "$4,916"。单位由列标题承载。
  • 财年:使用实际年份(FY2022、FY2023、FY2024),绝不使用相对标签(FY-2、FY-1)。
  • 负数:使用括号,例如 (2.3%)
  • 百分比:保留一位小数
  • 大数字:使用逗号作为千位分隔符

页脚(文档页脚,而非内嵌): 将来源署名和免责声明放在实际文档页脚中(每页重复出现),而不是作为底部的内嵌正文文本。页脚在每页均为两行、居中:

  • 第 1 行:"Data: S&P Capital IQ via Kensho | Analysis: AI-generated | [Month Day, Year]"
  • 第 2 行:"For informational purposes only. Not investment advice."
  • 样式:7pt 斜体、居中、#666666 文本颜色
  • 对于同一家公司,该页脚文本必须在所有 tear sheet 类型中完全一致。不要因受众不同而改变措辞。
  • 每个 tear sheet、每种受众类型、每一页都必须包含此页脚。 不要省略。

组件函数

你必须使用这些确切的函数来创建文档元素。不要编写自定义 docx-js 样式代码。 将这些函数复制到生成的 Node 脚本中并调用它们。上述样式配置说明仍作为文档保留;这些函数才是强制执行机制。

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,
      }),
    ],
  });
}

在生成的脚本中的用法:

  1. 将上述所有函数和常量复制到生成的 Node.js 脚本中
  2. 调用 createHeaderBanner(...),而不是手动构建横幅段落和表格
  3. 为每个章节标题调用 createSectionHeader(...) —— 绝不要手动设置段落边框
  4. 为所有表格数据调用 createTable(...) —— 财务摘要、交易可比公司、并购活动、关系表、融资历史等。对于并购活动表格(IB/M&A 模板),传入 { accentHeader: true }。对于非数值表格(例如关系、所有权),该函数仍能正常工作 —— 它只会右对齐包含数值的单元格。
  5. 对于财报亮点、战略契合度、整合考量和对话开场白,调用 createBulletList(items, "synthesis")
  6. 对于关系条目,调用 createBulletList(items, "informational")
  7. 将 createFooter(date) 传给 Document 构造函数的 footers.default 属性

这些函数消除了什么:

  • 黑色背景表格(在所有地方强制使用 ShadingType.CLEAR)
  • 章节标题下方的独立水平分隔线段落(强制在段落本身设置 border.bottom)
  • 页眉中带边框的键值表格(强制设置 borders: none)
  • 不一致的项目符号样式(仅强制使用 • 字符)
  • 缺失的页脚(提供确切的页脚结构)

工作流

第 1 步:识别输入

在继续之前收集最多四项内容:

  1. 公司 —— 名称或股票代码。如果只有股票代码,请通过初始查询解析出完整的公司名称(例如,使用公司信息工具)。
  2. 受众 —— 四种类型之一:
    • 股票研究 —— 面向评估投资价值的买方/卖方分析师
    • IB / M&A —— 面向在交易背景下对公司进行画像的银行家
    • Corp Dev —— 面向评估收购目标的内部战略团队
    • Sales / BD —— 面向准备客户会议的商业团队
  3. 可比公司(可选)—— 如果用户已有具体的可比公司,请记录下来。否则,该 skill 将从 S&P Global 数据中识别同行。这对于股票研究、IB/M&A 和 Corp Dev 的 tear sheet 很重要。
  4. 页面长度偏好(可选)—— 默认值因受众而异(见下文),但用户可以覆盖。

如果用户未指定受众,请询问。

第 2 步:阅读特定受众的参考文件

从该 skill 的目录中阅读相应的参考文件:

  • 股票研究 → references/equity-research.md
  • IB / M&A → references/ib-ma.md
  • Corp Dev → references/corp-dev.md
  • Sales / BD → references/sales-bd.md

每个参考文件定义了章节、查询计划、格式化指南和页面长度默认值。

步骤 3:通过 S&P Global MCP 拉取数据

首先: 创建中间文件目录:

mkdir -p /tmp/tear-sheet/

使用 S&P Global MCP 工具(也称为 Kensho LLM-ready API)。Claude 将可以访问用于金融数据、公司信息、市场数据、一致预期、财报电话会议记录、并购交易和业务关系的结构化工具。每个参考文件中的查询计划描述了每个部分需要检索哪些数据——将其映射到对话中可用的相应 S&P Global 工具。

在每个查询步骤之后,立即将检索到的数据写入参考文件查询计划中指定的中间文件。 不要延迟写入——写入磁盘的数据可以在长对话中免受上下文退化影响。

查询策略: 每个参考文件都包含一个查询计划,其中有 4-6 个数据检索步骤。这些是起点,而不是刚性约束。优先保证数据完整性,而不是尽量减少调用次数:

  • 始终拉取 4 个财年的财务数据,即使只显示 3 年。需要第 4 年(最早一年)来计算第一个显示年度的收入同比增长。否则,最早一年的增长率将显示为 "N/A"——这看起来像缺失数据,而不是设计选择。
  • 按原样执行查询计划,使用与所需数据匹配的任意 S&P Global 工具。
  • 如果某个工具调用返回不完整结果,请尝试替代工具或更窄的查询。例如,如果公司摘要不包含业务分部明细,请直接尝试业务分部工具。
  • 如果某个数据点在定向重试后仍未返回,请继续——将其标记为 "N/A" 或 "Not disclosed"。
  • 绝不编造数据。如果工具没有返回数字,不要根据训练知识进行估算。

用户指定的可比公司: 如果用户提供了可比公司,请明确查询每个可比公司的财务数据和倍数。如果未提供可比公司,请使用工具返回的任何同业数据,或使用竞争对手工具根据公司所在行业识别同业。

来自用户的可选上下文: 留意用户自然提供的额外上下文。如果他们提到收购方是谁("我们正在为我们的平台评估这个机会")、他们销售什么("我们向银行销售数据分析")或可能的买家是谁("这对 Salesforce 或 Microsoft 会很有吸引力"),请将该上下文纳入相关综合部分(战略契合度、对话开场白、交易角度)。不要主动提示用户提供这些信息——如果用户主动提供,就直接使用。

私营公司处理: CIQ 包含私营公司数据,因此以相同方式查询。但是,预期结果会更稀疏。为私营公司生成内容时:

  • 跳过:股价、52 周区间、贝塔、股价表现、一致预期、交易可比公司
  • 侧重:业务概览、关系、所有权结构、任何可获得的财务数据
  • 在页眉中显著标注 "Private Company"
步骤 3b:计算派生指标

在所有数据收集完成并写入中间文件后,通过一次专门的独立处理计算所有派生指标。此步骤仅进行计算 —— 不发起新的 MCP 查询。

将所有中间文件重新读入上下文,然后计算:

  • 利润率: 毛利率 %、EBITDA 利润率 %、FCF 利润率 %、营业利润率 %
  • 增长率: 营收同比增长率、分部营收同比增长率、EPS 同比增长率
  • 效率比率: FCF 转换率(FCF/EBITDA)、研发费用占营收百分比、资本开支占营收百分比
  • 资本结构: 净债务(总债务 − 现金及等价物)、净债务 / EBITDA
  • 分部构成: 各分部营收占合并总营收的百分比(按数据完整性规则 8,以合并营收作为分母)

验证(从算术验证移入): 在此计算处理过程中,执行所有算术检查:

  • 利润率计算: 验证 EBITDA 利润率 = EBITDA / 营收、毛利率 = 毛利润 / 营收等。如果计算出的利润率与原始数字不符,使用基于原始组成部分的计算结果。
  • 增长率: 验证同比增长率 =(当期 − 上期)/ 上期。如果你有基础数值,不要依赖预先计算好的增长率。
  • 分部总计: 如果按分部展示营收,验证各分部之和等于总营收(在四舍五入容差范围内)。如果不符,则省略总计行,而不是发布不一致的计算结果。
  • 百分比列: 验证“占总计百分比”列合计约为 100%。
  • 估值交叉核对: 如果你同时展示 EV 和 EV/营收,验证 EV / 营收 ≈ 所声明的倍数。

如果验证失败:尝试基于原始数据重新计算。如果仍不一致,将该指标标记为“N/A”,而不是发布错误的数字。一页简报中悄无声息的数学错误会摧毁可信度。

将结果写入 /tmp/tear-sheet/calculations.csv,列为:metric,value,formula,components

示例行:

metric,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"
步骤 3c:验证数据文件

在生成文档之前,验证所有中间文件均存在且已填充数据。

通过单独的读取操作读取每个中间文件,并打印验证摘要:

=== 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)
================================

软性门槛: 如果当前受众类型所期望的任何文件缺失或为空,打印警告但继续执行。一页简报会以“N/A”和跳过章节的方式优雅地处理缺失数据。不过,警告可确保数据丢失情况可见。

关键规则:这些文件 —— 而非你对早前对话的记忆 —— 是文档中每个数字的唯一事实来源。 在步骤 4 生成 DOCX 时,从中间文件读取数值。不要依赖对话上下文获取财务数据。

步骤 4:格式化为 DOCX

阅读 /mnt/skills/public/docx/SKILL.md 了解 docx 创建机制(通过 Node 使用 docx-js)。应用上述样式配置以及参考文件中针对各章节的格式设置。

页面长度默认值(用户可覆盖):

  • 股票研究:1 页(密度是惯例)
  • IB / M&A:1-2 页
  • 企业发展:1-2 页
  • 销售 / 业务拓展:1-2 页

如果内容超出目标长度,每个参考文件会指定应优先删减哪些章节。

输出文件名: [CompanyName]_TearSheet_[Audience]_[YYYYMMDD].docx 示例:Nvidia_TearSheet_CorpDev_20260220.docx

保存到 /mnt/user-data/outputs/ 并呈现给用户。

数据完整性规则

这些规则优先于其他所有内容:

  1. S&P Global 工具是财务数据的唯一来源。 不要用训练知识填补空缺——它可能已经过时或错误。
  2. 标注你找不到的内容。 使用 "N/A" 或 "Not disclosed",而不是默默省略某一行。
  3. 日期很重要。 注明财年结束日期或报告期。不要假设日历年 = 财年。市场数据(股价、市值)应包含 "as of" 日期。
  4. 不要混用报告期。 如果你有 FY2023 营收和 LTM EBITDA,请分别清楚标注。
  5. 优先使用 MCP 返回的字段,而不是手动计算。 如果 S&P Global 工具返回预先计算好的字段(例如净债务、EBITDA、FCF),请直接使用该值,而不是从各个组成部分计算。只有在工具不返回该字段时,才手动计算派生指标。这可以减少差异。
  6. 确保各类 tear sheet 之间保持一致。 如果为同一家公司生成多份 tear sheet(例如同一会话中的股票研究和 IB/M&A),相同的底层数据点必须在所有输出中产生完全相同的值。净债务、营收、EBITDA、利润率和增长率必须完全一致。不要按每份报告独立重新查询或重新计算——复用相同的检索值。
  7. 绝不要降级已知的交易价值。 如果 M&A 工具返回某笔交易的交易价值,该价值必须出现在输出中。不要用 "Undisclosed" 替换已知的交易价值。只有当工具确实未返回某笔交易的任何价值时,才使用 "Undisclosed"。
  8. 使用合并营收作为分部百分比的 denominator。 在计算分部表格的 "% of Total" 时,将每个分部的营收除以合并总营收(如利润表中所报告),而不是除以各分部营收之和。由于分部间抵销,各分部之和通常会超过合并营收。使用合并营收可确保百分比与文档其他位置显示的总营收数字保持一致。
  9. 只要可用,就始终包含 forward(NTM)倍数。 如果工具同时返回 trailing 和 forward 估值倍数,两者都必须出现在输出中。forward 倍数是股票研究、IB/M&A 和 corp dev 受众的主要估值参考。当 forward 数据可用时,绝不要只显示 trailing 倍数。
  10. 没有任何 S&P Global 工具会返回高管或管理层数据。 不要用训练数据填充管理层姓名、职务或履历详情——这违反规则 1,并会产生过时信息。如果模板中出现管理层部分,请完全省略。所有权结构(机构持有人、内部人持股比例、PE sponsor)只有在工具返回时才可包含——并以 "data permitting" 作为条件。

中间文件规则

所有从 MCP 工具检索到的数据都必须在文档生成之前持久化到结构化中间文件中。这些文件——而非对话上下文——是文档中每个数字的唯一事实来源。

设置: 在步骤 3 开始时,创建工作目录:

mkdir -p /tmp/tear-sheet/

查询后写入要求: 每个 MCP 查询步骤完成后,立即将检索到的数据写入相应的中间文件。不要等到所有查询都结束。每个参考文件的查询计划都规定了每一步之后应写入哪个(哪些)文件。

文件模式:

文件格式列 / 结构使用者
/tmp/tear-sheet/company-profile.txt键值文本name, ticker, exchange, HQ, sector, industry, founded, employees, market_cap, enterprise_value, stock_price, 52wk_high, 52wk_low, shares_outstanding, beta, ownership全部
/tmp/tear-sheet/financials.csvCSVperiod,line_item,value,source全部
/tmp/tear-sheet/segments.csvCSVperiod,segment_name,revenue,sourceER、IB、CD
/tmp/tear-sheet/valuation.csvCSVmetric,trailing,forward,sourceER、IB、CD
/tmp/tear-sheet/consensus.csvCSVmetric,fy_year,value,sourceER
/tmp/tear-sheet/earnings.txt结构化文本季度、日期、关键引述、指引、关键驱动因素ER、IB、Sales
/tmp/tear-sheet/relationships.txt结构化文本客户、供应商、合作伙伴、竞争对手——每项均附描述信息IB、CD、Sales
/tmp/tear-sheet/peer-comps.csvCSVticker,metric,value,sourceER、IB、CD
/tmp/tear-sheet/ma-activity.csvCSVdate,target,deal_value,type,rationale,sourceIB、CD
/tmp/tear-sheet/calculations.csvCSVmetric,value,formula,components全部(在步骤 3b 中写入)

缩写: ER = Equity Research,IB = IB/M&A,CD = Corp Dev,Sales = Sales/BD。

并非每种受众类型都会使用每个文件——参考文件定义了哪些查询步骤适用。与当前受众类型无关的文件无需创建。

仅限原始值。 中间文件存储工具返回的原始值。不要在这些文件中预先计算利润率、增长率或其他派生指标——这些在步骤 3b 中进行。

页数预算强制执行: 每个参考文件都规定了默认页数长度和编号的删减顺序。如果渲染后的文档超出目标,请按指定顺序进行删减——不要尝试将字体大小或页边距缩小到低于模板最小值。删减顺序是一个严格的优先级栈:在触及第 2 部分之前,先完整删减第 1 部分。

内容质量规则

  1. 针对受众改写每个叙述部分。 CIQ 公司摘要只是输入,不是输出。每种受众类型都需要不同的描述:股票研究需要简洁且以论点为导向;IB 需要 pitchbook 风格的叙述;Corp Dev 需要以产品为重点;Sales/BD 需要通俗易懂的语言。绝不要将 CIQ 摘要逐字粘贴到任何 tear sheet 中。
  2. 按受众区分财报要点。 同一场财报电话会议会为不同读者带来不同的要点。股票研究关注分部层面的业绩以及相对于市场共识的超预期/不及预期。IB 关注利润率走势和战略评论。Sales/BD 关注能够创造对话切入点的战略主题。不要在不同类型的 tear sheet 中重复使用相同的要点。
  3. 综合部分是差异化所在。 战略契合度分析、整合考量、对话切入点和业务概览段落正是 tear sheet 体现价值的地方。这些部分需要分析性推理,将数据点串联成一条叙事线索——只罗列公司名称而不提供背景信息不算是综合。
  4. 在分部表格中标注待完成的剥离交易。 如果某公司已宣布待剥离某个分部或业务单元,请在分部表格中添加脚注或括注,说明该待完成交易(例如,“Mobility* — 待完成剥离,预计 2026 年中”)。对于 Corp Dev 和 IB/M&A tear sheet,请在分部表格下方添加一行说明,列出剔除被剥离分部后的备考收入和收入构成。这有助于读者在不自行计算的情况下评估“持续经营”业务。
算术验证

→ 算术验证现已在步骤 3b(计算派生指标)中强制执行。 所有利润率计算、增长率、分部合计、百分比列和估值交叉核对都会在文档生成开始之前、在专门的计算环节中进行验证。完整验证清单见步骤 3b。

评论

支持 Markdown