# LSFX Mock Large Transaction 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` 的银行流水生成逻辑，使同一 `logId` 的分页流水稳定包含可命中后端大额交易 8 条规则的样本，同时保留随机噪声流水。

**Architecture:** 在 `lsfx-mock-server` 内新增独立的大额交易样本生成模块，维护默认身份池、阈值常量与 5 组规则样本簇；`statement_service.py` 负责调用样本生成器、补足噪声流水、统一分配流水 ID 并缓存分页结果；测试分成样本生成测试和 API 返回测试两层，最后补一份实施记录文档。

**Tech Stack:** Python 3, FastAPI, pytest, httpx TestClient

---

### Task 1: 固化样本生成边界与文件结构

**Files:**
- Create: `lsfx-mock-server/services/statement_rule_samples.py`
- Modify: `lsfx-mock-server/services/statement_service.py`
- Test: `lsfx-mock-server/tests/test_statement_service.py`

- [ ] **Step 1: Write the failing test**

先新增一个最小单测，约束后续服务层会调用“命中样本 + 噪声流水”两段式生成，而不是继续只走单条完全随机逻辑：

```python
from services.statement_service import StatementService


def test_generate_statements_should_include_seeded_samples_before_noise():
    service = StatementService()

    statements = service._generate_statements(group_id=1000, log_id=20001, count=30)

    assert len(statements) >= 30
    assert any(item["userMemo"] == "购买房产首付款" for item in statements)
```

- [ ] **Step 2: Run test to verify it fails**

Run:

```bash
cd lsfx-mock-server
pytest tests/test_statement_service.py -k seeded_samples -v
```

Expected:

- `FAIL`
- 原因是 `tests/test_statement_service.py` 尚不存在，且 `StatementService` 还没有命中样本生成能力

- [ ] **Step 3: Write minimal implementation**

先搭文件骨架和最小接口，不一次性塞完整逻辑，但要保证首个样本断言可以通过：

```python
# lsfx-mock-server/services/statement_rule_samples.py
DEFAULT_LARGE_TRANSACTION_THRESHOLDS = {
    "SINGLE_TRANSACTION_AMOUNT": 1111,
    "CUMULATIVE_TRANSACTION_AMOUNT": 50000001,
    "ANNUAL_TURNOVER": 50000001,
    "LARGE_CASH_DEPOSIT": 2000001,
    "FREQUENT_CASH_DEPOSIT": 5,
    "FREQUENT_TRANSFER": 100001,
}


def build_large_transaction_seed_statements(group_id: int, log_id: int) -> list[dict]:
    return [
        {
            "groupId": group_id,
            "batchId": log_id,
            "userMemo": "购买房产首付款",
            "customerName": "杭州贝壳房地产经纪有限公司",
            "drAmount": 680000.0,
            "crAmount": 0.0,
            "cashType": "对公转账",
            "cretNo": "330101198801010011",
        }
    ]
```

```python
# lsfx-mock-server/services/statement_service.py
from services.statement_rule_samples import build_large_transaction_seed_statements


def _generate_statements(self, group_id: int, log_id: int, count: int) -> List[Dict]:
    statements = build_large_transaction_seed_statements(group_id, log_id)
    while len(statements) < count:
        statements.append(self._generate_random_statement(len(statements), group_id, log_id))
    return statements
```

- [ ] **Step 4: Run test to verify it passes**

Run:

```bash
cd lsfx-mock-server
pytest tests/test_statement_service.py -k seeded_samples -v
```

Expected:

- `PASS`
- 说明样本生成器与服务层的最小整合入口已经建立

- [ ] **Step 5: Commit**

```bash
git add lsfx-mock-server/services/statement_rule_samples.py lsfx-mock-server/services/statement_service.py lsfx-mock-server/tests/test_statement_service.py
git commit -m "拆分Mock大额交易样本生成骨架"
```

### Task 2: 用单元测试锁定 8 条规则样本口径

**Files:**
- Modify: `lsfx-mock-server/services/statement_rule_samples.py`
- Test: `lsfx-mock-server/tests/test_statement_service.py`

- [ ] **Step 1: Write the failing test**

在 `tests/test_statement_service.py` 中补一组口径测试，至少覆盖 8 条规则的关键命中特征：

