<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>问卷系统 - category - 沐木</title><link>https://oldletter.cn/categories/%E9%97%AE%E5%8D%B7%E7%B3%BB%E7%BB%9F/</link><description>问卷系统 - category - 沐木</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Fri, 01 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://oldletter.cn/categories/%E9%97%AE%E5%8D%B7%E7%B3%BB%E7%BB%9F/" rel="self" type="application/rss+xml"/><item><title>问卷考试系统设计（一）：架构选型与数据模型</title><link>https://oldletter.cn/posts/qnr-01-architecture/</link><pubDate>Wed, 01 Apr 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/qnr-01-architecture/</guid><description><![CDATA[<h2 id="为什么还要自建一套">为什么还要自建一套？</h2>
<p>问卷星、腾讯问卷、考试系统都有自己的适用场景，而且这些平台都做得很成熟。普通调研、满意度收集、报名表、简单测评，用它们效率非常高。腾讯问卷有自定义逻辑 / DSL 相关能力，问卷星也支持常见的题目逻辑、API、量表和计分配置。真要只是发一份问卷、收一批答案、导出 Excel，我肯定不会自己造轮子。</p>
<p>我这里说的“问卷考试系统”，更类似是<strong>心理量表 + 考试流程 + 报告引擎 + 内部系统集成</strong>揉在一起的东西。它不是一个单纯的问卷，也不是一个单纯的考试。踩坑之后我发现，问题不在于现成平台有没有某个功能，而在于这些功能是不是能按我们的方式组合、审计、复用和长期维护。</p>
<h3 id="现成问卷平台的边界">现成问卷平台的边界</h3>
<p>问卷星、腾讯问卷这类 SaaS 平台适合快速搭建和运营，优势很明显：</p>
<ul>
<li>题型丰富，跳题、显示逻辑、配额、回收渠道都比较完善</li>
<li>常规计分、基础报告、数据导出基本够用</li>
<li>高级版/企业版通常还会提供 API、团队协作、品牌定制、更多逻辑能力</li>
<li>对非研发团队很友好，产品、运营、老师自己就能配置</li>
</ul>
<p>但到心理量表和内部业务系统结合时，边界就开始出现了。</p>
<p><strong>第一，算法可控性不够。</strong> SCL-90 有 9 个因子分，CES-D 有正性情感题需要反向计分，MMPI 又有 L/F/K 等效度量表。平台可能支持“维度计分”或者“反向题”，但我们还会遇到更细的规则：题目版本修订、常模分层、测谎阈值、T 分/Z 分转换、异常答题判定、人工复核标记。只要规则需要被代码审计、被测试用例覆盖、被历史版本追溯，单靠页面配置就会吃力。</p>
<p><strong>第二，报告不是简单的结果页。</strong> 心理测评的报告往往不是“你得了 78 分”这么简单。它要解释每个维度的含义，要有雷达图、柱状图、风险提示、建议话术，还要能输出 PDF、归档、推送到业务系统。模板一多，报告本身就变成了一个小型内容系统。</p>
<p><strong>第三，数据合规和私有化要求绕不开。</strong> 有些答卷数据涉及个人状态、组织内部评估、学校或企业的敏感信息。即使平台本身安全可靠，业务上也可能要求数据留在自己的库里，权限走自己的组织架构，审计走自己的日志链路。</p>
<p><strong>第四，长期成本和集成成本要算总账。</strong> SaaS 平台按版本、账号、回收量、API 能力收费，短期省事，长期不一定便宜。更麻烦的是集成：用户体系、组织架构、权限、报告归档、消息通知、数据看板都要打通，越往后越类似在平台外面再套一层系统。</p>
<p>所以我末尾的判断是：现成平台并非不能用，重点是<strong>如果核心价值在“量表算法和报告自动化”上，自建会更可控</strong>。</p>
<h3 id="考试系统模型哪里不贴合">考试系统模型哪里不贴合</h3>
<p>考试系统当然也能改。它的流程很完整：题库、试卷、答题、交卷、评分、成绩统计，和我们要做的东西确实很类似。</p>
<p>问题在于考试系统默认有一套很强的假设：答案有对错，题目有标准答案，分数代表掌握程度。心理测评不是这个模型。“近段时间一周你感到情绪低落的频率”这种题，选“没有”和选“经常”只是反映状态，不存在谁更正确。反向题也并非为了“迷惑考生”，重点是为了修正量表方向和控制作答质量。</p>
<p>再比如考试系统常见的防作弊逻辑是切屏检测、人脸识别、随机抽题；心理量表更关心直线作答、过快作答、矛盾回答、测谎题异常。看起来都是“质量控制”，实际关注点完全不同。</p>
<h3 id="什么时候值得自建">什么时候值得自建</h3>
<p>我的经验是，不要一上来就自建。先用这个清单判断：</p>
<table>
  <thead>
      <tr>
          <th>场景</th>
          <th>更适合现成平台</th>
          <th>更适合自建</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>问卷规模</td>
          <td>临时调研、活动报名、满意度收集</td>
          <td>长期产品能力，需要持续迭代</td>
      </tr>
      <tr>
          <td>计分规则</td>
          <td>总分、简单维度、固定报告</td>
          <td>多维度、反向计分、常模、效度量表、复杂阈值</td>
      </tr>
      <tr>
          <td>数据要求</td>
          <td>可接受平台托管和导出</td>
          <td>私有化、审计、权限、合规要求强</td>
      </tr>
      <tr>
          <td>系统集成</td>
          <td>Excel 导出即可</td>
          <td>要打通用户、组织、业务流程、消息、档案</td>
      </tr>
      <tr>
          <td>成本模型</td>
          <td>短期使用，人数不大</td>
          <td>长期高频使用，企业版/API 成本明显</td>
      </tr>
      <tr>
          <td>可维护性</td>
          <td>配置改完就结束</td>
          <td>算法要测试、版本要追溯、报告要复用</td>
      </tr>
  </tbody>
