448 lines
14 KiB
Markdown
448 lines
14 KiB
Markdown
# Credit Parsing Mock Server 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` 中新增征信解析 Mock 能力,提供 `POST /xfeature-mngs/conversation/htmlEval` 接口,支持稳定随机生成征信 `payload`、最小错误码模拟与健康检查能力。
|
||
|
||
**Architecture:** 复用现有 FastAPI 应用与测试基础设施,不新建独立服务。征信解析能力拆成 `schema 配置 + payload 生成 service + 调试 service + 独立 router` 四层,接口层只负责 `form-data` 接收与响应组装,字段生成和错误处理分别下沉到独立 service 中,避免与现有 LSFX Mock 逻辑混杂。
|
||
|
||
**Tech Stack:** Python 3, FastAPI, pytest, python-multipart, JSON, Bash
|
||
|
||
---
|
||
|
||
## File Structure
|
||
|
||
- `lsfx-mock-server/config/credit_feature_schema.json`: 固化 Excel 中 30 个征信指标字段的主题域、字段名、类型与枚举范围。
|
||
- `lsfx-mock-server/config/credit_response_examples.json`: 固化成功与失败响应模板,避免响应结构散落在路由中。
|
||
- `lsfx-mock-server/services/credit_payload_service.py`: 负责稳定随机种子生成、schema 加载和 `payload` 构造。
|
||
- `lsfx-mock-server/services/credit_debug_service.py`: 负责参数校验、调试标记识别与错误响应封装。
|
||
- `lsfx-mock-server/routers/credit_api.py`: 负责新增征信解析接口和征信健康检查接口。
|
||
- `lsfx-mock-server/main.py`: 注册征信解析 router,并在应用描述中补充接口说明。
|
||
- `lsfx-mock-server/README.md`: 补充征信解析 Mock 的启动方式、接口路径和调试用法。
|
||
- `lsfx-mock-server/tests/test_credit_payload_service.py`: 锁定稳定随机与字段类型语义。
|
||
- `lsfx-mock-server/tests/test_credit_api.py`: 锁定征信解析接口成功、缺参、错误码与健康检查行为。
|
||
- `lsfx-mock-server/tests/test_startup.py`: 锁定新 router 已被主应用注册。
|
||
- `docs/reports/implementation/2026-03-23-credit-parsing-mock-server-backend-record.md`: 记录本次后端实施内容。
|
||
- `docs/tests/records/2026-03-23-credit-parsing-mock-server-backend-verification.md`: 记录本次后端验证命令与结果。
|
||
|
||
### Task 1: 建立征信字段 schema 和稳定随机 payload 生成能力
|
||
|
||
**Files:**
|
||
- Create: `lsfx-mock-server/config/credit_feature_schema.json`
|
||
- Create: `lsfx-mock-server/config/credit_response_examples.json`
|
||
- Create: `lsfx-mock-server/services/credit_payload_service.py`
|
||
- Create: `lsfx-mock-server/tests/test_credit_payload_service.py`
|
||
- Reference: `docs/design/2026-03-23-credit-parsing-mock-server-design.md`
|
||
- Reference: `assets/征信解析/征信解析接口payload.xlsx`
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
在 `lsfx-mock-server/tests/test_credit_payload_service.py` 中先补两条失败用例,锁定“同一输入返回稳定随机相同结果”和“schema 中的状态字段严格落在枚举范围内”:
|
||
|
||
```python
|
||
from services.credit_payload_service import CreditPayloadService
|
||
|
||
|
||
def test_generate_payload_should_be_stable_for_same_input():
|
||
service = CreditPayloadService("config/credit_feature_schema.json")
|
||
|
||
payload1 = service.generate_payload(
|
||
model="LXCUSTALL",
|
||
h_type="PERSON",
|
||
filename="credit-report-a.html",
|
||
)
|
||
payload2 = service.generate_payload(
|
||
model="LXCUSTALL",
|
||
h_type="PERSON",
|
||
filename="credit-report-a.html",
|
||
)
|
||
|
||
assert payload1 == payload2
|
||
assert set(payload1.keys()) == {"lx_header", "lx_debt", "lx_publictype"}
|
||
assert len(payload1["lx_debt"]) == 21
|
||
assert len(payload1["lx_publictype"]) == 6
|
||
|
||
|
||
def test_generate_payload_should_use_schema_type_rules():
|
||
service = CreditPayloadService("config/credit_feature_schema.json")
|
||
|
||
payload = service.generate_payload(
|
||
model="LXCUSTALL",
|
||
h_type="ENTERPRISE",
|
||
filename="credit-report-b.html",
|
||
)
|
||
|
||
assert payload["lx_debt"]["uncle_bank_house_state"] in {"正常", "逾期", "不良"}
|
||
assert payload["lx_header"]["report_time"].count("-") == 2
|
||
assert payload["lx_publictype"]["civil_cnt"].isdigit()
|
||
```
|
||
|
||
- [ ] **Step 2: Run test to verify it fails**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd lsfx-mock-server
|
||
python3 -m pytest tests/test_credit_payload_service.py -v
|
||
```
|
||
|
||
Expected:
|
||
|
||
- `FAIL`
|
||
- 原因是 `CreditPayloadService`、schema 配置文件和响应模板尚不存在
|
||
|
||
- [ ] **Step 3: Write minimal implementation**
|
||
|
||
按最小路径落地:
|
||
|
||
1. 在 `lsfx-mock-server/config/credit_feature_schema.json` 中把 Excel 30 个字段完整整理为 JSON,至少包含:
|
||
|
||
```json
|
||
[
|
||
{ "domain": "lx_header", "field": "query_cert_no", "type": "string" },
|
||
{ "domain": "lx_header", "field": "query_cust_name", "type": "string" },
|
||
{ "domain": "lx_header", "field": "report_time", "type": "string" },
|
||
{ "domain": "lx_debt", "field": "uncle_bank_house_state", "type": "status", "options": ["正常", "逾期", "不良"] }
|
||
]
|
||
```
|
||
|
||
2. 在 `lsfx-mock-server/config/credit_response_examples.json` 中固化成功/失败模板:
|
||
|
||
```json
|
||
{
|
||
"success": { "message": "成功", "payload": {}, "status_code": "0" },
|
||
"errors": {
|
||
"ERR_99999": { "message": "关键参数缺失,参数名: XX", "payload": null, "status_code": "ERR_99999" }
|
||
}
|
||
}
|
||
```
|
||
|
||
3. 在 `lsfx-mock-server/services/credit_payload_service.py` 中实现最小服务:
|
||
|
||
```python
|
||
class CreditPayloadService:
|
||
def __init__(self, schema_path: str):
|
||
self.schema_path = schema_path
|
||
self.schema = self._load_schema()
|
||
|
||
def generate_payload(self, model: str, h_type: str, filename: str) -> dict:
|
||
rng = random.Random(self._build_seed(model, h_type, filename))
|
||
...
|
||
```
|
||
|
||
4. 生成规则只保留设计文档确认的 4 类:
|
||
- `string`
|
||
- `amount`
|
||
- `count`
|
||
- `status`
|
||
|
||
- [ ] **Step 4: Run test to verify it passes**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd lsfx-mock-server
|
||
python3 -m pytest tests/test_credit_payload_service.py -v
|
||
```
|
||
|
||
Expected:
|
||
|
||
- `PASS`
|
||
- 同一输入生成稳定一致结果,不同字段类型输出格式符合约定
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git add lsfx-mock-server/config/credit_feature_schema.json lsfx-mock-server/config/credit_response_examples.json lsfx-mock-server/services/credit_payload_service.py lsfx-mock-server/tests/test_credit_payload_service.py
|
||
git commit -m "新增征信解析字段配置与生成服务"
|
||
```
|
||
|
||
### Task 2: 实现参数校验、错误码模拟和征信解析接口
|
||
|
||
**Files:**
|
||
- Create: `lsfx-mock-server/services/credit_debug_service.py`
|
||
- Create: `lsfx-mock-server/routers/credit_api.py`
|
||
- Create: `lsfx-mock-server/tests/test_credit_api.py`
|
||
- Modify: `lsfx-mock-server/tests/conftest.py`
|
||
- Reference: `docs/design/2026-03-23-credit-parsing-mock-server-design.md`
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
在 `lsfx-mock-server/tests/test_credit_api.py` 中先补 4 类失败/成功用例,确保接口不走 FastAPI 默认 422,而是走说明书约定的业务响应:
|
||
|
||
```python
|
||
def test_html_eval_should_return_credit_payload(client):
|
||
response = client.post(
|
||
"/xfeature-mngs/conversation/htmlEval",
|
||
data={"model": "LXCUSTALL", "hType": "PERSON"},
|
||
files={"file": ("credit.html", b"<html></html>", "text/html")},
|
||
)
|
||
|
||
assert response.status_code == 200
|
||
data = response.json()
|
||
assert data["status_code"] == "0"
|
||
assert data["message"] == "成功"
|
||
assert "lx_header" in data["payload"]
|
||
|
||
|
||
def test_html_eval_should_return_err_99999_for_missing_model(client):
|
||
response = client.post(
|
||
"/xfeature-mngs/conversation/htmlEval",
|
||
data={"hType": "PERSON"},
|
||
files={"file": ("credit.html", b"<html></html>", "text/html")},
|
||
)
|
||
|
||
assert response.status_code == 200
|
||
assert response.json()["status_code"] == "ERR_99999"
|
||
|
||
|
||
def test_html_eval_should_return_err_10003_for_invalid_h_type(client):
|
||
response = client.post(
|
||
"/xfeature-mngs/conversation/htmlEval",
|
||
data={"model": "LXCUSTALL", "hType": "JSON"},
|
||
files={"file": ("credit.html", b"<html></html>", "text/html")},
|
||
)
|
||
|
||
assert response.status_code == 200
|
||
assert response.json()["status_code"] == "ERR_10003"
|
||
|
||
|
||
def test_html_eval_should_support_debug_error_marker(client):
|
||
response = client.post(
|
||
"/xfeature-mngs/conversation/htmlEval",
|
||
data={"model": "error_ERR_10001", "hType": "PERSON"},
|
||
files={"file": ("credit.html", b"<html></html>", "text/html")},
|
||
)
|
||
|
||
assert response.status_code == 200
|
||
assert response.json()["status_code"] == "ERR_10001"
|
||
```
|
||
|
||
- [ ] **Step 2: Run test to verify it fails**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd lsfx-mock-server
|
||
python3 -m pytest tests/test_credit_api.py -v
|
||
```
|
||
|
||
Expected:
|
||
|
||
- `FAIL`
|
||
- 原因是征信解析 router 和调试 service 尚不存在
|
||
|
||
- [ ] **Step 3: Write minimal implementation**
|
||
|
||
按设计文档实现最小闭环:
|
||
|
||
1. 在 `lsfx-mock-server/services/credit_debug_service.py` 中定义错误码映射和业务校验入口:
|
||
|
||
```python
|
||
class CreditDebugService:
|
||
ERROR_MESSAGES = {
|
||
"ERR_99999": "关键参数缺失,参数名: XX",
|
||
"ERR_10001": "无效的证件号码",
|
||
"ERR_10002": "无效的主题域",
|
||
"ERR_10003": "报文类型无效,仅支持JSON/XML",
|
||
"ERR_10004": "无效机构号或行社号",
|
||
"ERR_10005": "无权限访问",
|
||
"ERR_10006": "尽调报告生成异常:异步事件发送失败",
|
||
}
|
||
```
|
||
|
||
2. 在 `lsfx-mock-server/routers/credit_api.py` 中让参数全部使用可空 `Form(None)` / `File(None)`,再手工校验,避免缺参时被 FastAPI 直接拦成 422:
|
||
|
||
```python
|
||
@router.post("/xfeature-mngs/conversation/htmlEval")
|
||
async def html_eval(
|
||
model: Optional[str] = Form(None),
|
||
hType: Optional[str] = Form(None),
|
||
file: Optional[UploadFile] = File(None),
|
||
):
|
||
...
|
||
```
|
||
|
||
3. 正常流程调用 `CreditPayloadService.generate_payload()`。
|
||
4. 失败流程统一返回 `message + payload(null) + status_code`。
|
||
5. 在 `tests/conftest.py` 中按需补共享 fixture,例如测试上传的默认 HTML 文件内容。
|
||
|
||
- [ ] **Step 4: Run test to verify it passes**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd lsfx-mock-server
|
||
python3 -m pytest tests/test_credit_api.py -v
|
||
```
|
||
|
||
Expected:
|
||
|
||
- `PASS`
|
||
- 接口成功、缺参、非法 `hType`、调试错误码都返回预期业务结构
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git add lsfx-mock-server/services/credit_debug_service.py lsfx-mock-server/routers/credit_api.py lsfx-mock-server/tests/test_credit_api.py lsfx-mock-server/tests/conftest.py
|
||
git commit -m "新增征信解析接口与错误模拟"
|
||
```
|
||
|
||
### Task 3: 注册新 router 并补启动文档
|
||
|
||
**Files:**
|
||
- Modify: `lsfx-mock-server/main.py`
|
||
- Modify: `lsfx-mock-server/README.md`
|
||
- Modify: `lsfx-mock-server/tests/test_startup.py`
|
||
- Reference: `lsfx-mock-server/dev.py`
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
先在 `lsfx-mock-server/tests/test_startup.py` 中补应用注册断言,锁定主应用已经包含征信解析接口和健康检查接口:
|
||
|
||
```python
|
||
from main import app
|
||
|
||
|
||
def test_app_should_register_credit_mock_routes():
|
||
paths = {route.path for route in app.routes}
|
||
|
||
assert "/xfeature-mngs/conversation/htmlEval" in paths
|
||
assert "/credit/health" in paths
|
||
```
|
||
|
||
- [ ] **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` 还未注册征信解析 router
|
||
|
||
- [ ] **Step 3: Write minimal implementation**
|
||
|
||
1. 在 `lsfx-mock-server/main.py` 中引入并注册 `credit_api.router`:
|
||
|
||
```python
|
||
from routers import api, credit_api
|
||
|
||
app.include_router(api.router, tags=["流水分析接口"])
|
||
app.include_router(credit_api.router, tags=["征信解析接口"])
|
||
```
|
||
|
||
2. 在应用描述中补充征信解析能力说明。
|
||
3. 在 `lsfx-mock-server/README.md` 中补充:
|
||
- 新接口路径
|
||
- `curl` 或 `requests` 调用示例
|
||
- `error_ERR_10001` 调试方式
|
||
- `GET /credit/health` 用法
|
||
|
||
- [ ] **Step 4: Run test to verify it passes**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd lsfx-mock-server
|
||
python3 -m pytest tests/test_startup.py tests/test_credit_api.py -v
|
||
```
|
||
|
||
Expected:
|
||
|
||
- `PASS`
|
||
- 主应用已注册征信解析 router,README 与运行方式保持一致
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git add lsfx-mock-server/main.py lsfx-mock-server/README.md lsfx-mock-server/tests/test_startup.py
|
||
git commit -m "注册征信解析Mock路由并补充说明"
|
||
```
|
||
|
||
### Task 4: 端到端验证并沉淀实施记录
|
||
|
||
**Files:**
|
||
- Create: `docs/reports/implementation/2026-03-23-credit-parsing-mock-server-backend-record.md`
|
||
- Create: `docs/tests/records/2026-03-23-credit-parsing-mock-server-backend-verification.md`
|
||
- Reference: `docs/design/2026-03-23-credit-parsing-mock-server-design.md`
|
||
- Reference: `docs/plans/backend/2026-03-23-credit-parsing-mock-server-backend-implementation.md`
|
||
|
||
- [ ] **Step 1: Run targeted automated tests**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd lsfx-mock-server
|
||
python3 -m pytest tests/test_credit_payload_service.py tests/test_credit_api.py tests/test_startup.py -v
|
||
```
|
||
|
||
Expected:
|
||
|
||
- `PASS`
|
||
- 征信字段生成、接口返回和路由注册全部通过
|
||
|
||
- [ ] **Step 2: Run manual startup and smoke verification**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
cd lsfx-mock-server
|
||
python3 main.py > /tmp/credit-mock-server.log 2>&1 &
|
||
echo $! > /tmp/credit-mock-server.pid
|
||
```
|
||
|
||
再执行:
|
||
|
||
```bash
|
||
curl -s http://127.0.0.1:8000/credit/health
|
||
|
||
curl -s -X POST http://127.0.0.1:8000/xfeature-mngs/conversation/htmlEval \
|
||
-F model=LXCUSTALL \
|
||
-F hType=PERSON \
|
||
-F file=@/tmp/sample-credit.html
|
||
```
|
||
|
||
Expected:
|
||
|
||
- 健康检查返回 `healthy`
|
||
- 成功接口返回 `status_code: "0"`,且 `payload` 包含三大主题域
|
||
|
||
- [ ] **Step 3: Stop the started process**
|
||
|
||
Run:
|
||
|
||
```bash
|
||
kill "$(cat /tmp/credit-mock-server.pid)"
|
||
rm -f /tmp/credit-mock-server.pid
|
||
```
|
||
|
||
Expected:
|
||
|
||
- 本次验证启动的 Mock 进程已停止,不留下端口占用
|
||
|
||
- [ ] **Step 4: Write implementation and verification records**
|
||
|
||
在 `docs/reports/implementation/2026-03-23-credit-parsing-mock-server-backend-record.md` 中记录:
|
||
|
||
- 新增接口、配置文件、service 和测试文件
|
||
- 稳定随机策略
|
||
- 说明书歧义处理方式
|
||
|
||
在 `docs/tests/records/2026-03-23-credit-parsing-mock-server-backend-verification.md` 中记录:
|
||
|
||
- 自动化测试命令与结果
|
||
- 手工 `curl` 验证命令与结果
|
||
- 启停服务过程
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git add docs/reports/implementation/2026-03-23-credit-parsing-mock-server-backend-record.md docs/tests/records/2026-03-23-credit-parsing-mock-server-backend-verification.md
|
||
git commit -m "补充征信解析Mock后端实施与验证记录"
|
||
```
|