☰
季度销售复盘自动化:从Excel到PPT的LLM工作流
2026/10/2 15:27:53 网站建设 项目流程

季度末把销售明细整理成复盘报告,再顺手出一版能直接上会的汇报 PPT,这件事听起来像是两个独立任务,实际上是一条完整的数据加工链路。我过去几年每到季度末都要重复走一遍这个流程,早期靠手工透视表加复制粘贴,一份报告磨两天,PPT 再磨一天,改到第三版的时候数据口径还对不上。后来我把这条链路拆成了几个固定环节,用 WorkBuddy 配合 Excel 和 Python 做半自动化处理,现在从原始销售表到复盘报告初稿大概四十分钟,PPT 骨架十分钟能出来,剩下的时间全部花在业务解读上。

这篇内容适合两类人看:一类是手里有大量 Excel 销售数据、每季度都要写复盘但不想重复造轮子的业务分析岗;另一类是刚开始接触 LLM 辅助办公、想知道怎么把大模型能力接进自己日常工作流的技术同学。我会把整条链路拆开讲,包括数据清洗阶段怎么处理脏数据、指标计算阶段怎么保证口径一致、报告生成阶段怎么让 LLM 输出结构稳定的内容、PPT 阶段怎么把文字自动映射到版式,以及中间踩过的那些坑。所有步骤都可以直接抄作业,参数和提示词我会给到能复现的程度。

1. 先想清楚这条链路到底在解决什么问题

很多人一上来就问用什么工具,其实工具是最后一步。先要把问题定义清楚,否则工具选得再对也是白搭。季度销售复盘这件事,本质上要回答三个问题:卖了多少、为什么是这个数、下一步怎么办。第一个问题是纯计算,第二个问题需要拆维度,第三个问题需要结合业务判断。这三个问题的数据来源是同一张销售明细表,但加工深度完全不同。

1.1 手工流程到底卡在哪

我观察过身边同事的常规做法,基本是这样一条路径:打开销售明细表,先做数据透视,拉出各区域各产品线的销售额,然后复制到另一个表格里算同比环比,再手动写几段文字描述,最后打开 PPT 模板,把数字和文字一个个填进去。这条路径在数据量小的时候没问题,一旦明细表超过五万行,或者维度超过四五个,问题就集中爆发了。

第一个卡点是口径不一致。透视表里算的是含税金额,写报告的时候引用的是不含税金额,两个数字对不上,汇报现场被领导问一句就露馅。第二个卡点是重复劳动。同样的透视逻辑,这个季度做完下个季度还要再做一遍,每次都要重新拖字段。第三个卡点是文字和数字脱节。数字是算出来的,文字是拍脑袋写的,两者之间没有强关联,导致报告读起来像是两份材料拼在一起。

提示:在动手做自动化之前,先把你现在手工流程里每一步的输入输出写下来,尤其是每个数字的计算口径。这一步花二十分钟,能省掉后面反复对数的几个小时。

1.2 自动化链路的分层设计

我把整条链路分成四层,每层职责单一,层与层之间用中间文件衔接。这样设计的好处是任何一层出问题都不会影响其他层,调试的时候可以单独替换某一层。

层级职责输入输出主要工具
数据层清洗、补全、标准化原始销售明细干净的结构化表Python + pandas
指标层计算核心指标与派生指标干净表指标汇总表Python + Excel 公式
文本层生成复盘叙述文字指标汇总表报告文字稿WorkBuddy / LLM
呈现层生成汇报 PPT报告文字稿 + 指标表PPT 文件WorkBuddy + 模板

这个分层不是拍脑袋定的,而是根据"变更频率"来的。数据层的清洗规则每个季度可能微调,指标层的计算逻辑相对稳定,文本层的提示词需要反复打磨,呈现层的版式基本固定。变更频率不同的东西放在不同层,改起来才不会牵一发动全身。

1.3 为什么选 WorkBuddy 而不是纯手工或纯脚本

