其他深度

Odoo 问卷后台统计为什么会“每题长得不一样”:矩阵题、量表题与汇总管线讲透

很多人以为 Odoo Survey 的结果页只是把答案数量数一遍再画图,但从 survey_question.py 与 survey_user_input.py 看,后台其实按题型走了多条统计管线:choice、matrix、scale、numerical 各有不同表格和图表结构,scored 问题还会额外计算正确率、partial 与常见答案。理解这层,你才知道为什么“每道题的统计长得不一样”并不是前端花活,而是模型设计本身。

其他
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 6 阅读

先说结论

Odoo Survey 的统计页并不是“统一拿答案表 count 一遍”。

它的真实设计是:题型先决定统计结构,统计结构再决定图表长相

也就是说,结果页看起来“每道题长得不一样”,不是因为前端随便画,而是因为后端在 survey.question 里先把不同题型的数据组织成了不同的数据骨架。

第一层:为什么统计入口放在 question 上,而不是 report 上

survey_question.py 里的 _prepare_statistics() 很关键。

它不是从“整份问卷报告”出发,而是逐题处理:

  1. 找到当前题目对应的 user input lines
  2. 区分答案行、评论行、跳过行
  3. 计算 summary 数据
  4. 再按题型生成 table_data 和 graph_data

这意味着 Odoo 把“统计”理解成题目模型自身的行为,而不是一个独立报表引擎。

这样做的好处是:每增加一种题型,就可以在题目模型内部定义自己的统计语义,而不必把所有特殊逻辑堆到统一报表层。

第二层:为什么 _get_stats_data() 是整个分流器

_get_stats_data() 本质上是统计分发器。

它会根据 question_type 走不同分支:

  • simple_choice:按建议答案统计
  • multiple_choice:也是按答案统计,但图表包一层 question key
  • matrix:走矩阵专属 graph builder
  • scale:走量表专属计数逻辑
  • 其他文本 / 数值 / 日期题:不做统一图表,而是保留原始答案列表

这说明 Odoo 非常清楚:题型不同,统计对象也不同。

例如:

  • choice 题统计的是“选项被选次数”
  • matrix 题统计的是“行 × 列组合次数”
  • scale 题统计的是“数值刻度分布”
  • text 题统计的是“回答明细”,不是频数图

如果硬要用同一种结构去装,最后一定会丢信息。

第三层:为什么矩阵题必须单独走 _get_stats_graph_data_matrix()

矩阵题最能体现 Odoo 的分层思路。

_get_stats_graph_data_matrix() 里,系统先拿到:

  • 所有列候选 suggested_answer_ids
  • 所有行 matrix_row_ids

然后建立 (row, answer) 二维计数字典。

这就把矩阵题从普通单选题里彻底区分出来了。

因为矩阵题真正想表达的不是“哪一个选项最常被选”,而是:

  • 每一行分别被怎样评价
  • 每一列在所有行里的分布如何

所以最后产出的:

  • table_data 是按行展开,每行内再包含若干列计数
  • graph_data 是按列分组,每组里再列出所有行的计数

这不是“写得复杂”,而是矩阵题天然就是二维结构。

第四层:为什么量表题不复用 suggested answers

_get_stats_data_scale() 里有个很明显的设计判断:scale 题直接按 range(scale_min, scale_max + 1) 生成统计桶,而不是去读 survey.question.answer

这代表在 Odoo 看来,量表题和普通选择题虽然界面都像“选一个值”,但数据语义并不一样:

  • choice 的核心是“选项对象”
  • scale 的核心是“数值区间”

所以 scale 统计天然更接近分布图,而不是标签计数图。

这也是为什么 scale 题还能直接复用 _get_stats_summary_data_numerical() 去算最大值、最小值、平均值。

第五层:为什么 summary data 不是每题都一样

_get_stats_summary_data() 会继续根据题型追加不同摘要:

  • choice 题:正确人数、部分正确人数
  • numerical / scale:最大值、最小值、平均值
  • numerical / date / datetime / scale:常见值与 scored 汇总

这里最重要的信号是:Odoo 不追求一个“统一摘要模板”,而是让摘要跟着题型能力走。

这比很多系统强行让所有题共享同一套 KPI 更合理。

因为对文本题来说,“平均值”毫无意义;但对量表题来说,平均值和最常见评分就非常关键。

第六层:为什么 survey.user_input 还要再做一次 _prepare_statistics()

很多人看到 survey.question 已经准备统计,就会疑惑:为什么 survey_user_input.py 里还有一个 _prepare_statistics()

因为这两个统计层级根本不一样。

survey.question._prepare_statistics()

它处理的是按题聚合后的整体报告,也就是管理员在后台看到的结果分布。

survey.user_input._prepare_statistics()

它处理的是单份答卷的得分拆解,重点是:

  • 每个 section 对应多少题
  • 正确 / partial / incorrect / skipped 各多少
  • 整份答卷 totals 如何展示

换句话说:

  • question 统计回答“大家整体怎么答”
  • user_input 统计回答“某个人这次答得怎么样”

这两者不是重复,而是两种完全不同的观察视角。

第七层:为什么 partial 这种状态必须单独存在

survey.user_input._prepare_statistics() 里,多选题会专门判断 partial

这点很重要,因为现实考试并不总是“全对或全错”。

如果一个用户选中了部分正确答案,但没有全选对:

  • 统计上它不该被算成完全正确
  • 也不该被粗暴打成完全错误

partial 的存在,让 Odoo 能在测评题里表达“部分掌握”。

这对培训考试、认证试卷、内部测评都很有价值。

实战里最容易误解的地方

1. 以为所有题都应该出柱状图

文本题、日期题本来就更适合明细展示,而不是统一画 chart。

2. 以为 scale 和 single choice 可以共用同一套统计

界面像,不代表数据语义像。量表题是数值分布,不是标签对象分布。

3. 以为后台题目报告和个人答卷报告是一回事

前者按题聚合,后者按答卷评分,目标完全不同。

4. 忽略 skipped 与 comment 行

Odoo 在统计时会显式区分跳过答案、评论行、有效答案行,不是所有 user_input_line 都一锅炖。

结论

Odoo Survey 结果页之所以成熟,不是因为图表多,而是因为它先把不同题型的统计语义拆清楚了。

如果你要做二开、导出、外部 BI 接口,最该尊重的是这几层边界:

  • _prepare_statistics():逐题装配统计上下文
  • _get_stats_data():按题型分流
  • _get_stats_graph_data_matrix():处理二维矩阵结构
  • _get_stats_data_scale():处理数值量表分布
  • survey.user_input._prepare_statistics():处理单份答卷得分视角

只要把这条链理解透,Survey 的统计为什么“每题长得不一样”,你就不会再觉得它是前端随机发挥了。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。