Skip to content

Latest commit

 

History

History
296 lines (193 loc) · 6.44 KB

File metadata and controls

296 lines (193 loc) · 6.44 KB

关键逻辑说明

一、phase2 的核心逻辑变化

phase1 的问题模型是:

问卷包含题目

phase2 的问题模型是:

问卷引用题目版本

这意味着当前系统里有三层不同语义:

  1. question_root
    • 表示“同一道题”的稳定身份
  2. question_version
    • 表示该题某个具体版本的内容
  3. survey.questions[]
    • 表示某问卷里使用了哪个版本,以及问卷内的顺序和跳转关系

二、题目版本逻辑

2.1 为什么必须版本化

用户要求同时满足:

  • 题目可复用
  • 题目可修改
  • 旧问卷不能被污染
  • 可以找回旧版本

因此系统不能原地覆盖题目内容,只能新增版本。

2.2 当前规则

  • 创建题目时同时创建 root + version1
  • 修改题目内容时创建新版本
  • 恢复旧版本时也会创建新版本
  • question_root.latest_version_id 指向当前最新版本
  • 问卷始终引用具体 question_version_id

2.3 旧问卷稳定性的保证

问卷中的每道题保存:

{
  "qid": "q1",
  "order": 1,
  "question_root_id": "...",
  "question_version_id": "...",
  "snapshot": {
    "title": "...",
    "type": "...",
    "options": [],
    "validation": {}
  },
  "jump_rules": []
}

这样即使题目资产继续演化:

  • 已发布问卷仍保留原来的 snapshot
  • 已收集的数据仍能对应当时的题目内容

三、共享与题库逻辑

3.1 题目共享

题目支持三种可见性:

  • private
  • shared
  • public

权限规则:

  • owner 可查看、改共享、创建版本、恢复版本
  • shared user 可查看、可在自己的问卷里引用
  • public user 可查看、可引用
  • 非 owner 进入题目详情页时只展示只读界面

3.2 题库

题库是题目资产的组织容器,不是题目内容本身的一部分。

题库当前支持:

  • 创建题库
  • 向题库加入题目
  • 从题库移出题目
  • 从题库页进入问卷编排
  • 共享用户只读查看题库

四、问卷编排逻辑

4.1 问卷页职责

问卷编辑页不再直接维护题目本体内容,而只负责:

  • 问卷元信息
  • 选题
  • 排序
  • 跳转
  • 切换引用版本

4.2 选题来源

问卷可以从两类来源选题:

  • 题目资产列表
  • 题库

加入问卷时默认引用所选题目的最新版本,也允许后续切换到同一题的其他版本。

4.3 排序语义

排序由后端统一处理,而不是前端自己改 order

当前行为:

  • 上移/下移会整体重排 questions[]
  • order 始终被重新编号为 1..n
  • 排序后所有 __order__:N 类型的跳转目标会被重写到新的题序
  • 若排序会导致某条跳转变成后退跳转,则操作直接拒绝

五、跳转逻辑

5.1 当前跳转目标格式

系统现在支持两种跳转目标:

  • __end__
  • __order__:N

phase2 前端问卷编排页默认使用“跳到第 N 题”的形式,而不再暴露对内部 qid 的依赖。

5.2 前跳约束

跳转规则始终是前跳:

  • 不允许跳到自己
  • 不允许跳到当前题之前
  • 不允许形成循环

这套约束在:

  • 新增题目
  • 修改跳转
  • 排序
  • 删题

这些路径上都会重新校验。

5.3 运行时跳转

填写页的跳转引擎仍由:

  • jump_service.py
  • static/js/fill.js

共同实现,并保持一致语义。


六、答卷与统计逻辑

6.1 答卷存储

phase2 下答卷同时保留两套信息:

  • answers
    • 保持 phase1 兼容
  • answer_items
    • 为跨问卷单题统计提供稳定索引

answer_items 中记录:

  • qid
  • question_root_id
  • question_version_id
  • answer

6.2 统计能力

当前支持两类统计:

整卷统计

  • GET /api/surveys/{survey_id}/stats
  • GET /api/surveys/{survey_id}/stats/{qid}

单题跨问卷统计

  • GET /api/questions/{root_id}/stats
  • 可选 version_id 参数按版本筛选

6.3 各题型统计规则

  • 单选题:每个选项人数、总回答人数
  • 多选题:每个选项被选次数
  • 文本题:返回填写内容列表
  • 数字题:返回填写内容与平均值

七、校验设计

7.1 设计目标

校验逻辑的目标不是简单判断“有没有填”,而是保证:

  • 不同题型的约束能统一表达
  • 被跳过的题目不被错误校验
  • 前后端保持一致语义
  • phase1 与 phase2 问卷结构都能继续工作

7.2 当前校验范围

系统当前校验以下内容:

  • 单选题是否作答
  • 多选题最少/最多选择数量
  • 文本题最短/最长长度
  • 数字题最小值/最大值
  • 数字题是否必须为整数

7.3 校验规则来源

校验规则来自题目内容本身:

  • phase1 内嵌题直接来自 questions[].validation
  • phase2 引用题来自 snapshot.validation

这保证已发布问卷永远按问卷冻结时的规则校验,而不是按题目资产最新版本校验。

7.4 前后端职责分工

  • 前端负责即时提示和基础交互体验
  • 后端负责最终校验与拒绝非法提交

这样即使前端被绕过,后端仍然能保证数据合法。

7.5 为什么跳过题目不校验

在存在跳转逻辑的情况下,一部分题目本来就不会出现在当前填写路径中。
因此系统在提交时会先根据跳转路径判断“哪些题是本次实际经过的题”,然后只校验这些题。

否则会出现:

  • 用户根本没看到某题
  • 但提交时却因为该题必答而报错

7.6 当前对应实现

  • 后端校验主逻辑:app/services/validation_service.py
  • 填写页联动提示:static/js/fill.js

八、核心系统公理

当前实现已经把以下约束固化成代码和回归测试:

  1. 题目必须独立存在
  2. 题目内容必须通过版本演化
  3. 已发布问卷不因新版本出现而改变
  4. 不同问卷可以同时使用同一道题的不同版本
  5. 共享用户可以使用共享题目,但不能修改题目资产
  6. 共享用户查看共享题目页和题库页时只看到只读界面

九、当前对应实现文件

  • 题目资产与版本:app/services/question_service.py
  • 题库:app/services/question_bank_service.py
  • 问卷引用与排序:app/services/survey_service.py
  • 答卷与 answer_itemsapp/services/response_service.py
  • 统计:app/services/stats_service.py
  • 问卷填写跳转:app/services/jump_service.pystatic/js/fill.js
  • 问卷编排前端:templates/survey_edit.htmlstatic/js/survey_edit.js