纯手工的问题前面说了,纯脚本的问题是门槛太高,业务同学改不动。WorkBuddy 这类工具的价值在于它把 LLM 能力封装成了可调用的技能单元,你不需要懂模型部署,只需要把数据喂进去、把提示词写清楚,它就能输出结构化的文本。对于复盘报告这种"数据确定、表达灵活"的任务,LLM 恰好补上了脚本最不擅长的部分。

我实测下来,指标计算部分用 Python 做,准确率和速度都远超 LLM;文字生成部分用 LLM 做,灵活度和可读性远超模板拼接。两者结合,各干各擅长的事,整条链路的稳定性最高。如果你所在的环境暂时用不了 WorkBuddy,用任何支持结构化输出的 LLM 接口都能替代,核心思路是一样的。

2. 数据层:把脏表洗成能算的表

数据层是整个链路的地基,这一步做不干净,后面全是错的。我见过太多人跳过清洗直接透视,结果算出来的数字自己都不敢信。销售明细表常见的脏数据有几类:日期格式混乱、区域名称不统一、金额字段混入文本、空值处理不当。下面逐个说处理办法。

2.1 日期和维度字段的标准化

日期字段最常见的问题是同一列里混着"2024/1/5""2024-01-05""1月5日"三种格式。pandas 的 to_datetime 能处理大部分情况,但遇到中文日期会失败,需要先做一次替换。我的做法是统一转成 ISO 格式的字符串,再做后续处理。

import pandas as pd df = pd.read_excel("sales_raw.xlsx") # 日期标准化:先替换中文符号,再统一转换 df["订单日期"] = ( df["订单日期"] .astype(str) .str.replace("年", "-") .str.replace("月", "-") .str.replace("日", "") .str.replace("/", "-") ) df["订单日期"] = pd.to_datetime(df["订单日期"], errors="coerce") # 检查转换失败的行 bad_dates = df[df["订单日期"].isna()] print(f"日期转换失败 {len(bad_dates)} 行")

维度字段的标准化更麻烦,因为区域名称往往有别名。比如"华东""华东区""东区"指的是同一个区域,但透视的时候会被当成三个。解决办法是维护一张映射表,用 map 做替换。这张映射表建议单独存一个 Excel 文件,业务同学发现新别名随时补充,不用改代码。

region_map = { "华东区": "华东", "东区": "华东", "华东大区": "华东", "华南区": "华南", "南区": "华南", "华北区": "华北", "北区": "华北", } df["区域"] = df["区域"].map(region_map).fillna(df["区域"])

注意:map 之后一定要用 fillna 兜底,否则映射表里没覆盖到的区域会变成 NaN,直接丢失数据。这个坑我踩过一次,某个新开的区域没加进映射表,导致那个区域整个季度的数据在报告里消失了。

2.2 金额字段的清洗与校验

金额字段混入文本是另一个高频问题,常见的是"1,234.56 元""¥1234""约 1200"这几种。处理思路是先去掉非数字字符,再转 float。但"约 1200"这种带模糊词的,去掉"约"之后数字是准的,可以直接用;如果是"1200 左右"这种,建议标记出来人工确认。

import re def clean_amount(x): if pd.isna(x): return 0.0 s = str(x) s = re.sub(r"[^\d.\-]", "", s) try: return float(s) except ValueError: return 0.0 df["销售额"] = df["销售额"].apply(clean_amount) df["成本"] = df["成本"].apply(clean_amount) df["毛利"] = df["销售额"] - df["成本"]

清洗完之后必须做一次校验,检查有没有异常值。我的习惯是看三个数:销售额为负的行数、销售额超过某个阈值的行数、毛利为负的行数。前两个可能是录入错误,第三个可能是真实的促销亏损,需要业务确认。

2.3 空值和重复行的处理策略

空值处理没有统一答案,要看字段含义。订单号为空的行基本是废数据,直接删;区域为空的行可以归到"未分配",保留但标记;金额为空的行按 0 处理,但要记录数量。重复行的判断标准是订单号加产品编码,两个都一样才算重复。

