# 接口完整对齐实施计划

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

**Goal:** 根据 `兰溪-流水分析对接3.md` 文档，完整对齐 Mock 服务器的所有7个接口实现

**Architecture:** 保持无数据库架构，通过扩展内存数据模型支持完整字段，新增1个接口，完善6个现有接口的响应字段

**Tech Stack:** FastAPI, Python 3.8+, Pydantic

---

## Task 1: 扩展 FileRecord 数据模型

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

**Step 1: 读取现有 file_service.py 文件**

先查看当前的 FileRecord 实现。

**Step 2: 扩展 FileRecord 类添加所有新字段**

在 `FileRecord` 类中添加以下字段：

```python
from dataclasses import dataclass, field
from typing import List
import uuid
from datetime import datetime

@dataclass
class FileRecord:
    """文件记录模型（扩展版）"""
    # 原有字段
    log_id: int
    group_id: int
    file_name: str
    status: int = -5  # -5 表示解析成功
    upload_status_desc: str = "data.wait.confirm.newaccount"
    parsing: bool = True  # True表示正在解析

    # 新增字段 - 账号和主体信息
    account_no_list: List[str] = field(default_factory=list)
    enterprise_name_list: List[str] = field(default_factory=list)

    # 新增字段 - 银行和模板信息
    bank_name: str = "ZJRCU"
    real_bank_name: str = "ZJRCU"
    template_name: str = "ZJRCU_T251114"
    data_type_info: List[str] = field(default_factory=lambda: ["CSV", ","])

    # 新增字段 - 文件元数据
    file_size: int = 50000
    download_file_name: str = ""
    file_package_id: str = field(default_factory=lambda: str(uuid.uuid4()).replace('-', ''))

    # 新增字段 - 上传用户信息
    file_upload_by: int = 448
    file_upload_by_user_name: str = "admin@support.com"
    file_upload_time: str = field(default_factory=lambda: datetime.now().strftime("%Y-%m-%d %H:%M:%S"))

    # 新增字段 - 法律实体信息
    le_id: int = 10000
    login_le_id: int = 10000
    log_type: str = "bankstatement"
    log_meta: str = "{\"lostHeader\":[],\"balanceAmount\":true}"
    lost_header: List[str] = field(default_factory=list)

    # 新增字段 - 记录统计
    rows: int = 0
    source: str = "http"
    total_records: int = 150
    is_split: int = 0

    # 新增字段 - 交易日期范围
    trx_date_start_id: int = 20240101
    trx_date_end_id: int = 20241231
```

**Step 3: 验证服务能正常启动**

```bash
python main.py
```

预期：服务启动成功，无报错信息。

---

## Task 2: 更新 upload_file 方法初始化所有字段

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

**Step 1: 读取 upload_file 方法**

查看当前的 `upload_file` 方法实现。

**Step 2: 根据文件名推断银行名称**

在 `upload_file` 方法中添加银行名称推断逻辑：

```python
def _infer_bank_name(self, filename: str) -> tuple:
    """根据文件名推断银行名称和模板名称"""
    if "支付宝" in filename or "alipay" in filename.lower():
        return "ALIPAY", "ALIPAY_T220708"
    elif "绍兴银行" in filename or "BSX" in filename:
        return "BSX", "BSX_T240925"
    else:
        return "ZJRCU", "ZJRCU_T251114"

async def upload_file(self, group_id: int, file: UploadFile, background_tasks: BackgroundTasks) -> dict:
    """上传文件并初始化所有字段"""
    # 生成新的 log_id
    self.current_log_id += 1
    log_id = self.current_log_id

    # 推断银行信息
    bank_name, template_name = self._infer_bank_name(file.filename)

    # 生成合理的交易日期范围
    import random
    from datetime import datetime, timedelta

    end_date = datetime.now()
    start_date = end_date - timedelta(days=random.randint(90, 365))
    trx_date_start_id = int(start_date.strftime("%Y%m%d"))
    trx_date_end_id = int(end_date.strftime("%Y%m%d"))

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

    # 创建完整的文件记录
    file_record = FileRecord(
        log_id=log_id,
        group_id=group_id,
        file_name=file.filename,
        download_file_name=file.filename,
        bank_name=bank_name,
        real_bank_name=bank_name,
        template_name=template_name,
        account_no_list=[account_no],
        enterprise_name_list=enterprise_names,
        le_id=10000 + random.randint(0, 9999),
        login_le_id=10000 + random.randint(0, 9999),
        file_size=random.randint(10000, 100000),
        total_records=random.randint(100, 300),
        trx_date_start_id=trx_date_start_id,
        trx_date_end_id=trx_date_end_id,
        parsing=True,
        status=-5
    )

    # 存储记录
    self.file_records[log_id] = file_record

    # 添加后台任务（延迟解析）
    background_tasks.add_task(self._delayed_parse, log_id)

    # 构建响应
    return self._build_upload_response(file_record)
```