</table>
<p>如果只是普通问卷，用成熟平台更省心；如果要做成企业内部长期运行的测评能力，那就需要一套更工程化的系统。</p>]]></description></item><item><title>问卷考试系统设计（二）：题目 DSL 设计——用 JSON 描述一切题型</title><link>https://oldletter.cn/posts/qnr-02-dsl-design/</link><pubDate>Sun, 05 Apr 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/qnr-02-dsl-design/</guid><description><![CDATA[<p><strong>上一篇：<a href="/posts/qnr-01-architecture/" rel="">问卷考试系统设计（一）：架构选型与数据模型</a></strong></p>
<p>上一篇聊了整体架构和数据模型，这篇讲整个系统核心的部分：怎么用一套统一的 JSON 结构，把单选、多选、量表、矩阵、排序、填空……所有题型都描述清楚。然后让引擎把这些 JSON 跑起来，条件跳题、题目分组、随机排序，这些才是让问卷从&quot;能编辑&quot;变成&quot;能填写&quot;的关键。</p>
<hr>
<h2 id="为什么用-json-而不是多张表">为什么用 JSON 而不是多张表</h2>
<h3 id="传统建模的麻烦">传统建模的麻烦</h3>
<p>直观的做法，每种题型一张表：</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-sql">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">question_single_choice</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">id</span><span class="w"> </span><span class="nb">BIGINT</span><span class="w"> </span><span class="k">PRIMARY</span><span class="w"> </span><span class="k">KEY</span><span class="p">,</span><span class="w"> </span><span class="n">stem</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">option_a</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span><span class="w"> </span><span class="n">option_b</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span><span class="w"> </span><span class="n">option_c</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span><span class="w"> </span><span class="n">option_d</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">correct_answer</span><span class="w"> </span><span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">10</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">question_likert</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">id</span><span class="w"> </span><span class="nb">BIGINT</span><span class="w"> </span><span class="k">PRIMARY</span><span class="w"> </span><span class="k">KEY</span><span class="p">,</span><span class="w"> </span><span class="n">stem</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">scale_type</span><span class="w"> </span><span class="nb">VARCHAR</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span><span class="w"> </span><span class="n">is_reverse</span><span class="w"> </span><span class="nb">BOOLEAN</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="c1">-- 10 种题型 10 张表……</span></span></span></code></pre></div></div>
<p>问题很明显：</p>]]></description></item><item><title>问卷考试系统设计（三）：评分引擎--维度计分、公式 DSL 与常模</title><link>https://oldletter.cn/posts/qnr-03-scoring-engine/</link><pubDate>Fri, 10 Apr 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/qnr-03-scoring-engine/</guid><description><![CDATA[<p><strong>上一篇:<a href="/posts/qnr-02-dsl-design/" rel="">问卷考试系统设计(二):题目 DSL 设计</a></strong></p>
<p>评分引擎是整个系统里容易出错、也需要优先可审计的部分。心理量表和普通考试上限的差异也在这里：不能只把考试系统那套 <code>sum()</code> 逻辑搬过来，很多规则要单独设计。</p>
<hr>
<h2 id="心理量表评分-vs-普通考试">心理量表评分 vs 普通考试</h2>
<p>两者的差异很大。</p>
<p><strong>普通考试</strong>的计分模型很直白:答对得 1 分,答错得 0 分,加起来就是总分。题目按章节组织,分数按章节汇总,出个柱状图就完事了。</p>
<p>到<strong>心理量表</strong>这里，逻辑就不一样了。题目常用 Likert 等级量表&ndash;“从不”到“总是”、“非常不同意”到“非常同意”，每个选项对应一个分值，<strong>没有对错之分，只有程度差异</strong>。量表按心理学维度组织，比如焦虑、抑郁、躯体化，每道题在设计阶段就归属到某个维度了。</p>
<p>更关键的差异:</p>
<ul>
<li><strong>反向计分题</strong>:防止被试不动脑子全选同一个选项。正常题&quot;我觉得工作很有成就感&quot;选&quot;总是&quot;得 5 分,反向题&quot;我觉得工作毫无意义&quot;选&quot;总是&quot;得 1 分。考试没有这种概念。</li>
<li><strong>测谎题</strong>:检测被试是否在刻意美化自己。考试也不需要这个。</li>
<li><strong>维度分析</strong>:心理量表的核心价值是维度分&ndash;焦虑多少分、抑郁多少分、躯体化多少分,分别对应什么严重程度。光给一个总分没有意义。</li>
<li><strong>常模参照</strong>:原始分需要转换成标准分(T 分/Z 分),然后和常模比较才能得出有意义的结论。这是心理量表和考试上限的区别,后面会详细讲。</li>
</ul>
<table>
  <thead>
      <tr>
          <th>维度</th>
          <th>普通考试</th>
          <th>心理量表</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>计分逻辑</td>
          <td>对/错二元</td>
          <td>程度量表(1-5 Likert)</td>
      </tr>
      <tr>
          <td>反向题</td>
          <td>不存在</td>
          <td>存在,需要反转分数</td>
      </tr>
      <tr>
          <td>维度结构</td>
          <td>章节/知识点</td>
          <td>心理学维度(如焦虑、抑郁)</td>
      </tr>
      <tr>
          <td>质量检测</td>
          <td>无</td>
          <td>测谎题、一致性检测</td>
      </tr>
      <tr>
          <td>结果呈现</td>
          <td>总分</td>
          <td>维度分 + T分/Z分 + 常模比较</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="维度模型设计">维度模型设计</h2>
