skills/financial-report-writing/scripts/document_formatter.py
wangyitong a65adcc2e5 Initial commit: merged, deduplicated, and vetted skill collection
Sources: extracted from two upstream archives (skill-repo, skills-main),
merged with the following policy:

- 15 broken symlinks (pointing to /Users/jameslee/.cc-switch/skills or
  ../../.agents/skills on a foreign machine) discarded
- 3 real name collisions with identical content (ai-pair, ifind-http-api,
  zhipu-websearch) kept as one copy
- Functional overlaps deduped keeping the strongest variant:
  - docx family: kept docx (official, full toolchain) + docx-cn
    (GB/T 9704 Chinese official-document constants),
    dropped docx_writer (no scripts, name collided with docx)
  - humanizer family: kept humanizer-zh (6 zh reference docs),
    dropped humanizer (en, redundant for CN workflow)
- Skills that only ran in a foreign environment removed:
  ablemind-ops, app-publish, hlb-design-system, openclaw-adj-skill,
  claude-driver
- alphapai excluded from this public repo because its SKILL.md hard-coded
  live credentials

Result: 25 skills, 572 files, ~7.5 MB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 14:47:12 +08:00

378 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Word文档格式化模块
按照GB/T 9704-2012标准格式化金融研究报告
"""
from docx import Document
from docx.shared import Pt, Mm, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
class GBT9704Formatter:
"""
GB/T 9704-2012党政机关公文格式化器
"""
# 字体映射(号数 -> 磅值)
FONT_SIZES = {
'三号': 16,
'四号': 14,
'小四': 12,
'五号': 10.5
}
def __init__(self, doc: Document):
"""
初始化格式化器
Args:
doc: Document对象
"""
self.doc = doc
self._setup_page()
def _setup_page(self):
"""设置页面格式"""
for section in self.doc.sections:
# 页面边距(单位:毫米)
section.top_margin = Mm(37)
section.bottom_margin = Mm(35)
section.left_margin = Mm(28)
section.right_margin = Mm(26)
def set_font(self, run, font_name: str, size: int = None, bold: bool = None, color: RGBColor = None):
"""
设置字体
Args:
run: Run对象
font_name: 字体名称(中文字体)
size: 字号(磅值),如 16, 14, 12
bold: 是否加粗
color: 字体颜色
"""
run.font.name = font_name
run._element.rPr.rFonts.set(qn('w:eastAsia'), font_name)
if size is not None:
run.font.size = Pt(size)
if bold is not None:
run.font.bold = bold
if color is not None:
run.font.color.rgb = color
def add_heading(self, text: str, level: int = 1, font_name: str = '黑体', size: int = 16):
"""
添加标题
Args:
text: 标题文本
level: 标题级别1-3
font_name: 字体名称
size: 字号
Returns:
Paragraph对象
"""
if level == 1:
heading = self.doc.add_heading(text, level=1)
run = heading.runs[0]
self.set_font(run, font_name, size, bold=True)
heading.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
heading.paragraph_format.space_before = Pt(0)
heading.paragraph_format.space_after = Pt(0)
return heading
else:
# 二级、三级标题使用"一、"、"(一)"等格式
prefix_map = {1: '一、', 2: '(一)', 3: '1.'}
prefix = prefix_map.get(level, '')
paragraph = self.doc.add_paragraph(f"{prefix}{text}")
run = paragraph.runs[0]
self.set_font(run, font_name, size, bold=True)
# 段落格式
paragraph.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.JUSTIFY
paragraph.paragraph_format.space_before = Pt(0)
paragraph.paragraph_format.space_after = Pt(0)
paragraph.paragraph_format.line_spacing = Pt(25)
paragraph.paragraph_format.line_spacing_rule = WD_LINE_SPACING.EXACTLY
return paragraph
def add_paragraph(self, text: str,
font_name: str = '仿宋',
size: int = 14,
first_line_indent: bool = True,
alignment: str = 'justify'):
"""
添加正文段落
Args:
text: 段落文本
font_name: 字体名称
size: 字号(磅值)
first_line_indent: 是否首行缩进
alignment: 对齐方式('left', 'center', 'right', 'justify'
Returns:
Paragraph对象
"""
paragraph = self.doc.add_paragraph(text)
run = paragraph.runs[0]
self.set_font(run, font_name, size)
# 段落格式
alignment_map = {
'left': WD_ALIGN_PARAGRAPH.LEFT,
'center': WD_ALIGN_PARAGRAPH.CENTER,
'right': WD_ALIGN_PARAGRAPH.RIGHT,
'justify': WD_ALIGN_PARAGRAPH.JUSTIFY
}
paragraph.paragraph_format.alignment = alignment_map.get(alignment, WD_ALIGN_PARAGRAPH.JUSTIFY)
if first_line_indent:
# 首行缩进2字符28pt
paragraph.paragraph_format.first_line_indent = Pt(28)
# 行距固定值28pt
paragraph.paragraph_format.line_spacing = Pt(28)
paragraph.paragraph_format.line_spacing_rule = WD_LINE_SPACING.EXACTLY
# 段前段后距为0
paragraph.paragraph_format.space_before = Pt(0)
paragraph.paragraph_format.space_after = Pt(0)
return paragraph
def add_bullet_list(self, items: List[str], font_name: str = '仿宋', size: int = 14):
"""
添加项目符号列表
Args:
items: 列表项
font_name: 字体名称
size: 字号
"""
for item in items:
paragraph = self.doc.add_paragraph(item, style='List Bullet')
run = paragraph.runs[0]
self.set_font(run, font_name, size)
paragraph.paragraph_format.line_spacing = Pt(28)
paragraph.paragraph_format.line_spacing_rule = WD_LINE_SPACING.EXACTLY
paragraph.paragraph_format.space_before = Pt(0)
paragraph.paragraph_format.space_after = Pt(0)
def add_three_line_table(self, data: List[List[str]],
headers: List[str],
font_name: str = '仿宋',
size: int = 12):
"""
添加三线表
Args:
data: 表格数据(二维列表)
headers: 表头
font_name: 字体名称
size: 字号
Returns:
Table对象
"""
# 创建表格
table = self.doc.add_table(rows=len(data) + 1, cols=len(headers))
self._set_three_line_table_borders(table)
# 设置表头
header_cells = table.rows[0].cells
for i, header in enumerate(headers):
cell = header_cells[i]
self._set_cell_font(cell, header, font_name, size, bold=True)
cell.paragraphs[0].paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
# 设置数据行
for row_idx, row_data in enumerate(data, start=1):
cells = table.rows[row_idx].cells
for i, cell_data in enumerate(row_data):
cell = cells[i]
self._set_cell_font(cell, str(cell_data), font_name, size)
cell.paragraphs[0].paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
return table
def _set_three_line_table_borders(self, table):
"""设置三线表边框"""
tblPr = table._tbl.tblPr
tblBorders = OxmlElement('w:tblBorders')
# 顶底边框 1.5pt (sz=18)
for pos in ['top', 'bottom']:
border = OxmlElement(f'w:{pos}')
border.set(qn('w:val'), 'single')
border.set(qn('w:sz'), '18')
border.set(qn('w:color'), 'auto')
tblBorders.append(border)
# 内部横线 0.75pt (sz=9)
for pos in ['insideH']:
border = OxmlElement(f'w:{pos}')
border.set(qn('w:val'), 'single')
border.set(qn('w:sz'), '9')
border.set(qn('w:color'), 'auto')
tblBorders.append(border)
# 无左右和竖线
for pos in ['left', 'right', 'insideV']:
border = OxmlElement(f'w:{pos}')
border.set(qn('w:val'), 'none')
tblBorders.append(border)
tblPr.append(tblBorders)
def _set_cell_font(self, cell, text: str, font_name: str, size: int, bold: bool = False):
"""设置单元格字体"""
cell.text = ''
run = cell.paragraphs[0].add_run(text)
self.set_font(run, font_name, size, bold=bold)
def add_page_number_footer(self):
"""添加页码页脚"""
for section in self.doc.sections:
footer = section.footer
# 添加页码段落
p = footer.paragraphs[0] if footer.paragraphs else footer.add_paragraph()
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
# 页码格式:— 1 —
self.add_footer_run(p, '', font_name='仿宋', size=12)
# 添加页码字段
self.add_page_number_field(p)
self.add_footer_run(p, ' ', font_name='仿宋', size=12)
self.add_footer_run(p, '', font_name='仿宋', size=12)
def add_footer_run(self, paragraph, text: str, font_name: str, size: int):
"""添加页脚run"""
run = paragraph.add_run(text)
self.set_font(run, font_name, size)
def add_page_number_field(self, paragraph):
"""添加页码字段"""
fldChar1 = OxmlElement('w:fldChar')
fldChar1.set(qn('w:fldCharType'), 'begin')
instrText = OxmlElement('w:instrText')
instrText.set(qn('xml:space'), 'preserve')
instrText.text = "PAGE"
fldChar2 = OxmlElement('w:fldChar')
fldChar2.set(qn('w:fldCharType'), 'end')
run = paragraph.add_run()
run._r.append(fldChar1)
run._r.append(instrText)
run._r.append(fldChar2)
self.set_font(run, '仿宋', 12)
def add_cover_page(self, title: str, organization: str, date_str: str):
"""
添加封面页
Args:
title: 报告标题
organization: 机构名称
date_str: 日期
"""
# 标题
paragraph = self.doc.add_paragraph()
run = paragraph.add_run(title)
self.set_font(run, '黑体', 22, bold=True)
paragraph.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
paragraph.paragraph_format.space_before = Pt(100)
paragraph.paragraph_format.space_after = Pt(50)
# 机构名称
paragraph = self.doc.add_paragraph()
run = paragraph.add_run(organization)
self.set_font(run, '黑体', 16)
paragraph.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
paragraph.paragraph_format.space_after = Pt(20)
# 日期
paragraph = self.doc.add_paragraph()
run = paragraph.add_run(date_str)
self.set_font(run, '黑体', 16)
paragraph.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER
# 添加分页符
self.doc.add_page_break()
def add_data_source_footnote(self, text: str = "数据来源Wind资讯"):
"""
添加数据来源注脚
Args:
text: 注脚文本
"""
paragraph = self.doc.add_paragraph()
run = paragraph.add_run(text)
self.set_font(run, '仿宋', 12)
paragraph.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.RIGHT
# 便捷函数
def create_formatted_document() -> Tuple[Document, GBT9704Formatter]:
"""
创建格式化的Word文档
Returns:
(Document, GBT9704Formatter) 文档对象和格式化器
"""
doc = Document()
formatter = GBT9704Formatter(doc)
return doc, formatter
if __name__ == '__main__':
# 测试代码
doc, formatter = create_formatted_document()
# 添加封面
formatter.add_cover_page(
'某某股票投资研究报告',
'某某证券研究所',
'2026年2月6日'
)
# 添加核心提要
formatter.add_heading('核心提要', level=1)
formatter.add_bullet_list([
'该公司2025年三季度净资产收益率ROE为15.2%,盈利能力较强',
'当前市盈率TTM为25.3倍,略高于行业平均水平',
'技术面显示股价呈现震荡上行趋势,建议关注回调机会',
'给予"买入"评级目标价格35元'
])
# 添加投资评级表格
formatter.add_heading('一、投资评级', level=1)
data = [
['买入', '35.00元', '2026年12月31日', '中风险']
]
formatter.add_three_line_table(
data,
['投资建议', '目标价格', '有效期', '风险等级']
)
# 添加数据来源
formatter.add_data_source_footnote()
# 保存文档
output_path = 'test_report.docx'
doc.save(output_path)
print(f"测试报告已保存到: {output_path}")