diff --git a/backend/app/bundle/verifier.js b/backend/app/bundle/verifier.js index 0ba72100..90309994 100644 --- a/backend/app/bundle/verifier.js +++ b/backend/app/bundle/verifier.js @@ -269,6 +269,8 @@ scopeTitle: '分析范围', scopeNote: '这一段由系统按这次运行的分析结论写成,写作者改不了;点开编号看判定语、判定标准、输入和复算。', scopeMissing: '没拆', scopeAll: '候选维度都拆过了', scopeUnknown: '候选维度核对不了', scopeTests: '检验', scopeTestsText: (n) => `这次做了 ${n} 次差异检验`, scopeParent: (alias) => `下钻自 ${alias}`, scopeDims: (dims) => `按${dims}`, + // 「检验」后面接的校正说明(ScopeBlock.corrected),同导出页(report_export/build.py 的 SCOPE_CORRECTED) + scopeCorrected: '(一次里比了几对的已按次数校正)', scopeUncorrected: ',几次检验之间没有按次数校正', dSql: 'SQL', dRow: (row) => `快照第 ${row + 1} 行`, nullValue: '空值(NULL)', appendedNote: '这些事件追加在封存之后,不在封存范围内,不参与清单核对,也不能当作证据。', @@ -2048,7 +2050,7 @@ return parts } - /** 报告文档里系统写的「分析范围」:现象、拆过(下钻按链缩进)、排除、算不了、没拆、检验了几次。编号点得开那条结论 */ + /** 报告文档里系统写的「分析范围」:现象、拆过(下钻按链缩进)、排除、算不了、没拆、检验了几次、校正过没有。编号点得开那条结论 */ function renderScope(doc, report) { const scope = isObj(doc.analysis_scope) ? doc.analysis_scope : null if (!scope) return null @@ -2073,7 +2075,10 @@ } else if (Object.prototype.hasOwnProperty.call(scope, 'not_decomposed')) { rows.push(h('dt', {}, T.scopeMissing), h('dd', {}, String(scope.note || T.scopeUnknown))) } - if (Number.isInteger(scope.tests) && scope.tests > 0) rows.push(h('dt', {}, T.scopeTests), h('dd', {}, T.scopeTestsText(scope.tests))) + if (Number.isInteger(scope.tests) && scope.tests > 0) { + const corrected = scope.corrected === true ? T.scopeCorrected : scope.corrected === false ? T.scopeUncorrected : '' + rows.push(h('dt', {}, T.scopeTests), h('dd', {}, T.scopeTestsText(scope.tests) + corrected)) + } if (!rows.length) return null return h('section', { class: 'scope', 'data-scope': '' }, h('h4', {}, T.scopeTitle), h('p', { class: 'note' }, T.scopeNote), h('dl', { class: 'kv' }, rows)) diff --git a/backend/app/report_export/build.py b/backend/app/report_export/build.py index 7cbbb079..099748cf 100644 --- a/backend/app/report_export/build.py +++ b/backend/app/report_export/build.py @@ -298,6 +298,9 @@ def render_body(doc: Mapping[str, Any]) -> str: #: 「分析范围」各组的叫法(00-总方案 §3.6),按这个顺序写 SCOPE_TITLES = (("phenomena", "现象"), ("decomposed", "拆过"), ("excluded", "排除"), ("refused", "算不了")) SCOPE_NOTE = "这一段由系统按这次运行的分析结论写成,写作者改不了;点开编号看判定标准、计算、输入和复算。" +#: 「检验」那一项后面接的校正说明(analysis.contract.ScopeBlock.corrected):只做了一次的,一次里比了几对的已按次数校正; +#: 做了不止一次的,几次之间没有校正。和可验证报告的核对页(bundle/verifier.js 的 scopeCorrected)同一种说法 +SCOPE_CORRECTED = {True: "(一次里比了几对的已按次数校正)", False: ",几次检验之间没有按次数校正"} #: 分析范围里一个编号的片段键(api/evidence.SCOPE_REF 同一个写法):点开时按它找出处链 _SCOPE_REF = "scope:{alias}" @@ -319,7 +322,7 @@ def _scope_item(item: Mapping[str, Any], *, depth: int = 0) -> str: def render_scope(doc: Mapping[str, Any]) -> str: """报告文档里系统写的「分析范围」(analysis.contract.ScopeBlock):现象、拆过(下钻按链缩进)、排除、算不了、没拆、 - 检验了几次。文档没有这个字段(老文档、这次没有分析结论、报告节点关了这一段)时是空串,页面一个字都不多。""" + 检验了几次、校正过没有。文档没有这个字段(老文档、这次没有分析结论、报告节点关了这一段)时是空串,页面一个字都不多。""" scope = doc.get("analysis_scope") if not isinstance(scope, Mapping): return "" @@ -343,7 +346,9 @@ def render_scope(doc: Mapping[str, Any]) -> str: rows.append(f"
没拆
{_esc(scope.get('note') or '候选维度核对不了')}
") tests = scope.get("tests") if isinstance(tests, int) and not isinstance(tests, bool) and tests > 0: - rows.append(f"
检验
这次做了 {tests} 次差异检验
") + corrected = scope.get("corrected") + note = SCOPE_CORRECTED.get(corrected, "") if isinstance(corrected, bool) else "" + rows.append(f"
检验
这次做了 {tests} 次差异检验{note}
") if not rows: return "" return (f'