# 删除订单号为空的废数据 df = df[df["订单号"].notna()] # 区域为空归到未分配 df["区域"] = df["区域"].fillna("未分配") # 按订单号+产品编码去重,保留最后一条 df = df.drop_duplicates(subset=["订单号", "产品编码"], keep="last") # 输出清洗报告 print(f"清洗后行数:{len(df)}") print(f"未分配区域行数:{len(df[df['区域'] == '未分配'])}")

清洗报告这一步别省,它是你和业务同学对齐数据的依据。报告里写清楚删了多少行、为什么删、保留了多少行,后面数字对不上的时候能快速定位。

3. 指标层:把干净表算成能讲的数

数据洗干净之后,接下来是算指标。这一步的核心不是算得快,而是算得对、算得一致。我见过太多报告因为同比环比口径不一致被质疑,所以指标层的关键是"口径固化",把每个指标的计算逻辑写死在代码里,每次跑出来的结果都一致。

3.1 核心指标的拆解逻辑

季度销售复盘的核心指标就那么几个:销售额、毛利、毛利率、同比、环比、目标完成率。但每个指标往下拆都有讲究。销售额要拆成"量 × 价",因为销售额涨了可能是卖得多,也可能是卖得贵,这两种情况的业务含义完全不同。毛利要拆成"销售额 - 成本",成本里还要区分固定成本和变动成本。

指标计算公式拆解维度业务含义
销售额单价 × 数量区域、产品线、客户规模
毛利销售额 - 成本同上盈利能力
毛利率毛利 / 销售额同上盈利效率
同比(本期 - 去年同期) / 去年同期同上增长趋势
环比(本期 - 上期) / 上期同上短期波动
完成率实际 / 目标同上目标达成

这张表建议直接写进代码注释里,后面任何人接手都能看懂每个指标是怎么来的。口径固化不是写在文档里就完事,要写进代码,让代码成为唯一的口径来源。

3.2 同比环比的正确算法

同比环比看起来简单,实际最容易出错。同比是跟去年同期比,环比是跟上期比,但"上期"的定义要明确:是上一个季度,还是上一个同等长度的周期?如果是季度复盘,环比就是上一季度;如果是月度复盘,环比就是上个月。这个定义要在报告开头写清楚,避免歧义。

# 假设 df 已经按季度聚合 quarterly = df.groupby(["季度", "区域"]).agg( 销售额=("销售额", "sum"), 成本=("成本", "sum"), ).reset_index() quarterly["毛利"] = quarterly["销售额"] - quarterly["成本"] quarterly["毛利率"] = quarterly["毛利"] / quarterly["销售额"] # 同比:跟去年同季度比 quarterly["去年同期销售额"] = quarterly.groupby("区域")["销售额"].shift(4) quarterly["同比"] = ( (quarterly["销售额"] - quarterly["去年同期销售额"]) / quarterly["去年同期销售额"] ) # 环比:跟上一季度比 quarterly["上期销售额"] = quarterly.groupby("区域")["销售额"].shift(1) quarterly["环比"] = ( (quarterly["销售额"] - quarterly["上期销售额"]) / quarterly["上期销售额"] )

shift(4) 这个参数是按季度来的,一年四个季度,所以同比是往前推四行。如果你的数据是按月聚合的,同比就是 shift(12)。这个参数写错是高频错误,建议在代码里加一行注释说明为什么是 4 或 12。

3.3 目标完成率的对齐问题

目标完成率 = 实际 / 目标,但目标数据往往在另一张表里,而且粒度可能跟实际数据不一致。比如实际数据是按区域的,目标数据是按大区的,这时候需要先做粒度对齐。我的做法是把目标表也做一次标准化,统一到跟实际数据相同的粒度,再做除法。

