<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>ECharts - tag - 沐木</title><link>https://oldletter.cn/tags/echarts/</link><description>ECharts - tag - 沐木</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/tags/echarts/" rel="self" type="application/rss+xml"/><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>