# 获取单个文件上传状态接口优化实施计划

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** 优化 `/watson/api/project/bs/upload` 接口，实现基于 logId 的确定性数据生成，不依赖上传记录。

**Architecture:** 使用 `random.seed(log_id)` 确保相同 logId 生成相同数据，完全独立于文件上传记录，符合 Mock 服务器定位。

**Tech Stack:** FastAPI, Python random/datetime, pytest

---

## Task 1: 修复 FileRecord 类的 log_meta 默认值

**Files:**
- Modify: `services/file_service.py:47`

**Step 1: 修改 log_meta 默认值**

在 `services/file_service.py` 第 47 行，将：

```python
log_meta: str = "{\"lostHeader\":[],\"balanceAmount\":true}"
```

改为：

```python
log_meta: str = "{\"lostHeader\":[],\"balanceAmount\":\"-1\"}"
```

**Step 2: 验证修改**

运行: `python -c "from services.file_service import FileRecord; r = FileRecord(log_id=1, group_id=1, file_name='test.csv'); print(r.log_meta)"`

预期输出:
```
{"lostHeader":[],"balanceAmount":"-1"}
```

**Step 3: 提交修复**

```bash
git add services/file_service.py
git commit -m "fix: 修复 FileRecord.log_meta 中 balanceAmount 值为字符串 '-1'"
```

---

## Task 2: 编写测试 - 带 logId 查询返回数据

**Files:**
- Modify: `tests/test_api.py`

**Step 1: 编写测试用例**

在 `tests/test_api.py` 文件末尾添加：

```python
def test_get_upload_status_with_log_id():
    """测试带 logId 参数查询返回非空 logs"""
    response = client.get("/watson/api/project/bs/upload?groupId=1000&logId=13994")

    assert response.status_code == 200
    data = response.json()

    # 验证基本响应结构
    assert data["code"] == "200"
    assert data["status"] == "200"
    assert data["successResponse"] is True

    # 验证 logs 不为空
    assert len(data["data"]["logs"]) == 1

    # 验证返回的 logId 正确
    log = data["data"]["logs"][0]
    assert log["logId"] == 13994

    # 验证固定成功状态
    assert log["status"] == -5
    assert log["uploadStatusDesc"] == "data.wait.confirm.newaccount"

    # 验证 logMeta 格式正确
    assert log["logMeta"] == "{\"lostHeader\":[],\"balanceAmount\":\"-1\"}"
```

**Step 2: 运行测试验证失败**

运行: `pytest tests/test_api.py::test_get_upload_status_with_log_id -v`

预期: FAIL（因为还未实现）

**Step 3: 提交测试**

```bash
git add tests/test_api.py
git commit -m "test: 添加带 logId 查询的测试用例"
```

---

## Task 3: 编写测试 - 不带 logId 查询返回空数组

**Files:**
- Modify: `tests/test_api.py`

**Step 1: 编写测试用例**

在 `tests/test_api.py` 文件末尾添加：

```python
def test_get_upload_status_without_log_id():
    """测试不带 logId 参数查询返回空 logs 数组"""
    response = client.get("/watson/api/project/bs/upload?groupId=1000")

    assert response.status_code == 200
    data = response.json()

    # 验证基本响应结构
    assert data["code"] == "200"
    assert data["status"] == "200"
    assert data["successResponse"] is True

    # 验证 logs 为空
    assert len(data["data"]["logs"]) == 0

    # 验证其他字段存在
    assert data["data"]["status"] == ""
    assert data["data"]["accountId"] == 8954
    assert data["data"]["currency"] == "CNY"
```

**Step 2: 运行测试验证失败**

运行: `pytest tests/test_api.py::test_get_upload_status_without_log_id -v`

预期: FAIL（因为还未实现）

**Step 3: 提交测试**

```bash
git add tests/test_api.py
git commit -m "test: 添加不带 logId 查询的测试用例"
```

---

## Task 4: 编写测试 - 确定性数据生成

**Files:**
- Modify: `tests/test_api.py`

**Step 1: 编写测试用例**

在 `tests/test_api.py` 文件末尾添加：