target = pd.read_excel("target.xlsx") # 目标表按大区,实际表按区域,先映射 target["区域"] = target["大区"].map(big_region_map) target = target.groupby("区域")["目标销售额"].sum().reset_index() # 合并 result = quarterly.merge(target, on="区域", how="left") result["完成率"] = result["销售额"] / result["目标销售额"]

merge 的时候用 left join,保证实际数据不丢。如果某个区域没有目标数据,完成率会是 NaN,这时候要么补目标,要么在报告里标注"该区域未设目标"。我倾向于后者,因为强行补一个目标数字反而会误导。

4. 文本层:让 LLM 写出像人写的复盘

指标算完之后,接下来是把数字翻译成文字。这一步是 WorkBuddy 这类工具的主场。但 LLM 有个通病:你给它一堆数字,它容易编。所以文本层的关键是"约束",把数字和结论的对应关系锁死,让 LLM 只做表达,不做判断。

4.1 提示词的结构化设计

我打磨过很多版提示词,最后稳定下来的结构是四段式:角色设定、数据输入、输出要求、示例。角色设定告诉 LLM 它是什么身份,数据输入给它事实,输出要求规定格式,示例给它参照。这四段缺一不可,尤其是示例,能大幅提升输出稳定性。

你是一名资深销售分析师,负责撰写季度销售复盘报告。 以下是本季度各区域的销售数据: {data_table} 请按以下要求撰写复盘: 1. 先写整体表现,包括总销售额、同比、环比、完成率 2. 再分区域写,每个区域一段,突出增长或下滑的原因 3. 最后写问题与建议,问题要具体,建议要可执行 4. 所有数字必须来自上面的数据表,不得编造 5. 语言简洁,避免形容词堆砌 示例段落: 本季度总销售额 1.2 亿元,同比增长 15%,环比增长 8%,完成率 102%。 华东区域表现突出,销售额 4500 万元,同比增长 22%,主要得益于新品上市。

这个提示词里最关键的是第 4 条"不得编造"。实测下来,加上这条之后 LLM 编数字的概率明显下降。但光靠提示词还不够,后面还要做一次数字校验。

4.2 数字校验的兜底机制

LLM 输出的文字里会引用数字,这些数字必须跟指标表对得上。我的做法是把 LLM 输出里的所有数字提取出来,跟指标表里的数字做比对,对不上的标红,人工确认。这一步用正则就能做,不需要复杂的 NLP。

import re def extract_numbers(text): # 提取数字,包括带小数点和百分号的 pattern = r"\d+\.?\d*%?" return re.findall(pattern, text) report_text = llm_output numbers_in_text = extract_numbers(report_text) # 跟指标表比对,这里简化处理 for num in numbers_in_text: if num not in known_numbers: print(f"待确认数字:{num}")

这个校验不能保证 100% 准确,因为文字里的数字可能有单位换算,但能拦住大部分明显的编造。我实测下来,加了校验之后,报告里数字出错的概率从大概三成降到了不到一成。

4.3 让文字有业务洞察而不是数字复述

LLM 最容易犯的毛病是把数字复述一遍,比如"华东销售额 4500 万,同比增长 22%",这跟直接看表格没区别。要让它写出洞察,需要在提示词里明确要求"解释原因"和"给出判断"。但 LLM 不知道业务背景,所以要把背景信息也喂给它。

我的做法是在数据表之外,再给 LLM 一段"业务背景",包括本季度的重要事件、市场变化、内部调整等。这些信息来自业务同学的输入,LLM 负责把它们和数字关联起来。比如背景里写"本季度华东上线了新品 A",LLM 就会把华东的增长归因到新品 A 上。

提示:业务背景这段信息不要写太长,控制在 200 字以内,写多了 LLM 会抓不住重点。只写跟数字变化直接相关的事件。

5. 呈现层:把报告文字映射成 PPT

报告文字有了,最后一步是出 PPT。这一步很多人觉得最难,其实只要版式固定,映射逻辑可以完全自动化。核心思路是:把报告拆成固定数量的模块,每个模块对应 PPT 的一页或几页,然后用模板填充。

