财报问数助手的口径与答案可靠性边界:分母谜面、排行间距与覆盖盲区(优秀范文三)
摘要
范文一已做解析器三版本迭代+双层评估(抽取 100%、计算 81.7%),范文二做了指标数值诊断(值域/秩相关/更替稳定性)。但"计算正确率为什么到不了 100%"这个数字本身悬而未决——它究竟是算法犯错,还是口径使然?本文换一个更贴近"误差从哪来、答案边界在哪"的角度,复用了财报与全部 120 条问句,新增八组实验重新解剖,得到四个被"81.7%"掩盖的事实:第一,"81.7%"的 18.3% 缺口几乎不是算法错误——把可答的 98 条 Q/C/R 问句逐一核对,答错 0 条;缺口全部来自口径分母(22 条 T 类趋势题不计答案却计入分母),即抽取一旦正确,计算从不错。第二,难度×意图矩阵显示:凡可答(非 T)的 Q/C/R 单元格计算正确率一律 100%,只有 T 类(趋势)因无计算口径而计 0%——全站唯一的"失分"是全类,而不是个别错题。第三,真正的可靠性风险在排行问句:R 类"谁第一"的行业第一与第二间距从毛利率的 29.37 一直缩到净现比的 0.07——净现比的第一名几乎与第二名无差别,这类排行答案先天不可信;第四,问句集覆盖不足:120 条问句只命中了 52.1% 的(公司,年,指标)三元组,大量报表单元从未被问及,评估的"全对"建立在欠采样之上。这些发现把"计算正确率 81.7% 还差 18.3%"的诊断,改成了"系统在可答范围内其实 100% 精确、真正的短板在排行可信度与问句覆盖"的边界地图。
一、问题重述与数据
赛题要求为 8 家上市公司×3 年财报(会计恒等自洽)构建"智能问数"助手。范文一构造了 120 条三级难度×四意图问句(Q 查询/ C 对比/ R 排行/ T 趋势),并给出抽取准确率 100%、计算正确率 81.7% 的 v3 解析器评估。
本文不改报表、不改解析器,只把"81.7%"这个数字拆开问三件事:
- 那 18.3% 的口径缺口到底是算错还是分母使然?
- 哪些问句先天容易给出不可信答案(排行间距、近并列)?
- 120 条问句到底覆盖了多少报表信息、有没有盲区?
二、符号与核心方法
沿用范文一的双层评估:抽取准确率 、计算正确率 ,其中 为问句总数。关键口径点:所有问题都计入分母 ,但 T 类趋势题没有"计算"阶段(无单点查值/排行对象),故其在该口径下天然计 0。
新增评测口径:
- 可答子集:,对这些题逐一核对答案;
- 排行间距:,指标 的行业第一与第二之差,衡量"谁第一"的可信度;
- 三元组覆盖:问句实际触及的(公司,年,指标)单元数 / 全部 单元。
三、结果 I:难度×意图计算正确率矩阵
图 1 把计算正确率按 3 难度×4 意图铺成矩阵:凡可答的单元格(L1/L2/L3 的 Q/C/R,共 98 条)计算正确率全部 100%,只有 T 类(趋势)全部为 0%(无计算口径)。
这张矩阵是"81.7%"最有力的注解:它没有任何一个"算出错误答案"的单元格——所有 0% 都集中在 T 列(口径上不计答案),所有 100% 都集中在可答的 Q/C/R 列。也就是说,系统的计算能力在可答范围内是精确的;差异完全来自"趋势题要不要计入答案分母"这一口径选择,而非算法缺陷。
四、结果 II:R 类排行间距
图 2 对 2023 年各指标看"行业第一与第二名"的间距:毛利率 29.37、ROE 29.66、净利率 22.76 等领先很坚实;而流动比率 0.79、资产负债率 0.54、存货周转率 0.18、净现比仅 0.07。
这是"谁第一"类问句可信度的直接度量:间距大的指标,#1 无可争议(如毛利率南岭领先 29.37),答案可靠;间距小的指标(净现比 0.07),#1 与 #2 几乎并驾,答案在数值噪音下就可能翻盘。这与范文二"净现比更替频繁"的结论互相印证。对可靠问答的启示:R 类问句应根据间距给出不同置信注释——间距小于某一阈值时应输出"第一、第二名差距很小",而非生硬地报一个可能被舍入推翻的"第一"。
五、结果 III:相对时间问句边界
图 3 把含"去年"(15 条)与"前年"(15 条)的问句单独评估:两者计算正确率均 100%。
相对时间消歧(范文一 v3 引入的词表"去年=2023/前年=2022")在两类问句上完全成立——没有出现"把去年解析错成今年"或"前年越界"的错误。这验证了相对时间是 v3 中投入产出比最高的改进(同范文一结论),且在边界(前年,即最早可用年份)处依然稳健。对可靠问答,这是一个可放心的正面结论。
六、结果 IV:负值与极端口径
图 4 统计各指标在 24 公司-年单元中的负值单元:仅营收增长率有 2 个负值单元(负增长公司),其余指标全部非负。
这个极值检查的结论是"干净":利润相关指标(毛利率、净利率、ROE)在此合成数据里都为正(南岭等高端公司毛利 93%),唯一可能出现负的是营收增长(如某年负增长)。对问答助手而言,负值本身不破坏可答性——只要解析正确,负值照常计算;但它提示口径上要显式支持"增长率为负"的表述,避免把负增长误判为数据异常或口径错误。
七、结果 V:别名词典完备性
图 5 对八指标逐一构造"标准公司+标准年+各别名"的探针问句,统计能否解析到正确指标:八个指标的全部别名(毛利率 2、净利率 2、资产负债率 3、流动比率 1、ROE 3、存货周转率 2、净现比 1、营收增长 2)全部命中,无任何歧义或失败。
这是范文一 v2 补全 3 组口语别名后的成体系验证:别名词典对每类指标都是完备的——用户用任何合法别名问同一指标,都会落到目标,不存在"同一家公司同一个数,换个说法就问不到"的覆盖空洞。对可靠问答,这从输入侧消除了别名这一风险源。
八、结果 VI:round 容差敏感性
图 6 把 Q/C 类的答案集合比较容差从 round(9 位) 一路收紧到 round(1 位):计算正确率始终 100%(98/98)。
这说明数据中的(公司,年,指标)取值彼此分得足够开——即便只保留 1 位小数,也不会有两个不同公司"撞值"导致集合比较误判。这与范文二"净现比近并列 9.5%"看似矛盾,实为互补:近并列发生在"相对差 <1%"的宽松口径(如 0.997 vs 1.003),而 round(1 位) 的整数位比较已足够分开它们;真正会在 1 位小数撞车的情况在本数据集不存在。对可靠问答,这证明 collection-set 比较口径对舍入 robust,无需为了准确性牺牲精度。
九、结果 VII:问句-报表三元组覆盖
图 7 统计 120 条问句触及的(公司,年,指标)三元组:仅 100/192(52.1%)。
这是全篇最被低估的发现:近一半的报表信息单元从未被任何问句问过。问句集虽在"被问到的"单元上做到 100% 正确,但覆盖不足意味着评估只验证了半个财报空间。R 类(行业题)能覆盖全部 8 家公司某年的某指标,但 Q/C 类只点名个别公司,导致大量(公司,年,指标)从未进入测试。对评估可靠性的启示:样本不足的问句集可能给出虚高的正确率——若要更可信地衡量系统能力,应扩充问句覆盖到更多单元,而不是在 52.1% 的覆盖上高枕无忧。
十、结果 VIII:计算错点归因
图 8 把 98 条可答(非 T)问句逐一核对:答对 98、答错 0。
这张图直接给出"81.7%"的最终归因:计算正确率 81.7% = 98 答对 / 120 总问句,而 120−98=22 的缺口全部是 T 类趋势题(无计算口径、按 0 计),没有任何一条是真算错的。换言之,如果口径改为"可答问句上的正确率",结果是 100%。范文一所谓"触及口径上限"正是此意。这提醒论文与评估在使用"正确率"时,必须把分母口径讲清楚——同一批数据,除以 120 是 81.7%,除以 98 是 100%,两者相差 18.3% 却都不是"算错"。
十一、结论
四个被"计算正确率 81.7%"掩盖的边界事实重塑了评估的可信框架:
- 计算缺口是分母谜面——98 条可答问句 100% 答对,18.3% 缺口全来自 T 题口径、非算法错误;
- 排行问句先天含可信度梯度——间距从 29.37 到 0.07,"谁第一"不如"第一名优势显著"可靠;
- 别名词典与时间消歧完备、负值可答、容差不敏感——输入侧与计算侧均无雷区;
- 问句覆盖仅 52.1%——近半报表单元未被测试,正确率建立在欠采样之上。
这些结论共同指向一个可靠问答信条:报告一个正确率前,先回答"分母是什么、覆盖了哪些单元、排行间距多大"——否则 81.7% 与 100% 都可能是同一批数据的两种口径。
十二、模型验证(四路一致)
本文正文、配图、附录代码、真源 tools/gen_tidy2026b_3.py 四路数字完全一致。附录代码块可在 tools/ 目录下独立运行复现全部关键数字(难度×意图矩阵、排行间距、相对时间、负值计数、别名命中、容差敏感性、三元组覆盖、错点归因),所有结果由固定随机种子确定,双跑字节一致。
参考文献
[1] 中国注册会计师协会. 财务成本管理. 中国财政经济出版社.
[2] 泰迪杯 2026 B 赛题组委会. 上市公司财报智能问数赛题数据说明.
附录:核心 Python 实现
import sys, os
_HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.abspath(os.path.join(_HERE, "..", "..", "..", "tools")))
import gen_tidy2026b_3 as SRC
r = SRC.gen_tidy2026b_3()
# E1 难度×意图计算正确率
for Lv in ("L1", "L2", "L3"):
cells = []
for it in ("Q", "C", "R", "T"):
n, a = r["e1"][(Lv, it)]
cells.append("%s n%d (%.0f%%)" % (it, n, a if a is not None else 0))
print("E1 %s %s" % (Lv, " ".join(cells)))
# E2 排行间距
for m, (gp, f, s2) in sorted(r["e2"].items(), key=lambda kv: -kv[1][0]):
print("E2 %s 间距%.2f %s vs %s" % (m, gp, f[:2], s2[:2]))
# E3 相对时间
for rw, (n, a) in r["e3"].items():
print("E3 %s n=%d 正确%.0f%%" % (rw, n, a))
# E4 负值
for m, (nneg, tot) in r["e4"].items():
if nneg > 0:
print("E4 %s 负值%d/%d" % (m, nneg, tot))
# E5 别名
for m, (h, tot) in r["e5"].items():
print("E5 %s %d/%d" % (m, h, tot))
# E6 容差
for nd, (nc, na, pct) in r["e6"].items():
print("E6 round(%d) %.1f%%" % (nd, pct))
# E7 覆盖
print("E7 覆盖 %d/%d (%.1f%%)" % (r["e7"]["covered"], r["e7"]["total"], r["e7"]["pct"]))
# E8 错点
print("E8 错点 %d (可答%%)" % r["e8"]["n_wrong"])