```python
def test_deterministic_data_generation():
    """测试相同 logId 多次查询返回相同的核心字段值"""
    # 第一次查询
    response1 = client.get("/watson/api/project/bs/upload?groupId=1000&logId=13994")
    log1 = response1.json()["data"]["logs"][0]

    # 第二次查询
    response2 = client.get("/watson/api/project/bs/upload?groupId=1000&logId=13994")
    log2 = response2.json()["data"]["logs"][0]

    # 验证关键字段相同
    assert log1["logId"] == log2["logId"]
    assert log1["bankName"] == log2["bankName"]
    assert log1["accountNoList"] == log2["accountNoList"]
    assert log1["enterpriseNameList"] == log2["enterpriseNameList"]
    assert log1["status"] == log2["status"]
    assert log1["logMeta"] == log2["logMeta"]
    assert log1["templateName"] == log2["templateName"]
    assert log1["trxDateStartId"] == log2["trxDateStartId"]
    assert log1["trxDateEndId"] == log2["trxDateEndId"]

def test_field_completeness():
    """测试返回数据包含完整的 26 个字段"""
    response = client.get("/watson/api/project/bs/upload?groupId=1000&logId=13994")
    log = response.json()["data"]["logs"][0]

    # 验证所有必需字段存在
    required_fields = [
        "accountNoList", "bankName", "dataTypeInfo", "downloadFileName",
        "enterpriseNameList", "fileSize", "fileUploadBy", "fileUploadByUserName",
        "fileUploadTime", "isSplit", "leId", "logId", "logMeta", "logType",
        "loginLeId", "lostHeader", "realBankName", "rows", "source", "status",
        "templateName", "totalRecords", "trxDateEndId", "trxDateStartId",
        "uploadFileName", "uploadStatusDesc"
    ]

    for field in required_fields:
        assert field in log, f"缺少字段: {field}"
```

**Step 2: 运行测试验证失败**

运行: `pytest tests/test_api.py::test_deterministic_data_generation tests/test_api.py::test_field_completeness -v`

预期: FAIL（因为还未实现）

**Step 3: 提交测试**

```bash
git add tests/test_api.py
git commit -m "test: 添加确定性和字段完整性测试用例"
```

---

## Task 5: 实现 _generate_deterministic_record() 方法

**Files:**
- Modify: `services/file_service.py`

**Step 1: 在 FileService 类中添加新方法**

在 `services/file_service.py` 的 `FileService` 类中，在 `_delayed_parse` 方法之后（约第 200 行）添加：

```python
def _generate_deterministic_record(self, log_id: int, group_id: int) -> dict:
    """
    基于 logId 生成确定性的文件记录

    Args:
        log_id: 文件ID（用作随机种子）
        group_id: 项目ID

    Returns:
        文件记录字典（26个字段）
    """
    # 银行类型选项
    bank_options = [
        ("ALIPAY", "ALIPAY_T220708"),
        ("BSX", "BSX_T240925"),
        ("ZJRCU", "ZJRCU_T251114")
    ]

    bank_name, template_name = random.choice(bank_options)

    # 生成交易日期范围
    end_date = datetime.now()
    start_date = end_date - timedelta(days=random.randint(90, 365))

    # 生成账号和主体
    account_no = f"{random.randint(10000000000, 99999999999)}"
    enterprise_names = ["测试主体"] if random.random() > 0.3 else [""]

    return {
        "accountNoList": [account_no],
        "bankName": bank_name,
        "dataTypeInfo": ["CSV", ","],
        "downloadFileName": f"测试文件_{log_id}.csv",
        "enterpriseNameList": enterprise_names,
        "fileSize": random.randint(10000, 100000),
        "fileUploadBy": 448,
        "fileUploadByUserName": "admin@support.com",
        "fileUploadTime": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        "isSplit": 0,
        "leId": 10000 + random.randint(0, 9999),
        "logId": log_id,
        "logMeta": "{\"lostHeader\":[],\"balanceAmount\":\"-1\"}",
        "logType": "bankstatement",
        "loginLeId": 10000 + random.randint(0, 9999),
        "lostHeader": [],
        "realBankName": bank_name,
        "rows": 0,
        "source": "http",
        "status": -5,
        "templateName": template_name,
        "totalRecords": random.randint(100, 300),
        "trxDateEndId": int(end_date.strftime("%Y%m%d")),
        "trxDateStartId": int(start_date.strftime("%Y%m%d")),
        "uploadFileName": f"测试文件_{log_id}.pdf",
        "uploadStatusDesc": "data.wait.confirm.newaccount"
    }
```

**Step 2: 验证语法正确**

运行: `python -m py_compile services/file_service.py`

预期: 无输出（表示语法正确）

**Step 3: 提交代码**

```bash
git add services/file_service.py
git commit -m "feat: 添加 _generate_deterministic_record 方法"
```

---

## Task 6: 重构 get_upload_status() 方法

**Files:**
- Modify: `services/file_service.py:265-300`