5.1 PPT 结构的标准化

汇报 PPT 的结构基本是固定的:封面、目录、整体表现、分区域表现、问题与建议、下一步计划、封底。这个结构不要每季度改,改一次模板就要重新调映射逻辑。我的做法是把结构写进配置文件,LLM 生成文字的时候按这个结构分段,填充的时候按段落对应页面。

页面内容来源版式
封面固定文字 + 季度标题页
目录固定目录页
整体表现报告第一段 + 核心指标表图文页
分区域表现报告第二段多栏页
问题与建议报告第三段要点页
下一步计划报告第四段要点页

这个映射表是整条链路里最需要根据实际情况调整的部分。不同公司的 PPT 模板不一样,页面数量也不一样,但思路是一样的:先定结构,再定映射。

5.2 用 python-pptx 做模板填充

python-pptx 是操作 PPT 的标准库,能读写 pptx 文件。我的做法是先做一个模板文件,里面放好占位符,然后用代码替换占位符。占位符用 {{变量名}} 的形式,替换的时候用字符串查找。

from pptx import Presentation prs = Presentation("template.pptx") def replace_text(shape, old, new): if shape.has_text_frame: for para in shape.text_frame.paragraphs: for run in para.runs: if old in run.text: run.text = run.text.replace(old, new) # 遍历所有幻灯片和形状 for slide in prs.slides: for shape in slide.shapes: replace_text(shape, "{{季度}}", "2024Q1") replace_text(shape, "{{总销售额}}", "1.2亿") replace_text(shape, "{{同比}}", "15%") prs.save("report.pptx")

这段代码的关键是遍历所有形状,因为占位符可能在任何位置。实测下来,文本框、表格、图表标题里的占位符都能替换到。但要注意,如果占位符被拆成了多个 run,替换会失败,所以模板里做占位符的时候要确保它是一个完整的 run。

5.3 图表自动生成的取舍

PPT 里的图表要不要自动生成,这是个取舍。自动生成的好处是省事,坏处是样式难控制。我的做法是核心图表(比如销售额趋势)自动生成,用 matplotlib 出图再插入 PPT;次要图表(比如区域占比)用 PPT 自带的图表,手动调一次样式,后面复用。

import matplotlib.pyplot as plt plt.rcParams["font.sans-serif"] = ["SimHei"] plt.rcParams["axes.unicode_minus"] = False fig, ax = plt.subplots(figsize=(8, 4)) ax.bar(quarterly["区域"], quarterly["销售额"]) ax.set_title("各区域销售额") plt.tight_layout() plt.savefig("chart.png", dpi=150)

中文字体一定要设置,否则图表里的中文会变成方块。SimHei 是 Windows 自带的黑体,Mac 上要换成 PingFang SC 或者 Arial Unicode MS。这个坑几乎每个第一次用 matplotlib 出中文图的人都会踩。

6. 整条链路跑通后的实测数据与踩坑记录

上面四层讲完,整条链路就通了。但真正跑起来,问题比想象的多。这一节我把实测中遇到的问题和解决办法列出来,都是真金白银换来的经验。

6.1 实测效率对比

先给一组我自己的实测数据,让你对效率提升有个直观感受。测试样本是某季度约 8 万行销售明细,维度包括区域、产品线、客户、月份。

环节手工耗时自动化耗时备注
数据清洗90 分钟3 分钟首次配置映射表多花 20 分钟
指标计算60 分钟1 分钟口径固化后无需重复配置
报告撰写120 分钟8 分钟含 LLM 生成和人工校验
PPT 制作90 分钟5 分钟模板首次制作约 60 分钟
合计360 分钟17 分钟不含首次配置时间

首次配置整条链路大概花了半天,之后每个季度复用,边际成本极低。这个投入产出比在季度复盘这种高频重复任务上非常划算。

6.2 踩过的坑:Excel 加载项被禁用

