Files
ccdi/docs/plans/backend/2026-03-22-lsfx-rule-hit-mode-backend-implementation.md

332 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LSFX Mock Rule Hit Mode Backend 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:**`lsfx-mock-server` 增加可通过命令行切换的规则命中模式,在默认保持稳定随机子集命中的前提下,支持切换为“全部兼容规则命中”。
**Architecture:** 保持现有 `FileService -> StatementService -> FileRecord 缓存` 主链路不变,只在启动层新增命令行参数解析,在配置层新增统一模式值,在规则计划层新增 `subset/all` 两种编排路径。`all` 模式不做字面全开,而是通过显式互斥组裁剪产出“全部兼容规则命中”计划,避免样本语义冲突。
**Tech Stack:** Python 3, FastAPI, uvicorn, pydantic-settings, pytest, Markdown
---
## File Structure
- `lsfx-mock-server/config/settings.py`: 新增 `RULE_HIT_MODE` 配置项,统一暴露规则命中模式。
- `lsfx-mock-server/main.py`: 新增普通启动命令行参数解析,并在启动前校验模式值。
- `lsfx-mock-server/dev.py`: 新增热重载启动入口,支持 `--reload --rule-hit-mode ...`
- `lsfx-mock-server/services/file_service.py`: 为 `subset/all` 两种模式生成命中计划,并显式维护互斥组裁剪逻辑。
- `lsfx-mock-server/tests/test_file_service.py`: 锁定默认随机子集模式、`all` 模式全集逻辑和互斥组行为。
- `lsfx-mock-server/tests/test_startup.py`: 锁定命令行参数解析、非法参数报错和热重载启动参数透传。
- `lsfx-mock-server/README.md`: 更新普通启动、热重载启动与“全部兼容规则命中”的准确说明。
- `docs/reports/implementation/2026-03-22-lsfx-rule-hit-mode-backend-record.md`: 记录本次后端实施范围、命中模式语义和落地结果。
- `docs/tests/records/2026-03-22-lsfx-rule-hit-mode-backend-verification.md`: 记录测试命令、启动验证和进程清理结果。
### Task 1: 接入命令行启动参数并统一规则命中模式配置
**Files:**
- Modify: `lsfx-mock-server/config/settings.py`
- Modify: `lsfx-mock-server/main.py`
- Create: `lsfx-mock-server/dev.py`
- Create: `lsfx-mock-server/tests/test_startup.py`
- Reference: `docs/superpowers/specs/2026-03-22-lsfx-rule-hit-mode-design.md`
- [ ] **Step 1: Write the failing test**
先在 `lsfx-mock-server/tests/test_startup.py` 中补三条失败用例,锁定启动参数语义:
```python
import pytest
from main import parse_args as parse_main_args
from dev import parse_args as parse_dev_args
def test_main_parse_args_should_default_to_subset():
args = parse_main_args([])
assert args.rule_hit_mode == "subset"
def test_main_parse_args_should_accept_all_mode():
args = parse_main_args(["--rule-hit-mode", "all"])
assert args.rule_hit_mode == "all"
def test_dev_parse_args_should_reject_invalid_mode():
with pytest.raises(SystemExit):
parse_dev_args(["--rule-hit-mode", "invalid"])
```
- [ ] **Step 2: Run test to verify it fails**
Run:
```bash
cd lsfx-mock-server
python3 -m pytest tests/test_startup.py -v
```
Expected:
- `FAIL`
- 原因是 `main.py` 与热重载入口尚未提供可测试的参数解析函数
- [ ] **Step 3: Write minimal implementation**
按最小路径实现:
1.`config/settings.py` 中新增默认配置:
```python
RULE_HIT_MODE: str = "subset"
```
2.`main.py` 中新增参数解析函数,只允许 `subset|all`
```python
def parse_args(argv=None):
parser = argparse.ArgumentParser()
parser.add_argument("--rule-hit-mode", choices=["subset", "all"], default="subset")
return parser.parse_args(argv)
```
3.`main.py` 启动前,将 `rule_hit_mode` 写入环境变量,再初始化/读取 `settings`
4. 新增 `dev.py`,复用同一套参数解析,支持:
```bash
python dev.py --reload --rule-hit-mode all
```
5. `dev.py` 内部调用 `uvicorn.run("main:app", reload=True, ...)` 或等价方式,不再要求用户直接运行裸 `uvicorn main:app --reload ...`
- [ ] **Step 4: Run test to verify it passes**
Run:
```bash
cd lsfx-mock-server
python3 -m pytest tests/test_startup.py -v
```
Expected:
- `PASS`
- 默认模式为 `subset`
- `all` 模式可被普通启动与热重载入口正确解析
- [ ] **Step 5: Commit**
```bash
git add lsfx-mock-server/config/settings.py lsfx-mock-server/main.py lsfx-mock-server/dev.py lsfx-mock-server/tests/test_startup.py
git commit -m "补充Mock规则命中模式启动参数"
```
### Task 2: 在 FileService 中实现全部兼容规则命中计划
**Files:**
- Modify: `lsfx-mock-server/services/file_service.py`
- Modify: `lsfx-mock-server/tests/test_file_service.py`
- Reference: `lsfx-mock-server/services/statement_rule_samples.py`
- Reference: `docs/superpowers/specs/2026-03-22-lsfx-rule-hit-mode-design.md`
- [ ] **Step 1: Write the failing test**
`lsfx-mock-server/tests/test_file_service.py` 中先补失败用例,锁定 `all` 模式语义:
```python
def test_build_rule_hit_plan_should_return_all_compatible_rules_in_all_mode(monkeypatch):
monkeypatch.setattr("services.file_service.settings.RULE_HIT_MODE", "all")
service = FileService(staff_identity_repository=FakeStaffIdentityRepository())
plan = service._build_rule_hit_plan(10001)
assert plan["large_transaction_hit_rules"] == LARGE_TRANSACTION_RULE_CODES
assert plan["phase1_hit_rules"] == PHASE1_RULE_CODES
assert plan["phase2_statement_hit_rules"] == PHASE2_STATEMENT_RULE_CODES
assert plan["phase2_baseline_hit_rules"] == PHASE2_BASELINE_RULE_CODES
def test_build_rule_hit_plan_should_keep_subset_mode_as_default():
service = FileService(staff_identity_repository=FakeStaffIdentityRepository())
plan1 = service._build_rule_hit_plan(10001)
plan2 = service._build_rule_hit_plan(10001)
assert plan1 == plan2
assert 2 <= len(plan1["large_transaction_hit_rules"]) <= 4
def test_build_rule_hit_plan_should_drop_conflicting_rules_from_all_mode(monkeypatch):
monkeypatch.setattr("services.file_service.settings.RULE_HIT_MODE", "all")
monkeypatch.setattr(
"services.file_service.RULE_CONFLICT_GROUPS",
[["SALARY_QUICK_TRANSFER", "SALARY_UNUSED"]],
)
service = FileService(staff_identity_repository=FakeStaffIdentityRepository())
plan = service._build_rule_hit_plan(10001)
assert not (
"SALARY_QUICK_TRANSFER" in plan["phase2_statement_hit_rules"]
and "SALARY_UNUSED" in plan["phase2_statement_hit_rules"]
)
```
- [ ] **Step 2: Run test to verify it fails**
Run:
```bash
cd lsfx-mock-server
python3 -m pytest tests/test_file_service.py -k "rule_hit_plan" -v
```
Expected:
- `FAIL`
- 原因是当前 `_build_rule_hit_plan()` 只有稳定随机子集逻辑,尚无模式切换和互斥裁剪
- [ ] **Step 3: Write minimal implementation**
`lsfx-mock-server/services/file_service.py` 中按职责做最小拆分:
1. 保留现有四类规则池常量。
2. 新增互斥组常量,第一版允许为空列表:
```python
RULE_CONFLICT_GROUPS = []
```
3. 新增模式编排辅助函数:
```python
def _build_subset_rule_hit_plan(self, log_id: int) -> dict:
...
def _build_all_compatible_rule_hit_plan(self) -> dict:
...
def _apply_conflict_groups(self, rule_plan: dict) -> dict:
...
```
4. `_build_rule_hit_plan()` 只负责分发:
```python
if settings.RULE_HIT_MODE == "all":
return self._apply_conflict_groups(self._build_all_compatible_rule_hit_plan())
return self._build_subset_rule_hit_plan(log_id)
```
5. 不修改 `FileRecord` 字段结构和后续消费链路,只改变计划生成方式。
- [ ] **Step 4: Run test to verify it passes**
Run:
```bash
cd lsfx-mock-server
python3 -m pytest tests/test_file_service.py -k "rule_hit_plan" -v
```
Expected:
- `PASS`
- 默认仍为随机子集
- `all` 模式返回全部兼容规则
- 若未来配置互斥组,同组规则不会同时出现在结果里
- [ ] **Step 5: Commit**
```bash
git add lsfx-mock-server/services/file_service.py lsfx-mock-server/tests/test_file_service.py
git commit -m "补充Mock全部兼容规则命中计划"
```
### Task 3: 更新文档并完成后端验证记录
**Files:**
- Modify: `lsfx-mock-server/README.md`
- Create: `docs/reports/implementation/2026-03-22-lsfx-rule-hit-mode-backend-record.md`
- Create: `docs/tests/records/2026-03-22-lsfx-rule-hit-mode-backend-verification.md`
- [ ] **Step 1: Update README with accurate startup instructions**
更新 `lsfx-mock-server/README.md`
- 普通启动示例改为:
```bash
python main.py --rule-hit-mode subset
python main.py --rule-hit-mode all
```
- 热重载示例改为:
```bash
python dev.py --reload --rule-hit-mode subset
python dev.py --reload --rule-hit-mode all
```
- 文案统一使用“全部兼容规则命中”,不使用“全部规则命中”。
- [ ] **Step 2: Run targeted verification**
Run:
```bash
cd lsfx-mock-server
python3 -m pytest tests/test_startup.py tests/test_file_service.py -k "rule_hit_plan or parse_args" -v
```
Expected:
- `PASS`
- 启动参数与规则计划两条主链路均被锁定
- [ ] **Step 3: Run startup smoke tests and stop processes**
分别执行并记录:
```bash
cd lsfx-mock-server
python3 main.py --rule-hit-mode all > /tmp/lsfx_main.log 2>&1 & echo $! > /tmp/lsfx_main.pid
sleep 3
kill "$(cat /tmp/lsfx_main.pid)"
rm -f /tmp/lsfx_main.pid
python3 dev.py --reload --rule-hit-mode all > /tmp/lsfx_dev.log 2>&1 & echo $! > /tmp/lsfx_dev.pid
sleep 5
kill "$(cat /tmp/lsfx_dev.pid)"
rm -f /tmp/lsfx_dev.pid
```
Expected:
- 两种启动方式均成功拉起
- 结束验证后无残留进程
- [ ] **Step 4: Write implementation and verification records**
在实施记录中写清:
- 默认模式保持不变
- `all` 的准确语义是“全部兼容规则命中”
- 当前互斥组为空或具体清单
- 热重载改为项目脚本入口
在验证记录中写清:
- 测试命令及结果
- 启动验证命令及结果
- 进程清理动作
- [ ] **Step 5: Commit**
```bash
git add lsfx-mock-server/README.md docs/reports/implementation/2026-03-22-lsfx-rule-hit-mode-backend-record.md docs/tests/records/2026-03-22-lsfx-rule-hit-mode-backend-verification.md
git commit -m "补充Mock命中模式后端实施与验证记录"
```