# 上虞个人利率测算输入参数后端 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:** 补齐个人创建 DTO、流程转换和模型调用参数，使个人模型请求完整覆盖 Excel 要求的输入字段。

**Architecture:** 维持现有“页面提交 -> DTO -> Workflow -> ModelInvokeDTO -> form-urlencoded 调用”的链路，只在个人创建与模型调用相关文件中补齐字段和转换规则。通过后端单元测试与真实接口联调覆盖必填、正常和分支场景。

**Tech Stack:** Spring Boot、MyBatis-Plus、Lombok、JUnit、Maven

---

## 后端模型输入参数确认

个人链路最终需要发给模型的 16 个参数如下：

- `serialNum`：服务层自动生成
- `orgCode`：服务层默认值，当前代码为 `892000`
- `runType`：服务层默认值 `1`
- `custIsn`：页面输入透传
- `custType`：个人链路固定 `个人`
- `custName`：页面输入，调用模型前解密后透传
- `idType`：页面输入透传
- `idNum`：页面输入，调用模型前解密后透传
- `guarType`：页面输入透传
- `applyAmt`：页面输入透传
- `loanPurpose`：页面输入透传
- `loanTerm`：页面输入透传
- `bizProof`：页面开关，调用模型前转 `0/1`
- `loanLoop`：页面开关，调用模型前转 `0/1`
- `collThirdParty`：页面开关，调用模型前转 `0/1`
- `collType`：页面下拉透传

调用方式确认：

- 参数载体：`ModelInvokeDTO`
- 组装方式：`BeanUtils.copyProperties(loanPricingWorkflow, modelInvokeDTO)`
- 请求格式：`application/x-www-form-urlencoded`
- 发送入口：`ModelService#invokeModel`

## 文件结构

- Modify: [ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java](/Users/wkc/Desktop/loan-pricing/loan-pricing/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java)
  - 增加 `loanPurpose`、`loanTerm`
- Modify: [ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java](/Users/wkc/Desktop/loan-pricing/loan-pricing/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java)
  - 将新增字段映射到 `LoanPricingWorkflow`
- Modify: [ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/ModelInvokeDTO.java](/Users/wkc/Desktop/loan-pricing/loan-pricing/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/ModelInvokeDTO.java)
  - 增加 `loanTerm`、`loanLoop`
- Modify: [ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingModelService.java](/Users/wkc/Desktop/loan-pricing/loan-pricing/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingModelService.java)
  - 在个人模型调用前规范化 `0/1`
- Create: [ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java](/Users/wkc/Desktop/loan-pricing/loan-pricing/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java)
  - 覆盖字段存在与值转换

### Task 1: 为新增字段补失败测试