```python
from services.statement_rule_samples import build_large_transaction_seed_statements


def test_large_transaction_seed_should_cover_all_eight_rules():
    statements = build_large_transaction_seed_statements(group_id=1000, log_id=20001)

    assert any(item["userMemo"] == "购买房产首付款" and item["drAmount"] > 0 for item in statements)
    assert any("税" in item["userMemo"] and item["drAmount"] > 0 for item in statements)
    assert any(item["crAmount"] > 1111 for item in statements)
    assert len([item for item in statements if item["cashType"] == "现金存款"]) >= 1
    assert len([item for item in statements if item["customerName"] == "浙江远望贸易有限公司"]) >= 3
```

再补几个针对性断言：

- 同一身份证、同一日期，至少 6 笔 `crAmount > 2000001` 的存现样本
- 至少 1 笔 `userMemo` 含 `手机银行转账` 且 `drAmount > 100001`
- 所有“收入样本”都避开工资排除词

- [ ] **Step 2: Run test to verify it fails**

Run:

```bash
cd lsfx-mock-server
pytest tests/test_statement_service.py -k "eight_rules or cash_deposit or transfer" -v
```

Expected:

- `FAIL`
- 原因是样本构造函数还没有真实返回 8 条规则所需数据

- [ ] **Step 3: Write minimal implementation**

在 `statement_rule_samples.py` 中先把 5 组样本簇写实：

```python
IDENTITY_POOL = {
    "staff_primary": {"name": "模型测试员工", "id_card": "330101198801010011", "account": "6222024000000001"},
    "family_primary": {"name": "模型测试家属", "id_card": "330101199001010022", "account": "6222024000000002"},
    "staff_secondary": {"name": "模型二测试员工", "id_card": "330101198802020033", "account": "6222024000000003"},
    "family_secondary": {"name": "模型二测试家属", "id_card": "330101199202020044", "account": "6222024000000004"},
}
```

```python
def build_large_transaction_seed_statements(group_id: int, log_id: int) -> list[dict]:
    statements = []
    statements.extend(build_house_or_car_samples(group_id, log_id))
    statements.extend(build_tax_samples(group_id, log_id))
    statements.extend(build_income_samples(group_id, log_id))
    statements.extend(build_cash_deposit_samples(group_id, log_id))
    statements.extend(build_large_transfer_samples(group_id, log_id))
    return statements
```

- [ ] **Step 4: Run test to verify it passes**

Run:

```bash
cd lsfx-mock-server
pytest tests/test_statement_service.py -v
```

Expected:

- `PASS`
- 测试输出能证明 8 条规则所需的关键样本全部存在

- [ ] **Step 5: Commit**

```bash
git add lsfx-mock-server/services/statement_rule_samples.py lsfx-mock-server/tests/test_statement_service.py
git commit -m "补充Mock大额交易八条规则样本"
```

### Task 3: 重构 StatementService 合并样本与噪声流水

**Files:**
- Modify: `lsfx-mock-server/services/statement_service.py`
- Test: `lsfx-mock-server/tests/test_statement_service.py`

- [ ] **Step 1: Write the failing test**

补两个服务层测试：

```python
def test_generate_statements_should_fill_noise_up_to_requested_count():
    service = StatementService()

    statements = service._generate_statements(group_id=1000, log_id=20001, count=80)

    assert len(statements) == 80


def test_get_bank_statement_should_keep_same_cached_result_for_same_log_id():
    service = StatementService()

    page1 = service.get_bank_statement({"groupId": 1000, "logId": 30001, "pageNow": 1, "pageSize": 20})
    page2 = service.get_bank_statement({"groupId": 1000, "logId": 30001, "pageNow": 1, "pageSize": 20})

    assert page1["data"]["bankStatementList"] == page2["data"]["bankStatementList"]
```

- [ ] **Step 2: Run test to verify it fails**

Run:

```bash
cd lsfx-mock-server
pytest tests/test_statement_service.py -k "fill_noise or cached_result" -v
```

Expected:

- `FAIL`
- 原因是当前 `_generate_statements()` 会直接按 `count` 循环生成单条随机流水，既不保底命中样本，也没有样本与噪声的装配逻辑

- [ ] **Step 3: Write minimal implementation**

把 `statement_service.py` 改成以下结构：

```python
def _generate_statements(self, group_id: int, log_id: int, count: int) -> List[Dict]:
    seed_statements = build_large_transaction_seed_statements(group_id, log_id)
    total_count = max(count, len(seed_statements))
    noise_count = total_count - len(seed_statements)

    statements = list(seed_statements)
    for index in range(noise_count):
        statements.append(self._generate_random_statement(index, group_id, log_id))

    statements = self._assign_statement_ids(statements, log_id)
    random.shuffle(statements)
    return statements
```

