Browse Source

fix#错误主题-期望与结果描述逻辑不清晰。

hefuzheng 1 tuần trước cách đây
mục cha
commit
0ba72017cc

BIN
code/post_clear_check/src/post_clear_check/__pycache__/dimension_body.cpython-312.pyc


BIN
code/post_clear_check/src/post_clear_check/__pycache__/feishu.cpython-312.pyc


BIN
code/post_clear_check/src/post_clear_check/__pycache__/trs_account.cpython-312.pyc


+ 33 - 6
code/post_clear_check/src/post_clear_check/dimension_body.py

@@ -822,13 +822,32 @@ def _brief(values: Sequence[str]) -> str:
     return shown
 
 
+def _calc_parts(view: _View) -> tuple[str, tuple[str, ...]]:
+    """拆开代入/正向期望与分号后的说明。说明不进「期望:」行。"""
+
+    parts = [part.strip() for part in view.calc_text.split(";") if part.strip()]
+    if not parts:
+        return "", ()
+    return parts[0], tuple(parts[1:])
+
+
 def _expect(view: _View) -> str:
     if view.calc_value is None and view.verdict == VERDICT_SUSPECT:
         return "期望值无法计算"
-    head = view.calc_text.split(";", 1)[0].strip()
+    head, notes = _calc_parts(view)
+    number = _number(view.calc_value)
+    if notes:
+        # 有说明段时首段是正向期望或代入式。
+        if head and head != view.formula_text:
+            return head
+        if number is not None:
+            return number
+        return view.formula_text
     if head and head != view.formula_text:
+        if view.calc_value is None and view.verdict == VERDICT_ERROR:
+            # 旧写法整段都是失败原因,期望改走公式,原因留给结论。
+            return view.formula_text
         return head
-    number = _number(view.calc_value)
     if number is not None:
         return number
     return view.formula_text
@@ -847,18 +866,26 @@ def _actual(view: _View) -> str:
 
 
 def _conclusion(view: _View) -> str:
+    head, notes = _calc_parts(view)
     expected = _number(view.calc_value)
     current = _number(view.check_value)
+    right = current if current is not None else "缺失"
     if view.verdict == VERDICT_ERROR:
-        left = expected if expected is not None else _expect(view)
-        right = current if current is not None else "缺失"
+        if expected is not None:
+            return f"结论:错误。期望 {expected},当前 {right}。不符合预期。"
+        # 存在性/硬门槛:说明(或旧版整段原因)进结论,不再套「期望 {原因}」。
+        reason_parts = [part.strip().rstrip("。") for part in notes if part.strip()]
+        if not reason_parts and head and head != view.formula_text and view.calc_value is None:
+            reason_parts = [head.strip().rstrip("。")]
+        if reason_parts:
+            reason = "。".join(reason_parts)
+            return f"结论:错误。{reason},当前 {right}。不符合预期。"
+        left = _expect(view)
         return f"结论:错误。期望 {left},当前 {right}。不符合预期。"
     if view.verdict == VERDICT_SUSPECT and view.calc_value is None:
-        right = current if current is not None else "缺失"
         return f"结论:可疑。期望值无法计算,当前 {right}。"
     if view.verdict == VERDICT_SUSPECT:
         left = expected if expected is not None else _expect(view)
-        right = current if current is not None else "缺失"
         return f"结论:可疑。期望 {left},当前 {right}。无法判定。"
     return "结论:正确"
 

+ 18 - 4
code/post_clear_check/src/post_clear_check/feishu.py

@@ -591,11 +591,16 @@ def _expect_label(line: ConclusionView, formula: str) -> str:
     return f"期望 {expected}"
 
 
-def _actual_text(line: ConclusionView, formula: str, subst: str) -> str:
-    """实据:代入式和当前值。代入式与公式重复时不写。"""
+def _actual_text(line: ConclusionView, formula: str, subst: str, notes: Sequence[str] = ()) -> str:
+    """实据:代入式和当前值。代入式与公式重复时不写。
+
+    有说明段且算不出期望值时,首段是正向门槛期望,不塞进实据;原因在结论说明里。
+    """
 
     current = _value_text(line.check_value)
     current_text = "当前缺失" if current is None else f"当前 {current}"
+    if line.calc_value is None and notes:
+        return current_text
     if subst and _show_substitution(formula, subst):
         return f"{subst}。{current_text}"
     if line.calc_value is None:
@@ -607,13 +612,22 @@ def _actual_text(line: ConclusionView, formula: str, subst: str) -> str:
 
 
 def _conclusion(line: ConclusionView, notes: Sequence[str]) -> str:
-    """结论句接上说明和原来的期望句。说明不再单独成行。"""
+    """结论句接上说明和原来的期望句。说明不再单独成行。
+
+    错误且无期望数值、已有说明时,说明已含原因,不再追加「期望与当前不一致」。
+    """
 
     bits = [VERDICT_LABEL[line.verdict]]
     for note in notes:
         cleaned = note.strip().rstrip("。")
         if cleaned:
             bits.append(cleaned)
+    if line.verdict == VERDICT_ERROR and notes and line.calc_value is None:
+        current = _value_text(line.check_value)
+        right = "当前缺失" if current is None else f"当前 {current}"
+        bits.append(right)
+        bits.append(_MISMATCH.rstrip("。"))
+        return "结论:" + "。".join(bits) + "。"
     tail = expectation(line).strip().rstrip("。")
     if tail:
         bits.append(tail)
@@ -630,7 +644,7 @@ def _detail_block(line: ConclusionView, chain_name: str) -> str:
     rows = [f"节点:{formula or '核对'}"]
     rows.extend(_scope_rows(chain_name, report, (owner,) if owner else ()))
     rows.append(f"期望:{_expect_label(line, formula)}")
-    rows.append(f"实据:{_actual_text(line, formula, subst)}")
+    rows.append(f"实据:{_actual_text(line, formula, subst, notes)}")
     if basis:
         rows.append(f"依据:{basis}")
     rows.append(_conclusion(line, notes))

+ 6 - 2
code/post_clear_check/src/post_clear_check/trs_account.py