**Step 3: 实现 _build_upload_response 方法**

```python
def _build_upload_response(self, file_record: FileRecord) -> dict:
    """构建上传接口的完整响应"""
    return {
        "code": "200",
        "data": {
            "accountsOfLog": {
                str(file_record.log_id): [
                    {
                        "bank": file_record.bank_name,
                        "accountName": file_record.enterprise_name_list[0] if file_record.enterprise_name_list else "",
                        "accountNo": file_record.account_no_list[0] if file_record.account_no_list else "",
                        "currency": "CNY"
                    }
                ]
            },
            "uploadLogList": [
                {
                    "accountNoList": file_record.account_no_list,
                    "bankName": file_record.bank_name,
                    "dataTypeInfo": file_record.data_type_info,
                    "downloadFileName": file_record.download_file_name,
                    "enterpriseNameList": file_record.enterprise_name_list,
                    "filePackageId": file_record.file_package_id,
                    "fileSize": file_record.file_size,
                    "fileUploadBy": file_record.file_upload_by,
                    "fileUploadByUserName": file_record.file_upload_by_user_name,
                    "fileUploadTime": file_record.file_upload_time,
                    "leId": file_record.le_id,
                    "logId": file_record.log_id,
                    "logMeta": file_record.log_meta,
                    "logType": file_record.log_type,
                    "loginLeId": file_record.login_le_id,
                    "lostHeader": file_record.lost_header,
                    "realBankName": file_record.real_bank_name,
                    "rows": file_record.rows,
                    "source": file_record.source,
                    "status": file_record.status,
                    "templateName": file_record.template_name,
                    "totalRecords": file_record.total_records,
                    "trxDateEndId": file_record.trx_date_end_id,
                    "trxDateStartId": file_record.trx_date_start_id,
                    "uploadFileName": file_record.file_name,
                    "uploadStatusDesc": file_record.upload_status_desc
                }
            ],
            "uploadStatus": 1
        },
        "status": "200",
        "successResponse": True
    }
```

**Step 4: 验证上传接口返回完整字段**

重启服务并调用上传接口，检查响应是否包含所有字段。

---

## Task 3: 添加 get_upload_status 方法

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

**Step 1: 实现 get_upload_status 方法**

在 `FileService` 类中添加新方法：

```python
def get_upload_status(self, group_id: int, log_id: int = None) -> dict:
    """获取文件上传状态（接口5）"""
    logs = []

    if log_id:
        # 返回特定文件的状态
        if log_id in self.file_records:
            record = self.file_records[log_id]
            if record.group_id == group_id:
                logs.append(self._build_log_detail(record))
    else:
        # 返回该项目的所有文件状态
        for record in self.file_records.values():
            if record.group_id == group_id:
                logs.append(self._build_log_detail(record))

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

def _build_log_detail(self, record: FileRecord) -> dict:
    """构建日志详情对象"""
    return {
        "accountNoList": record.account_no_list,
        "bankName": record.bank_name,
        "dataTypeInfo": record.data_type_info,
        "downloadFileName": record.download_file_name,
        "enterpriseNameList": record.enterprise_name_list,
        "fileSize": record.file_size,
        "fileUploadBy": record.file_upload_by,
        "fileUploadByUserName": record.file_upload_by_user_name,
        "fileUploadTime": record.file_upload_time,
        "isSplit": record.is_split,
        "leId": record.le_id,
        "logId": record.log_id,
        "logMeta": record.log_meta,
        "logType": record.log_type,
        "loginLeId": record.login_le_id,
        "lostHeader": record.lost_header,
        "realBankName": record.real_bank_name,
        "rows": record.rows,
        "source": record.source,
        "status": record.status,
        "templateName": record.template_name,
        "totalRecords": record.total_records,
        "trxDateEndId": record.trx_date_end_id,
        "trxDateStartId": record.trx_date_start_id,
        "uploadFileName": record.file_name,
        "uploadStatusDesc": record.upload_status_desc
    }
```

