WPS AI 的“AI 写公式”可以把一句话变成公式,但公式对不对,要用数据验证,尤其是边界值和空白单元格。 这篇做
formula_check.py:把公式和测试数据写进 xlsx,用 LibreOffice 无界面模式重新计算,再和期望值逐行比较。
先看清成品
- WPS 官网演示图中的
=SUMIF(A2:A4,"Phone",C2:C4)用在中文类别“手机”上,实际结果是 0,不是 32997。- 把“大于 10000”写成
>=的常见偏差,在边界值 10000 上被发现。- 5 个用例中 2 个按预期失败,3 个通过;pytest 7 项通过。
合规提醒:WPS AI 需要登录 WPS 账号,在官方客户端或网页版中使用,功能、次数和会员权益以官方说明为准(本文不列价格)。本文不调用任何非官方接口,也不自动化操作 WPS;脚本只在本地检查你自己的文件。把公司文档交给 AI 处理之前,请确认符合公司的数据安全规定。示例数据都是虚构的。
运行说明:box 上没有 WPS 客户端和账号,没有真正调用 WPS AI。本地脚本和测试都在 box 上真实运行过(LibreOffice 25.2.3、poppler-utils、python-pptx 1.0.2、openpyxl 3.1.5)。“AI 生成的”公式、摘要和 PPT 都是我自己写的样例,用来演示核对方法。
做完之后你会得到什么?
| 成果 | 说明 |
|---|---|
formula_check.py |
逐个用例重算并比较;有失败时退出码为 1 |
cases.json |
5 个用例:数据、公式、期望值 |
test_formula.py |
7 项测试(需要 LibreOffice) |
需要准备哪些环境?
| 项目 | 说明 |
|---|---|
| Python | 3.9 以上,pip install openpyxl python-pptx pytest |
| LibreOffice | A 篇用于无界面重算公式(apt install libreoffice-calc) |
| poppler-utils | B 篇用于提取 PDF 文字(apt install poppler-utils) |
| WPS Office | 最新版客户端,登录账号;旧版本可以在 ai.wps.cn 升级 |
关键事实
| 事实 | 来源 |
|---|---|
| 入口:WPS 表格 → 右上方 WPS AI 选项卡 → AI 写公式;可以点击公式中的部分查看解释 | WPS 社区官方教程 |
官网演示:类别列是中文“手机”,公式是 =SUMIF(A2:A4, "Phone", C2:C4) |
WPS 官网“AI 办公套件”页面(繁体中文页) |
官网演示图中的不一致,很可能是页面本地化时留下的,不代表 WPS AI 的真实输出。我把它作为用例,因为它正好说明“公式看起来对,用数据一算就错”。LibreOffice 和 WPS 的函数实现可能有细微差别,验证通过后仍要在 WPS 中核对一次。
文件结构
69-wps-ai-A-src/
├── formula_check.py
├── cases.json
└── test_formula.py
第 1 步:验证脚本
"""验证 WPS AI“AI 写公式”生成的公式:把公式和测试数据写进 xlsx,用 LibreOffice 无界面模式重新计算,再和你预期的结果逐行比较。
用法:cases.json 中写数据、公式(行号用 {r} 占位)和每行的期望值。
LibreOffice 和 WPS 的函数实现可能有细微差别;这里验证的是公式逻辑,最终仍要在 WPS 里核对一遍。"""
import argparse, json, os, shutil, subprocess, sys, tempfile
from openpyxl import Workbook, load_workbook
def recalc(path):
"""用 soffice 打开并另存,得到带计算结果的 xlsx。"""
out = tempfile.mkdtemp()
r = subprocess.run(["soffice", "--headless", "--norestore", f"-env:UserInstallation=file://{out}/profile",
"--convert-to", "xlsx:Calc MS Excel 2007 XML", "--outdir", out, path], capture_output=True, text=True, timeout=120)
res = os.path.join(out, os.path.basename(path))
if not os.path.exists(res): raise SystemExit("LibreOffice 转换失败:" + r.stderr[-500:])
return res
def run_case(case, workdir):
wb = Workbook(); ws = wb.active
ws.append(case["header"])
for row in case["rows"]: ws.append(row)
col = case.get("target_col", chr(ord("A") + len(case["header"])))
cells = []
if "{r}" in case["formula"]: # 逐行公式
for i in range(len(case["rows"])):
r = i + 2; ws[f"{col}{r}"] = case["formula"].replace("{r}", str(r)); cells.append(f"{col}{r}")
else: # 单个汇总公式
cell = case.get("cell", f"{col}2"); ws[cell] = case["formula"]; cells.append(cell)
src = os.path.join(workdir, case["name"] + ".xlsx"); wb.save(src)
ws2 = load_workbook(recalc(src), data_only=True).active
got = [ws2[c].value for c in cells]; exp = case["expect"]
bad = [(c, g, e) for c, g, e in zip(cells, got, exp) if not (g == e or (isinstance(g, (int, float)) and isinstance(e, (int, float)) and abs(g - e) < 1e-9))]
return got, bad
def main(argv=None):
ap = argparse.ArgumentParser(); ap.add_argument("cases"); a = ap.parse_args(argv)
if not shutil.which("soffice"): raise SystemExit("需要安装 LibreOffice(soffice)")
work = tempfile.mkdtemp(); fails = 0
for case in json.load(open(a.cases, encoding="utf-8")):
got, bad = run_case(case, work)
print(("✓" if not bad else "✗"), case["name"], case["formula"])
for c, g, e in bad: print(f" {c}: 得到 {g!r},期望 {e!r}")
fails += bool(bad)
return 1 if fails else 0
if __name__ == "__main__":
sys.exit(main())
第 2 步:用例
[
{
"name": "sumif_official_demo",
"note": "WPS 官网“AI 办公套件”页面的演示图:表格类别是中文“手机”,公式条件却是 \"Phone\"(可能是页面本地化遗留,不代表 WPS AI 的真实输出)",
"header": [
"类别",
"单价",
"总销售额"
],
"rows": [
[
"空调",
2999,
105000
],
[
"手机",
1099,
32997
],
[
"电视",
2999,
5998
]
],
"formula": "=SUMIF(A2:A4,\"Phone\",C2:C4)",
"cell": "D2",
"expect": [
32997
]
},
{
"name": "sumif_fixed",
"header": [
"类别",
"单价",
"总销售额"
],
"rows": [
[
"空调",
2999,
105000
],
[
"手机",
1099,
32997
],
[
"电视",
2999,
5998
]
],
"formula": "=SUMIF(A2:A4,\"手机\",C2:C4)",
"cell": "D2",
"expect": [
32997
]
},
{
"name": "if_target",
"note": "本系列 WPS AI 介绍页中的示例需求:C 列销售额大于 10000 显示“达标”,否则显示“未达标”",
"header": [
"姓名",
"部门",
"销售额"
],
"rows": [
[
"甲",
"一部",
12000
],
[
"乙",
"一部",
10000
],
[
"丙",
"二部",
9999.5
],
[
"丁",
"二部",
null
]
],
"formula": "=IF(C{r}>10000,\"达标\",\"未达标\")",
"target_col": "D",
"expect": [
"达标",
"未达标",
"未达标",
"未达标"
]
},
{
"name": "if_boundary_ge",
"note": "常见偏差:把“大于”写成 >=,边界值 10000 会被判为达标",
"header": [
"姓名",
"部门",
"销售额"
],
"rows": [
[
"甲",
"一部",
12000
],
[
"乙",
"一部",
10000
]
],
"formula": "=IF(C{r}>=10000,\"达标\",\"未达标\")",
"target_col": "D",
"expect": [
"达标",
"未达标"
]
},
{
"name": "if_blank_flagged",
"note": "空白单元格会被当成 0;如果希望单独标出缺数据,需要先判断空值",
"header": [
"姓名",
"部门",
"销售额"
],
"rows": [
[
"丁",
"二部",
null
]
],
"formula": "=IF(C{r}=\"\",\"缺数据\",IF(C{r}>10000,\"达标\",\"未达标\"))",
"target_col": "D",
"expect": [
"缺数据"
]
}
]
第 3 步:测试
import json, shutil, tempfile, pytest
import formula_check as F
pytestmark = pytest.mark.skipif(not shutil.which("soffice"), reason="需要 LibreOffice")
CASES = {c["name"]: c for c in json.load(open("cases.json", encoding="utf-8"))}
@pytest.mark.parametrize("name,ok", [("sumif_official_demo", False), ("sumif_fixed", True), ("if_target", True),
("if_boundary_ge", False), ("if_blank_flagged", True)])
def test_cases(name, ok):
got, bad = F.run_case(CASES[name], tempfile.mkdtemp())
assert (bad == []) is ok
def test_demo_returns_zero():
got, _ = F.run_case(CASES["sumif_official_demo"], tempfile.mkdtemp())
assert got == [0]
def test_formula_error_reported():
case = {"name": "err", "header": ["a"], "rows": [[1]], "formula": "=NOSUCHFUNC(A2)", "cell": "B2", "expect": [1]}
got, bad = F.run_case(case, tempfile.mkdtemp())
assert bad and isinstance(got[0], str) and got[0].startswith("#")
第 4 步:在 WPS 中生成公式(手动)
在 WPS 表格的 AI 写公式中输入需求,例如“如果 C 列销售额大于 10000,D 列显示达标,否则显示未达标”。把生成的公式复制到 cases.json,行号改成 {r},然后运行验证。(本文没有实际操作这一步。)
第 5 步:运行
$ python formula_check.py cases.json
✗ sumif_official_demo =SUMIF(A2:A4,"Phone",C2:C4)
D2: 得到 0,期望 32997
✓ sumif_fixed =SUMIF(A2:A4,"手机",C2:C4)
✓ if_target =IF(C{r}>10000,"达标","未达标")
✗ if_boundary_ge =IF(C{r}>=10000,"达标","未达标")
D3: 得到 '达标',期望 '未达标'
✓ if_blank_flagged =IF(C{r}="","缺数据",IF(C{r}>10000,"达标","未达标"))
$ python -m pytest -q
7 passed
怎么检查结果?
- 每个用例至少包含:一个普通值、一个边界值(正好等于阈值)、一个空白单元格。
- 失败时会显示单元格、实际值和期望值。
- 公式写错(例如函数名错误)时,得到的是
#NAME?之类的错误字符串,同样会被判为失败。
调试示例
1. 官网演示的公式在中文数据上算出 0。 我把 WPS 官网 AI 办公套件页面演示图中的数据和公式原样写成用例:类别列是“空调、手机、电视”,公式是 =SUMIF(A2:A4,"Phone",C2:C4)。LibreOffice 的计算结果是 0,因为“Phone”不等于“手机”。把条件改成 "手机" 后得到 32997。
这个问题只看公式是看不出来的:语法完全正确,只是条件和数据对不上。
2. 边界值。 “大于 10000”很容易被写成 >=。用例 if_boundary_ge 中,10000 这一行得到“达标”,期望是“未达标”。用例里如果没有正好等于 10000 的数据,这个错误会一直藏着。
3. 空白单元格。 销售额为空的那一行,IF(C5>10000,…) 返回“未达标”,因为空白被当成 0。这是否符合需求,取决于你的业务规则;如果需要单独标出,就先判断 C5=""(用例 if_blank_flagged)。
常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 得到 0 | SUMIF 的条件和数据不一致(例如语言或空格) | 核对条件文本 |
得到 #NAME? |
函数名错误,或 LibreOffice 不支持这个函数 | 换成通用函数,或在 WPS 中核对 |
| LibreOffice 转换失败 | soffice 被其他进程占用 | 脚本每次都用独立的配置目录;仍然失败时关闭其他 LibreOffice 窗口 |
| 每个用例要 1 秒多 | 每次都启动一次 soffice | 用例多时可以合并到一个工作簿 |
模板规则
- 每个公式都要用数据验证,不能只看语法。
- 用例必须包含边界值和空白单元格。
- 验证通过后,在 WPS 中再看一次。
项目资源
| 用途 | 地址 |
|---|---|
| WPS AI 详细体验教程(WPS 社区官方帖) | https://bbs.wps.cn/topic/2498 |
| WPS AI 官网 | https://ai.wps.cn/ |
| WPS 官网 AI 办公套件页面 | https://zh-hant.wps.com/feature/zh-CN-ai-office-suite/ |
公式由 AI 来写,正确性由数据来证明。