Claude Code本地化部署实现数据分析师自动化报告生成
1. 项目概述:当Claude Code真正坐进分析师工位
你有没有过这种体验:凌晨一点半,盯着Excel里三十七张工作表发呆,手边是刚改完第七版的“Q3客户触点归因分析报告”,而邮件草稿箱里躺着HR刚发来的《新员工入职数据看板需求说明书》,截止时间是明早九点?我干了八年数据分析师,在三家不同行业的公司做过报表体系搭建,最常被问的问题不是“这个指标怎么算”,而是“Freddie,能不能把上个月的模板复制一份,我填个数就行?”——这句话背后藏着整个行业的隐痛:我们花了70%的时间在重复性取数、格式调整、图表重绘和文字润色上,真正做洞察、提建议、推业务的时间,连30%都不到。
这次我决定不抄模板了。我把Claude Code请进了我的本地开发环境,给它配了一套完整的“分析师上岗包”:数据库连接权限、带注释的表结构文档、五条核心SQL范式、一套图表配色规范,甚至还有去年七月报告的Markdown源文件。这不是让它写诗,也不是让它编故事,而是让它像一个刚通过试用期、能独立跑通ETL+可视化+初稿撰写全流程的初级分析师那样,从零开始产出一份可交付的客户联系行为分析报告。关键词很明确: Claude Code、数据分析师、自动化报告、SQL生成、Jupyter集成、结构化提示工程、本地化部署 。它不依赖任何云端API调用,所有操作都在我本机完成;它不猜测业务逻辑,所有判断都基于我提供的上下文;它不自由发挥,每一步都严格遵循我设定的待办清单闭环。这篇文章不是讲“AI有多厉害”,而是记录一个真实从业者如何把一个大模型工具,拧成一把精准、可控、能嵌入现有工作流的螺丝刀。如果你也每天被周报、月报、季报追着跑,或者正为团队新人上手慢、报表口径不一致、临时加急需求焦头烂额,那接下来这五千多字,就是你明天早上就能打开终端直接复现的操作手册。
2. 核心思路拆解:为什么必须放弃“自由发挥”,转向“流程闭环”
很多人第一次尝试让大模型写SQL时,会本能地扔过去一句:“帮我查一下最近三个月的用户投诉率变化”。结果呢?模型可能返回一条语法正确的SELECT语句,但WHERE条件里用的是 created_date > '2024-04-01' ,而你的生产库字段名其实是 contact_created_at_utc ;它可能把“投诉率”理解成 COUNT(complaint_id)/COUNT(*) ,而你实际业务定义是 COUNT(complaint_id)/COUNT(DISTINCT user_id) ;更糟的是,它可能压根没意识到你的 complaint_id 字段在 contacts 表里是空值占85%,真正的投诉标识藏在 contact_reason_code 字段的特定枚举值里。这不是模型能力问题,而是信息不对称问题——它没有坐在你的工位上,没看过你贴在显示器边上的《数据字典V3.2》,没听你和产品总监争论过“活跃用户”的三种定义口径,更没经历过你因为漏掉一个LEFT JOIN导致日报延迟两小时的惨痛教训。
所以,我彻底放弃了“给任务、等结果”的粗放模式,转而构建一个 类人类分析师的上岗培训+标准化作业流程(SOP)双轨制框架 。这个框架有两根支柱:第一根叫“ 上下文即权限 ”,第二根叫“ 待办清单即控制权 ”。
先说第一根支柱。“上下文即权限”意味着,我不再把Claude Code当成一个需要猜谜的黑箱,而是把它当作一个需要完整入职材料的新同事。我给它的不是“你要分析什么”,而是“你被允许知道什么”。这份材料包含五个不可省略的模块:
- 表结构白皮书 :不是简单的
DESCRIBE contacts;输出,而是逐列解释。比如contact_channel字段,我写的是:“字符串类型,取值为'phone'/'email'/'chat'/'social_media'四类,其中'social_media'包含微博、微信公众号、小红书三个子渠道,需在分析中单独展开;注意该字段存在约2%的NULL值,代表未记录渠道的旧数据,处理策略为归入'unknown'类别并单独标注”。 - 核心指标计算手册 :明确写出每个业务指标的SQL实现公式。例如“月度联系率”定义为:
(本月有效联系数 / 本月去重活跃用户数) * 100%,其中“有效联系数”=COUNT(*) FILTER (WHERE contact_status = 'resolved' OR contact_status = 'in_progress'),“活跃用户数”=COUNT(DISTINCT user_id) FILTER (WHERE last_active_date >= current_date - INTERVAL '30 days')。 - 高频查询速查卡 :提供三条最常被复用的SQL骨架。比如“按渠道分组统计”模板:
SELECT contact_channel, COUNT(*) as total_contacts, COUNT(*) FILTER (WHERE contact_reason_code IN ('billing', 'refund')) as finance_related FROM contacts WHERE created_at_ts >= '{start_date}' AND created_at_ts < '{end_date}' GROUP BY contact_channel ORDER BY total_contacts DESC;。这里用{start_date}占位符,后续由Claude自动替换,既保证灵活性,又杜绝硬编码错误。 - 图表规范指南 :规定所有柱状图必须使用
#1f77b4主色,折线图用#ff7f0e,双轴图左轴用蓝色系、右轴用橙色系;时间序列图X轴必须为ISO标准日期格式(YYYY-MM-DD),Y轴标题必须包含单位(如“联系量(次)”、“比率(%)”);所有图表下方必须添加数据来源脚注:“数据来源:contacts表,2025-07-01至2025-07-31”。 - 报告结构蓝图 :提供一份去年七月报告的纯文本结构示例,精确到二级标题层级:“## 一、整体趋势概览\n### 1.1 联系总量变化\n> 图1:2025年6-7月联系量双轴对比图\n### 1.2 渠道分布结构\n> 图2:7月各渠道联系量占比饼图”。
这五份材料加起来不到2000字,但它们构成了Claude Code的“认知边界”。它不再需要猜测“contact_reason_code”是什么,因为白皮书里写了;它不会乱用COUNT(*)算活跃用户,因为手册里明确定义了过滤条件;它画出的图表颜色、字体、坐标轴标签,和我上周五发给老板的PPT里一模一样。这就是“上下文即权限”的威力——你给得越具体,它犯错的空间就越小。
第二根支柱“待办清单即控制权”,解决的是模型“多线程失控”的顽疾。大模型的默认工作模式是“发散式联想”,它看到“分析联系趋势”,脑子里立刻蹦出二十个可能方向:要不要看地域分布?要不要关联用户生命周期阶段?要不要做情感分析?这种自由对创意写作是财富,对报表生产就是灾难。我的解决方案是: 用文件系统模拟一个物理待办看板,用原子化任务驱动每一次模型响应 。我创建了一个 july_2025_analysis/todo.txt 文件,初始内容如下:
## PENDING TASKS
1. 读取表结构白皮书文件 `docs/contacts_schema_v3.md`,确认所有字段含义。
2. 在 `july_2025_analysis/` 目录下新建 Jupyter Notebook `contacts_analysis_july_2025.ipynb`。
3. 【QUERY 1】生成SQL:计算2025年6月与7月各渠道联系量及环比变化率。执行查询,保存结果为DataFrame,绘制分组柱状图,导出为 `01_channel_volume_comparison.png`。
4. 【QUERY 2】生成SQL:计算2025年7月各联系原因代码(contact_reason_code)的分布占比,筛选TOP5原因,绘制环形图,导出为 `02_reason_distribution.png`。
5. 【QUERY 3】生成SQL:计算2025年7月各渠道的平均首次响应时长(单位:分钟),排除NULL值,绘制水平条形图,导出为 `03_response_time_by_channel.png`。
## COMPLETED TASKS
关键设计在于:Claude Code的每次响应, 只能且必须 完成 PENDING TASKS 列表里的第一项。做完后,它要做的第一件事不是写分析,而是编辑 todo.txt 文件,把该项从 PENDING TASKS 剪切到 COMPLETED TASKS 区。下一次调用时,它会重新读取这个文件,自然就看到新的第一项任务。这个机制带来了三个质变:
- 可观测性 :我可以随时打开
todo.txt,一眼看清当前进度是卡在第3步的SQL生成,还是第4步的图表导出失败。 - 可中断性 :如果中途发现某条SQL逻辑有误,我手动修改
todo.txt,把错误任务移回PENDING TASKS,下次调用就自动重试。 - 可组合性 :当
PENDING TASKS清空后,我触发第二个独立的Claude会话,专门处理报告撰写。这个会话的输入只有todo.txt的COMPLETED TASKS内容、所有已生成的PNG图表、以及报告模板文件。两个会话完全解耦,互不干扰。
这就像给高速行驶的列车装上了轨道和信号灯。它不再是一辆想往哪开就往哪开的越野车,而是一列严格按照时刻表停靠指定站点的高铁。自由度降低了,但可靠性和可复现性提升了十倍。这才是工程化落地的核心——不是追求单次惊艳,而是确保每次运行都稳如老狗。
3. 实操细节解析:从数据库连接到图表导出的全链路配置
光有思路不够,实操中每一个环节的配置细节,都决定了Claude Code是顺利跑通,还是在第一步就卡死。我用的是PostgreSQL 15作为本地测试库,Python 3.11环境,Claude Code运行在Ollama 0.3.3上(模型为 claude-code:3.5-sonnet )。下面我把整个链路拆解成四个不可跳过的技术锚点,每个都附上我踩坑后验证过的最优配置。
3.1 数据库安全接入:不用密码,用连接字符串环境变量
很多教程教你在提示词里直接写 host=localhost port=5432 dbname=mydb user=postgres password=mypass ,这是大忌。一是密码硬编码在日志里极不安全,二是Claude Code在生成SQL时可能把连接字符串当普通文本拼接进去,引发语法错误。我的方案是: 用环境变量注入连接信息,并在Jupyter内核启动时预加载 。
首先,在系统级环境变量中设置:
export DB_CONNECTION_STRING="postgresql://analyst:securepass@localhost:5432/customer_analytics"
注意,这里的用户名 analyst 是一个只读角色,我在数据库里执行了:
CREATE ROLE analyst WITH LOGIN PASSWORD 'securepass';
GRANT CONNECT ON DATABASE customer_analytics TO analyst;
GRANT USAGE ON SCHEMA public TO analyst;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO analyst;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO analyst;
这样,即使Claude Code生成了恶意SQL(比如 DROP TABLE contacts ),也会因权限不足而报错,不会造成实质破坏。
然后,在Claude Code的系统提示词里,我明确告知它:“你无需构造数据库连接字符串。Jupyter Notebook内核已预配置好名为 engine 的SQLAlchemy引擎对象,你只需使用 pd.read_sql(query, engine) 即可执行查询。” 这句话看似简单,却消除了90%的连接类错误。我测试过,当Claude试图自己拼接连接串时,有67%的概率把端口号写成 5433 (PostgreSQL默认是5432),或把数据库名拼错成 customer_anayltics 。而预置 engine 对象后,它只需要专注SQL逻辑本身。
3.2 SQL生成的“防幻觉”三重校验机制
Claude Code生成SQL的最大风险不是语法错误,而是 语义幻觉 ——它写的SQL能跑通,但结果完全不符合业务意图。比如要求“计算7月各渠道联系量”,它可能生成:
SELECT contact_channel, COUNT(*)
FROM contacts
WHERE EXTRACT(MONTH FROM created_at_ts) = 7
GROUP BY contact_channel;
这条SQL语法完美,但致命错误在于: EXTRACT(MONTH FROM created_at_ts) = 7 会匹配所有年份的7月数据,而不是仅2025年7月。为堵住这类漏洞,我设计了三重校验:
第一重:时间范围硬约束模板
我在“高频查询速查卡”里,所有涉及时间的模板都强制使用 BETWEEN 而非 EXTRACT 。例如:
-- ✅ 正确模板(带年份硬约束)
SELECT contact_channel, COUNT(*)
FROM contacts
WHERE created_at_ts BETWEEN '2025-07-01' AND '2025-07-31 23:59:59'
GROUP BY contact_channel;
-- ❌ 错误示例(Claude易犯)
SELECT contact_channel, COUNT(*)
FROM contacts
WHERE EXTRACT(YEAR FROM created_at_ts) = 2025
AND EXTRACT(MONTH FROM created_at_ts) = 7
GROUP BY contact_channel;
我特意在提示词里强调:“所有时间过滤条件必须使用ISO 8601标准字符串进行BETWEEN比较,禁止使用EXTRACT、DATE_PART等函数。日期字符串格式必须为'YYYY-MM-DD',时间字符串格式必须为'YYYY-MM-DD HH:MM:SS'。”
第二重:字段存在性实时验证
Claude Code在生成SQL前,必须先执行 SELECT column_name FROM information_schema.columns WHERE table_name = 'contacts'; 获取当前表的真实字段列表,并与我的表结构白皮书交叉比对。我在提示词里写死规则:“若查询中引用的字段名不在上述列表中,或白皮书中未定义其业务含义,则必须停止生成,向用户报告该字段缺失。” 这招让我揪出了三次严重幻觉:一次它想用 user_segment 字段(实际已废弃),一次它假设 contact_reason_code 有 'fraud' 值(实际枚举值里只有 'billing' 、 'technical' 等),还有一次它试图JOIN一个根本不存在的 user_profiles 表。
第三重:结果集合理性断言
每条SQL执行后,Claude Code必须对结果集做三类断言:
- 行数断言:
len(df) > 0,若为空则报错“查询结果为空,请检查时间范围或过滤条件”; - 数值范围断言:对比率类字段,
df['rate'].between(0, 100).all(),若超限则报错“比率超出合理范围[0,100],请检查计算逻辑”; - 分类完整性断言:对渠道类字段,
set(df['contact_channel']) <= {'phone','email','chat','social_media','unknown'},若出现未知值则报错“检测到未定义渠道值,需更新表结构白皮书”。
这三重校验加起来,让Claude Code生成的SQL从“能跑就行”升级到“跑得对、跑得稳、跑得懂”。
3.3 Jupyter Notebook的自动化初始化与状态持久化
Claude Code需要在一个干净、可预测的Jupyter环境中工作。我拒绝让它每次从零开始创建Notebook,因为那意味着要重复配置内核、安装包、设置路径。我的方案是: 预生成一个带元数据的Notebook模板,并用Git管理其版本 。
我创建了一个 templates/analysis_notebook_template.ipynb ,其 metadata 部分已预设:
{
"kernelspec": {
"name": "python3",
"display_name": "Python 3.11"
},
"language_info": {
"name": "python",
"version": "3.11.8",
"mimetype": "text/x-python",
"codemirror_mode": {"name": "ipython", "version": 3}
}
}
并且,该Notebook的首个cell已固化为:
# 初始化环境
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sqlalchemy import create_engine
import os
# 从环境变量加载数据库连接
engine = create_engine(os.getenv("DB_CONNECTION_STRING"))
# 设置图表全局样式
plt.style.use('seaborn-v0_8')
sns.set_palette(['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728'])
plt.rcParams['font.sans-serif'] = ['DejaVu Sans', 'Arial']
plt.rcParams['axes.unicode_minus'] = False
当Claude Code执行“新建Notebook”任务时,它实际做的是: shutil.copy('templates/analysis_notebook_template.ipynb', 'july_2025_analysis/contacts_analysis_july_2025.ipynb') 。这样,每次生成的Notebook都继承了统一的环境配置、包导入和样式设置,避免了因matplotlib后端不一致导致的图表渲染失败(我曾因此浪费三小时排查 plt.show() 不显示的问题)。
更关键的是状态持久化。Claude Code在Notebook中执行的每一步,都必须将中间结果显式保存为变量,并在下一个cell中引用。例如,QUERY 1执行后,它必须写:
# QUERY 1: 各渠道联系量及环比
df_channel_volume = pd.read_sql("""
SELECT contact_channel, COUNT(*) as volume,
LAG(COUNT(*)) OVER (ORDER BY contact_channel) as prev_volume
FROM contacts
WHERE created_at_ts BETWEEN '2025-07-01' AND '2025-07-31 23:59:59'
GROUP BY contact_channel
""", engine)
# 保存为文件供后续分析使用
df_channel_volume.to_parquet('july_2025_analysis/df_channel_volume.parquet')
这样,当它进入QUERY 2时,可以直接 pd.read_parquet('july_2025_analysis/df_channel_volume.parquet') 读取,而不必重新查询数据库。这不仅加速了流程,更重要的是切断了“状态漂移”——每个任务都基于确定的输入数据,不会因上游SQL微调而意外改变下游结果。
3.4 图表导出的像素级一致性控制
最后一步,也是最容易被忽视的一步:图表导出。很多教程只说“用matplotlib画图”,但实际生产中,一张图的成败往往在毫米之间。我制定了四条铁律:
铁律一:尺寸与DPI锁定
所有图表必须显式设置 figsize=(10, 6) 和 dpi=120 。 figsize 确保在Word文档中缩放时比例协调, dpi=120 是屏幕显示与打印输出的平衡点(低于96会模糊,高于150在Word里易失真)。Claude Code生成的绘图代码必须包含:
fig, ax = plt.subplots(figsize=(10, 6), dpi=120)
# ... 绘图逻辑 ...
plt.savefig('july_2025_analysis/01_channel_volume_comparison.png',
bbox_inches='tight', pad_inches=0.1)
铁律二:字体嵌入与抗锯齿
在 plt.rcParams 中启用:
plt.rcParams['pdf.fonttype'] = 42 # 确保字体嵌入PDF
plt.rcParams['ps.fonttype'] = 42 # 同上
plt.rcParams['lines.antialiased'] = True # 抗锯齿
plt.rcParams['patch.antialiased'] = True # 抗锯齿
这解决了中文乱码和线条毛刺两大痛点。我曾因 pdf.fonttype=3 导致导出的PNG里中文全变成方框,重装字体库都没用,最后发现是这个参数没设。
铁律三:色彩空间强制sRGB
在保存PNG前,必须插入:
import matplotlib.image as mpimg
# ... 绘图完成后 ...
plt.savefig('path.png', bbox_inches='tight', pad_inches=0.1)
# 强制转换为sRGB色彩空间
img = mpimg.imread('path.png')
# (此处省略色彩空间转换代码,实际使用PIL库)
虽然代码略长,但它确保了在Windows、macOS、Linux三端打开时,蓝色永远是 #1f77b4 ,不会因系统默认色彩配置不同而偏紫或偏青。
铁律四:文件名与路径的绝对唯一性
所有导出文件名必须以数字序号开头( 01_ , 02_ ),且路径中不能含空格或中文。Claude Code在生成文件名时,必须调用 slugify() 函数(我预装了 python-slugify 包)处理所有动态部分。例如,当它想生成“7月各渠道联系量对比图.png”时,实际执行:
from slugify import slugify
filename = f"01_{slugify('7月各渠道联系量对比图')}.png"
# 结果是 "01_7-yue-ge-qu-dao-lian-xi-liang-dui-bi-tu.png"
这杜绝了因文件名非法导致的 OSError: Invalid argument 错误——我见过太多人栽在这个看似 trivial 的坑里。
这四条铁律,把图表从“能看见”提升到“能交付”。当老板把PNG拖进PPT时,他看到的不是一张模糊的截图,而是一张和他三年前看过的所有报表风格完全一致的专业图表。
4. 完整实操流程:从零启动到交付Word报告的七步闭环
现在,让我们把前面所有的设计、配置、校验,串成一条可执行的、无歧义的七步闭环流水线。这不是理论推演,而是我昨天下午三点零七分,在自己MacBook上完整跑通的真实记录。每一步都标注了耗时、关键命令和预期输出,你可以打开终端,跟着做。
4.1 第一步:环境初始化(耗时:2分钟)
打开终端,确保Ollama正在运行:
ollama list | grep claude-code
# 应输出:claude-code:3.5-sonnet latest b2a3c1d... 4.2GB
克隆项目仓库(我已将所有模板、脚本、文档整理成开源项目):
git clone https://github.com/freddie-robinson/claude-data-analyst.git
cd claude-data-analyst
设置环境变量(永久写入 ~/.zshrc ):
echo 'export DB_CONNECTION_STRING="postgresql://analyst:securepass@localhost:5432/customer_analytics"' >> ~/.zshrc
source ~/.zshrc
验证数据库连接:
python -c "from sqlalchemy import create_engine; print(create_engine(os.getenv('DB_CONNECTION_STRING')).connect().execute('SELECT 1').scalar())"
# 应输出:1
提示:这一步是基石。如果
DB_CONNECTION_STRING验证失败,后面所有步骤都会卡在“无法连接数据库”。务必在此处确认。
4.2 第二步:创建分析工作区(耗时:15秒)
运行初始化脚本,它会创建目录、复制模板、生成初始todo.txt:
python scripts/init_analysis.py --month "2025-07"
# 输出:
# Created directory: july_2025_analysis
# Copied template notebook
# Generated initial todo.txt with 5 pending tasks
检查生成的 july_2025_analysis/todo.txt ,确认 PENDING TASKS 第一项是“读取表结构白皮书文件...”。这标志着流程正式启动。
4.3 第三步:Claude Code执行QUERY 1(耗时:47秒)
启动Claude Code会话,粘贴第一个提示词(即 prompts/query_writer_first_message.txt 的内容)。它会自动执行:
- 读取
docs/contacts_schema_v3.md(约1200字,耗时3秒) - 创建
july_2025_analysis/contacts_analysis_july_2025.ipynb(耗时2秒) - 生成QUERY 1的SQL(耗时8秒)
- 执行SQL并获取DataFrame(耗时12秒)
- 绘制分组柱状图(耗时15秒)
- 导出
01_channel_volume_comparison.png(耗时5秒) - 更新
todo.txt,将任务1移至COMPLETED TASKS(耗时2秒)
此时, todo.txt 内容变为:
## PENDING TASKS
2. 在 `july_2025_analysis/` 目录下新建 Jupyter Notebook `contacts_analysis_july_2025.ipynb`。
3. 【QUERY 1】生成SQL:计算2025年6月与7月各渠道联系量及环比变化率...
## COMPLETED TASKS
1. 读取表结构白皮书文件 `docs/contacts_schema_v3.md`,确认所有字段含义。
注意:任务2和3还在
PENDING TASKS,因为Claude Code只处理第一项。这是设计使然,不是bug。
4.4 第四步:Claude Code执行QUERY 2至QUERY 5(耗时:3分12秒)
重复第三步,但这次粘贴的是 prompts/query_writer_loop.txt 提示词(专为循环执行设计)。它会自动:
- 读取更新后的
todo.txt - 发现
PENDING TASKS第一项是任务2(创建Notebook),但该文件已存在,于是跳过并标记为完成 - 接着处理任务3(QUERY 1),但该SQL已执行过,于是跳过并标记为完成
- 真正执行任务4(QUERY 2),生成SQL、执行、绘图、导出
02_reason_distribution.png - 然后执行任务5(QUERY 3),生成SQL、执行、绘图、导出
03_response_time_by_channel.png - 最后执行任务6(QUERY 4)和任务7(QUERY 5),分别导出
04_contact_duration_trend.png和05_user_segment_breakdown.png
全部完成后, todo.txt 的 PENDING TASKS 应为空, COMPLETED TASKS 包含全部7项。此时, july_2025_analysis/ 目录下应有:
contacts_analysis_july_2025.ipynb(含5个已执行的cell)01_至05_共5个PNG图表文件df_*.parquet中间数据文件(5个)
4.5 第五步:触发报告撰写会话(耗时:8秒)
关闭当前Claude Code窗口,新开一个。粘贴第二个提示词( prompts/report_writer.txt )。它会:
- 读取
todo.txt确认所有查询已完成 - 读取
templates/report_template.md(一份结构清晰的Markdown模板) - 依次读取5个PNG文件的路径和描述
- 生成
july_2025_analysis/contacts_analysis_july_2025.md,内容严格遵循模板结构,所有图表引用正确,文字分析基于图表数据,不添加任何主观推测
生成的MD文件首段示例:
## 一、整体趋势概览
### 1.1 联系总量变化
图1显示,2025年7月总联系量为12,843次,较6月的11,207次增长14.6%。增长主要来自电话渠道(+22.3%)和社交媒体渠道(+18.7%),而邮件渠道下降3.2%。值得注意的是,聊天渠道(chat)联系量达4,521次,首次超过电话渠道(4,389次),成为单月第一大接触渠道。
提示:Claude Code在此阶段绝不生成任何未在图表中体现的数据。如果图1没显示“聊天渠道首次第一”,它绝不会写这句话。这是“只报告数据,不解释原因”的铁律。
4.6 第六步:Word报告合成(耗时:23秒)
报告撰写会话的最后一个任务,是调用Python脚本合成Word文档:
python scripts/generate_docx.py \
--md_file "july_2025_analysis/contacts_analysis_july_2025.md" \
--image_dir "july_2025_analysis/" \
--output "july_2025_analysis/july_2025_report.docx"
该脚本使用 python-docx 库,核心逻辑是:
- 解析MD文件,识别
这样的图片引用 - 将对应PNG文件嵌入Word,保持原始宽高比
- 所有标题应用内置样式(Heading 1, Heading 2)
- 图片下方自动添加题注:“图1:2025年6-7月联系量双轴对比图”
- 文档属性中写入作者“Claude Code Analyst”和生成时间
最终生成的 july_2025_report.docx ,打开效果与我手工制作的报告完全一致:字体是Calibri,标题加粗,图表居中,题注右对齐,页眉有公司Logo占位符。
4.7 第七步:人工审核与微调(耗时:6分钟)
这是整个流程中唯一需要人工介入的环节,但耗时极短。我只做三件事:
- 数据一致性抽查 :打开
july_2025_analysis/contacts_analysis_july_2025.ipynb,随机选一个cell(如QUERY 3),手动运行df.head(),核对前5行数据是否与Word报告中描述的数值一致。 - 图表可读性检查 :放大查看
03_response_time_by_channel.png,确认电话渠道的条形图长度是否真的比邮件渠道长(视觉验证),确认Y轴标签是“平均首次响应时长(分钟)”。 - 文字严谨性修正 :在Word中,将报告中一处“可能由于暑期促销活动”改为“根据市场部同步,暑期促销活动于7月15日启动”,因为Claude Code的原始表述属于无依据推测,我手动替换成可验证的事实。
这三步做完,报告即可发送。全程从初始化到交付,耗时约12分钟,而我手工制作同样报告,通常需要90分钟以上。节省的78分钟,足够我深入分析一个异常波动点,或者给销售团队做一次快速数据解读。
5. 常见问题与独家排查技巧实录
在跑了23次完整流程(包括故意注入错误来测试鲁棒性)后,我整理出这份“血泪经验”问题速查表。它不讲原理,只说现象、原因和三秒内能执行的解决方案。这些都是我在凌晨两点调试时,用咖啡和键盘敲出来的真东西。
| 问题现象 | 根本原因 | 三秒解决法 | 我的实测效果 |
|---|---|---|---|
| Claude Code反复报错“无法连接数据库” | 环境变量 DB_CONNECTION_STRING 未被Jupyter内核读取,或Ollama会话未继承shell环境 |
在Claude Code提示词开头,强制添加一行: import os; print("DB env:", os.getenv('DB_CONNECTION_STRING')[:20]) 。若输出 None ,说明环境变量未生效,立即执行 source ~/.zshrc 并重启Ollama |
100%定位,5秒内修复 |
生成的SQL总是把 contact_reason_code 拼成 contact_reason_id |
表结构白皮书中该字段描述太简略,未强调“code”是唯一合法后缀 | 在白皮书该字段描述末尾, 加粗 添加:“⚠️ 重要:字段名严格为 contact_reason_code ,任何含 id 、 name 、 desc 的变体均为错误” |
Claude Code再未拼错过此字段 |
| 图表导出后在Word里显示为黑底白字 | matplotlib默认背景为透明,Word不支持,需强制设为白色 | 在绘图代码开头,添加 plt.figure(facecolor='white') 和 ax.set_facecolor('white') |
所有图表背景瞬间变白,无需重绘 |
todo.txt 更新后,Claude Code仍读取旧内容 |
文件系统缓存或Claude Code内部缓存未刷新 | 在提示词中, 强制要求 :“每次执行任务前,必须先执行 cat july_2025_analysis/todo.txt 命令,并将输出内容作为本次响应的首要参考” |
缓存问题彻底消失 |
报告MD文件中图片路径错误,显示为  而非真实路径 |
generate_docx.py 脚本中的正则表达式未适配Claude Code生成的MD格式 |
修改脚本,将 re.search(r'!\[\]\((.+?)\)', line) 改为 re.search(r'!\[.*?\]\((.+?)\)', line) ,增加对alt文本的兼容 |
100%匹配所有图片引用格式 |
| QUERY 2执行后报错“column 'contact_reason_code' does not exist” | 数据库中该字段名实际为 reason_code ,表结构白皮书未及时更新 |
在提示词中加入校验指令:“执行任何SQL前,必须先运行 SELECT column_name FROM information_schema.columns WHERE table_name = 'contacts' ,并将结果与白皮书逐行比对。若发现差异,立即停止并报告” |
首次运行即捕获,避免后续全盘失败 |
| Word报告中图表尺寸忽大忽小,排版混乱 | python-docx 插入图片时未指定宽度,依赖Word自动缩放 |
修改 generate_docx.py ,在 document.add_picture() 后,立即执行 last_paragraph = document.paragraphs[-1]; last_run = last_paragraph.runs[-1]; last_run.font.size = Pt(10) |
更多推荐
所有评论(0)