**Step 2: 验证方法能正确查询文件记录**

在代码中确保 `file_records` 字典正确初始化和管理。

---

## Task 4: 在 API 路由中添加新接口

**Files:**
- Modify: `routers/api.py`

**Step 1: 读取现有 api.py 文件**

查看当前的路由定义。

**Step 2: 添加 GET 接口路由**

在接口5的位置（check_parse_status 和 delete_files 之间）添加：

```python
# ==================== 接口5：获取文件上传状态 ====================
@router.get("/watson/api/project/bs/upload")
async def get_upload_status(
    groupId: int = Form(..., description="项目id"),
    logId: Optional[int] = Form(None, description="文件id"),
):
    """获取单个文件上传后的状态

    如果不提供 logId，返回该项目的所有文件状态
    """
    return file_service.get_upload_status(groupId, logId)
```

**Step 3: 确认导入了 Optional**

在文件顶部确认：

```python
from typing import List, Optional
```

**Step 4: 验证新接口出现在 Swagger 文档中**

重启服务，访问 http://localhost:8000/docs，确认能看到新的 GET 接口。

---

## Task 5: 更新 check_parse_status 响应字段

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

**Step 1: 修改 check_parse_status 方法**

确保返回的 pendingList 包含所有字段：

```python
def check_parse_status(self, group_id: int, inprogress_list: str) -> dict:
    """检查文件解析状态"""
    log_ids = [int(id.strip()) for id in inprogress_list.split(",")]

    pending_list = []
    all_parsing_complete = True

    for log_id in log_ids:
        if log_id in self.file_records:
            record = self.file_records[log_id]
            if record.parsing:
                all_parsing_complete = False

            pending_list.append(self._build_log_detail(record))

    return {
        "code": "200",
        "data": {
            "parsing": not all_parsing_complete,
            "pendingList": pending_list
        },
        "status": "200",
        "successResponse": True
    }
```

**Step 2: 验证解析状态接口返回完整字段**

调用接口4，检查响应中的 pendingList 是否包含所有字段。

---

## Task 6: 更新 delete_files 方法响应格式

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

**Step 1: 修改 delete_files 方法**

确保响应的 code 字段为 "200 OK"：

```python
def delete_files(self, group_id: int, log_ids: List[int], user_id: int) -> dict:
    """批量删除文件"""
    deleted_count = 0
    for log_id in log_ids:
        if log_id in self.file_records:
            del self.file_records[log_id]
            deleted_count += 1

    return {
        "code": "200 OK",  # 注意：这里是 "200 OK" 不是 "200"
        "data": {
            "message": "delete.files.success"
        },
        "message": "delete.files.success",
        "status": "200",
        "successResponse": True
    }
```

**Step 2: 验证删除接口响应格式正确**

调用删除接口，检查响应的 code 字段是否为 "200 OK"。

---

## Task 7: 更新 token.json 响应模板

**Files:**
- Modify: `config/responses/token.json`

**Step 1: 确认 analysisType 为 Integer**

确保 token.json 中的 analysisType 字段类型正确：

```json
{
  "success_response": {
    "code": "200",
    "data": {
      "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.mock_token_{project_id}",
      "projectId": "{project_id}",
      "projectNo": "{project_no}",
      "entityName": "{entity_name}",
      "analysisType": 0
    },
    "message": "create.token.success",
    "status": "200",
    "successResponse": true
  }
}
```

