chore(skill): 清理全部 23 条历史校验警告

- 11 个超 20KB 的 SKILL.md 拆分,大章节移入 references/details.md
- 7 处非标准字段(arguments/user_invocable)移入 metadata
- 修 req-dev 4 条相对链接;validate 跳过行内代码避免 ![](...) 误报
- validate-skills.py: 23 warns -> 0 warns,0 errors
This commit is contained in:
johnQ
2026-10-31 22:21:07 +08:00
parent 7d47762303
commit 237631147a
29 changed files with 4013 additions and 3928 deletions
+3 -109
View File
@@ -5,9 +5,6 @@ metadata:
invocation: user-invoked
disable-model-invocation: true
---
# 商务合同撰写技能 (biz-contract)
## 概述
本技能用于辅助商务合同的撰写与管理,支持多家公司主体、多种合同类型。
@@ -634,112 +631,9 @@ api.publish_to_feishu(doc_id, "物流服务合同-妗晨与名风")
---
## 飞书文档集成
本技能已集成飞书文档,可将合同直接创建到飞书云文档。
## 详细内容
### 权限说明
以下章节已移到 [references/details.md](references/details.md):
**重要**:飞书文档默认对组织内权限是开放的,允许编辑。创建的合同文档在组织内成员均可查看和编辑。如需限制访问权限,请在飞书中手动调整文档权限设置。
### 可用工具
| 工具 | 功能 |
|------|------|
| `create_document` | 创建空白飞书文档 |
| `write_document` | 向已有文档写入内容 |
| `create_and_write_document` | 创建文档并写入内容(推荐) |
| `get_document_info` | 获取文档信息 |
### 使用示例
```
# 创建合同文档
使用 feishu-doc MCP 的 create_and_write_document 工具:
- title: "软件订阅服务合同-智慧云彩与妗晨"
- content: [Markdown 格式的合同内容]
- folder_token: [可选,指定文件夹]
```
### 获取文件夹 Token
1. 在飞书中打开目标文件夹
2. 复制 URL 中的 folder token(格式如:`fldcnXXXXXX`)
3. 调用工具时传入 folder_token 参数
### 飞书输出格式规范
#### 开票信息表格
物流合同中的开票信息应使用**表格**格式展示,列宽配置:
| 配置项 | 值 | 说明 |
|--------|-----|------|
| 项目列宽 | 120px | 显示"名称"、"纳税人识别号"等标签 |
| 内容列宽 | 480px | 显示具体信息 |
```python
# 创建开票信息表格示例
from feishu_docx import FeishuDocx
docx = FeishuDocx()
doc = docx.create_document("物流服务合同")
# 创建表格(4行2列,指定列宽)
table = docx.create_table(doc["document_id"], rows=4, cols=2, col_widths=[120, 480])
# 填充内容
docx.fill_table(doc["document_id"], table, [
["名称", "重庆妗晨工贸有限公司"],
["纳税人识别号", "91500104MA7EJTPA6D"],
["地址、电话", "重庆市大渡口区跳磴镇海康路106号1-1 / 15213397998"],
["开户行及账号", "中国农业银行重庆大渡口天安支行 31230401040004386"],
])
```
#### 签章格式
推荐使用**独立区块式**签章格式:
```
【甲 方】
单位名称:重庆妗晨工贸有限公司
法定代表人(或授权代表): (签字/盖章)
单位盖章:
签订日期: 年 月 日
─────────────────────────────────────────────────────
【乙 方】
单位名称:北京名风新能源科技有限公司
法定代表人(或授权代表): (签字/盖章)
单位盖章:
签订日期: 年 月 日
```
**特点**:甲乙双方独立区块,留白充足,便于盖章签字。
#### 物流合同税率
| 服务类型 | 税率 | 发票类型 |
|----------|------|----------|
| 交通运输服务 | 9% | 增值税专用发票 |
#### 默认存储路径
| 平台 | 路径 |
|------|------|
| 飞书云文档 | ai-proj/01运营 (folder_token: `C80gfkRnzlonQ5d4AhOcOACDnNg`) |
| 思源笔记 | 商务合同笔记本 /物流合同/{甲方简称}-{乙方简称}-{年份} |
- 飞书文档集成
@@ -0,0 +1,111 @@
# skills 详细内容
## 飞书文档集成
本技能已集成飞书文档,可将合同直接创建到飞书云文档。
### 权限说明
**重要**:飞书文档默认对组织内权限是开放的,允许编辑。创建的合同文档在组织内成员均可查看和编辑。如需限制访问权限,请在飞书中手动调整文档权限设置。
### 可用工具
| 工具 | 功能 |
|------|------|
| `create_document` | 创建空白飞书文档 |
| `write_document` | 向已有文档写入内容 |
| `create_and_write_document` | 创建文档并写入内容(推荐) |
| `get_document_info` | 获取文档信息 |
### 使用示例
```
# 创建合同文档
使用 feishu-doc MCP 的 create_and_write_document 工具:
- title: "软件订阅服务合同-智慧云彩与妗晨"
- content: [Markdown 格式的合同内容]
- folder_token: [可选,指定文件夹]
```
### 获取文件夹 Token
1. 在飞书中打开目标文件夹
2. 复制 URL 中的 folder token(格式如:`fldcnXXXXXX`)
3. 调用工具时传入 folder_token 参数
### 飞书输出格式规范
#### 开票信息表格
物流合同中的开票信息应使用**表格**格式展示,列宽配置:
| 配置项 | 值 | 说明 |
|--------|-----|------|
| 项目列宽 | 120px | 显示"名称"、"纳税人识别号"等标签 |
| 内容列宽 | 480px | 显示具体信息 |
```python
# 创建开票信息表格示例
from feishu_docx import FeishuDocx
docx = FeishuDocx()
doc = docx.create_document("物流服务合同")
# 创建表格(4行2列,指定列宽)
table = docx.create_table(doc["document_id"], rows=4, cols=2, col_widths=[120, 480])
# 填充内容
docx.fill_table(doc["document_id"], table, [
["名称", "重庆妗晨工贸有限公司"],
["纳税人识别号", "91500104MA7EJTPA6D"],
["地址、电话", "重庆市大渡口区跳磴镇海康路106号1-1 / 15213397998"],
["开户行及账号", "中国农业银行重庆大渡口天安支行 31230401040004386"],
])
```
#### 签章格式
推荐使用**独立区块式**签章格式:
```
【甲 方】
单位名称:重庆妗晨工贸有限公司
法定代表人(或授权代表): (签字/盖章)
单位盖章:
签订日期: 年 月 日
─────────────────────────────────────────────────────
【乙 方】
单位名称:北京名风新能源科技有限公司
法定代表人(或授权代表): (签字/盖章)
单位盖章:
签订日期: 年 月 日
```
**特点**:甲乙双方独立区块,留白充足,便于盖章签字。
#### 物流合同税率
| 服务类型 | 税率 | 发票类型 |
|----------|------|----------|
| 交通运输服务 | 9% | 增值税专用发票 |
#### 默认存储路径
| 平台 | 路径 |
|------|------|
| 飞书云文档 | ai-proj/01运营 (folder_token: `C80gfkRnzlonQ5d4AhOcOACDnNg`) |
| 思源笔记 | 商务合同笔记本 /物流合同/{甲方简称}-{乙方简称}-{年份} |
+3 -358
View File
@@ -5,9 +5,6 @@ metadata:
invocation: user-invoked
disable-model-invocation: true
---
# 商业计划书撰写技能 (biz-plan)
## 概述
本技能用于辅助商业计划书的撰写与优化,特别适用于 B2B SaaS、供应链平台、产业互联网等领域。
@@ -403,361 +400,9 @@ disable-model-invocation: true
---
## PPT 设计能力
本技能支持生成商业计划书 PPT,输出为 HTML 格式的演示文稿,可直接在浏览器中全屏演示,也可转换为 PDF 或截图使用。
## 详细内容
### PPT 输出方式
以下章节已移到 [references/details.md](references/details.md):
当用户要求生成 PPT 时,使用以下方式:
1. **HTML 演示文稿**(推荐)
- 生成单个 HTML 文件
- 支持全屏演示(按 F 键)
- 支持键盘翻页(← →)
- 可直接打印为 PDF
- 响应式设计,适配各种屏幕
2. **Markdown 演示脚本**
- 输出每页内容的文字稿
- 用户可自行复制到 PPT 软件
### HTML PPT 模板
生成 PPT 时使用以下 HTML 模板结构:
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>商业计划书 - [公司名称]</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #1a1a2e;
color: #fff;
overflow: hidden;
}
.slide {
display: none;
width: 100vw;
height: 100vh;
padding: 60px 80px;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
position: relative;
}
.slide.active {
display: flex;
flex-direction: column;
justify-content: center;
}
/* 封面样式 */
.slide.cover {
text-align: center;
justify-content: center;
align-items: center;
}
.slide.cover h1 {
font-size: 72px;
font-weight: 700;
margin-bottom: 20px;
background: linear-gradient(90deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.slide.cover .slogan {
font-size: 28px;
color: #a0aec0;
margin-bottom: 60px;
}
.slide.cover .info {
font-size: 18px;
color: #718096;
}
/* 内容页样式 */
.slide h2 {
font-size: 48px;
font-weight: 600;
margin-bottom: 40px;
color: #667eea;
}
.slide h3 {
font-size: 32px;
font-weight: 500;
margin-bottom: 24px;
color: #a0aec0;
}
.slide p, .slide li {
font-size: 24px;
line-height: 1.8;
color: #e2e8f0;
}
.slide ul {
list-style: none;
padding-left: 0;
}
.slide ul li {
padding: 12px 0;
padding-left: 40px;
position: relative;
}
.slide ul li::before {
content: "▸";
position: absolute;
left: 0;
color: #667eea;
}
/* 数据卡片 */
.metrics {
display: flex;
gap: 40px;
margin-top: 40px;
}
.metric-card {
flex: 1;
background: rgba(102, 126, 234, 0.1);
border: 1px solid rgba(102, 126, 234, 0.3);
border-radius: 16px;
padding: 32px;
text-align: center;
}
.metric-card .value {
font-size: 48px;
font-weight: 700;
color: #667eea;
}
.metric-card .label {
font-size: 18px;
color: #a0aec0;
margin-top: 8px;
}
/* 两栏布局 */
.two-columns {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 60px;
margin-top: 40px;
}
/* 流程图 */
.flow {
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
margin-top: 40px;
}
.flow-item {
background: rgba(102, 126, 234, 0.15);
border-radius: 12px;
padding: 24px 32px;
text-align: center;
}
.flow-arrow {
color: #667eea;
font-size: 32px;
}
/* 页码 */
.page-number {
position: absolute;
bottom: 30px;
right: 40px;
font-size: 16px;
color: #4a5568;
}
/* 进度条 */
.progress {
position: fixed;
top: 0;
left: 0;
height: 4px;
background: linear-gradient(90deg, #667eea, #764ba2);
transition: width 0.3s;
z-index: 100;
}
/* 导航提示 */
.nav-hint {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
font-size: 14px;
color: #4a5568;
}
</style>
</head>
<body>
<div class="progress" id="progress"></div>
<!-- Slide 1: 封面 -->
<div class="slide cover active">
<h1>[公司名称]</h1>
<p class="slogan">[一句话 Slogan]</p>
<p class="info">[日期] · 商业计划书</p>
<span class="page-number">1 / N</span>
</div>
<!-- Slide 2: 公司定位 -->
<div class="slide">
<h2>公司定位</h2>
<h3>[一句话定位]</h3>
<div class="metrics">
<div class="metric-card">
<div class="value">[X]年</div>
<div class="label">深耕行业</div>
</div>
<div class="metric-card">
<div class="value">[X]家</div>
<div class="label">头部客户</div>
</div>
<div class="metric-card">
<div class="value">[X]轮</div>
<div class="label">完成融资</div>
</div>
</div>
<span class="page-number">2 / N</span>
</div>
<!-- 更多 slides... -->
<p class="nav-hint">← → 翻页 · F 全屏 · ESC 退出</p>
<script>
const slides = document.querySelectorAll('.slide');
const progress = document.getElementById('progress');
let current = 0;
function showSlide(n) {
slides[current].classList.remove('active');
current = (n + slides.length) % slides.length;
slides[current].classList.add('active');
progress.style.width = ((current + 1) / slides.length * 100) + '%';
}
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight' || e.key === ' ') showSlide(current + 1);
if (e.key === 'ArrowLeft') showSlide(current - 1);
if (e.key === 'f' || e.key === 'F') {
document.documentElement.requestFullscreen?.();
}
});
// 触摸滑动支持
let touchStartX = 0;
document.addEventListener('touchstart', e => touchStartX = e.touches[0].clientX);
document.addEventListener('touchend', e => {
const diff = e.changedTouches[0].clientX - touchStartX;
if (Math.abs(diff) > 50) showSlide(current + (diff > 0 ? -1 : 1));
});
</script>
</body>
</html>
```
### BP PPT 标准页面结构
生成商业计划书 PPT 时,按以下结构组织:
| 页码 | 页面类型 | 内容要点 | 设计建议 |
|------|----------|----------|----------|
| 1 | 封面 | 公司名、Slogan、日期 | 居中大标题,渐变背景 |
| 2 | 公司定位 | 一句话定位 + 核心数据 | 3-4 个数据卡片 |
| 3 | 行业痛点 | 3-4 个核心痛点 | 图标 + 简短描述 |
| 4 | 解决方案 | 产品价值主张 | 流程图或产品矩阵 |
| 5 | 商业模式 | 收入来源、盈利方式 | 模式图 + 收入占比 |
| 6 | 市场规模 | TAM/SAM/SOM | 同心圆图 + 数字 |
| 7 | 竞争分析 | 竞争矩阵、差异化 | 2x2 矩阵图 |
| 8 | 运营数据 | 核心指标、增长趋势 | 4 个大数字卡片 |
| 9 | 客户案例 | 标杆客户、成果 | Logo + 数据对比 |
| 10 | 团队介绍 | 核心成员背景 | 头像 + 简介 |
| 11 | 里程碑 | 发展历程 | 时间线 |
| 12 | 财务预测 | 3 年收入预测 | 柱状图示意 |
| 13 | 战略规划 | 未来规划 | 阶段性目标 |
| 14 | 融资计划 | 金额、用途、联系方式 | 饼图 + 联系信息 |
### PPT 设计原则
1. **一页一重点**:每页只传达一个核心信息
2. **大字少字**:标题 48px+,正文 24px+,每页文字不超过 50 字
3. **数据可视化**:用数字卡片、图表代替文字描述
4. **留白充足**:内容不要铺满,保持呼吸感
5. **配色统一**:使用 2-3 种主色,保持一致性
6. **高对比度**:深色背景 + 浅色文字,或浅色背景 + 深色文字
### 配色方案
**深色科技风**(推荐):
- 背景:#1a1a2e → #16213e(渐变)
- 主色:#667eea(蓝紫色)
- 辅助:#764ba2(紫色)
- 文字:#e2e8f0(浅灰)
- 次要文字:#a0aec0(中灰)
**浅色商务风**:
- 背景:#ffffff
- 主色:#2196F3(蓝色)
- 辅助:#1976D2(深蓝)
- 文字:#212121(深灰)
- 次要文字:#616161(中灰)
### 使用示例
用户请求:
> 帮我生成商业计划书 PPT
输出方式:
1. 生成完整的 HTML 文件
2. 保存到用户指定位置(如桌面、项目目录)
3. 用户用浏览器打开即可演示
```bash
# 打开 PPT
open ~/Desktop/bp-presentation.html
# 或在浏览器中打开后按 F 进入全屏模式
```
### 导出为 PDF
HTML PPT 可通过浏览器打印功能导出为 PDF:
1. 在浏览器中打开 HTML 文件
2. 按 Cmd+P (Mac) 或 Ctrl+P (Windows)
3. 选择"另存为 PDF"
4. 设置页面大小为横向 A4 或 16:9
### 注意事项
1. **内容优先**:先确定 BP 内容,再生成 PPT
2. **数据占位**:敏感数据可用 [X] 占位
3. **迭代优化**:可分页生成,逐步完善
4. **本地演示**:HTML 文件完全离线可用,无需网络
- PPT 设计能力
@@ -0,0 +1,360 @@
# skills 详细内容
## PPT 设计能力
本技能支持生成商业计划书 PPT,输出为 HTML 格式的演示文稿,可直接在浏览器中全屏演示,也可转换为 PDF 或截图使用。
### PPT 输出方式
当用户要求生成 PPT 时,使用以下方式:
1. **HTML 演示文稿**(推荐)
- 生成单个 HTML 文件
- 支持全屏演示(按 F 键)
- 支持键盘翻页(← →)
- 可直接打印为 PDF
- 响应式设计,适配各种屏幕
2. **Markdown 演示脚本**
- 输出每页内容的文字稿
- 用户可自行复制到 PPT 软件
### HTML PPT 模板
生成 PPT 时使用以下 HTML 模板结构:
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>商业计划书 - [公司名称]</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
background: #1a1a2e;
color: #fff;
overflow: hidden;
}
.slide {
display: none;
width: 100vw;
height: 100vh;
padding: 60px 80px;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
position: relative;
}
.slide.active {
display: flex;
flex-direction: column;
justify-content: center;
}
/* 封面样式 */
.slide.cover {
text-align: center;
justify-content: center;
align-items: center;
}
.slide.cover h1 {
font-size: 72px;
font-weight: 700;
margin-bottom: 20px;
background: linear-gradient(90deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.slide.cover .slogan {
font-size: 28px;
color: #a0aec0;
margin-bottom: 60px;
}
.slide.cover .info {
font-size: 18px;
color: #718096;
}
/* 内容页样式 */
.slide h2 {
font-size: 48px;
font-weight: 600;
margin-bottom: 40px;
color: #667eea;
}
.slide h3 {
font-size: 32px;
font-weight: 500;
margin-bottom: 24px;
color: #a0aec0;
}
.slide p, .slide li {
font-size: 24px;
line-height: 1.8;
color: #e2e8f0;
}
.slide ul {
list-style: none;
padding-left: 0;
}
.slide ul li {
padding: 12px 0;
padding-left: 40px;
position: relative;
}
.slide ul li::before {
content: "▸";
position: absolute;
left: 0;
color: #667eea;
}
/* 数据卡片 */
.metrics {
display: flex;
gap: 40px;
margin-top: 40px;
}
.metric-card {
flex: 1;
background: rgba(102, 126, 234, 0.1);
border: 1px solid rgba(102, 126, 234, 0.3);
border-radius: 16px;
padding: 32px;
text-align: center;
}
.metric-card .value {
font-size: 48px;
font-weight: 700;
color: #667eea;
}
.metric-card .label {
font-size: 18px;
color: #a0aec0;
margin-top: 8px;
}
/* 两栏布局 */
.two-columns {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 60px;
margin-top: 40px;
}
/* 流程图 */
.flow {
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
margin-top: 40px;
}
.flow-item {
background: rgba(102, 126, 234, 0.15);
border-radius: 12px;
padding: 24px 32px;
text-align: center;
}
.flow-arrow {
color: #667eea;
font-size: 32px;
}
/* 页码 */
.page-number {
position: absolute;
bottom: 30px;
right: 40px;
font-size: 16px;
color: #4a5568;
}
/* 进度条 */
.progress {
position: fixed;
top: 0;
left: 0;
height: 4px;
background: linear-gradient(90deg, #667eea, #764ba2);
transition: width 0.3s;
z-index: 100;
}
/* 导航提示 */
.nav-hint {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
font-size: 14px;
color: #4a5568;
}
</style>
</head>
<body>
<div class="progress" id="progress"></div>
<!-- Slide 1: 封面 -->
<div class="slide cover active">
<h1>[公司名称]</h1>
<p class="slogan">[一句话 Slogan]</p>
<p class="info">[日期] · 商业计划书</p>
<span class="page-number">1 / N</span>
</div>
<!-- Slide 2: 公司定位 -->
<div class="slide">
<h2>公司定位</h2>
<h3>[一句话定位]</h3>
<div class="metrics">
<div class="metric-card">
<div class="value">[X]年</div>
<div class="label">深耕行业</div>
</div>
<div class="metric-card">
<div class="value">[X]家</div>
<div class="label">头部客户</div>
</div>
<div class="metric-card">
<div class="value">[X]轮</div>
<div class="label">完成融资</div>
</div>
</div>
<span class="page-number">2 / N</span>
</div>
<!-- 更多 slides... -->
<p class="nav-hint">← → 翻页 · F 全屏 · ESC 退出</p>
<script>
const slides = document.querySelectorAll('.slide');
const progress = document.getElementById('progress');
let current = 0;
function showSlide(n) {
slides[current].classList.remove('active');
current = (n + slides.length) % slides.length;
slides[current].classList.add('active');
progress.style.width = ((current + 1) / slides.length * 100) + '%';
}
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight' || e.key === ' ') showSlide(current + 1);
if (e.key === 'ArrowLeft') showSlide(current - 1);
if (e.key === 'f' || e.key === 'F') {
document.documentElement.requestFullscreen?.();
}
});
// 触摸滑动支持
let touchStartX = 0;
document.addEventListener('touchstart', e => touchStartX = e.touches[0].clientX);
document.addEventListener('touchend', e => {
const diff = e.changedTouches[0].clientX - touchStartX;
if (Math.abs(diff) > 50) showSlide(current + (diff > 0 ? -1 : 1));
});
</script>
</body>
</html>
```
### BP PPT 标准页面结构
生成商业计划书 PPT 时,按以下结构组织:
| 页码 | 页面类型 | 内容要点 | 设计建议 |
|------|----------|----------|----------|
| 1 | 封面 | 公司名、Slogan、日期 | 居中大标题,渐变背景 |
| 2 | 公司定位 | 一句话定位 + 核心数据 | 3-4 个数据卡片 |
| 3 | 行业痛点 | 3-4 个核心痛点 | 图标 + 简短描述 |
| 4 | 解决方案 | 产品价值主张 | 流程图或产品矩阵 |
| 5 | 商业模式 | 收入来源、盈利方式 | 模式图 + 收入占比 |
| 6 | 市场规模 | TAM/SAM/SOM | 同心圆图 + 数字 |
| 7 | 竞争分析 | 竞争矩阵、差异化 | 2x2 矩阵图 |
| 8 | 运营数据 | 核心指标、增长趋势 | 4 个大数字卡片 |
| 9 | 客户案例 | 标杆客户、成果 | Logo + 数据对比 |
| 10 | 团队介绍 | 核心成员背景 | 头像 + 简介 |
| 11 | 里程碑 | 发展历程 | 时间线 |
| 12 | 财务预测 | 3 年收入预测 | 柱状图示意 |
| 13 | 战略规划 | 未来规划 | 阶段性目标 |
| 14 | 融资计划 | 金额、用途、联系方式 | 饼图 + 联系信息 |
### PPT 设计原则
1. **一页一重点**:每页只传达一个核心信息
2. **大字少字**:标题 48px+,正文 24px+,每页文字不超过 50 字
3. **数据可视化**:用数字卡片、图表代替文字描述
4. **留白充足**:内容不要铺满,保持呼吸感
5. **配色统一**:使用 2-3 种主色,保持一致性
6. **高对比度**:深色背景 + 浅色文字,或浅色背景 + 深色文字
### 配色方案
**深色科技风**(推荐):
- 背景:#1a1a2e → #16213e(渐变)
- 主色:#667eea(蓝紫色)
- 辅助:#764ba2(紫色)
- 文字:#e2e8f0(浅灰)
- 次要文字:#a0aec0(中灰)
**浅色商务风**:
- 背景:#ffffff
- 主色:#2196F3(蓝色)
- 辅助:#1976D2(深蓝)
- 文字:#212121(深灰)
- 次要文字:#616161(中灰)
### 使用示例
用户请求:
> 帮我生成商业计划书 PPT
输出方式:
1. 生成完整的 HTML 文件
2. 保存到用户指定位置(如桌面、项目目录)
3. 用户用浏览器打开即可演示
```bash
# 打开 PPT
open ~/Desktop/bp-presentation.html
# 或在浏览器中打开后按 F 进入全屏模式
```
### 导出为 PDF
HTML PPT 可通过浏览器打印功能导出为 PDF:
1. 在浏览器中打开 HTML 文件
2. 按 Cmd+P (Mac) 或 Ctrl+P (Windows)
3. 选择"另存为 PDF"
4. 设置页面大小为横向 A4 或 16:9
### 注意事项
1. **内容优先**:先确定 BP 内容,再生成 PPT
2. **数据占位**:敏感数据可用 [X] 占位
3. **迭代优化**:可分页生成,逐步完善
4. **本地演示**:HTML 文件完全离线可用,无需网络
+6 -454
View File
@@ -5,9 +5,6 @@ metadata:
invocation: user-invoked
disable-model-invocation: true
---
# 财务对账技能
## 功能概述
- **银行对账**: 银行流水 vs 系统收付款记录匹配
@@ -481,457 +478,6 @@ def export_reconciliation_to_excel(
---
## 常见对账场景
### 场景1: 月末银行对账
```python
# 1. 加载数据
bank_df = load_bank_statement('银行流水_202601.xlsx')
system_df = load_system_records('收付款记录_202601.xlsx')
# 2. 执行对账
result = reconcile(bank_df, system_df)
# 3. 查看汇总
print(f"已匹配: {result['summary']['matched_count']} 笔")
print(f"银行未达: {result['summary']['bank_only_count']} 笔, 金额 {result['summary']['bank_only_amount']:,.2f}")
print(f"企业未达: {result['summary']['system_only_count']} 笔, 金额 {result['summary']['system_only_amount']:,.2f}")
# 4. 生成调节表
report = generate_bank_reconciliation_report(
bank_balance=150000.00, # 银行对账单余额
book_balance=145000.00, # 企业账面余额
bank_only=result['bank_only'],
system_only=result['system_only'],
as_of_date='2026-01-31'
)
print(report)
# 5. 导出 Excel
export_reconciliation_to_excel(result, '银行对账_202601.xlsx', '2026-01-31')
```
### 场景2: 客户对账
```python
# 加载客户交易明细
ar_transactions = pd.read_excel('应收明细.xlsx')
# 筛选特定客户
customer_data = ar_transactions[ar_transactions['customer'] == '深圳XX公司']
# 生成对账单
statement = generate_ar_statement(
customer_name='深圳XX公司',
transactions=customer_data,
as_of_date='2026-01-31'
)
print(statement)
```
### 场景3: 账龄分析
```python
# 加载未收款数据
receivables = pd.read_excel('应收账款.xlsx')
# 账龄分析
aging = aging_analysis(
receivables=receivables,
as_of_date='2026-01-31',
aging_buckets=[30, 60, 90, 180, 365]
)
print(aging)
# 导出
aging.to_excel('账龄分析_202601.xlsx')
```
### 场景4: 扫描单据OCR识别与对账匹配(诉讼证据核对)
适用于:承运商/供应商提供扫描件作为诉讼证据,需要与我司对账单进行匹配核对。
```python
from pdf2image import convert_from_path
import pytesseract
import pandas as pd
import re
def ocr_pdf_extract_orders(pdf_path: str, dpi: int = 150) -> pd.DataFrame:
"""
从 PDF 扫描件中 OCR 提取订单信息
Args:
pdf_path: PDF 文件路径
dpi: 图像分辨率,越高识别越准但速度越慢
Returns:
DataFrame 包含 order_no, page, tc_no 等字段
"""
print(f"正在转换 PDF...")
images = convert_from_path(pdf_path, dpi=dpi)
print(f"共 {len(images)} 页,开始 OCR 识别...")
all_records = []
for i, img in enumerate(images, 1):
if i % 10 == 0:
print(f" 处理进度: {i}/{len(images)}")
text = pytesseract.image_to_string(img, lang='chi_sim+eng')
# 提取多种订单号格式(根据实际业务调整)
hm_orders = re.findall(r'(HM\d{12,15})(?:-\d+)?', text) # 华住订单
hpo_orders = re.findall(r'(HPO-\d{8}-\d+)', text) # 华住采购订单
fy_orders = re.findall(r'(FY\d{9,})', text) # 凤悦订单
phgco_orders = re.findall(r'(PHGCO\d{9,})', text) # 锦江订单
tc_orders = re.findall(r'(TC\d{8,})', text) # 运单号
# 提取日期
dates = re.findall(r'(\d{4}-\d{2}-\d{2})', text)
all_orders = hm_orders + hpo_orders + fy_orders + phgco_orders
for order in all_orders:
all_records.append({
'order_no': order,
'page': i,
'tc_no': tc_orders[0] if tc_orders else None,
'date': dates[0] if dates else None,
'raw_text': text[:500] # 保留部分原文用于核查
})
df = pd.DataFrame(all_records)
df = df.drop_duplicates(subset=['order_no'])
print(f"提取完成,共 {len(df)} 条唯一订单")
return df
def extract_base_order_no(order_no: str) -> str:
"""
提取基础订单号(移除后缀如 -1, -2)
例如: HM202505227320-1 -> HM202505227320
"""
if pd.isna(order_no):
return None
order_str = str(order_no)
match = re.match(r'(HM\d{12,15}|HPO-\d{8}-\d+|FY\d{9,}|PHGCO\d{9,})', order_str)
return match.group(1) if match else order_str
def reconcile_scanned_with_excel(
scanned_df: pd.DataFrame,
excel_path: str,
main_sheet: str,
pending_sheet: str = None,
order_column: str = '华住/锦江订单号'
) -> dict:
"""
扫描单据与 Excel 对账单匹配
Args:
scanned_df: OCR 提取的订单 DataFrame
excel_path: Excel 对账单路径
main_sheet: 主数据 Sheet 名称
pending_sheet: 待结算/次月结 Sheet 名称(可选)
order_column: 订单号列名
Returns:
{
'matched_main': 在主表中匹配的订单,
'matched_pending': 在待结算表中匹配的订单,
'not_found': 未找到的订单,
'summary': 汇总统计
}
"""
# 加载主表
df_main = pd.read_excel(excel_path, sheet_name=main_sheet)
# 处理表头(如果第一行是表头)
if df_main.iloc[0].astype(str).str.contains(order_column).any():
df_main.columns = df_main.iloc[0]
df_main = df_main.iloc[1:].reset_index(drop=True)
# 提取基础订单号
main_orders = set(df_main[order_column].apply(extract_base_order_no).dropna())
# 加载待结算表(如有)
pending_orders = set()
df_pending = None
if pending_sheet:
try:
df_pending = pd.read_excel(excel_path, sheet_name=pending_sheet)
pending_orders = set(df_pending[order_column].apply(extract_base_order_no).dropna())
except:
pass
all_excel_orders = main_orders | pending_orders
# 扫描件订单号
scan_orders = set(scanned_df['order_no'].apply(extract_base_order_no).dropna())
# 匹配分析
matched_main = scan_orders & main_orders
matched_pending = scan_orders & pending_orders
not_found = scan_orders - all_excel_orders
# 汇总
summary = {
'scan_total': len(scan_orders),
'matched_main_count': len(matched_main),
'matched_pending_count': len(matched_pending),
'not_found_count': len(not_found),
'match_rate': (len(matched_main) + len(matched_pending)) / len(scan_orders) * 100 if scan_orders else 0
}
return {
'matched_main': matched_main,
'matched_pending': matched_pending,
'not_found': not_found,
'summary': summary,
'df_main': df_main,
'df_pending': df_pending
}
def generate_litigation_reconciliation_report(
result: dict,
scanned_df: pd.DataFrame,
output_path: str
):
"""
生成诉讼对账分析报告
"""
summary = result['summary']
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
# Sheet1: 汇总
summary_df = pd.DataFrame({
'项目': [
'扫描件订单数',
'在主对账单中找到',
'在待结算表中找到',
'完全未找到',
'匹配率'
],
'数值': [
summary['scan_total'],
summary['matched_main_count'],
summary['matched_pending_count'],
summary['not_found_count'],
f"{summary['match_rate']:.1f}%"
]
})
summary_df.to_excel(writer, sheet_name='对账汇总', index=False)
# Sheet2: 未找到的订单
if result['not_found']:
not_found_df = scanned_df[
scanned_df['order_no'].apply(extract_base_order_no).isin(result['not_found'])
][['order_no', 'page', 'tc_no', 'date']]
not_found_df.columns = ['订单号', 'PDF页码', '运单号', '日期']
not_found_df.to_excel(writer, sheet_name='未找到订单', index=False)
# Sheet3: 待结算订单详情
if result['matched_pending'] and result['df_pending'] is not None:
pending_detail = result['df_pending'][
result['df_pending']['华住/锦江订单号'].apply(extract_base_order_no).isin(result['matched_pending'])
]
pending_detail.to_excel(writer, sheet_name='待结算订单', index=False)
print(f"报告已保存: {output_path}")
```
#### 使用示例
```python
# 1. 从 PDF 扫描件提取订单
pdf_path = "承运商证据_6月业务.pdf"
scanned_df = ocr_pdf_extract_orders(pdf_path, dpi=150)
# 2. 与 Excel 对账单匹配
result = reconcile_scanned_with_excel(
scanned_df=scanned_df,
excel_path="对账单_2025年6月.xlsx",
main_sheet="2025.6",
pending_sheet="次月结",
order_column="华住/锦江订单号"
)
# 3. 查看匹配结果
print(f"匹配率: {result['summary']['match_rate']:.1f}%")
print(f"未找到: {result['summary']['not_found_count']} 条")
if result['not_found']:
print("\n未找到的订单:")
for order in result['not_found']:
print(f" {order}")
# 4. 生成报告
generate_litigation_reconciliation_report(
result=result,
scanned_df=scanned_df,
output_path="诉讼对账分析报告.xlsx"
)
```
#### OCR 识别注意事项
1. **环境依赖**:
```bash
# macOS
brew install tesseract tesseract-lang poppler
pip install pdf2image pytesseract pandas openpyxl
```
2. **识别精度**:
- DPI 150 适合快速扫描,DPI 200-250 适合精确识别
- 中文识别需要 `chi_sim` 语言包
3. **订单号格式差异**:
- PDF 中可能是 `HM202505227320`
- Excel 中可能是 `HM202505227320-1`、`HM202505227320-2`(带后缀)
- 匹配时应使用基础订单号
4. **常见问题**:
- 空白页(签收单背面)会被自动跳过
- OCR 可能误识别字符(如 5→S, 0→O),建议人工抽查
- 大 PDF 处理较慢,可先用低 DPI 预览
5. **"次月结"/"待结算" 订单**:
- 这类订单通常标记为"没有报价"、"未建单"等
- 虽然在系统中有记录,但可能未计入正式结算金额
- 诉讼中需特别关注这类订单的状态
6. **多承运商订单归属校验(重要风险点)**:
- 承运商可能将其他公司的订单混入证据,企图多收运费
- 必须检查PDF订单是否出现在其他承运商的对账单中
- 如果订单同时出现在多个承运商对账单中,则为异常订单
#### 多承运商订单归属校验
```python
def check_order_ownership(
scanned_df: pd.DataFrame,
my_company_excel: str,
other_carrier_excels: list[str],
order_column: str = '华住/锦江订单号'
) -> dict:
"""
检查订单归属,识别是否有其他承运商的订单被混入
Args:
scanned_df: OCR 提取的订单 DataFrame
my_company_excel: 我司对账单路径
other_carrier_excels: 其他承运商对账单路径列表
order_column: 订单号列名
Returns:
{
'my_orders': 属于我司的订单,
'other_carrier_orders': 属于其他承运商的订单(异常),
'disputed_orders': 同时出现在多处的争议订单,
'summary': 汇总
}
"""
# 提取PDF订单
scan_orders = set(scanned_df['order_no'].apply(extract_base_order_no).dropna())
# 加载我司对账单订单
my_orders = set()
try:
xl = pd.ExcelFile(my_company_excel)
for sheet in xl.sheet_names:
df = pd.read_excel(my_company_excel, sheet_name=sheet)
for col in df.columns:
if '订单' in str(col):
orders = df[col].apply(extract_base_order_no).dropna()
my_orders.update(orders)
except Exception as e:
print(f"加载我司对账单失败: {e}")
# 加载其他承运商对账单订单
other_orders = {} # {carrier_name: set of orders}
for excel_path in other_carrier_excels:
carrier_name = excel_path.split('/')[-1]
carrier_orders = set()
try:
xl = pd.ExcelFile(excel_path)
for sheet in xl.sheet_names:
df = pd.read_excel(excel_path, sheet_name=sheet)
for col in df.columns:
if '订单' in str(col):
orders = df[col].apply(extract_base_order_no).dropna()
carrier_orders.update(orders)
other_orders[carrier_name] = carrier_orders
except:
pass
# 分析订单归属
all_other_orders = set()
for orders in other_orders.values():
all_other_orders.update(orders)
# 分类
only_mine = scan_orders & my_orders - all_other_orders # 仅在我司
only_others = scan_orders & all_other_orders - my_orders # 仅在其他承运商(异常!)
in_both = scan_orders & my_orders & all_other_orders # 同时出现(争议)
nowhere = scan_orders - my_orders - all_other_orders # 哪里都没有
# 识别异常订单属于哪个承运商
abnormal_details = []
for order in only_others:
for carrier, orders in other_orders.items():
if order in orders:
page = scanned_df[scanned_df['order_no'].apply(extract_base_order_no) == order]['page'].values
abnormal_details.append({
'order_no': order,
'belongs_to': carrier,
'pdf_page': page[0] if len(page) > 0 else None
})
summary = {
'scan_total': len(scan_orders),
'my_orders_count': len(only_mine),
'other_carrier_count': len(only_others), # 异常订单数
'disputed_count': len(in_both),
'not_found_count': len(nowhere)
}
return {
'my_orders': only_mine,
'other_carrier_orders': only_others,
'abnormal_details': abnormal_details,
'disputed_orders': in_both,
'not_found': nowhere,
'summary': summary
}
```
#### 使用示例(多承运商校验)
```python
# 检查订单归属
result = check_order_ownership(
scanned_df=scanned_df,
my_company_excel="智慧云彩对账单_2025年6月.xlsx",
other_carrier_excels=[
"友利速运对账单_2025年6月.xlsx",
"其他承运商对账单.xlsx"
]
)
# 检查是否有异常订单
if result['summary']['other_carrier_count'] > 0:
print(f"⚠️ 发现 {result['summary']['other_carrier_count']} 个异常订单!")
print("这些订单属于其他承运商,不应由我司支付:")
for item in result['abnormal_details']:
print(f" {item['order_no']} -> 属于 {item['belongs_to']} (PDF第{item['pdf_page']}页)")
else:
print("✓ 未发现异常订单,所有订单归属正确")
```
---
## 注意事项
1. **数据格式统一**: 确保日期、金额格式一致后再对账
@@ -949,3 +495,9 @@ else:
- **data-excel**: 处理复杂的 Excel 数据转换
- **feishu**: 将对账报告保存到飞书云文档
- **siyuan**: 记录对账工作日志到思源笔记
## 详细内容
以下章节已移到 [references/details.md](references/details.md):
- 常见对账场景
@@ -0,0 +1,453 @@
# skills 详细内容
## 常见对账场景
### 场景1: 月末银行对账
```python
# 1. 加载数据
bank_df = load_bank_statement('银行流水_202601.xlsx')
system_df = load_system_records('收付款记录_202601.xlsx')
# 2. 执行对账
result = reconcile(bank_df, system_df)
# 3. 查看汇总
print(f"已匹配: {result['summary']['matched_count']} 笔")
print(f"银行未达: {result['summary']['bank_only_count']} 笔, 金额 {result['summary']['bank_only_amount']:,.2f}")
print(f"企业未达: {result['summary']['system_only_count']} 笔, 金额 {result['summary']['system_only_amount']:,.2f}")
# 4. 生成调节表
report = generate_bank_reconciliation_report(
bank_balance=150000.00, # 银行对账单余额
book_balance=145000.00, # 企业账面余额
bank_only=result['bank_only'],
system_only=result['system_only'],
as_of_date='2026-01-31'
)
print(report)
# 5. 导出 Excel
export_reconciliation_to_excel(result, '银行对账_202601.xlsx', '2026-01-31')
```
### 场景2: 客户对账
```python
# 加载客户交易明细
ar_transactions = pd.read_excel('应收明细.xlsx')
# 筛选特定客户
customer_data = ar_transactions[ar_transactions['customer'] == '深圳XX公司']
# 生成对账单
statement = generate_ar_statement(
customer_name='深圳XX公司',
transactions=customer_data,
as_of_date='2026-01-31'
)
print(statement)
```
### 场景3: 账龄分析
```python
# 加载未收款数据
receivables = pd.read_excel('应收账款.xlsx')
# 账龄分析
aging = aging_analysis(
receivables=receivables,
as_of_date='2026-01-31',
aging_buckets=[30, 60, 90, 180, 365]
)
print(aging)
# 导出
aging.to_excel('账龄分析_202601.xlsx')
```
### 场景4: 扫描单据OCR识别与对账匹配(诉讼证据核对)
适用于:承运商/供应商提供扫描件作为诉讼证据,需要与我司对账单进行匹配核对。
```python
from pdf2image import convert_from_path
import pytesseract
import pandas as pd
import re
def ocr_pdf_extract_orders(pdf_path: str, dpi: int = 150) -> pd.DataFrame:
"""
从 PDF 扫描件中 OCR 提取订单信息
Args:
pdf_path: PDF 文件路径
dpi: 图像分辨率,越高识别越准但速度越慢
Returns:
DataFrame 包含 order_no, page, tc_no 等字段
"""
print(f"正在转换 PDF...")
images = convert_from_path(pdf_path, dpi=dpi)
print(f"共 {len(images)} 页,开始 OCR 识别...")
all_records = []
for i, img in enumerate(images, 1):
if i % 10 == 0:
print(f" 处理进度: {i}/{len(images)}")
text = pytesseract.image_to_string(img, lang='chi_sim+eng')
# 提取多种订单号格式(根据实际业务调整)
hm_orders = re.findall(r'(HM\d{12,15})(?:-\d+)?', text) # 华住订单
hpo_orders = re.findall(r'(HPO-\d{8}-\d+)', text) # 华住采购订单
fy_orders = re.findall(r'(FY\d{9,})', text) # 凤悦订单
phgco_orders = re.findall(r'(PHGCO\d{9,})', text) # 锦江订单
tc_orders = re.findall(r'(TC\d{8,})', text) # 运单号
# 提取日期
dates = re.findall(r'(\d{4}-\d{2}-\d{2})', text)
all_orders = hm_orders + hpo_orders + fy_orders + phgco_orders
for order in all_orders:
all_records.append({
'order_no': order,
'page': i,
'tc_no': tc_orders[0] if tc_orders else None,
'date': dates[0] if dates else None,
'raw_text': text[:500] # 保留部分原文用于核查
})
df = pd.DataFrame(all_records)
df = df.drop_duplicates(subset=['order_no'])
print(f"提取完成,共 {len(df)} 条唯一订单")
return df
def extract_base_order_no(order_no: str) -> str:
"""
提取基础订单号(移除后缀如 -1, -2)
例如: HM202505227320-1 -> HM202505227320
"""
if pd.isna(order_no):
return None
order_str = str(order_no)
match = re.match(r'(HM\d{12,15}|HPO-\d{8}-\d+|FY\d{9,}|PHGCO\d{9,})', order_str)
return match.group(1) if match else order_str
def reconcile_scanned_with_excel(
scanned_df: pd.DataFrame,
excel_path: str,
main_sheet: str,
pending_sheet: str = None,
order_column: str = '华住/锦江订单号'
) -> dict:
"""
扫描单据与 Excel 对账单匹配
Args:
scanned_df: OCR 提取的订单 DataFrame
excel_path: Excel 对账单路径
main_sheet: 主数据 Sheet 名称
pending_sheet: 待结算/次月结 Sheet 名称(可选)
order_column: 订单号列名
Returns:
{
'matched_main': 在主表中匹配的订单,
'matched_pending': 在待结算表中匹配的订单,
'not_found': 未找到的订单,
'summary': 汇总统计
}
"""
# 加载主表
df_main = pd.read_excel(excel_path, sheet_name=main_sheet)
# 处理表头(如果第一行是表头)
if df_main.iloc[0].astype(str).str.contains(order_column).any():
df_main.columns = df_main.iloc[0]
df_main = df_main.iloc[1:].reset_index(drop=True)
# 提取基础订单号
main_orders = set(df_main[order_column].apply(extract_base_order_no).dropna())
# 加载待结算表(如有)
pending_orders = set()
df_pending = None
if pending_sheet:
try:
df_pending = pd.read_excel(excel_path, sheet_name=pending_sheet)
pending_orders = set(df_pending[order_column].apply(extract_base_order_no).dropna())
except:
pass
all_excel_orders = main_orders | pending_orders
# 扫描件订单号
scan_orders = set(scanned_df['order_no'].apply(extract_base_order_no).dropna())
# 匹配分析
matched_main = scan_orders & main_orders
matched_pending = scan_orders & pending_orders
not_found = scan_orders - all_excel_orders
# 汇总
summary = {
'scan_total': len(scan_orders),
'matched_main_count': len(matched_main),
'matched_pending_count': len(matched_pending),
'not_found_count': len(not_found),
'match_rate': (len(matched_main) + len(matched_pending)) / len(scan_orders) * 100 if scan_orders else 0
}
return {
'matched_main': matched_main,
'matched_pending': matched_pending,
'not_found': not_found,
'summary': summary,
'df_main': df_main,
'df_pending': df_pending
}
def generate_litigation_reconciliation_report(
result: dict,
scanned_df: pd.DataFrame,
output_path: str
):
"""
生成诉讼对账分析报告
"""
summary = result['summary']
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
# Sheet1: 汇总
summary_df = pd.DataFrame({
'项目': [
'扫描件订单数',
'在主对账单中找到',
'在待结算表中找到',
'完全未找到',
'匹配率'
],
'数值': [
summary['scan_total'],
summary['matched_main_count'],
summary['matched_pending_count'],
summary['not_found_count'],
f"{summary['match_rate']:.1f}%"
]
})
summary_df.to_excel(writer, sheet_name='对账汇总', index=False)
# Sheet2: 未找到的订单
if result['not_found']:
not_found_df = scanned_df[
scanned_df['order_no'].apply(extract_base_order_no).isin(result['not_found'])
][['order_no', 'page', 'tc_no', 'date']]
not_found_df.columns = ['订单号', 'PDF页码', '运单号', '日期']
not_found_df.to_excel(writer, sheet_name='未找到订单', index=False)
# Sheet3: 待结算订单详情
if result['matched_pending'] and result['df_pending'] is not None:
pending_detail = result['df_pending'][
result['df_pending']['华住/锦江订单号'].apply(extract_base_order_no).isin(result['matched_pending'])
]
pending_detail.to_excel(writer, sheet_name='待结算订单', index=False)
print(f"报告已保存: {output_path}")
```
#### 使用示例
```python
# 1. 从 PDF 扫描件提取订单
pdf_path = "承运商证据_6月业务.pdf"
scanned_df = ocr_pdf_extract_orders(pdf_path, dpi=150)
# 2. 与 Excel 对账单匹配
result = reconcile_scanned_with_excel(
scanned_df=scanned_df,
excel_path="对账单_2025年6月.xlsx",
main_sheet="2025.6",
pending_sheet="次月结",
order_column="华住/锦江订单号"
)
# 3. 查看匹配结果
print(f"匹配率: {result['summary']['match_rate']:.1f}%")
print(f"未找到: {result['summary']['not_found_count']} 条")
if result['not_found']:
print("\n未找到的订单:")
for order in result['not_found']:
print(f" {order}")
# 4. 生成报告
generate_litigation_reconciliation_report(
result=result,
scanned_df=scanned_df,
output_path="诉讼对账分析报告.xlsx"
)
```
#### OCR 识别注意事项
1. **环境依赖**:
```bash
# macOS
brew install tesseract tesseract-lang poppler
pip install pdf2image pytesseract pandas openpyxl
```
2. **识别精度**:
- DPI 150 适合快速扫描,DPI 200-250 适合精确识别
- 中文识别需要 `chi_sim` 语言包
3. **订单号格式差异**:
- PDF 中可能是 `HM202505227320`
- Excel 中可能是 `HM202505227320-1`、`HM202505227320-2`(带后缀)
- 匹配时应使用基础订单号
4. **常见问题**:
- 空白页(签收单背面)会被自动跳过
- OCR 可能误识别字符(如 5→S, 0→O),建议人工抽查
- 大 PDF 处理较慢,可先用低 DPI 预览
5. **"次月结"/"待结算" 订单**:
- 这类订单通常标记为"没有报价"、"未建单"等
- 虽然在系统中有记录,但可能未计入正式结算金额
- 诉讼中需特别关注这类订单的状态
6. **多承运商订单归属校验(重要风险点)**:
- 承运商可能将其他公司的订单混入证据,企图多收运费
- 必须检查PDF订单是否出现在其他承运商的对账单中
- 如果订单同时出现在多个承运商对账单中,则为异常订单
#### 多承运商订单归属校验
```python
def check_order_ownership(
scanned_df: pd.DataFrame,
my_company_excel: str,
other_carrier_excels: list[str],
order_column: str = '华住/锦江订单号'
) -> dict:
"""
检查订单归属,识别是否有其他承运商的订单被混入
Args:
scanned_df: OCR 提取的订单 DataFrame
my_company_excel: 我司对账单路径
other_carrier_excels: 其他承运商对账单路径列表
order_column: 订单号列名
Returns:
{
'my_orders': 属于我司的订单,
'other_carrier_orders': 属于其他承运商的订单(异常),
'disputed_orders': 同时出现在多处的争议订单,
'summary': 汇总
}
"""
# 提取PDF订单
scan_orders = set(scanned_df['order_no'].apply(extract_base_order_no).dropna())
# 加载我司对账单订单
my_orders = set()
try:
xl = pd.ExcelFile(my_company_excel)
for sheet in xl.sheet_names:
df = pd.read_excel(my_company_excel, sheet_name=sheet)
for col in df.columns:
if '订单' in str(col):
orders = df[col].apply(extract_base_order_no).dropna()
my_orders.update(orders)
except Exception as e:
print(f"加载我司对账单失败: {e}")
# 加载其他承运商对账单订单
other_orders = {} # {carrier_name: set of orders}
for excel_path in other_carrier_excels:
carrier_name = excel_path.split('/')[-1]
carrier_orders = set()
try:
xl = pd.ExcelFile(excel_path)
for sheet in xl.sheet_names:
df = pd.read_excel(excel_path, sheet_name=sheet)
for col in df.columns:
if '订单' in str(col):
orders = df[col].apply(extract_base_order_no).dropna()
carrier_orders.update(orders)
other_orders[carrier_name] = carrier_orders
except:
pass
# 分析订单归属
all_other_orders = set()
for orders in other_orders.values():
all_other_orders.update(orders)
# 分类
only_mine = scan_orders & my_orders - all_other_orders # 仅在我司
only_others = scan_orders & all_other_orders - my_orders # 仅在其他承运商(异常!)
in_both = scan_orders & my_orders & all_other_orders # 同时出现(争议)
nowhere = scan_orders - my_orders - all_other_orders # 哪里都没有
# 识别异常订单属于哪个承运商
abnormal_details = []
for order in only_others:
for carrier, orders in other_orders.items():
if order in orders:
page = scanned_df[scanned_df['order_no'].apply(extract_base_order_no) == order]['page'].values
abnormal_details.append({
'order_no': order,
'belongs_to': carrier,
'pdf_page': page[0] if len(page) > 0 else None
})
summary = {
'scan_total': len(scan_orders),
'my_orders_count': len(only_mine),
'other_carrier_count': len(only_others), # 异常订单数
'disputed_count': len(in_both),
'not_found_count': len(nowhere)
}
return {
'my_orders': only_mine,
'other_carrier_orders': only_others,
'abnormal_details': abnormal_details,
'disputed_orders': in_both,
'not_found': nowhere,
'summary': summary
}
```
#### 使用示例(多承运商校验)
```python
# 检查订单归属
result = check_order_ownership(
scanned_df=scanned_df,
my_company_excel="智慧云彩对账单_2025年6月.xlsx",
other_carrier_excels=[
"友利速运对账单_2025年6月.xlsx",
"其他承运商对账单.xlsx"
]
)
# 检查是否有异常订单
if result['summary']['other_carrier_count'] > 0:
print(f"⚠️ 发现 {result['summary']['other_carrier_count']} 个异常订单!")
print("这些订单属于其他承运商,不应由我司支付:")
for item in result['abnormal_details']:
print(f" {item['order_no']} -> 属于 {item['belongs_to']} (PDF第{item['pdf_page']}页)")
else:
print("✓ 未发现异常订单,所有订单归属正确")
```
---