@@ -412,11 +412,15 @@ def _interest_line(
         day = as_date(row.get("nature_date")) or ctx.business_date
         ccy = _text_ccy(row) or "未知币种"
         month_text = f"{day.year:04d}-{day.month:02d}"
+        # 期望写正向门槛;失败原因放分号后说明,避免飞书「期望」行抄成缺利率原文。
         emit(
             lines,
             formula=F_MONTHLY_RATE,
-            basis="有非零持仓成本时必须有有效月度融资利率,与 TRS016 区间导出一致",
-            calc_text=f"{month_text}缺少{ccy}有效月度融资利率,无法导出TRS016",
+            basis="",
+            calc_text=(
+                f"当月{ccy}有效月度融资利率已维护且年化大于 0"
+                f";{month_text}缺少{ccy}有效月度融资利率,无法导出TRS016"
+            ),
             calc_value=None,
             report_name=TRS016_NAME,
             column="当月融资利率",

BIN
code/post_clear_check/tests/__pycache__/test_feishu.cpython-312-pytest-9.1.1.pyc


BIN
code/post_clear_check/tests/__pycache__/test_trs_reports.cpython-312-pytest-9.1.1.pyc


+ 37 - 0
code/post_clear_check/tests/test_feishu.py

@@ -27,6 +27,7 @@ from post_clear_check.chain import (
     DIM_ISSUE,
     DIM_ORDER,
     DIM_SALES,
+    DIM_TRS,
 )
 from post_clear_check.config import Settings
 from post_clear_check.feishu import (
@@ -1516,6 +1517,42 @@ def test_dimension_report_body_keeps_correct_rows_on_error_theme() -> None:
     assert "筛选:主订单 ORD001 · 资金账户 FA00000001" in body
 
 
+def test_dimension_presence_error_keeps_expect_positive_and_reason_in_conclusion() -> None:
+    """存在性错误:期望写正向门槛,缺利率原因进结论,不再套「期望 {原因}」。"""
+
+    body = render_body(
+        business_date=DAY,
+        trade_date=DAY,
+        batch_no="TB",
+        chain_name=DIM_TRS,
+        record_no="21",
+        theme=VERDICT_ERROR,
+        lines=(
+            _dim(
+                "有效月度融资利率 = 当月结算币种在 cfg_holdings_interest_rate 维护且年化大于 0",
+                VERDICT_ERROR,
+                calc=(
+                    "当月HKD有效月度融资利率已维护且年化大于 0"
+                    ";2026-11缺少HKD有效月度融资利率,无法导出TRS016"
+                ),
+                check="TRS016 持仓成本金额利息计算表.当月融资利率 缺失(TRS用户账号 chenminhkd)",
+                calc_value=None,
+                check_value=None,
+                report_code="TRS016",
+                basis="产品原型 16 Holdings Amount Interest Report.md 「利率获取与硬性校验」",
+            ),
+        ),
+    )
+    assert "期望:当月HKD有效月度融资利率已维护且年化大于 0" in body
+    assert "期望:2026-11缺少HKD有效月度融资利率" not in body
+    assert "实据:TRS016 持仓成本金额利息计算表.当月融资利率 缺失(TRS用户账号 chenminhkd)" in body
+    assert (
+        "结论:错误。2026-11缺少HKD有效月度融资利率,无法导出TRS016,当前 缺失。不符合预期。"
+        in body
+    )
+    assert "期望 2026-11缺少" not in body
+
+
 def test_report_bases_group_same_file_and_expect_uses_commas() -> None:
     """同一份原型只写一次文件名。期望的多个字段用逗号连成一句。"""
 

+ 3 - 1
code/post_clear_check/tests/test_trs_reports.py

@@ -406,7 +406,9 @@ def test_null_finance_rate_with_cost_is_error() -> None:
     assert F_INTEREST not in formulas
     rate = _exact(lines, F_MONTHLY_RATE)
     assert rate.verdict == "error"
-    assert "2026-10缺少HKD有效月度融资利率,无法导出TRS016" in rate.calc_text
+    head, _, note = rate.calc_text.partition(";")
+    assert head == "当月HKD有效月度融资利率已维护且年化大于 0"
+    assert "2026-10缺少HKD有效月度融资利率,无法导出TRS016" in note
     assert all("TRS015" not in text for line in lines for text in (line.formula_text, line.check_text))
 
 

+ 1 - 1
doc/业务链路核对设计.md

@@ -518,7 +518,7 @@ TRS 的字段公式不在 Excel 里,在产品原型 `TRS/4 业务引起数据
 
 分报表只收带这些编号的结论行。交收推演、T 日交收、增额主键、增额累计、期初钱包、T 日钱包、候选范围、原始流水、票据与子票据比对是步骤节点,写在「步骤核对」。标题用公式等号左边的步骤名。不拿第一条的票据号、子订单号或币种当报表名。
 
-同一报表、同一依据的正确列收成一块。错误和可疑逐条写节点、期望、实据、依据、结论。没有报表编号的,清单里写步骤和对象。主题是错误时,正确列仍留在分报表,不写「正确与可疑留在核对明细」。分报表/步骤总结论为错误或可疑时,整段同样套第 7.2 节的红加粗或黄标签,不只清单逐条。
+同一报表、同一依据的正确列收成一块。错误和可疑逐条写节点、期望、实据、依据、结论。没有报表编号的,清单里写步骤和对象。存在性/硬门槛错误(算不出期望数值)时:`calc_text` 首段写正向期望,分号后写失败原因;「期望:」只用首段或公式,「结论:」写原因与当前实据,禁止再套「期望 {失败原因},当前缺失」。主题是错误时,正确列仍留在分报表,不写「正确与可疑留在核对明细」。分报表/步骤总结论为错误或可疑时,整段同样套第 7.2 节的红加粗或黄标签,不只清单逐条。
 
 ```text
 销售报表 错误

+ 1 - 1
doc/使用说明.md

@@ -174,7 +174,7 @@ python -m post_clear_check
 
 OTC009、OTC012 是实时导出,没有落库结果,`report_topup_payment`、`report_extension_fee` 两张表没有写入方。增额、展期不比对这两张表,取数也不读它们。增额核的是本批 `input` 里归属本营业日的 `top_up` 主键,以及这些发生额记入 `work/3` 后的增额;展期核的是已通过且审批归属本营业日的任务主键,以及 `work/3` 的到期日。卖出释放和展期费流水不进这两条链路。
 
-飞书按订单链路、发行报表、销售报表、TRS 报表的顺序发。标题形如 `订单链路 可疑`、`发行报表 正确`。失败重发在末尾加「重发」。标题不写系统和清算步骤。颜色按这张卡的总结论:有错误为红,否则有可疑为橙,否则为蓝。
+飞书按订单链路、发行报表、销售报表、TRS 报表的顺序发。标题形如 `订单链路 可疑`、`发行报表 正确`。失败重发在末尾加「重发」。标题不写系统和清算步骤。颜色按这张卡的总结论:有错误为红,否则有可疑为橙,否则为蓝。错误清单里:有期望数值的仍写「期望 x,当前 y」;算不出期望数值的存在性错误,期望写正向门槛或公式,失败原因放在结论里,不写「期望 {缺少…}」。
 
 订单链路正文是摘要、每单链路、盘中公式、清算对照、关联报表、错误清单、可疑清单、无法展开。摘要里的笔数按主单计。每单链路写两段:执行事实是否与实时源表一致;需要 TRS 执行根的对冲,成交股数是否对上,并说明自动单按 ext_ref、补录按委托号或母单号。不列表,也不把未核对的票据和已撤单写进去。同一字段的正确行收成一块,结论写「正确(n/n)」。清算对照除订单自己的已发布执行列、工作快照外,再写交收台账、销售资金流水、TRS 发生额与汇率、清算留痕;没有对应结论行就写「本轮未核」,不编造金额。关联报表按发行/销售的 OTC003、OTC004、OTC007/OTC008 展开期望、实据和依据。三端门禁在摘要里各一行。