确认 `analysisType` 的值是数字 0，不是字符串 "0"。

**Step 2: 验证接口1响应正确**

调用 getToken 接口，检查 analysisType 的类型。

---

## Task 8: 创建 upload_status.json 响应模板

**Files:**
- Create: `config/responses/upload_status.json`

**Step 1: 创建新的响应模板文件**

创建文件并添加内容（虽然实际响应在代码中构建，但保留模板作为参考）：

```json
{
  "success_response": {
    "code": "200",
    "data": {
      "logs": [
        {
          "accountNoList": ["18785967364"],
          "bankName": "ALIPAY",
          "dataTypeInfo": ["CSV", ","],
          "downloadFileName": "支付宝.csv",
          "enterpriseNameList": ["曾孝成"],
          "fileSize": 16322,
          "fileUploadBy": 448,
          "fileUploadByUserName": "admin@support.com",
          "fileUploadTime": "2025-03-13 08:45:32",
          "isSplit": 0,
          "leId": 10741,
          "logId": 13994,
          "logMeta": "{\"lostHeader\":[],\"balanceAmount\":\"-1\"}",
          "logType": "bankstatement",
          "loginLeId": 10741,
          "lostHeader": [],
          "realBankName": "ALIPAY",
          "rows": 0,
          "source": "http",
          "status": -5,
          "templateName": "ALIPAY_T220708",
          "totalRecords": 127,
          "trxDateEndId": 20231231,
          "trxDateStartId": 20230102,
          "uploadFileName": "支付宝.pdf",
          "uploadStatusDesc": "data.wait.confirm.newaccount"
        }
      ],
      "status": "",
      "accountId": 8954,
      "currency": "CNY"
    },
    "status": "200",
    "successResponse": true
  }
}
```

---

## Task 9: 更新 bank_statement.json 响应模板

**Files:**
- Modify: `config/responses/bank_statement.json`

**Step 1: 补充流水记录的所有字段**

确保 bank_statement.json 包含所有50+个字段：

```json
{
  "success_response": {
    "code": "200",
    "data": {
      "bankStatementList": [
        {
          "accountId": 0,
          "accountMaskNo": "101015251071645",
          "accountingDate": "2024-02-01",
          "accountingDateId": 20240201,
          "archivingFlag": 0,
          "attachments": 0,
          "balanceAmount": 4814.82,
          "bank": "ZJRCU",
          "bankComments": "",
          "bankStatementId": 12847662,
          "bankTrxNumber": "1a10458dd5c3366d7272285812d434fc",
          "batchId": 19135,
          "cashType": "1",
          "commentsNum": 0,
          "crAmount": 0,
          "cretNo": "230902199012261247",
          "currency": "CNY",
          "customerAccountMaskNo": "597671502",
          "customerBank": "",
          "customerId": -1,
          "customerName": "小店",
          "customerReference": "",
          "downPaymentFlag": 0,
          "drAmount": 245.8,
          "exceptionType": "",
          "groupId": 16238,
          "internalFlag": 0,
          "leId": 16308,
          "leName": "张传伟",
          "overrideBsId": 0,
          "paymentMethod": "",
          "sourceCatalogId": 0,
          "split": 0,
          "subBankstatementId": 0,
          "toDoFlag": 0,
          "transAmount": 245.8,
          "transFlag": "P",
          "transTypeId": 0,
          "transformAmount": 0,
          "transformCrAmount": 0,
          "transformDrAmount": 0,
          "transfromBalanceAmount": 0,
          "trxBalance": 0,
          "trxDate": "2024-02-01 10:33:44",
          "userMemo": "财付通消费_小店"
        }
      ],
      "totalCount": 131
    },
    "status": "200",
    "successResponse": true
  }
}
```

**Step 2: 验证流水查询接口返回所有字段**

调用 getBSByLogId 接口，检查响应是否包含所有字段。

---

## Task 10: 添加 40100 错误码

**Files:**
- Modify: `utils/error_simulator.py`

**Step 1: 在 ERROR_CODES 字典中添加新错误码**

