From 31ed7a96f39d658affc6cf97ab4ad5fbee0136a1 Mon Sep 17 00:00:00 2001 From: ThearchyHelios Date: Wed, 21 Oct 2026 15:55:20 +0000 Subject: [PATCH] =?UTF-8?q?=E8=AF=81=E6=8D=AE=E9=9D=A2=E6=9D=BF=E7=9A=84?= =?UTF-8?q?=E8=AE=A1=E7=AE=97=E4=B8=80=E6=AD=A5=E7=85=A7=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E7=AB=AF=E5=86=99=E5=A5=BD=E7=9A=84=E5=AD=97=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=EF=BC=9A=E5=85=B7=E5=90=8D=E7=9A=84=E6=95=B0=E6=8C=89=E7=BB=93?= =?UTF-8?q?=E8=AE=BA=E8=AE=B0=E7=9A=84=E6=A0=BC=E5=BC=8F=EF=BC=88=E5=8D=A0?= =?UTF-8?q?=E6=AF=94=2061.9%=EF=BC=8C=E4=B8=8D=E5=86=8D=E5=86=99=200.61855?= =?UTF-8?q?6701031=EF=BC=89=E3=80=81=E7=BB=93=E6=9E=9C=E8=A1=A8=E7=9A=84?= =?UTF-8?q?=E6=A0=BC=E5=90=8C=E6=A0=B7=EF=BC=88=E5=8F=98=E5=8C=96=E7=8E=87?= =?UTF-8?q?=20-14.6%=EF=BC=89=EF=BC=8C=E7=A9=BA=E6=A0=BC=E5=86=99=E3=80=8C?= =?UTF-8?q?=E2=80=94=E3=80=8D=E4=B8=8D=E5=86=99=20null=EF=BC=9B=E5=88=97?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E7=85=A7=E7=BB=93=E6=9E=9C=E8=A1=A8=E7=9A=84?= =?UTF-8?q?=20column=5Ftypes=E3=80=82=E6=AF=94=E7=8E=87=E6=8B=86=E8=A7=A3?= =?UTF-8?q?=E7=9A=84=E6=95=B4=E4=BD=93=E6=AF=94=E7=8E=87=E9=82=A3=E4=B8=80?= =?UTF-8?q?=E6=A0=BC=E5=92=8C=E5=AF=B9=E7=85=A7=E7=9A=84=E9=82=A3=E4=B8=80?= =?UTF-8?q?=E6=A0=BC=E4=B9=9F=E6=8C=89=E6=A0=BC=E5=BC=8F=E5=86=99=EF=BC=88?= =?UTF-8?q?3.5%=EF=BC=8C=E4=B8=8D=E6=98=AF=200.035=EF=BC=89=E3=80=82?= =?UTF-8?q?=E5=89=8D=E7=AB=AF=E7=85=A7=E5=8E=9F=E6=A0=B7=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=EF=BC=88=E5=8F=AA=E8=A1=A5=E4=B8=80=E5=8F=A5=E8=AF=B4=E6=98=8E?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/evidence.py | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/backend/app/api/evidence.py b/backend/app/api/evidence.py index 5deb102d..961591f7 100644 --- a/backend/app/api/evidence.py +++ b/backend/app/api/evidence.py @@ -1173,12 +1173,17 @@ async def _analysis_chain(seg: dict[str, Any], cite: dict[str, Any], entry: dict def _analysis_calc(alias: str, result: dict[str, Any], cited: dict[str, Any], hidden: set[str]) -> dict[str, Any]: """计算一步:结果表(前 table_top_k 段加上被引用的那一行附近,最多 MAX_ANALYSIS_ROWS 行,高亮被引用的格)、具名的数(原值、 - 渲染出来的字)、关键数的代入式。结果表里万一有取自遮罩列的列(输入查询步骤里遮着的列名),照查询步骤写「已遮罩」。""" + 渲染出来的字)、关键数的代入式。数和格都按结论记的格式写出来(占比、变化率是百分数,比率的变化是百分点),和正文引用 + 之后显示的字一样;结果表的空格写「—」不写 null,面板照原样显示。结果表里万一有取自遮罩列的列(输入查询步骤里遮着的 + 列名),照查询步骤写「已遮罩」。""" values = result.get("values") if isinstance(result.get("values"), dict) else {} + named = result.get("value_formats") if isinstance(result.get("value_formats"), dict) else {} shown_values = [] for key, raw in values.items(): try: - text = render_cell(raw) + # 具名的数按结论记的格式写(占比、变化率是百分数),和引用之后正文显示的字一样 + conv = named.get(key) if isinstance(named.get(key), str) and named.get(key) else None + text = render_cell(raw, conv) except RenderError: text = MISSING shown_values.append({"key": str(key), "raw": raw, "rendered": text, @@ -1201,8 +1206,8 @@ def _analysis_calc(alias: str, result: dict[str, Any], cited: dict[str, Any], hi step["table"] = { "columns": columns, "labels": dict(table.get("labels") or {}) if isinstance(table.get("labels"), dict) else {}, "total_rows": len(rows), "row_index": picked, - "rows": [[MASKED if i in masked else v for i, v in enumerate(rows[r])] if isinstance(rows[r], list) else rows[r] - for r in picked], + "rows": [[MASKED if i in masked else _shown_cell(v, table, columns[i]) for i, v in enumerate(rows[r])] + if isinstance(rows[r], list) else rows[r] for r in picked], "masked": [columns[i] for i in sorted(masked)], "highlight": {"cells": [[row, str(cited.get("column"))]] if isinstance(row, int) else []}, **({"window_truncated": True} if len(index) > MAX_ANALYSIS_ROWS else {}), @@ -1210,6 +1215,22 @@ def _analysis_calc(alias: str, result: dict[str, Any], cited: dict[str, Any], hi return step +def _shown_cell(raw: Any, table: Mapping[str, Any], column: str) -> str: + """结果表的一格按结论记的格式写出来(占比、变化率是百分数,比率的变化是百分点),空格写「—」不写 null:面板照原样显示, + 读者看到的和正文引用那一格时一样。列类型照结果表的 column_types(文本列照原样,不按值猜);渲染不出来(不是 0 却显示成 0) + 的照原样写。""" + if raw is None: + return MISSING + formats = table.get("formats") if isinstance(table.get("formats"), dict) else {} + types = table.get("column_types") if isinstance(table.get("column_types"), dict) else {} + conv = formats.get(column) if isinstance(formats.get(column), str) and formats.get(column) else None + kind = types.get(column) if isinstance(types.get(column), str) else None + try: + return render_cell(raw, conv, kind=kind) + except RenderError: + return str(raw) + + async def _analysis_inputs(alias: str, result: dict[str, Any], catalog: dict[str, Any], doc: dict[str, Any], sealed: _Sealed, masks: _Masks, *, queries: bool = True ) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: @@ -1247,8 +1268,12 @@ async def _analysis_inputs(alias: str, result: dict[str, Any], catalog: dict[str sealed=sealed.trusted and query is not None) if role in totals and totals[role] in values: with contextlib.suppress(RenderError): - step["closure"] = {"mode": mode, "total_key": totals[role], "total": render_cell(values[totals[role]]), - "headline": render_cell(item.get("value")), "closed": not unclosed} + # 整体比率那一格和对照的那一格按结论记的格式写(比率拆解里都是比率,一起写成百分数),和具名的数、正文一致 + named = result.get("value_formats") if isinstance(result.get("value_formats"), dict) else {} + key = totals[role] + conv = named.get(key) if isinstance(named.get(key), str) and named.get(key) else None + step["closure"] = {"mode": mode, "total_key": key, "total": render_cell(values[key], conv), + "headline": render_cell(item.get("value"), conv), "closed": not unclosed} steps.append(step) if artifact and query is not None: wanted = cells.setdefault(artifact, [])