补充新增模型打标验证执行计划

This commit is contained in:
wkc
2026-03-20 15:13:19 +08:00
parent b44b133a21
commit 7cdf9212b6
2 changed files with 559 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
# 新增模型打标验证计划实施记录
## 修改目标
- 将已确认的“新增模型打标完整验证”文档归档到项目现有测试计划目录
- 基于确认后的验证计划补充一份可直接执行的实施计划
- 保持本次工作只覆盖验证方案与执行步骤,不混入修复内容
## 修改范围
- `docs/tests/plans/2026-03-20-bank-tag-new-model-validation-test-plan.md`
- `docs/tests/plans/2026-03-20-bank-tag-new-model-validation-execution-plan.md`
## 修改内容
### 1. 调整验证计划归档路径
- 将原先放在 `docs/superpowers/specs/` 下的验证设计文档迁移到 `docs/tests/plans/`
- 文件名调整为 `2026-03-20-bank-tag-new-model-validation-test-plan.md`
- 标题与正文表述从“设计”收敛为“验证计划”语境,和目录职责保持一致
### 2. 新增执行计划
- 新增 `docs/tests/plans/2026-03-20-bank-tag-new-model-validation-execution-plan.md`
- 按“环境与基线 -> Mock 自动化 -> 主工程自动化 -> 数据库核验 -> 接口端到端 -> 进程清理”的顺序拆解任务
- 为每个阶段补充了明确的执行命令、预期结果、停点条件和文档沉淀要求
## 目录选择理由
- `docs/tests/plans/` 当前已用于承载测试计划类文档
- 本次文档内容核心是验证目标、执行步骤、通过标准和失败停点
- 相比 `docs/plans/misc/`,该目录与本次任务的语义更一致
## 结果
- 验证计划路径已修正为项目目录下的测试计划位置
- 执行计划已补齐,后续可直接进入验证执行阶段
- 本次改动未触碰业务代码,也未启动前后端或 Mock 服务进程

View File