```python
ERROR_CODES = {
    "40100": {"code": "40100", "message": "未知异常"},
    "40101": {"code": "40101", "message": "appId错误"},
    "40102": {"code": "40102", "message": "appSecretCode错误"},
    "40104": {"code": "40104", "message": "可使用项目次数为0，无法创建项目"},
    "40105": {"code": "40105", "message": "只读模式下无法新建项目"},
    "40106": {"code": "40106", "message": "错误的分析类型，不在规定的取值范围内"},
    "40107": {"code": "40107", "message": "当前系统不支持的分析类型"},
    "40108": {"code": "40108", "message": "当前用户所属行社无权限"},
    "501014": {"code": "501014", "message": "无行内流水文件"},
}
```

**Step 2: 验证错误码能正确触发**

调用任意接口，在参数中包含 `error_40100`，检查是否返回对应错误。

---

## Task 11: 更新 CLAUDE.md 文档

**Files:**
- Modify: `CLAUDE.md`

**Step 1: 更新接口列表说明**

在 "API 接口说明" 部分更新为：

```markdown
## API 接口说明

7个核心接口：

1. `/account/common/getToken` (POST) - 创建项目并获取 Token
2. `/watson/api/project/remoteUploadSplitFile` (POST) - 上传流水文件（multipart/form-data）
3. `/watson/api/project/getJZFileOrZjrcuFile` (POST) - 拉取行内流水
4. `/watson/api/project/upload/getpendings` (POST) - 检查文件解析状态
5. `/watson/api/project/bs/upload` (GET) - 获取单个文件上传后的状态
6. `/watson/api/project/batchDeleteUploadFile` (POST) - 批量删除文件
7. `/watson/api/project/getBSByLogId` (POST) - 获取银行流水（分页）

详细接口文档请访问 Swagger UI (`/docs`) 或查看 `assets/兰溪-流水分析对接3.md`。
```

**Step 2: 更新注意事项**

添加关于响应字段完整性的说明：

```markdown
## 注意事项

- **数据持久化**: 所有数据存储在内存中，服务重启后数据丢失
- **响应字段完整性**: 所有接口响应字段完全对齐接口文档示例
- **并发安全**: 当前实现未考虑多线程安全，生产环境需要加锁
- **文件存储**: 上传的文件不实际保存，仅模拟元数据
- **错误标记**: 错误触发通过字符串匹配实现，确保测试数据唯一性
- **后台任务**: FastAPI BackgroundTasks 在同一进程内执行，不会阻塞响应
- **请求头处理**: X-Xencio-Client-Id 请求头不验证，接受任意值
```

---

## Task 12: 最终验证

**Files:**
- All modified files

**Step 1: 启动服务**

```bash
python main.py
```

预期：服务正常启动，无报错。

**Step 2: 访问 Swagger 文档**

访问 http://localhost:8000/docs

预期：能看到所有7个接口，包括新增的 GET 接口。

**Step 3: 测试所有7个接口**

使用 Swagger UI 或 curl 测试每个接口，确保：
1. 接口1：返回包含 analysisType (Integer) 的响应
2. 接口2：返回包含 accountsOfLog 和完整 uploadLogList 的响应
3. 接口3：返回 logId 数组
4. 接口4：返回包含完整字段的 pendingList
5. 接口5：返回包含完整字段的 logs 数组
6. 接口6：返回 code 为 "200 OK" 的响应
7. 接口7：返回包含所有50+字段的 bankStatementList

**Step 4: 测试错误码**

调用接口1，使用参数 `projectNo: "test_error_40100"`

预期：返回 40100 错误。

---

## Success Criteria

- [x] FileRecord 包含所有必需字段
- [x] upload_file 方法正确初始化所有字段
- [x] get_upload_status 方法正确实现
- [x] 新接口出现在 /docs 中
- [x] 所有响应字段完全对齐文档示例
- [x] 40100 错误码能正确触发
- [x] 服务启动无报错
- [x] 所有7个接口都能正常调用

---

## Notes

- 所有代码修改都保持向后兼容
- 无需数据库迁移（使用内存存储）
- 错误处理机制保持不变
- 请求头 X-Xencio-Client-Id 不验证
