tradingSystem/scripts/test_page_enum_guard.py

174 lines
8.9 KiB
Python
Raw Normal View History

PMS 文案第三批:页面枚举直出,并加一道静态守卫 页面是三千行的单文件,没有任何测试,改错了不会有东西报警。所以这一批先写守卫, 再靠守卫把问题扫出来——它比人工照清单改可靠,实际也确实多抓出了四处清单里没有的。 改掉的直出点: - 操作日志印的是函数名式的操作码(issue_command:EXIT_STOCK 这类)。冒号前是做了 什么、冒号后是对象,后半段复用页面已有的翻译组,英文码退到悬浮提示。 - 持仓表的冻结原因没走翻译,而同一份文件里另一处走了,是执行漏网。 - 提议卡两处印判决词。判决词本身不告诉人能不能买,句子主干换成原因。这里顺带 接上了 missing 字段——它一路从后端带到前端却从没被渲染过,"关注"态终于说得出 到底缺什么。 - 规则闸未通过项与裁决理由带着大写判据码。前端加一份剥前缀的实现,与后端 app/core/copy.py 同口径。 - 账本那一列原先叫「硬数字」,把整个字典打出来。改成只念人要读的几项,完整原文 退到悬浮提示。同一行的动作与研判两列也是英文原值,一起走翻译。 - 候选池落选明细、命令参数、安全垫状态、自主档位,都是直接印原值。 - 翻译兜底原先是原样返回,会让一个英文词单独立在页面上当句子。改成退到括号里。 守卫检查四件事:枚举字段直出、判据码没剥前缀、把整个对象打给交易员看、翻译兜底 裸奔。运维面板里那些有意显示原始数据的地方逐条登记放行,每条写明在哪个面板、 为什么放行。 例数 654 到 655,测试入口的例数表与哨兵位置清单同改——新增会送到页面的枚举字段 要往守卫的名单里登记一行,不登记等于不检查。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 14:43:22 +08:00
# -*- coding: utf-8 -*-
"""页面文案守卫:枚举字段不许直接印到页面上(静态扫描,不连库、不起浏览器)。
## 为什么要有它
app/web/static/index.html 是三千行的单文件页面**没有任何单测**改错了不会有
东西报警而它上面最容易出的错是把后端送来的枚举值直接塞进模板
页面上就出现 COMMAND_HALTREJECTissue_command:EXIT_STOCK 这种东西
页面里其实早就有翻译层T 表加 tx 函数问题是它被系统性地绕开了
2026-09-04 实测同一份文件里 :899 走了 tx('frozen', ):1533 没走印的是
同一个字段这不是没想到是执行漏网所以需要一道机器检查
## 它检查什么
**枚举字段直出**模板里形如 {{ x.frozen_reason }} 的插值字段名落在
ENUM_FIELDS 而外面没有包 tx( 或别的翻译函数就报
**判据码直出**failed_checksreason 这类会带 `CODE: ` 前缀的字段
必须过 plainCheck 才能显示
**整个对象打给人看**JSON.stringify 出现在给交易员看的区域就报
运维面板那几处是有意的登记在 JSON_OK 里放行
**翻译表兜底不许裸奔**tx 找不到码时必须把码退到括号里
不能让一个英文词单独立在页面上当句子
## 怎么维护
加了新的枚举字段就往 ENUM_FIELDS 里加一行这份名单是这道检查的全部依据
不加就等于不检查所以它和 T 表要一起改
跑法python3 scripts/test_page_enum_guard.py
"""
import os
import re
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
PAGE = os.path.join(os.path.dirname(HERE), "app", "web", "static", "index.html")
# 后端会送到页面的枚举字段。取值全集在 T 表里,两者要一起改。
# 只列**枚举**字段后端已经写成中文句子的自由文本basis、why、note 这类)不在此列,
# 那些本来就该原样显示。
# 字段名到翻译组的对应。**同名不等于同组**:全局参数的 autonomy 是
# full / propose_only / offparam_store.py:142而个股交易方案的 autonomy 是
# auto / confirmstrategy_service.py:23两套取值完全不同。所以这份对应表里
# 按"字段在哪个对象上"区分,不能只按字段名。
FIELD_GROUP = {
"frozen_reason": "frozen", "cushion_state": "cushion", "lot_kind": "lot",
"arbiter": "arbiter", "stype": "stype", "ref_source": "refSource",
"autonomy": "autonomy", # 全局参数那一套
"strategy_autonomy": "stratAutonomy", # 个股交易方案那一套,取值不同
"judge_verdict": "judgeVerdict", "origin_type": "originType",
"cancel_state": "cancelState", "conn_state": "connState",
"proc_state": "inboxState",
}
PMS 文案第三批:页面枚举直出,并加一道静态守卫 页面是三千行的单文件,没有任何测试,改错了不会有东西报警。所以这一批先写守卫, 再靠守卫把问题扫出来——它比人工照清单改可靠,实际也确实多抓出了四处清单里没有的。 改掉的直出点: - 操作日志印的是函数名式的操作码(issue_command:EXIT_STOCK 这类)。冒号前是做了 什么、冒号后是对象,后半段复用页面已有的翻译组,英文码退到悬浮提示。 - 持仓表的冻结原因没走翻译,而同一份文件里另一处走了,是执行漏网。 - 提议卡两处印判决词。判决词本身不告诉人能不能买,句子主干换成原因。这里顺带 接上了 missing 字段——它一路从后端带到前端却从没被渲染过,"关注"态终于说得出 到底缺什么。 - 规则闸未通过项与裁决理由带着大写判据码。前端加一份剥前缀的实现,与后端 app/core/copy.py 同口径。 - 账本那一列原先叫「硬数字」,把整个字典打出来。改成只念人要读的几项,完整原文 退到悬浮提示。同一行的动作与研判两列也是英文原值,一起走翻译。 - 候选池落选明细、命令参数、安全垫状态、自主档位,都是直接印原值。 - 翻译兜底原先是原样返回,会让一个英文词单独立在页面上当句子。改成退到括号里。 守卫检查四件事:枚举字段直出、判据码没剥前缀、把整个对象打给交易员看、翻译兜底 裸奔。运维面板里那些有意显示原始数据的地方逐条登记放行,每条写明在哪个面板、 为什么放行。 例数 654 到 655,测试入口的例数表与哨兵位置清单同改——新增会送到页面的枚举字段 要往守卫的名单里登记一行,不登记等于不检查。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 14:43:22 +08:00
ENUM_FIELDS = [
"frozen_reason", # 冻结来路COMMAND_HALT / BRAKE / MANUAL / NONE
"cushion_state", # 安全垫SOLID / THIN / NONE
"lot_kind", # 批次BASE / FILL / ADD / DCA / T0 / RECON
"arbiter", # 谁裁的rule / judge / user / system
PMS 文案第三批:页面枚举直出,并加一道静态守卫 页面是三千行的单文件,没有任何测试,改错了不会有东西报警。所以这一批先写守卫, 再靠守卫把问题扫出来——它比人工照清单改可靠,实际也确实多抓出了四处清单里没有的。 改掉的直出点: - 操作日志印的是函数名式的操作码(issue_command:EXIT_STOCK 这类)。冒号前是做了 什么、冒号后是对象,后半段复用页面已有的翻译组,英文码退到悬浮提示。 - 持仓表的冻结原因没走翻译,而同一份文件里另一处走了,是执行漏网。 - 提议卡两处印判决词。判决词本身不告诉人能不能买,句子主干换成原因。这里顺带 接上了 missing 字段——它一路从后端带到前端却从没被渲染过,"关注"态终于说得出 到底缺什么。 - 规则闸未通过项与裁决理由带着大写判据码。前端加一份剥前缀的实现,与后端 app/core/copy.py 同口径。 - 账本那一列原先叫「硬数字」,把整个字典打出来。改成只念人要读的几项,完整原文 退到悬浮提示。同一行的动作与研判两列也是英文原值,一起走翻译。 - 候选池落选明细、命令参数、安全垫状态、自主档位,都是直接印原值。 - 翻译兜底原先是原样返回,会让一个英文词单独立在页面上当句子。改成退到括号里。 守卫检查四件事:枚举字段直出、判据码没剥前缀、把整个对象打给交易员看、翻译兜底 裸奔。运维面板里那些有意显示原始数据的地方逐条登记放行,每条写明在哪个面板、 为什么放行。 例数 654 到 655,测试入口的例数表与哨兵位置清单同改——新增会送到页面的枚举字段 要往守卫的名单里登记一行,不登记等于不检查。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 14:43:22 +08:00
"stype", # 交易方案类型T0 / GRID / TRAIL
"autonomy", # 全局自主档full / propose_only / off
PMS 文案第三批:页面枚举直出,并加一道静态守卫 页面是三千行的单文件,没有任何测试,改错了不会有东西报警。所以这一批先写守卫, 再靠守卫把问题扫出来——它比人工照清单改可靠,实际也确实多抓出了四处清单里没有的。 改掉的直出点: - 操作日志印的是函数名式的操作码(issue_command:EXIT_STOCK 这类)。冒号前是做了 什么、冒号后是对象,后半段复用页面已有的翻译组,英文码退到悬浮提示。 - 持仓表的冻结原因没走翻译,而同一份文件里另一处走了,是执行漏网。 - 提议卡两处印判决词。判决词本身不告诉人能不能买,句子主干换成原因。这里顺带 接上了 missing 字段——它一路从后端带到前端却从没被渲染过,"关注"态终于说得出 到底缺什么。 - 规则闸未通过项与裁决理由带着大写判据码。前端加一份剥前缀的实现,与后端 app/core/copy.py 同口径。 - 账本那一列原先叫「硬数字」,把整个字典打出来。改成只念人要读的几项,完整原文 退到悬浮提示。同一行的动作与研判两列也是英文原值,一起走翻译。 - 候选池落选明细、命令参数、安全垫状态、自主档位,都是直接印原值。 - 翻译兜底原先是原样返回,会让一个英文词单独立在页面上当句子。改成退到括号里。 守卫检查四件事:枚举字段直出、判据码没剥前缀、把整个对象打给交易员看、翻译兜底 裸奔。运维面板里那些有意显示原始数据的地方逐条登记放行,每条写明在哪个面板、 为什么放行。 例数 654 到 655,测试入口的例数表与哨兵位置清单同改——新增会送到页面的枚举字段 要往守卫的名单里登记一行,不登记等于不检查。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 14:43:22 +08:00
"ref_source", # 参考位来源bionic / self_calc / user / none
"judge_verdict", # 研判闸结论PASS / REJECT / UNAVAILABLE / STRATEGY / STRATEGY_RISK
"origin_type", # 提议来路plan / proposal / system / strategy
"cancel_state", # 撤单进度NONE / REQUESTED / SENT
"conn_state", # 通道状态INIT / CONNECTING / ONLINE / OFFLINE / STOPPED
PMS 文案第三批:页面枚举直出,并加一道静态守卫 页面是三千行的单文件,没有任何测试,改错了不会有东西报警。所以这一批先写守卫, 再靠守卫把问题扫出来——它比人工照清单改可靠,实际也确实多抓出了四处清单里没有的。 改掉的直出点: - 操作日志印的是函数名式的操作码(issue_command:EXIT_STOCK 这类)。冒号前是做了 什么、冒号后是对象,后半段复用页面已有的翻译组,英文码退到悬浮提示。 - 持仓表的冻结原因没走翻译,而同一份文件里另一处走了,是执行漏网。 - 提议卡两处印判决词。判决词本身不告诉人能不能买,句子主干换成原因。这里顺带 接上了 missing 字段——它一路从后端带到前端却从没被渲染过,"关注"态终于说得出 到底缺什么。 - 规则闸未通过项与裁决理由带着大写判据码。前端加一份剥前缀的实现,与后端 app/core/copy.py 同口径。 - 账本那一列原先叫「硬数字」,把整个字典打出来。改成只念人要读的几项,完整原文 退到悬浮提示。同一行的动作与研判两列也是英文原值,一起走翻译。 - 候选池落选明细、命令参数、安全垫状态、自主档位,都是直接印原值。 - 翻译兜底原先是原样返回,会让一个英文词单独立在页面上当句子。改成退到括号里。 守卫检查四件事:枚举字段直出、判据码没剥前缀、把整个对象打给交易员看、翻译兜底 裸奔。运维面板里那些有意显示原始数据的地方逐条登记放行,每条写明在哪个面板、 为什么放行。 例数 654 到 655,测试入口的例数表与哨兵位置清单同改——新增会送到页面的枚举字段 要往守卫的名单里登记一行,不登记等于不检查。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 14:43:22 +08:00
]
# 这三个字段的取值是**开放集**,不许声称全集,也不许在兜底里吞掉原值:
# reject_code 由交易对端下发,协议文档明写可以补充
# level 上游信号原样透传,本仓库没有产出方
# accum_state 决策系统给的中文原文,本来就不需要翻译
# 它们不进上面的名单,但在这里登记一笔,免得以后有人当成漏网又去补翻译。
OPEN_SET_FIELDS = ["reject_code", "level", "accum_state", "msg_type"]
PMS 文案第三批:页面枚举直出,并加一道静态守卫 页面是三千行的单文件,没有任何测试,改错了不会有东西报警。所以这一批先写守卫, 再靠守卫把问题扫出来——它比人工照清单改可靠,实际也确实多抓出了四处清单里没有的。 改掉的直出点: - 操作日志印的是函数名式的操作码(issue_command:EXIT_STOCK 这类)。冒号前是做了 什么、冒号后是对象,后半段复用页面已有的翻译组,英文码退到悬浮提示。 - 持仓表的冻结原因没走翻译,而同一份文件里另一处走了,是执行漏网。 - 提议卡两处印判决词。判决词本身不告诉人能不能买,句子主干换成原因。这里顺带 接上了 missing 字段——它一路从后端带到前端却从没被渲染过,"关注"态终于说得出 到底缺什么。 - 规则闸未通过项与裁决理由带着大写判据码。前端加一份剥前缀的实现,与后端 app/core/copy.py 同口径。 - 账本那一列原先叫「硬数字」,把整个字典打出来。改成只念人要读的几项,完整原文 退到悬浮提示。同一行的动作与研判两列也是英文原值,一起走翻译。 - 候选池落选明细、命令参数、安全垫状态、自主档位,都是直接印原值。 - 翻译兜底原先是原样返回,会让一个英文词单独立在页面上当句子。改成退到括号里。 守卫检查四件事:枚举字段直出、判据码没剥前缀、把整个对象打给交易员看、翻译兜底 裸奔。运维面板里那些有意显示原始数据的地方逐条登记放行,每条写明在哪个面板、 为什么放行。 例数 654 到 655,测试入口的例数表与哨兵位置清单同改——新增会送到页面的枚举字段 要往守卫的名单里登记一行,不登记等于不检查。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 14:43:22 +08:00
# 这些字段带 `CODE: ` 前缀,显示前必须剥掉。
CODED_FIELDS = ["failed_checks"]
# 把整个对象打给人看,只有落在**交易员会看的区域**才算问题。两类不算:
# 一是存本地状态sessionStorage / localStorage那不是给人看的
# 二是运维面板里那几个"原样看接口返回"的框,读者是运维不是交易员,
# 它们本来就该显示原始数据。
# 判定靠变量名:写进 opsResult / planResult 这类运维面板的框,或者进浏览器存储,
# 都放行。别的地方出现 JSON.stringify 就报。
JSON_SKIP_MARKS = (
"sessionStorage", "localStorage", # 存本地状态,不是给人看的
"opsResult", "planResult", # 运维面板:有意显示接口原文
"console.", # 控制台
# 下面几处都在运维面板里,读者是运维不是交易员,本来就该看原始数据。
# 每加一条都要写清它在哪个面板、为什么放行。
"planStatus.theme_sync", # 上游计划诊断行:主题映射,排查用
"planStatus.query", # 上游计划诊断行:本次请求参数,排查用
"chgLog", # 运维面板「快照台账」:整行原文
"report.report", # 运维面板「快照台账」:日报原文
"wsc.queue", # 运维面板「ws 通道」:出口队列计数
"s.row.payload", # 运维面板「ws 通道」:报文原文
"s.row.hard_numbers", # 只在悬浮提示里作原文备查,正文已是中文
"s.row)", # 运维面板「快照台账」:整行原文
)
# 允许包住枚举字段的翻译函数。
WRAPPERS = ("tx(", "opCn(", "plainCheck(", "propVerdictLine(")
def fail(msgs):
for m in msgs:
print(" FAIL", m)
def main():
src = open(PAGE, encoding="utf-8").read()
lines = src.split("\n")
bad = []
# 一、枚举字段直出
for i, line in enumerate(lines, 1):
for f in ENUM_FIELDS:
# 只看插值与属性绑定里出现的字段引用
for m in re.finditer(r"[\w.\[\]]*\.%s\b" % re.escape(f), line):
seg = line[max(0, m.start() - 60):m.end() + 20]
if any(w in seg for w in WRAPPERS):
continue
# v-if 这类判断用途不算显示
if re.search(r'v-(if|else-if|show)="[^"]*\.%s\b' % re.escape(f), line):
continue
# JS 里做比较也不算显示
if re.search(r"\.%s\s*(===|!==|==|!=|&&|\|\||\?)" % re.escape(f), seg):
continue
if "{{" not in line and ":title=" not in line and ":content=" not in line:
continue
bad.append(f"{i} 行:枚举字段 {f} 直接印到页面上,没走翻译。\n"
f" {line.strip()[:120]}")
# 二、判据码直出
for i, line in enumerate(lines, 1):
for f in CODED_FIELDS:
if f"{f}" in line and "{{" in line and "plainCheck" not in line:
bad.append(f"{i} 行:{f} 带大写判据码前缀,显示前必须过 plainCheck。\n"
f" {line.strip()[:120]}")
# 三、整个对象打给人看
for i, line in enumerate(lines, 1):
if "JSON.stringify" in line:
if not any(k in line for k in JSON_SKIP_MARKS):
bad.append(f"{i} 行:把整个对象打给交易员看。要么改成中文说明,"
f"要么说明它落在运维面板并登记进 JSON_SKIP_MARKS。\n"
f" {line.strip()[:120]}")
# 四、兜底不许裸奔
m = re.search(r"const tx = \(g, c\) => \{(.*?)\n \};", src, re.S)
if not m:
bad.append("找不到 tx 函数,这道检查的前提没了")
elif "return String(c);" in m.group(1):
bad.append("tx 的兜底是原样返回,会让一个英文词单独立在页面上当句子。"
"改成把码退到括号里。")
# 五、翻译表与本文件的字段名单要对得上
for f in ENUM_FIELDS:
group = FIELD_GROUP.get(f)
PMS 文案第三批:页面枚举直出,并加一道静态守卫 页面是三千行的单文件,没有任何测试,改错了不会有东西报警。所以这一批先写守卫, 再靠守卫把问题扫出来——它比人工照清单改可靠,实际也确实多抓出了四处清单里没有的。 改掉的直出点: - 操作日志印的是函数名式的操作码(issue_command:EXIT_STOCK 这类)。冒号前是做了 什么、冒号后是对象,后半段复用页面已有的翻译组,英文码退到悬浮提示。 - 持仓表的冻结原因没走翻译,而同一份文件里另一处走了,是执行漏网。 - 提议卡两处印判决词。判决词本身不告诉人能不能买,句子主干换成原因。这里顺带 接上了 missing 字段——它一路从后端带到前端却从没被渲染过,"关注"态终于说得出 到底缺什么。 - 规则闸未通过项与裁决理由带着大写判据码。前端加一份剥前缀的实现,与后端 app/core/copy.py 同口径。 - 账本那一列原先叫「硬数字」,把整个字典打出来。改成只念人要读的几项,完整原文 退到悬浮提示。同一行的动作与研判两列也是英文原值,一起走翻译。 - 候选池落选明细、命令参数、安全垫状态、自主档位,都是直接印原值。 - 翻译兜底原先是原样返回,会让一个英文词单独立在页面上当句子。改成退到括号里。 守卫检查四件事:枚举字段直出、判据码没剥前缀、把整个对象打给交易员看、翻译兜底 裸奔。运维面板里那些有意显示原始数据的地方逐条登记放行,每条写明在哪个面板、 为什么放行。 例数 654 到 655,测试入口的例数表与哨兵位置清单同改——新增会送到页面的枚举字段 要往守卫的名单里登记一行,不登记等于不检查。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 14:43:22 +08:00
if group and f" {group}: {{" not in src and f" {group}:{{" not in src:
bad.append(f"字段 {f} 在名单里,但 T 表里没有对应的 {group}")
if bad:
print(f"页面文案守卫:发现 {len(bad)}")
fail(bad)
sys.exit(1)
print(f"ALL OK — 页面文案守卫:{len(ENUM_FIELDS)} 个枚举字段、"
f"{len(CODED_FIELDS)} 个带判据码的字段、整对象直出、翻译兜底 全部通过")
if __name__ == "__main__":
main()