@@ -0,0 +1,521 @@
# 新增模型打标完整验证 Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 对 2026-03-20 新加入的模型打标改动执行一轮完整验证,覆盖 Mock 随机命中、第一期真实规则、数据库事实和接口端到端结果。
**Architecture:** 这次工作不是改代码,而是按“环境与基线 -> 自动化回归 -> 数据库核验 -> 接口链路 -> 文档沉淀”五层顺序执行。只要某一层失败,就停在该层记录证据,不继续给出“验证通过”的结论,也不进入修复。
**Tech Stack:** Bash, Python 3, FastAPI, Java 21, Spring Boot 3, Maven, pytest, MySQL, curl, jq
---
## File Structure
- `docs/tests/plans/2026-03-20-bank-tag-new-model-validation-test-plan.md`: 已确认的验证计划,执行时必须严格对齐范围与停点。
- `docs/tests/plans/2026-03-20-bank-tag-new-model-validation-execution-plan.md`: 本执行计划,负责把验证计划拆成可执行步骤。
- `docs/reports/implementation/2026-03-20-bank-tag-new-model-validation-record.md`: 本次实施记录,记录执行内容与范围。
- `docs/tests/records/2026-03-20-bank-tag-new-model-validation-verification.md`: 本次验证记录记录命令、SQL、接口结果和结论。
- `docs/reports/implementation/2026-03-20-lsfx-mock-random-hit-rule-backend-record.md`: Mock 随机命中改动的既有实施记录。
- `docs/tests/records/2026-03-20-lsfx-mock-random-hit-rule-backend-verification.md`: Mock 随机命中改动的既有验证记录。
- `docs/reports/implementation/2026-03-20-bank-tag-real-rule-phase1-backend-record.md`: 第一期真实规则改动的既有实施记录。
- `docs/tests/records/2026-03-20-bank-tag-real-rule-phase1-backend-verification.md`: 第一期真实规则改动的既有验证记录。
- `ruoyi-admin/src/main/resources/application-dev.yml`: 读取数据库连接与后端本地配置。
- `sql/migration/2026-03-20-lsfx-mock-random-hit-rule-purchase-baseline.sql`: `LARGE_PURCHASE_TRANSACTION` 采购基线脚本。
- `lsfx-mock-server/tests/test_file_service.py`: Mock 规则命中计划测试。
- `lsfx-mock-server/tests/test_statement_service.py`: Mock 样本装配与缓存稳定性测试。
- `lsfx-mock-server/tests/integration/test_full_workflow.py`: Mock 端到端链路测试。
- `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/BankTagRuleConfigResolverTest.java`: 规则参数映射测试。
- `ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapperXmlTest.java`: 真实 SQL 结构测试。
- `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java`: 规则分发测试。
- `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceRiskCountRefreshTest.java`: 风险人数刷新回归测试。
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadController.java`: 拉取本行信息接口。
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankTagController.java`: 手动重算接口。
- `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankStatementController.java`: 流水详情接口。
- `bin/restart_java_backend.sh`: 后端 Jar 启停脚本。
### Task 1: 锁定执行环境、目标项目和记录文档
**Files:**
- Create: `docs/reports/implementation/2026-03-20-bank-tag-new-model-validation-record.md`
- Create: `docs/tests/records/2026-03-20-bank-tag-new-model-validation-verification.md`
- Reference: `docs/tests/plans/2026-03-20-bank-tag-new-model-validation-test-plan.md`
- Reference: `ruoyi-admin/src/main/resources/application-dev.yml`
- Reference: `docs/reports/implementation/2026-03-20-lsfx-mock-random-hit-rule-backend-record.md`
- Reference: `docs/reports/implementation/2026-03-20-bank-tag-real-rule-phase1-backend-record.md`
- [ ] **Step 1: 阅读既有计划与两份来源实施记录**
Run:
```bash
sed -n '1,220p' docs/tests/plans/2026-03-20-bank-tag-new-model-validation-test-plan.md
sed -n '1,220p' docs/reports/implementation/2026-03-20-lsfx-mock-random-hit-rule-backend-record.md
sed -n '1,220p' docs/reports/implementation/2026-03-20-bank-tag-real-rule-phase1-backend-record.md
```
Expected:
- 明确本次只验证双线已落地内容
- 不把第二期规则和修复方案带进执行范围
- [ ] **Step 2: 从数据库中选出本次端到端验证使用的项目**
Run:
```bash
python3 - <<'PY'
from pathlib import Path
import pymysql, re
text = Path('ruoyi-admin/src/main/resources/application-dev.yml').read_text(encoding='utf-8')
match = re.search(r"url:\s*jdbc:mysql://(?P<host>[^:/?#]+):(?P<port>\d+)/(?P<db>[^?\n]+).*?\n\s*username:\s*(?P<user>[^\n]+)\n\s*password:\s*(?P<pwd>[^\n]+)", text, re.S)
conn = pymysql.connect(
host=match.group('host'),
port=int(match.group('port')),
user=match.group('user').strip(),
password=match.group('pwd').strip(),
database=match.group('db').strip(),
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor,
)
with conn, conn.cursor() as cursor:
cursor.execute("""
SELECT project_id, project_name, lsfx_project_id, config_type
FROM ccdi_project
WHERE del_flag = '0'
AND lsfx_project_id IS NOT NULL
ORDER BY update_time DESC, project_id DESC
LIMIT 10
""")
for row in cursor.fetchall():
print(row)
PY
```
Expected:
- 至少找到 1 个可用于 LSFX 联调的项目
- 在实施记录和验证记录中记下最终采用的 `project_id`
- [ ] **Step 3: 创建本次实施记录与验证记录骨架**
在两个文档中先写入以下固定章节:
- 实施记录:验证目标、范围、执行阶段、产物路径
- 验证记录:执行命令、数据库核验、接口验证、结论、环境清理
- [ ] **Step 4: Commit**
```bash
git add docs/reports/implementation/2026-03-20-bank-tag-new-model-validation-record.md docs/tests/records/2026-03-20-bank-tag-new-model-validation-verification.md docs/tests/plans/2026-03-20-bank-tag-new-model-validation-execution-plan.md
git commit -m "补充新增模型打标验证执行计划"
```
### Task 2: 先完成 Mock 随机命中自动化回归
**Files:**
- Reference: `lsfx-mock-server/tests/test_file_service.py`
- Reference: `lsfx-mock-server/tests/test_statement_service.py`
- Reference: `lsfx-mock-server/tests/integration/test_full_workflow.py`
- Modify: `docs/tests/records/2026-03-20-bank-tag-new-model-validation-verification.md`
- Modify: `docs/reports/implementation/2026-03-20-bank-tag-new-model-validation-record.md`
- [ ] **Step 1: 跑 Mock 聚焦回归**
Run:
```bash
cd lsfx-mock-server
python3 -m pytest tests/test_file_service.py -k "rule_hit_plan or persist_rule_hit_plan" -v
python3 -m pytest tests/test_statement_service.py -k "rule_plan_should_only_include or withdraw_cnt_samples" -v
python3 -m pytest tests/test_statement_service.py -k "follow_rule_hit_plan or fixed_total_count_200 or cached_result" -v
python3 -m pytest tests/integration/test_full_workflow.py -k "same_rule_subset or share_same_primary_binding" -v
```
Expected:
- 全部 `PASS`
- 能证明规则命中计划、样本装配和缓存稳定性未回退
- [ ] **Step 2: 跑 Mock 全量回归**
Run:
```bash
cd lsfx-mock-server
python3 -m pytest tests/test_file_service.py tests/test_statement_service.py tests/test_api.py tests/integration/test_full_workflow.py -v
```
Expected:
- `PASS`
- 若失败,停止后续阶段并把失败用例和首个错误栈写入验证记录
- [ ] **Step 3: 将结果写入验证记录**
记录:
- 实际执行时间
- `passed / failed / warnings` 摘要
- 若存在 warning只说明是否为既有 warning不做修复
### Task 3: 完成主工程第一期真实规则自动化回归
**Files:**
- Reference: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/BankTagRuleConfigResolverTest.java`
- Reference: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapperXmlTest.java`
- Reference: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java`
- Reference: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceRiskCountRefreshTest.java`
- Modify: `docs/tests/records/2026-03-20-bank-tag-new-model-validation-verification.md`
- [ ] **Step 1: 逐步跑第一期目标测试**
Run:
```bash
mvn test -pl ccdi-project -Dtest=BankTagRuleConfigResolverTest
mvn test -pl ccdi-project -Dtest=CcdiBankTagAnalysisMapperXmlTest
mvn test -pl ccdi-project -Dtest=CcdiBankTagAnalysisMapperXmlTest,CcdiBankTagServiceImplTest
mvn test -pl ccdi-project -Dtest=CcdiBankTagAnalysisMapperXmlTest,BankTagRuleConfigResolverTest,CcdiBankTagServiceImplTest,CcdiBankTagServiceRiskCountRefreshTest
```
Expected:
- 全部 `BUILD SUCCESS`
- 若任一命令失败,停止后续阶段并把失败命令、失败类名和错误摘要写入验证记录
- [ ] **Step 2: 在验证记录中沉淀主工程自动化结果**
记录:
- 每条命令的执行结果
- 规则映射、真实 SQL、对象分发、风险人数刷新是否都保持通过
### Task 4: 做数据库基线和规则元数据核验
**Files:**
- Reference: `sql/migration/2026-03-20-lsfx-mock-random-hit-rule-purchase-baseline.sql`
- Reference: `sql/2026-03-16-bank-tagging.sql`
- Modify: `docs/tests/records/2026-03-20-bank-tag-new-model-validation-verification.md`
- Modify: `docs/reports/implementation/2026-03-20-bank-tag-new-model-validation-record.md`
- [ ] **Step 1: 幂等执行采购基线脚本**
Run:
```bash
bin/mysql_utf8_exec.sh sql/migration/2026-03-20-lsfx-mock-random-hit-rule-purchase-baseline.sql
```
Expected:
- 脚本执行成功
- 无中文乱码、无 SQL 报错
- [ ] **Step 2: 校验采购基线记录存在**
Run:
```bash
python3 - <<'PY'
from pathlib import Path
import pymysql, re
text = Path('ruoyi-admin/src/main/resources/application-dev.yml').read_text(encoding='utf-8')
match = re.search(r"url:\s*jdbc:mysql://(?P<host>[^:/?#]+):(?P<port>\d+)/(?P<db>[^?\n]+).*?\n\s*username:\s*(?P<user>[^\n]+)\n\s*password:\s*(?P<pwd>[^\n]+)", text, re.S)
conn = pymysql.connect(
host=match.group('host'),
port=int(match.group('port')),
user=match.group('user').strip(),
password=match.group('pwd').strip(),
database=match.group('db').strip(),
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor,
)
with conn, conn.cursor() as cursor:
cursor.execute("""
SELECT purchase_id, actual_amount, supplier_name
FROM ccdi_purchase_transaction
WHERE purchase_id = 'LSFXMOCKPUR001'
AND actual_amount > 100000
""")
print(cursor.fetchone())
PY
```
Expected:
- 返回 `LSFXMOCKPUR001`
- `actual_amount` 大于 `100000`
- [ ] **Step 3: 校验第一期规则元数据保持大写且可识别**
Run:
```bash
python3 - <<'PY'
from pathlib import Path
import pymysql, re
TARGET_RULES = (
'GAMBLING_SENSITIVE_KEYWORD','SPECIAL_AMOUNT_TRANSACTION','SUSPICIOUS_INCOME_KEYWORD',
'FOREX_BUY_AMT','FOREX_SELL_AMT','LARGE_PURCHASE_TRANSACTION',
'STOCK_TFR_LARGE','WITHDRAW_CNT','LARGE_STOCK_TRADING'
)
text = Path('ruoyi-admin/src/main/resources/application-dev.yml').read_text(encoding='utf-8')
match = re.search(r"url:\s*jdbc:mysql://(?P<host>[^:/?#]+):(?P<port>\d+)/(?P<db>[^?\n]+).*?\n\s*username:\s*(?P<user>[^\n]+)\n\s*password:\s*(?P<pwd>[^\n]+)", text, re.S)
conn = pymysql.connect(
host=match.group('host'),
port=int(match.group('port')),
user=match.group('user').strip(),
password=match.group('pwd').strip(),
database=match.group('db').strip(),
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor,
)
sql = f"""
SELECT model_code, rule_code, indicator_code
FROM ccdi_bank_tag_rule
WHERE rule_code IN ({','.join(['%s'] * len(TARGET_RULES))})
ORDER BY model_code, sort_order, rule_code
"""
with conn, conn.cursor() as cursor:
cursor.execute(sql, TARGET_RULES)
for row in cursor.fetchall():
print(row)
PY
```
Expected:
- 所有目标规则均能查到
- `rule_code``indicator_code` 继续保持全大写风格
- [ ] **Step 4: 将 SQL 与结果摘要写入验证记录**
记录:
- 执行的 SQL 或脚本命令
- 返回结果摘要
- 若数据缺失,明确归类为“数据基线异常”
### Task 5: 执行接口端到端验证并清理进程
**Files:**
- Reference: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadController.java`
- Reference: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankTagController.java`
- Reference: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankStatementController.java`
- Modify: `docs/tests/records/2026-03-20-bank-tag-new-model-validation-verification.md`
- Modify: `docs/reports/implementation/2026-03-20-bank-tag-new-model-validation-record.md`
- [ ] **Step 1: 启动 Mock 服务**
Run:
```bash
mkdir -p logs
cd lsfx-mock-server
nohup python3 main.py > ../logs/lsfx-mock-validation.log 2>&1 &
echo $! > ../logs/lsfx-mock-validation.pid
cd ..
```
Expected:
- `logs/lsfx-mock-validation.pid` 已生成
- `http://localhost:8000/docs` 可访问
- [ ] **Step 2: 启动后端 Jar 服务**
Run:
```bash
./bin/restart_java_backend.sh stop
nohup ./bin/restart_java_backend.sh start > logs/backend-validation.log 2>&1 &
echo $! > logs/backend-validation.pid
```
Expected:
- 后端监听在 `http://localhost:62318`
- 若启动失败,先看 `logs/backend-console.log`,记录失败后停止执行
- [ ] **Step 3: 登录并取 token**
Run:
```bash
curl -s http://localhost:62318/login/test \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"admin123"}' | tee /tmp/bank-tag-login.json
jq -r '.token' /tmp/bank-tag-login.json
```
Expected:
- 返回非空 token
- [ ] **Step 4: 触发拉取本行信息链路生成新的 Mock 流水**
先从数据库挑 1-3 个真实身份证号,再调用接口:
```bash
python3 - <<'PY' >/tmp/bank-tag-id-cards.json
from pathlib import Path
import json, pymysql, re
text = Path('ruoyi-admin/src/main/resources/application-dev.yml').read_text(encoding='utf-8')
match = re.search(r"url:\s*jdbc:mysql://(?P<host>[^:/?#]+):(?P<port>\d+)/(?P<db>[^?\n]+).*?\n\s*username:\s*(?P<user>[^\n]+)\n\s*password:\s*(?P<pwd>[^\n]+)", text, re.S)
conn = pymysql.connect(
host=match.group('host'),
port=int(match.group('port')),
user=match.group('user').strip(),
password=match.group('pwd').strip(),
database=match.group('db').strip(),
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor,
)
with conn, conn.cursor() as cursor:
cursor.execute("""
SELECT DISTINCT id_card
FROM ccdi_base_staff
WHERE del_flag = '0'
AND id_card IS NOT NULL
AND id_card <> ''
ORDER BY id ASC
LIMIT 3
""")
print(json.dumps([row['id_card'] for row in cursor.fetchall()], ensure_ascii=False))
PY
TOKEN=$(jq -r '.token' /tmp/bank-tag-login.json)
PROJECT_ID=<把 Task 1 选定的 project_id 填到这里>
curl -s http://localhost:62318/ccdi/file-upload/pull-bank-info \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d "{\"projectId\":${PROJECT_ID},\"idCards\":$(cat /tmp/bank-tag-id-cards.json),\"startDate\":\"2026-03-01\",\"endDate\":\"2026-03-20\"}"
```
Expected:
- 返回 `拉取任务已提交`
- 说明 Mock 随机命中链路已被主工程实际调用
- [ ] **Step 5: 触发整项目手动重算**
Run:
```bash
TOKEN=$(jq -r '.token' /tmp/bank-tag-login.json)
curl -s http://localhost:62318/ccdi/project/tags/rebuild \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d "{\"projectId\":${PROJECT_ID},\"modelCode\":null}"
```
Expected:
- 返回 `{"code":200,...}`
- 手动重算任务成功提交
- [ ] **Step 6: 轮询数据库确认重算任务成功,并查出一条新增模型命中记录**
Run:
```bash
python3 - <<'PY'
from pathlib import Path
import pymysql, re, time
PROJECT_ID = int("<把 Task 1 选定的 project_id 填到这里>")
TARGET_RULES = (
'GAMBLING_SENSITIVE_KEYWORD','SPECIAL_AMOUNT_TRANSACTION','SUSPICIOUS_INCOME_KEYWORD',
'FOREX_BUY_AMT','FOREX_SELL_AMT','LARGE_PURCHASE_TRANSACTION',
'STOCK_TFR_LARGE','WITHDRAW_CNT','LARGE_STOCK_TRADING'
)
text = Path('ruoyi-admin/src/main/resources/application-dev.yml').read_text(encoding='utf-8')
match = re.search(r"url:\s*jdbc:mysql://(?P<host>[^:/?#]+):(?P<port>\d+)/(?P<db>[^?\n]+).*?\n\s*username:\s*(?P<user>[^\n]+)\n\s*password:\s*(?P<pwd>[^\n]+)", text, re.S)
conn = pymysql.connect(
host=match.group('host'),
port=int(match.group('port')),
user=match.group('user').strip(),
password=match.group('pwd').strip(),
database=match.group('db').strip(),
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor,
)
with conn, conn.cursor() as cursor:
task = None
for _ in range(30):
cursor.execute("""
SELECT id, status, model_code, hit_count, success_rule_count, failed_rule_count
FROM ccdi_bank_tag_task
WHERE project_id = %s
ORDER BY id DESC
LIMIT 1
""", (PROJECT_ID,))
task = cursor.fetchone()
print(task)
if task and task['status'] == 'SUCCESS':
break
time.sleep(2)
cursor.execute(f"""
SELECT id, rule_code, bank_statement_id, object_key, reason_detail
FROM ccdi_bank_statement_tag_result
WHERE project_id = %s
AND rule_code IN ({','.join(['%s'] * len(TARGET_RULES))})
ORDER BY id DESC
LIMIT 10
""", (PROJECT_ID, *TARGET_RULES))
for row in cursor.fetchall():
print(row)
PY
```
Expected:
- 最新任务状态为 `SUCCESS`
- 至少查到 1 条目标规则命中结果
- [ ] **Step 7: 用命中的 `bank_statement_id` 回查接口详情**
Run:
```bash
TOKEN=$(jq -r '.token' /tmp/bank-tag-login.json)
BANK_STATEMENT_ID=<把 Step 6 查到的 bank_statement_id 填到这里>
curl -s "http://localhost:62318/ccdi/project/bank-statement/detail/${BANK_STATEMENT_ID}" \
-H "Authorization: Bearer $TOKEN"
```
Expected:
- 返回 `code = 200`
- `data.hitTags` 中能看到至少 1 个目标规则对应的命中标签
- [ ] **Step 8: 写结论并关闭本次启动的进程**
Run:
```bash
if [ -f logs/lsfx-mock-validation.pid ]; then kill "$(cat logs/lsfx-mock-validation.pid)" || true; rm -f logs/lsfx-mock-validation.pid; fi
./bin/restart_java_backend.sh stop || true
rm -f logs/backend-validation.pid
```
Expected:
- Mock 进程已关闭
- 后端 Jar 进程已关闭
- 验证记录中明确写出“已完成进程清理”
- [ ] **Step 9: Commit**
```bash
git add docs/reports/implementation/2026-03-20-bank-tag-new-model-validation-record.md docs/tests/records/2026-03-20-bank-tag-new-model-validation-verification.md docs/tests/plans/2026-03-20-bank-tag-new-model-validation-execution-plan.md
git commit -m "补充新增模型打标完整验证记录"
```