**Files:**
- Create: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java`

- [ ] **Step 1: 编写字段与转换测试**

```java
@Test
void shouldContainLoanPurposeLoanTermAndLoanLoop() {
    assertThat(ModelInvokeDTO.class.getDeclaredField("loanTerm")).isNotNull();
    assertThat(ModelInvokeDTO.class.getDeclaredField("loanLoop")).isNotNull();
}
```

```java
@Test
void shouldConvertPersonalBooleanFlagsToZeroOne() {
    // 构造个人流程对象，断言模型请求中的 bizProof/loanLoop/collThirdParty 为 1/0
}
```

- [ ] **Step 2: 运行测试并确认先失败**

Run: `mvn -pl ruoyi-loan-pricing -Dtest=LoanPricingModelServicePersonalParamsTest test`

Expected: FAIL，提示字段不存在或转换逻辑未实现

- [ ] **Step 3: 保持测试只覆盖本次改动相关链路**

```java
// 仅断言 PersonalLoanPricingCreateDTO / LoanPricingConverter / ModelInvokeDTO / LoanPricingModelService
```

- [ ] **Step 4: 增加最终模型请求参数断言**

```java
// 断言 requestBody 包含 serialNum、orgCode、runType、custIsn、custType、custName、
// idType、idNum、guarType、applyAmt、loanPurpose、loanTerm、bizProof、
// loanLoop、collThirdParty、collType
```

- [ ] **Step 5: 再次运行测试确认失败原因稳定**

Run: `mvn -pl ruoyi-loan-pricing -Dtest=LoanPricingModelServicePersonalParamsTest test`

Expected: FAIL，失败点与新增字段缺失一致

- [ ] **Step 6: 提交**

```bash
git add ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java
git commit -m "新增个人测算输入参数后端测试"
```

### Task 2: 补齐个人创建 DTO 与流程映射

**Files:**
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java`
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java`

- [ ] **Step 1: 在个人 DTO 中增加 `loanPurpose`**

```java
@NotBlank(message = "贷款用途不能为空")
@Pattern(regexp = "^(consumer|business)$", message = "贷款用途必须是 consumer 或 business")
private String loanPurpose;
```

- [ ] **Step 2: 在个人 DTO 中增加 `loanTerm`**

```java
@NotBlank(message = "借款期限不能为空")
private String loanTerm;
```

- [ ] **Step 3: 在转换器中映射新增字段**

```java
entity.setLoanPurpose(dto.getLoanPurpose());
entity.setLoanTerm(dto.getLoanTerm());
```

- [ ] **Step 4: 运行相关测试确认 DTO 与映射通过**

Run: `mvn -pl ruoyi-loan-pricing -Dtest=LoanPricingModelServicePersonalParamsTest test`

Expected: 仍可能 FAIL，但失败点已推进到模型调用层

- [ ] **Step 5: 提交**

```bash
git add ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java
git commit -m "补齐个人测算创建参数字段"
```

### Task 3: 补齐模型调用 DTO 与个人参数规范化

**Files:**
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/ModelInvokeDTO.java`
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingModelService.java`

- [ ] **Step 1: 在模型调用 DTO 中增加缺失字段**

```java
private String loanTerm;
private String loanLoop;
```

- [ ] **Step 2: 在个人模型调用前做 `0/1` 转换**

```java
if ("个人".equals(loanPricingWorkflow.getCustType())) {
    modelInvokeDTO.setBizProof(toZeroOne(modelInvokeDTO.getBizProof()));
    modelInvokeDTO.setLoanLoop(toZeroOne(modelInvokeDTO.getLoanLoop()));
    modelInvokeDTO.setCollThirdParty(toZeroOne(modelInvokeDTO.getCollThirdParty()));
}
```

- [ ] **Step 3: 抽出最小辅助方法，避免散落重复逻辑**

```java
private String toZeroOne(String value) {
    if ("true".equals(value) || "1".equals(value)) return "1";
    if ("false".equals(value) || "0".equals(value)) return "0";
    return value;
}
```

- [ ] **Step 4: 运行测试确认通过**

Run: `mvn -pl ruoyi-loan-pricing -Dtest=LoanPricingModelServicePersonalParamsTest test`

Expected: PASS

- [ ] **Step 5: 提交**

```bash
git add ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/ModelInvokeDTO.java ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingModelService.java
git commit -m "规范个人测算模型调用参数"
```

### Task 4: 后端联调与接口验证

**Files:**
- Verify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowController.java`
- Verify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImpl.java`

- [ ] **Step 1: 重新编译并重启后端进程**

Run: `mvn -pl ruoyi-admin -am package -DskipTests`

Expected: 打包成功，随后重启运行中的后端服务使最新代码生效

- [ ] **Step 2: 验证正常场景**

Run: 调用 `POST /loanPricing/workflow/create/personal`

Expected:
- 返回创建成功
- 请求体包含 `loanPurpose`、`loanTerm`
- 模型请求中完整带出 16 个参数
- 其中 `bizProof`、`loanLoop`、`collThirdParty` 为 `0/1`

- [ ] **Step 3: 验证必填缺失场景**

Run: 缺少 `loanPurpose` 或 `loanTerm` 分别调用接口

Expected: 返回参数校验失败

- [ ] **Step 4: 验证分支场景**

Run: 以不同开关组合调用接口

Expected:
- `bizProof=true` -> 模型入参 `1`
- `bizProof=false` -> 模型入参 `0`
- `loanLoop=true/false` 与 `collThirdParty=true/false` 同理

- [ ] **Step 5: 验证结束后停止后端进程并提交**

```bash
git add ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/ModelInvokeDTO.java ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingModelService.java ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java
git commit -m "完成个人测算输入参数后端联调"
```