分析范围

' diff --git a/backend/tests/test_analysis_evidence_api.py b/backend/tests/test_analysis_evidence_api.py index 3c454dc9..93b3b867 100644 --- a/backend/tests/test_analysis_evidence_api.py +++ b/backend/tests/test_analysis_evidence_api.py @@ -407,7 +407,7 @@ SCOPE = {"analysis_scope": { "decomposed": [{"alias": "A1", "op": "decompose", "mode": "additive", "dims": ["地区"], "verdict": "driver", "phrase": "主要来自华东"}], "excluded": [], "refused": [{"alias": "A1", "op": "baseline", "reason": "历史只有 5 期,至少要 8 期"}], - "not_decomposed": ["渠道"], "tests": 2}} + "not_decomposed": ["渠道"], "tests": 2, "corrected": False}} def data_block(html: str) -> dict[str, Any]: @@ -427,7 +427,8 @@ async def test_the_html_export_carries_the_analysis_chain_and_the_scope(client): assert data["notes"][phrase].startswith("判定语由系统按平台的方法算出判定") # 分析范围:正文后面渲染,编号点得开、和点开判定语是同一条链 assert '
现象" in html and "
拆过
" in html - assert "
算不了
" in html and "
渠道
" in html and "这次做了 2 次差异检验" in html + assert "
算不了
" in html and "
渠道
" in html + assert "这次做了 2 次差异检验,几次检验之间没有按次数校正" in html assert 'data-seg="scope:A1"' in html and "(按地区)" in html assert data["refs"]["scope:A1"] == data["refs"][phrase] assert f'data-seg="{phrase}"' in html and 'data-kind="analysis"' in html diff --git a/backend/tests/test_analysis_pages.py b/backend/tests/test_analysis_pages.py index 94e720de..7d058b76 100644 --- a/backend/tests/test_analysis_pages.py +++ b/backend/tests/test_analysis_pages.py @@ -161,7 +161,7 @@ async def test_the_bundle_page_shows_the_analysis_detail_and_the_scope(): assert out["verified"] == "" [scope] = out["scope"] assert scope.startswith("分析范围") and "现象A1:销售额下降" in scope and "拆过A1(按地区):主要来自华东" in scope - assert "没拆渠道" in scope and "这次做了 2 次差异检验" in scope + assert "没拆渠道" in scope and "这次做了 2 次差异检验,几次检验之间没有按次数校正" in scope phrase = next(text for text in out["opened"].values() if "出处分析结论 A1 的判定语" in text) assert "核对结果✓ 通过" in phrase and "分析结论里的值「销售额下降 194(-8.7%)" in phrase assert "判定语销售额下降 194(-8.7%)" in phrase and "方法与版本decompose.additive@1" in phrase