同时新增一个专用方法，为样本和噪声统一补齐：

- `bankStatementId`
- `bankTrxNumber`
- `uploadSequnceNumber`
- `batchId`
- `groupId`

- [ ] **Step 4: Run test to verify it passes**

Run:

```bash
cd lsfx-mock-server
pytest tests/test_statement_service.py -v
```

Expected:

- `PASS`
- 同一 `logId` 首次生成后被缓存，后续分页请求不会漂移

- [ ] **Step 5: Commit**

```bash
git add lsfx-mock-server/services/statement_service.py lsfx-mock-server/tests/test_statement_service.py
git commit -m "重构Mock流水生成并保留分页缓存一致性"
```

### Task 4: 扩展 API 测试覆盖分页与命中特征

**Files:**
- Modify: `lsfx-mock-server/tests/test_api.py`
- Modify: `lsfx-mock-server/tests/integration/test_full_workflow.py`

- [ ] **Step 1: Write the failing test**

在 API 层增加最小回归：

```python
def test_get_bank_statement_should_return_seed_samples(client):
    response = client.post(
        "/watson/api/project/getBSByLogId",
        data={"groupId": 1000, "logId": 40001, "pageNow": 1, "pageSize": 200}
    )

    assert response.status_code == 200
    items = response.json()["data"]["bankStatementList"]
    assert any(item["userMemo"] == "购买房产首付款" for item in items)
```

再补一个分页拼接测试，确认把前几页拼起来后可以找到：

- 税务样本
- 至少 6 笔同日存现
- 大额转账样本

- [ ] **Step 2: Run test to verify it fails**

Run:

```bash
cd lsfx-mock-server
pytest tests/test_api.py -k "seed_samples or bank_statement" -v
```

Expected:

- `FAIL`
- 原因是接口返回的仍是完全随机流水，无法稳定断言命中特征

- [ ] **Step 3: Write minimal implementation**

这里主要是补充测试，不引入新的 API 逻辑分支。若前一任务已完成，接口应天然满足这些断言；如仍失败，只允许回到 `statement_rule_samples.py` 或 `statement_service.py` 修正数据构造，不要在路由层堆业务判断。

- [ ] **Step 4: Run test to verify it passes**

Run:

```bash
cd lsfx-mock-server
pytest tests/test_api.py -v
pytest tests/integration/test_full_workflow.py -v
```

Expected:

- `PASS`
- 现有上传、拉取、查询等主流程回归不受影响

- [ ] **Step 5: Commit**

```bash
git add lsfx-mock-server/tests/test_api.py lsfx-mock-server/tests/integration/test_full_workflow.py
git commit -m "补充Mock流水分页与命中特征回归测试"
```

### Task 5: 补实施记录并完成最终验证

**Files:**
- Create: `docs/reports/implementation/2026-03-18-lsfx-mock-large-transaction-implementation.md`
- Modify: `docs/plans/backend/2026-03-18-lsfx-mock-large-transaction-backend-implementation.md`

- [ ] **Step 1: Write the implementation report skeleton**

记录以下内容：

- 改动文件清单
- 8 条规则对应的样本策略
- 实际跑过的 pytest 命令
- 已知约束：身份池必须存在于目标后端环境

建议骨架：

```markdown
# LSFX Mock 大额交易样本实施记录

## 变更概述
- ...

## 验证记录
- `cd lsfx-mock-server && pytest tests/test_statement_service.py -v`
- `cd lsfx-mock-server && pytest tests/test_api.py -v`
```

- [ ] **Step 2: Run final verification**

Run:

```bash
cd lsfx-mock-server
pytest tests/test_statement_service.py -v
pytest tests/test_api.py -v
pytest tests/integration/test_full_workflow.py -v
python verify_implementation.py
```

Expected:

- 所有测试通过
- `verify_implementation.py` 不报接口字段缺失

- [ ] **Step 3: Stop any started services**

如果验证过程中启动了：

- `python main.py`
- `uvicorn main:app --reload --host 0.0.0.0 --port 8000`

必须在结束时关闭对应进程，避免残留端口占用。

- [ ] **Step 4: Commit**

```bash
git add docs/reports/implementation/2026-03-18-lsfx-mock-large-transaction-implementation.md docs/plans/backend/2026-03-18-lsfx-mock-large-transaction-backend-implementation.md
git commit -m "补充Mock大额交易样本实施记录"
```