有一次跑脚本的时候,pandas 读 Excel 报错,提示引擎不可用。排查了半天发现是 Excel 的加载项被禁用了,导致 openpyxl 读取异常。解决办法是在 Excel 的选项里重新启用加载项,或者直接用 openpyxl 读,绕开 Excel 本身。

# 直接用 openpyxl 引擎,绕开 Excel 加载项 df = pd.read_excel("sales_raw.xlsx", engine="openpyxl")

这个问题的根源是 Excel 和 Python 读取 Excel 的机制不同,Excel 加载项被禁用不影响 openpyxl,但会影响某些依赖 Excel 的库。遇到读取异常,先换引擎试试,能省很多排查时间。

6.3 踩过的坑:LLM 输出的数字对不上

前面提过 LLM 会编数字,我实际遇到过更隐蔽的情况:LLM 把"同比增长 15%"写成了"增长 15 个百分点"。这两个说法在业务上完全不同,前者是相对增长,后者是绝对增长。这种错误靠数字校验抓不出来,因为数字本身是对的,错的是单位。

解决办法是在提示词里明确要求"区分百分比和百分点",并且在输出要求里加一条"所有增长率必须注明是同比还是环比"。这个坑提醒我,LLM 的校验不能只看数字,还要看数字的语境。

6.4 踩过的坑:PPT 占位符替换失败

python-pptx 替换占位符的时候,如果占位符在模板里被拆成了多个 run,替换会失败。比如 {{总销售额}} 可能被拆成 "{{总" 和 "销售额}}",查找的时候找不到完整的占位符。解决办法是在模板里做占位符的时候,确保它是一个连续的文本,不要中途改格式。

如果已经拆了,可以在代码里先合并 run 再替换:

def merge_runs(paragraph): text = "".join(run.text for run in paragraph.runs) for run in paragraph.runs: run.text = "" paragraph.runs[0].text = text

这个函数把段落里所有 run 的文字合并到第一个 run,然后再做替换。实测下来能解决大部分替换失败的问题。

6.5 踩过的坑:中文字体导致图表乱码

matplotlib 出图的时候,如果没设置中文字体,图表里的中文会变成方块。这个问题在 Windows 上设置 SimHei 就能解决,但在 Linux 服务器上可能没有这个字体。解决办法是提前把字体文件放到项目目录,用 font_manager 加载。

from matplotlib import font_manager font_path = "fonts/SourceHanSans.ttf" font_manager.fontManager.addfont(font_path) plt.rcParams["font.sans-serif"] = ["Source Han Sans"]

字体文件建议用开源字体,避免版权问题。思源黑体是个不错的选择,覆盖全,免费商用。

7. 这套方法还能怎么扩展

整条链路跑通之后,我发现它的适用场景远不止季度复盘。任何"数据确定、表达灵活、需要定期输出"的任务,都能套这个框架。比如月度经营分析、周度销售跟踪、项目进度汇报,甚至年度总结。核心逻辑是一样的:数据层清洗、指标层计算、文本层生成、呈现层输出。

扩展的时候有几个方向可以考虑。第一个方向是把触发方式从手动改成定时,比如每月 1 号自动跑一遍,生成报告草稿发到邮箱。第二个方向是把输出格式从 PPT 扩展到 Word 和网页,同一份文字稿可以渲染成多种格式。第三个方向是把 LLM 的提示词做成可配置的,不同业务线用不同的提示词模板,共用同一套数据和指标逻辑。

我个人在实际操作中的体会是,这套方法最大的价值不是省了多少时间,而是把"数据口径"这件事从人脑里搬到了代码里。以前口径靠记忆,现在口径靠代码,谁跑都是同一个结果。这一点在多人协作的场景下尤其重要,能省掉大量对数的时间。至于 LLM 那部分,提示词是需要持续打磨的,我到现在每个季度还会根据上一季度的输出问题微调提示词,这是个长期优化的过程,没有一劳永逸的版本。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询