**Step 1: 替换整个 get_upload_status() 方法**

在 `services/file_service.py` 中，找到 `get_upload_status` 方法（约第 265-300 行），完全替换为：

```python
def get_upload_status(self, group_id: int, log_id: int = None) -> dict:
    """
    获取文件上传状态（基于 logId 生成确定性数据）

    Args:
        group_id: 项目ID
        log_id: 文件ID（可选）

    Returns:
        上传状态响应字典
    """
    logs = []

    if log_id:
        # 使用 logId 作为随机种子，确保相同 logId 返回相同数据
        random.seed(log_id)

        # 生成确定性的文件记录
        record = self._generate_deterministic_record(log_id, group_id)
        logs.append(record)

    # 返回响应
    return {
        "code": "200",
        "data": {
            "logs": logs,
            "status": "",
            "accountId": 8954,
            "currency": "CNY"
        },
        "status": "200",
        "successResponse": True
    }
```

**Step 2: 验证语法正确**

运行: `python -m py_compile services/file_service.py`

预期: 无输出（表示语法正确）

**Step 3: 提交重构**

```bash
git add services/file_service.py
git commit -m "refactor: 重构 get_upload_status 方法实现独立数据生成"
```

---

## Task 7: 运行所有测试验证功能

**Files:**
- Test: `tests/test_api.py`

**Step 1: 运行新增的测试用例**

运行: `pytest tests/test_api.py::test_get_upload_status_with_log_id tests/test_api.py::test_get_upload_status_without_log_id tests/test_api.py::test_deterministic_data_generation tests/test_api.py::test_field_completeness -v`

预期: 所有测试 PASS

**Step 2: 运行完整的测试套件**

运行: `pytest tests/ -v`

预期: 所有测试 PASS（确保没有破坏其他功能）

**Step 3: 手动测试接口**

运行: `python main.py`（在后台启动服务器）

在另一个终端运行:
```bash
curl "http://localhost:8000/watson/api/project/bs/upload?groupId=1000&logId=13994"
```

预期: 返回包含 logId=13994 的 JSON 数据

**Step 4: 提交验证记录**

```bash
git add tests/
git commit -m "test: 验证所有测试通过"
```

---

## Task 8: 更新文档并提交

**Files:**
- Modify: `CLAUDE.md`（可选）

**Step 1: 检查是否需要更新 CLAUDE.md**

查看项目根目录的 `CLAUDE.md` 文件，确认是否需要添加关于接口独立性的说明。如果需要，在适当位置添加：

```markdown
### 接口说明

**获取单个文件上传状态接口 (`/watson/api/project/bs/upload`)**：
- 此接口完全独立工作，不依赖文件上传记录
- 基于 logId 参数生成确定性的随机数据
- 相同 logId 每次查询返回相同的核心字段值
```

**Step 2: 提交文档更新（如果有）**

```bash
git add CLAUDE.md
git commit -m "docs: 更新接口独立性说明"
```

**Step 3: 最终提交**

确保所有修改已提交：

```bash
git status
```

预期: 工作目录干净

---

## 验收清单

实施完成后，确认以下验收标准：

### 功能验收
- [x] 带 logId 参数查询返回非空 logs 数组
- [x] 不带 logId 参数查询返回空 logs 数组
- [x] 相同 logId 多次查询返回相同的核心字段值
- [x] 返回数据包含完整的 26 个字段
- [x] status 字段值为 -5
- [x] logMeta 字段中 balanceAmount 为字符串 "-1"

### 质量验收
- [x] 所有单元测试通过
- [x] 代码符合项目编码规范
- [x] 无语法错误和运行时错误
- [x] API 文档（Swagger UI）正确展示接口

### 文档验收
- [x] 代码注释完整清晰
- [x] 测试用例覆盖所有场景

---

## 实施说明

1. **TDD 流程**: 严格遵循"先写测试 → 运行失败 → 写代码 → 运行通过 → 提交"的流程
2. **频繁提交**: 每个小的步骤都有独立的提交，便于回滚和追踪
3. **独立性**: 此修改不影响其他接口（上传、解析状态检查等）
4. **确定性**: 使用 `random.seed(log_id)` 确保相同 logId 生成相同数据
5. **简单高效**: 代码简洁，无过度设计，符合 YAGNI 原则

---

## 参考资料

- 设计文档: `docs/plans/2026-03-12-upload-status-api-design.md`
- 接口文档: `assets/兰溪-流水分析对接3.md` 第374-516行
- 当前实现: `services/file_service.py` 第265-300行