<p>维度和题目不要做成简单多对多，还要记录反向题、测谎题和父子维度聚合规则：</p>
<div class="mermaid" id="id-3" data-mermaid-definition="Zmxvd2NoYXJ0IFRCCiAgICAgICAgICAgICAgICAgICAgU2NhbGVbIumHj&#43;ihqOeJiOacrCJdIC0tPiBSb290WyLmgLvnu7TluqYiXQogICAgICAgICAgICAgICAgICAgIFJvb3QgLS0&#43;IEQxWyLkuIDnuqfnu7TluqYgQSJdCiAgICAgICAgICAgICAgICAgICAgUm9vdCAtLT4gRDJbIuS4gOe6p&#43;e7tOW6piBCIl0KICAgICAgICAgICAgICAgICAgICBEMSAtLT4gTDFbIuWPtuWtkOe7tOW6piBBMSJdCiAgICAgICAgICAgICAgICAgICAgRDEgLS0&#43;IEwyWyLlj7blrZDnu7TluqYgQTIiXQogICAgICAgICAgICAgICAgICAgIEwxIC0tPiBRMVsi6aKY55uuIDEiXQogICAgICAgICAgICAgICAgICAgIEwxIC0tPiBRMlsi6aKY55uuIDI8YnIvPuWPjeWQkemimCJdCiAgICAgICAgICAgICAgICAgICAgTDIgLS0&#43;IFEzWyLpopjnm64gMyJdCiAgICAgICAgICAgICAgICAgICAgRDIgLS0&#43;IEwzWyLlj7blrZDnu7TluqYgQjEiXQogICAgICAgICAgICAgICAgICAgIEwzIC0tPiBRNFsi5rWL6LCO6aKYIl0KICAgICAgICAgICAgICAgICAgICBRMSAtLT4gU3VtWyLlj7blrZDnu7TluqbogZrlkIgiXQogICAgICAgICAgICAgICAgICAgIFEyIC0tPiBTdW0KICAgICAgICAgICAgICAgICAgICBRMyAtLT4gU3VtCiAgICAgICAgICAgICAgICAgICAgU3VtIC0tPiBQYXJlbnRbIueItue7tOW6puiBmuWQiCJd">flowchart TB
                    Scale[&#34;量表版本&#34;] --&gt; Root[&#34;总维度&#34;]
                    Root --&gt; D1[&#34;一级维度 A&#34;]
                    Root --&gt; D2[&#34;一级维度 B&#34;]
                    D1 --&gt; L1[&#34;叶子维度 A1&#34;]
                    D1 --&gt; L2[&#34;叶子维度 A2&#34;]
                    L1 --&gt; Q1[&#34;题目 1&#34;]
                    L1 --&gt; Q2[&#34;题目 2&lt;br/&gt;反向题&#34;]
                    L2 --&gt; Q3[&#34;题目 3&#34;]
                    D2 --&gt; L3[&#34;叶子维度 B1&#34;]
                    L3 --&gt; Q4[&#34;测谎题&#34;]
                    Q1 --&gt; Sum[&#34;叶子维度聚合&#34;]
                    Q2 --&gt; Sum
                    Q3 --&gt; Sum
                    Sum --&gt; Parent[&#34;父维度聚合&#34;]</div><h3 id="维度与题目的关联">维度与题目的关联</h3>
<p>每个量表由若干维度组成,每道题归属于一个维度。维度之间可以有层级关系&ndash;比如职业兴趣测评里先有&quot;人际/事务/研究&quot;这类一级维度,下面再拆更细的子维度。类似 SCL-90 这种经典量表则是相对扁平的 9 个因子,不要硬套层级。</p>]]></description></item><item><title>问卷考试系统设计（四）：质量控制——测谎题、Z3 组卷验证与异常检测</title><link>https://oldletter.cn/posts/qnr-04-quality-control/</link><pubDate>Wed, 15 Apr 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/qnr-04-quality-control/</guid><description><![CDATA[<p><strong>上一篇：<a href="/posts/qnr-03-scoring-engine/" rel="">问卷考试系统设计（三）：评分引擎</a></strong></p>
<p>问卷收上来一堆答案，怎么判断哪些是认真填的、哪些是随便糊弄的？另外，在出卷阶段，怎么保证组出来的试卷在分数分布上是合理的？这两个问题分别对应<strong>答题后质量检测</strong>和<strong>出卷时约束验证</strong>。</p>
<p>三道防线：<strong>测谎题</strong>检测被试是否在刻意美化，<strong>Z3 约束求解器</strong>在组卷阶段验证分数区间无冲突，<strong>异常模式检测</strong>抓全选、极端值、过快作答等刷卷行为。</p>
<p>质量控制分成卷前验证和卷后检测，两条链路末尾汇总成答卷可信度：</p>
<div class="mermaid" id="id-2" data-mermaid-definition="Zmxvd2NoYXJ0IFRCCiAgICAgICAgICAgICAgICAgICAgQVsi5Y&#43;R5biD5YmNIl0gLS0&#43;IEJbIlozIOe6puadn&#43;mqjOivgSJdCiAgICAgICAgICAgICAgICAgICAgQiAtLT4gQ1si6aKY6YeP44CB57u05bqm44CB5LqS5pal6KeE5YiZ5qOA5p&#43;lIl0KICAgICAgICAgICAgICAgICAgICBDIC0tPiBEeyLlj6/lj5HluIMifQogICAgICAgICAgICAgICAgICAgIEVbIuaPkOS6pOWQjiJdIC0tPiBGWyLmtYvosI7popjmo4DmtYsiXQogICAgICAgICAgICAgICAgICAgIEUgLS0&#43;IEdbIuS4gOiHtOaAp&#43;ajgOa1iyJdCiAgICAgICAgICAgICAgICAgICAgRSAtLT4gSFsi5L2c562U5qih5byP5qOA5rWLIl0KICAgICAgICAgICAgICAgICAgICBGIC0tPiBJWyLotKjph4/liIYiXQogICAgICAgICAgICAgICAgICAgIEcgLS0&#43;IEkKICAgICAgICAgICAgICAgICAgICBIIC0tPiBJCiAgICAgICAgICAgICAgICAgICAgRCAtLT4gSlsi6Zeu5Y2354mI5pys5Y&#43;v5L&#43;hIl0KICAgICAgICAgICAgICAgICAgICBJIC0tPiBLWyLnrZTljbfmnInmlYjmgKfnrYnnuqciXQ==">flowchart TB
                    A[&#34;发布前&#34;] --&gt; B[&#34;Z3 约束验证&#34;]
                    B --&gt; C[&#34;题量、维度、互斥规则检查&#34;]
                    C --&gt; D{&#34;可发布&#34;}
                    E[&#34;提交后&#34;] --&gt; F[&#34;测谎题检测&#34;]
                    E --&gt; G[&#34;一致性检测&#34;]
                    E --&gt; H[&#34;作答模式检测&#34;]
                    F --&gt; I[&#34;质量分&#34;]
                    G --&gt; I
                    H --&gt; I
                    D --&gt; J[&#34;问卷版本可信&#34;]
                    I --&gt; K[&#34;答卷有效性等级&#34;]</div><hr>
<h2 id="第一道防线测谎题检测装好人">第一道防线：测谎题，检测&quot;装好人&quot;</h2>
<h3 id="设计原理">设计原理</h3>
<p>测谎题的逻辑很简单：<strong>大多数人不太可能在所有测谎题上都选“完美”的那一端</strong>。</p>
<p>MMPI 的 L（Lie）效度量表是经典案例。它会放一些社会称许性很强、但现实中很难完全做到的题目，例如：</p>
<ul>
<li>“我从来没有说过谎”，大多数人都说过谎，全选“是”反而可疑</li>
<li>“我从不背后说人坏话”，太完美了，不真实</li>
<li>“我从来不会迟到”，现实里很难完全做到</li>
</ul>
<p>这些题目不计入心理维度的分数，唯一的用途就是检测被试的作答态度。</p>
<h3 id="评分逻辑">评分逻辑</h3>
<p>每道测谎题都有一个或多个&quot;可疑答案&quot;。被试选中了可疑答案，就记 1 分；否则记 0 分。末尾把所有测谎题的可疑分加起来，和阈值做比较。</p>
<p>阈值不要写死，更稳妥按量表手册和人群配置。下面只是工程示例：</p>
<ul>
<li><strong>VALID</strong>（有效）：可疑比例 ≤ 50%</li>
<li><strong>SUSPICIOUS</strong>（可疑）：50% &lt; 可疑比例 ≤ 80%</li>
<li><strong>INVALID</strong>（无效）：可疑比例 &gt; 80%</li>
</ul>
<h3 id="实现">实现</h3>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-java">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="n">LieScaleResult</span><span class="w"> </span><span class="nf">calculate</span><span class="p">(</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Question</span><span class="o">&gt;</span><span class="w"> </span><span class="n">lieScaleQuestions</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                                 </span><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"> </span><span class="n">Answer</span><span class="o">&gt;</span><span class="w"> </span><span class="n">answers</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">int</span><span class="w"> </span><span class="n">totalLieScore</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">0</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">Question</span><span class="w"> </span><span class="n">q</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="n">lieScaleQuestions</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Answer</span><span class="w"> </span><span class="n">answer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">answers</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">q</span><span class="p">.</span><span class="na">getId</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">answer</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kc">null</span><span class="p">)</span><span class="w"> </span><span class="k">continue</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kt">int</span><span class="w"> </span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">answer</span><span class="p">.</span><span class="na">getSelectedValue</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">q</span><span class="p">.</span><span class="na">getLieAnswerValues</span><span class="p">().</span><span class="na">contains</span><span class="p">(</span><span class="n">value</span><span class="p">))</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">totalLieScore</span><span class="o">++</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">double</span><span class="w"> </span><span class="n">lieRatio</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="kt">double</span><span class="p">)</span><span class="w"> </span><span class="n">totalLieScore</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="n">lieScaleQuestions</span><span class="p">.</span><span class="na">size</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">String</span><span class="w"> </span><span class="n">verdict</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">lieRatio</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="n">0</span><span class="p">.</span><span class="na">8</span><span class="p">)</span><span class="w"> </span><span class="n">verdict</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;INVALID&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">else</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">lieRatio</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="n">0</span><span class="p">.</span><span class="na">5</span><span class="p">)</span><span class="w"> </span><span class="n">verdict</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;SUSPICIOUS&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">else</span><span class="w"> </span><span class="n">verdict</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">&#34;VALID&#34;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">LieScaleResult</span><span class="p">(</span><span class="n">totalLieScore</span><span class="p">,</span><span class="w"> </span><span class="n">lieScaleQuestions</span><span class="p">.</span><span class="na">size</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                               </span><span class="n">lieRatio</span><span class="p">,</span><span class="w"> </span><span class="n">verdict</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div></div>
<blockquote>
<p><strong>注意</strong>：测谎题的可疑答案不一定总是更高分。有些题是&quot;选低分才可疑&quot;，有些还可能有多个可疑选项。稳妥做法是在题目配置里显式声明 <code>lieAnswerValues</code>。</p>]]></description></item><item><title>问卷考试系统设计（五）：前端引擎——渲染、状态管理与防作弊</title><link>https://oldletter.cn/posts/qnr-05-frontend/</link><pubDate>Mon, 20 Apr 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/qnr-05-frontend/</guid><description><![CDATA[<p><strong>上一篇：<a href="/posts/qnr-04-quality-control/" rel="">问卷考试系统设计（四）：质量控制</a></strong></p>
<p>后端写好了，接口也通了。前端要解决四个问题：怎么把后端返回的 JSON 渲染成可交互的答题界面、状态怎么管、考试场景怎么防作弊、手机上表格挤成一团怎么办。</p>
<p>前端答题状态要围绕一份本地快照流转，草稿和提交使用同一套校验结果：</p>
<div class="mermaid" id="id-2" data-mermaid-definition="c3RhdGVEaWFncmFtLXYyCiAgICAgICAgICAgICAgICAgICAgWypdIC0tPiBMb2FkaW5nRHNsCiAgICAgICAgICAgICAgICAgICAgTG9hZGluZ0RzbCAtLT4gSW5pdFN0b3JlOiDliqDovb3pl67ljbcgRFNMCiAgICAgICAgICAgICAgICAgICAgSW5pdFN0b3JlIC0tPiBBbnN3ZXJpbmc6IOWIneWni&#43;WMliBQaW5pYQogICAgICAgICAgICAgICAgICAgIEFuc3dlcmluZyAtLT4gUmVjb21wdXRlTG9naWM6IOmAiemhueWPmOWMlgogICAgICAgICAgICAgICAgICAgIFJlY29tcHV0ZUxvZ2ljIC0tPiBBbnN3ZXJpbmc6IOabtOaWsOi3s&#43;mimOe7k&#43;aenAogICAgICAgICAgICAgICAgICAgIEFuc3dlcmluZyAtLT4gU2F2aW5nRHJhZnQ6IOWumuaXtuaIluWkseeEpgogICAgICAgICAgICAgICAgICAgIFNhdmluZ0RyYWZ0IC0tPiBBbnN3ZXJpbmc6IOS/neWtmOaIkOWKnwogICAgICAgICAgICAgICAgICAgIEFuc3dlcmluZyAtLT4gU3VibWl0Q2hlY2s6IOeCueWHu&#43;aPkOS6pAogICAgICAgICAgICAgICAgICAgIFN1Ym1pdENoZWNrIC0tPiBBbnN3ZXJpbmc6IOagoemqjOWksei0pQogICAgICAgICAgICAgICAgICAgIFN1Ym1pdENoZWNrIC0tPiBTdWJtaXRBbnN3ZXI6IOagoemqjOmAmui/hwogICAgICAgICAgICAgICAgICAgIFN1Ym1pdEFuc3dlciAtLT4gU3VibWl0U2VjdXJpdHk6IOmZhOW4pui0qOmHj&#43;S4jumYsuS9nOW8iuaVsOaNrgogICAgICAgICAgICAgICAgICAgIFN1Ym1pdFNlY3VyaXR5IC0tPiBEb25lCiAgICAgICAgICAgICAgICAgICAgRG9uZSAtLT4gWypd">stateDiagram-v2
                    [*] --&gt; LoadingDsl
                    LoadingDsl --&gt; InitStore: 加载问卷 DSL
                    InitStore --&gt; Answering: 初始化 Pinia
                    Answering --&gt; RecomputeLogic: 选项变化
                    RecomputeLogic --&gt; Answering: 更新跳题结果
                    Answering --&gt; SavingDraft: 定时或失焦
                    SavingDraft --&gt; Answering: 保存成功
                    Answering --&gt; SubmitCheck: 点击提交
                    SubmitCheck --&gt; Answering: 校验失败
                    SubmitCheck --&gt; SubmitAnswer: 校验通过
                    SubmitAnswer --&gt; SubmitSecurity: 附带质量与防作弊数据
                    SubmitSecurity --&gt; Done
                    Done --&gt; [*]</div><hr>
<h2 id="一注册表模式一套-dsl-驱动所有题型渲染">一、注册表模式：一套 DSL 驱动所有题型渲染</h2>
<h3 id="硬编码的坑">硬编码的坑</h3>
<p>直观的写法是 if-else：</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-vue">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vue" data-lang="vue"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">SingleChoice</span> <span class="nt">v-if</span><span class="o">=</span><span class="s">&#34;q.type === &#39;single_choice&#39;&#34; :question=&#34;q&#34;</span><span class="p"> /&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">MultipleChoice</span> <span class="nt">v-else-if</span><span class="o">=</span><span class="s">&#34;q.type === &#39;multiple_choice&#39;&#34; :question=&#34;q&#34;</span><span class="p"> /&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">LikertScale</span> <span class="nt">v-else-if</span><span class="o">=</span><span class="s">&#34;q.type === &#39;likert&#39;&#34; :question=&#34;q&#34;</span><span class="p"> /&gt;</span></span></span></code></pre></div></div>
<p>每加一种题型，渲染层、校验层、状态层都得改。后面会越来越难维护。</p>]]></description></item><item><title>问卷考试系统设计（六）：报告生成——模板引擎、AI 接入与 PDF 导出</title><link>https://oldletter.cn/posts/qnr-06-report-ai/</link><pubDate>Sat, 25 Apr 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/qnr-06-report-ai/</guid><description><![CDATA[<p><strong>上一篇：<a href="/posts/qnr-05-frontend/" rel="">问卷考试系统设计（五）：前端引擎</a></strong></p>
<p>数据收集、评分计算都做完了，业务方更关心的是末尾一步：拿到一份类似样的报告。报告生成的完整链路：模板定结构、规则填数据、AI 写解读、图表画可视化、末尾拼成 PDF。</p>
<p>报告生成适合异步化，前端只订阅任务进度，不直接等待 PDF 渲染完成：</p>
<div class="mermaid" id="id-2" data-mermaid-definition="c2VxdWVuY2VEaWFncmFtCiAgICAgICAgICAgICAgICAgICAgcGFydGljaXBhbnQgQVBJIGFzIOaKpeWRiiBBUEkKICAgICAgICAgICAgICAgICAgICBwYXJ0aWNpcGFudCBRdWV1ZSBhcyDlvILmraXku7vliqEKICAgICAgICAgICAgICAgICAgICBwYXJ0aWNpcGFudCBSdWxlIGFzIOinhOWImeino&#43;ivuwogICAgICAgICAgICAgICAgICAgIHBhcnRpY2lwYW50IEFJIGFzIEFJIOaWh&#43;ahiAogICAgICAgICAgICAgICAgICAgIHBhcnRpY2lwYW50IENoYXJ0IGFzIOWbvuihqOa4suafkwogICAgICAgICAgICAgICAgICAgIHBhcnRpY2lwYW50IFBERiBhcyBQREYg5a&#43;85Ye6CiAgICAgICAgICAgICAgICAgICAgcGFydGljaXBhbnQgU1NFIGFzIFNTRSDov5vluqYKICAgICAgICAgICAgICAgICAgICBBUEktPj5RdWV1ZTog5o&#43;Q5Lqk6K&#43;E5YiG57uT5p6cCiAgICAgICAgICAgICAgICAgICAgUXVldWUtPj5TU0U6IDEwJSDlt7LmjqXmlLYKICAgICAgICAgICAgICAgICAgICBRdWV1ZS0&#43;PlJ1bGU6IOeUn&#43;aIkOinhOWImeino&#43;ivuwogICAgICAgICAgICAgICAgICAgIFF1ZXVlLT4&#43;QUk6IOeUn&#43;aIkOi&#43;heWKqeaWh&#43;ahiAogICAgICAgICAgICAgICAgICAgIFF1ZXVlLT4&#43;Q2hhcnQ6IOa4suafk&#43;WbvuihqCBQTkcKICAgICAgICAgICAgICAgICAgICBRdWV1ZS0&#43;PlBERjog5ou8IEhUTUwg5bm25a&#43;85Ye6IFBERgogICAgICAgICAgICAgICAgICAgIFF1ZXVlLT4&#43;U1NFOiAxMDAlIOaKpeWRiuWPr&#43;S4i&#43;i9vQ==">sequenceDiagram
                    participant API as 报告 API
                    participant Queue as 异步任务
                    participant Rule as 规则解读
                    participant AI as AI 文案
                    participant Chart as 图表渲染
                    participant PDF as PDF 导出
                    participant SSE as SSE 进度
                    API-&gt;&gt;Queue: 提交评分结果
                    Queue-&gt;&gt;SSE: 10% 已接收
                    Queue-&gt;&gt;Rule: 生成规则解读
                    Queue-&gt;&gt;AI: 生成辅助文案
                    Queue-&gt;&gt;Chart: 渲染图表 PNG
                    Queue-&gt;&gt;PDF: 拼 HTML 并导出 PDF
                    Queue-&gt;&gt;SSE: 100% 报告可下载</div><hr>
<h2 id="报告生成的几种模式">报告生成的几种模式</h2>
<p><strong>模板填充</strong>适合格式固定的报告，模板写好，变量一填就完事。</p>
<p><strong>规则引擎</strong>解决条件判断问题。焦虑得分 0-4 分是&quot;无焦虑&quot;，5-9 分是&quot;轻度焦虑&quot;，这种区间映射用硬编码也能写，但规则多了维护起来是噩梦。</p>
<p><strong>AI 生成</strong>处理自然语言解读。数字摆在那里，但用户要的是一段&quot;读得懂&quot;的文字。</p>
<p>实际项目中三者结合：模板定骨架，规则引擎填确定性结论，AI 只负责把结论写得更类似人话。先保证可复现，再谈润色。</p>]]></description></item><item><title>问卷考试系统设计（七）：数据统计、可视化与系列总结</title><link>https://oldletter.cn/posts/qnr-07-statistics/</link><pubDate>Fri, 01 May 2026 00:00:00 +0000</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/qnr-07-statistics/</guid><description><![CDATA[<p><strong>上一篇：<a href="/posts/qnr-06-report-ai/" rel="">问卷考试系统设计（六）：报告生成</a></strong></p>
<p>数据采集只是起点。问卷系统真正产生价值的时刻，是数据沉淀之后，研究者打开统计面板，看到分组差异、趋势变化和数据质量。这篇聚焦数据统计分析、多格式导出、隐私脱敏、大屏可视化，以及常模的建立过程。</p>
<p>统计分析要区分离线口径和实时看板，避免把探索性分析塞进在线请求：</p>
<div class="mermaid" id="id-2" data-mermaid-definition="Zmxvd2NoYXJ0IExSCiAgICAgICAgICAgICAgICAgICAgQVsi562U5Y235riF5rSXIl0gLS0&#43;IEJbIue7tOW6puWIhuaxh&#43;aAuyJdCiAgICAgICAgICAgICAgICAgICAgQiAtLT4gQ1si5o&#43;P6L&#43;w57uf6K6hIl0KICAgICAgICAgICAgICAgICAgICBDIC0tPiBEWyLmmL7okZfmgKfmo4DpqowiXQogICAgICAgICAgICAgICAgICAgIEQgLS0&#43;IEVbIuW4uOaooeaehOW7uiJdCiAgICAgICAgICAgICAgICAgICAgRSAtLT4gRlsi56a757q/5a&#43;85Ye6Il0KICAgICAgICAgICAgICAgICAgICBCIC0tPiBHWyLlrp7ml7bmjIfmoIfogZrlkIgiXQogICAgICAgICAgICAgICAgICAgIEcgLS0&#43;IEhbIldlYlNvY2tldCDmjqjpgIEiXQogICAgICAgICAgICAgICAgICAgIEggLS0&#43;IElbIuWkp&#43;Wxj&#43;eci&#43;advyJdCiAgICAgICAgICAgICAgICAgICAgRSAtLT4gSlsi5bi45qih54mI5pysIl0KICAgICAgICAgICAgICAgICAgICBKIC0tPiBLWyLor4TliIblvJXmk47or7vlj5YiXQ==">flowchart LR
                    A[&#34;答卷清洗&#34;] --&gt; B[&#34;维度分汇总&#34;]
                    B --&gt; C[&#34;描述统计&#34;]
                    C --&gt; D[&#34;显著性检验&#34;]
                    D --&gt; E[&#34;常模构建&#34;]
                    E --&gt; F[&#34;离线导出&#34;]
                    B --&gt; G[&#34;实时指标聚合&#34;]
                    G --&gt; H[&#34;WebSocket 推送&#34;]
                    H --&gt; I[&#34;大屏看板&#34;]
                    E --&gt; J[&#34;常模版本&#34;]
                    J --&gt; K[&#34;评分引擎读取&#34;]</div><hr>
<h2 id="一描述性统计">一、描述性统计</h2>
<p>拿到一批量表分数，第一件事是看整体面貌：</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-java">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"> </span><span class="n">DescriptiveStats</span><span class="w"> </span><span class="nf">descriptive</span><span class="p">(</span><span class="n">List</span><span class="o">&lt;</span><span class="n">Double</span><span class="o">&gt;</span><span class="w"> </span><span class="n">scores</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">DoubleSummaryStatistics</span><span class="w"> </span><span class="n">stats</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">scores</span><span class="p">.</span><span class="na">stream</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="na">mapToDouble</span><span class="p">(</span><span class="n">Double</span><span class="p">::</span><span class="n">doubleValue</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="na">summaryStatistics</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">double</span><span class="w"> </span><span class="n">mean</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">stats</span><span class="p">.</span><span class="na">getAverage</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">double</span><span class="w"> </span><span class="n">std</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Math</span><span class="p">.</span><span class="na">sqrt</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">scores</span><span class="p">.</span><span class="na">stream</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">.</span><span class="na">mapToDouble</span><span class="p">(</span><span class="n">v</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="n">Math</span><span class="p">.</span><span class="na">pow</span><span class="p">(</span><span class="n">v</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">mean</span><span class="p">,</span><span class="w"> </span><span class="n">2</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">.</span><span class="na">average</span><span class="p">().</span><span class="na">orElse</span><span class="p">(</span><span class="n">0</span><span class="p">.</span><span class="na">0</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">List</span><span class="o">&lt;</span><span class="n">Double</span><span class="o">&gt;</span><span class="w"> </span><span class="n">sorted</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">scores</span><span class="p">.</span><span class="na">stream</span><span class="p">().</span><span class="na">sorted</span><span class="p">().</span><span class="na">collect</span><span class="p">(</span><span class="n">Collectors</span><span class="p">.</span><span class="na">toList</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">double</span><span class="w"> </span><span class="n">median</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sorted</span><span class="p">.</span><span class="na">size</span><span class="p">()</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="n">2</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="n">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="o">?</span><span class="w"> </span><span class="p">(</span><span class="n">sorted</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">sorted</span><span class="p">.</span><span class="na">size</span><span class="p">()</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="n">2</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">1</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">sorted</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">sorted</span><span class="p">.</span><span class="na">size</span><span class="p">()</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="n">2</span><span class="p">))</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="n">2</span><span class="p">.</span><span class="na">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">:</span><span class="w"> </span><span class="n">sorted</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">sorted</span><span class="p">.</span><span class="na">size</span><span class="p">()</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="n">2</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">DescriptiveStats</span><span class="p">(</span><span class="n">mean</span><span class="p">,</span><span class="w"> </span><span class="n">std</span><span class="p">,</span><span class="w"> </span><span class="n">median</span><span class="p">,</span><span class="w"> </span><span class="n">stats</span><span class="p">.</span><span class="na">getMin</span><span class="p">(),</span><span class="w"> </span><span class="n">stats</span><span class="p">.</span><span class="na">getMax</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">stats</span><span class="p">.</span><span class="na">getCount</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span></span></span></code></pre></div></div>
<p>均值、标准差、中位数、极值，这几个数字能快速告诉研究者数据有没有异常、分布偏不偏。对于问卷系统这个体量的数据（通常几万条以内），纯 Java Stream 基本够用。</p>]]></description></item></channel></rss>