# CR-005　定價服務（pricing-service）契約缺口

| 項目 | 內容 |
|---|---|
| 編號 | CR-005 |
| 版本 | **1.2（2026-09-19 核定並執行完畢；2026-09-22 §8 #1 結案——`TX_TYPE` 增設）** |
| 狀態 | ✅ **已核定／已執行／已實測** |
| 提出日期 | 2026-09-19 |
| 核定日期 | 2026-09-19（BEAM PMO：四項全數授權我方設計） |
| 提出者 | WorkBuddy AI |
| 影響文件 | `SCS-006`（升 **1.1**）、`contracts/openapi/pricing-service.yaml`（由 `tools/gen_openapi.py` 產生）、`SDD-006 §3.4`、`ops/local/init/oracle/11-dev-pricing-seed.sql` |
| 關聯 | `HANDOVER §3.C #C-3`（`pricing-service` 為★關鍵路徑） |

> **核定結論（BEAM PMO 2026-09-19 原意）**
> 1. `fees` 端點——**以契約為主**（`GET` 查詢費用設定），除非與其他地方衝突。
> 2. `interest-inquiries` 參數——**由我方決定**。
> 3. ADB 輸入——**由我方設計**。
> 4. 範例數字——**由我方定奪**。
> 並指示「把 REST 端點做完」。四項之定奪見 **§6**，實作與實測見 **§7**。

---

## 1. 背景

`pricing-service` 為關鍵路徑——`statement-service` 之「最低應繳參數化」與
`posting-service` 之 `recalculateInterest` 皆等待它（見 `SCS-011 §10 #4`、
`SCS-007 §10`）。動工前盤點契約與規格，發現**4 處缺口**：
其中 **2 處屬規格與契約直接矛盾**，若自行認定將造成「文件與實物不一致」。

依 `GOV-003` 節奏第 3 條「有缺口先補 SPEC，不邊寫邊猜」，先提本 CR。

---

## 2. 缺口明細

### #1　`GET /api/v1/pricing/fees` —— 方法與語意矛盾 🔴

| 來源 | 記載 |
|---|---|
| `SCS-006 §6` | `/api/v1/pricing/fees` **POST**「費用計算」 |
| `contracts/openapi/pricing-service.yaml`（第 171 行） | `/api/v1/pricing/fees` **GET**「查詢費用設定」 |

兩者**方法不同、語意亦不同**（計算 vs 查詢）。且契約之 `FeeView`
（第 466 行）欄位為 `feeCode`／`feeName`／`amount`／`currency`，
**無任何輸入欄位**——無法表達「對哪個帳戶／哪筆交易計費」。

> 若依 `SCS-006` 實作為 POST 計算，則與現行契約不符；
> 若依契約實作為 GET 查詢，則 `SCS-006 §6` 記載錯誤。

### #2　`GET /api/v1/pricing/interest-inquiries` —— 無查詢參數 🔴

契約第 96–170 行宣告此端點，但 `parameters` **僅有 `X-Trace-Id`**，
**無任何查詢參數**（無 `accountId`／`periodStart`／`periodEnd`）。
回應卻是完整的 `InterestCalcView`（含 `interestAmount`／`details[]`）。

→ **呼叫方無從指定要查什麼**，端點無法實作。

### #3　ADB 計息缺「日終餘額序列」輸入 🔴

- `SCS-006 §7`：「`ADB`（平均日餘額）為預設，需傳入**日終餘額序列**與計息天數」
- `InterestCalcRequest`（契約第 361–389 行）欄位僅：
  `accountId`、`cardNo`、`periodStart`、`periodEnd`、
  **`principalAmount`（nullable，無預設）**

→ **沒有可傳入日終餘額序列的欄位**。ADB 無輸入即無法計算。

### #4　契約範例數字不自洽 🟡

`InterestCalcView` 之 example（契約第 419–435 行）：
`principalAmount=52345`、`days=30`、`appliedRate=0.185`、`newInterest=850`。

實算：

| 計息基礎 | 算式 | 結果 |
|---|---|---|
| 年利率／365 天 | `52345 × 0.185 × 30 ÷ 365` | **795.9** |
| 年利率／360 天 | `52345 × 0.185 × 30 ÷ 360` | **807.4** |

兩者**皆非 850**。範例與任一常見慣例都不吻合，
**不能作為演算法之驗收依據**。

---

## 3. 建議方案（待 BEAM PMO 裁定）

### #1 `fees` 端點

| 選項 | 說明 | 影響 |
|---|---|---|
| **A（建議）** | 二者**都要**：`POST /fees` 計算 ＋ `GET /fees` 查規則設定 | 契約新增 POST；`SCS-006 §6` 補 GET。語意最完整 |
| B | 僅 `POST /fees`（計算），改契約 | 查不到費用規則設定，WebUI 無從顯示 |
| C | 僅 `GET /fees`（查詢），改 `SCS-006 §6` | 費用計算無端點，`ISS-2.5` 無法驗收 |

### #2 `interest-inquiries` 參數

建議補 `accountId`（UUID，必填）、`periodStart`、`periodEnd`（選填，
預設最近一期）。查無資料時回 `CC-PRC-1002`（計息期間重疊）或 404。

### #3 ADB 輸入

| 選項 | 說明 |
|---|---|
| **A（建議）** | `InterestCalcRequest` 新增 `dailyBalances[]`（日期＋餘額）。由呼叫端（`posting-service`）提供 |
| B | 由 `pricing-service` 反查 `posting-service` 之日終餘額——**引入服務間依賴**，與 `SCS-006 §8`「計算為純函式」衝突 |
| C | 本期僅支援 `FLAT`（不計 ADB），ADB 待輸入來源確定後再開 |

### #4 範例數字

建議以**實測結果**回填，並於契約註明計息基礎（365 或 360 天）。

---

## 4. 不受缺口影響、可立即施作之範圍

以下與上述缺口無關，可先行實作並實測：

| 項目 | 依據 |
|---|---|
| `PricingRule` 聚合（20 欄位，含 `RULE_TYPE`／`CALC_METHOD`／`STATUS` 值域） | `SCS-006 §5`；**實測 Oracle `CC_PRC.PRICING_RULES` 20 欄全數吻合** |
| `PricingRuleRepository` 埠 ＋ JDBC 實作 | `SCS-006 §4` |
| 適用規則解析（依 `RULE_TYPE`／`PRODUCT_CODE`／`ORG`／`TIER`／`EFFECTIVE_FROM`-`TO`） | `SCS-006 §7`「試算須帶 `AS_OF`」 |
| `PRICING_RULES` 種子資料 | 實測現況 **0 筆**——無種子則任何查詢皆空 |
| 錯誤碼 `CC-PRC-1001/1002/3001/3002` | `SDD-006 §3.4`；`ErrorCodes.java` **8 個 PRC 常數已備** |

---

## 5. 誠實揭露（提出時）

- 本 CR 提出時**僅盤點缺口**，未變更任何契約或規格檔案。
- 契約為產生器產物（`tools/gen_openapi.py`），**不可手改 yaml**；
  核定後須改產生器再重跑（同 `CR-002` 之處理方式）。
- `pricing-service` 提出時僅有骨架（1 個 `PricingServiceApplication` ＋ 4 個 `LayeringTest`），
  **尚無任何領域程式**。

---

## 6. 核定結果與定奪（2026-09-19）

### #1　`fees` 端點 —— **以契約為準：`GET` 查詢費用設定**

- 依 BEAM PMO 指示「以契約為主，除非您發現與其他地方衝突」。
- 經查**無其他衝突**：`SDD-006 §3.4` 之 `CC-PRC-*` 錯誤碼與 `SCS-006 §8`
  皆未假定 `fees` 為寫入操作。
- 故 `SCS-006 §6` 表格之「`POST` 費用計算」**須隨之更正**（已於 1.1 版修正），
  使文件與契約一致。
- 本服務**不做費用計算**，僅回傳設定（金額取自 `FEE_AMOUNT`）。

### #2　`interest-inquiries` 參數 —— 定奪如下

| 參數 | 必填 | 說明 |
|---|:---:|---|
| `accountId` | ✅ | 帳戶識別碼 |
| `periodStart` | — | 省略時為 `periodEnd` 前 30 日 |
| `periodEnd` | — | 省略時為系統日 |
| `principalAmount` | — | 本服務**不保存帳戶餘額**，須由呼叫方提供；省略時本金視為 0 |
| `cardNo` | — | 省略時回應之 `cardNo` 為空 |

> **語意定奪**：`SCS-006 §6` 表格載明本端點為「**利率查詢**」，
> 故定位為<b>唯讀試算</b>——不寫入、不計違約金、不拆分預借現金；
> 與 `POST /interest-calculations`（正式試算，需冪等鍵）之差異即為「安全／唯讀」。

### #3　ADB 輸入 —— 定奪如下

`InterestCalcRequest` 新增 **6 個選填欄位**，每一項皆對應規格或契約描述中
原本無從滿足之要求：

| 欄位 | 需求來源 |
|---|---|
| `dailyBalances` | `SCS-006 §7`「ADB 需傳入日終餘額序列」 |
| `calcMethod` | `SCS-006 §6`「利息試算（**指定計算方法**／區間）」 |
| `dayCountBasis` | 範例數字不自洽（#4），須明定天數基礎 |
| `overduePeriods` | 契約描述「計循環利息與違約金（**至多三期**）」 |
| `cashAdvanceAmount` | 契約描述「明細分 GENERAL/**CASH_ADVANCE**」 |
| `priorCycleAdb` | `TWO_CYCLE` 需上期平均日餘額 |
| `productCode` | 規則選擇條件（`PRICING_RULES.PRODUCT_CODE`） |

**四種計息方法之定義**（原契約未定義，此為定案）：

| 方法 | 公式 | 備註 |
|---|---|---|
| `ADB` | Σ(日終餘額) × 日利率 | 預設 |
| `PREVIOUS_BALANCE` | 期初餘額 × 日利率 × 天數 | |
| `TWO_CYCLE` | (本期 ADB ＋ 上期 ADB) ÷ 2 × 日利率 × 天數 | **缺 `priorCycleAdb` 時回 `CC-PLT-4003`**，不靜默代以他法 |
| `FLAT` | ADB × 年利率 ÷ 12 | 固定月息，不依天數攤提 |

> **利率權威定奪**：以 **`ANNUAL_RATE`（年利率）** 為準，
> 日利率 ＝ `ANNUAL_RATE ÷ 基礎天數`。
> 理由：`RATE`（日利率）與 `ANNUAL_RATE` 係兩個獨立欄位，若以 `RATE` 為準，
> `dayCountBasis` 參數將**完全失去作用**；且年利率方為對帳單／合約上
> 可被人類核對之數值。

### #4　範例數字 —— 定奪如下

原範例 `interestAmount: 850` **算不出來**（BEAM PMO：「真是天才，我還真不知問題出在哪個環節」）。
實算：

```
52345 × 0.185 × 30 ÷ 365 ＝ 795.93…  → 796
52345 × 0.185 × 30 ÷ 360 ＝ 806.99…  → 807
52345 × 0.185 × 30 ÷ 330 ＝ 880.0     → 880   ← 皆非 850
```

**根因判定**：850 無任何合理天數基礎可對應，屬**範例撰寫時的臆值**，
非公式或欄位定義錯誤。故：

1. 契約範例更正為 **796**（`ACT_365`），並補 `principalAmount: 52345`
   （原範例為 `null`，與 796 之結果自相矛盾）。
2. 明定 **天數採半開區間 `[periodStart, periodEnd)`**：
   `2026-08-16 → 2026-09-15` ＝ **30 天**，與範例一致；
   且與「日終餘額序列每日一筆」之筆數定義吻合。
3. `dayCountBasis` 預設 **`ACT_365`**。

---

## 7. 實作結果與實測證據（2026-09-19）

### 7.1 交付項目

| 檔案 | 說明 |
|---|---|
| `application/dto/InterestCalcRequest` | 輸入 DTO（含 `RequestBuilder`）＋ `DailyBalance` |
| `application/dto/InterestCalcView` | 輸出 DTO ＋ `Detail` ＋ `CalcBasis`／`DayCountBasis` |
| `application/dto/FeeView` | 費用檢視 ＋ 中文顯示名對照 |
| `application/InterestCalculationService` | 試算／查詢與計算核心（純函式） |
| `application/FeeService` | 費用設定查詢 |
| `interfaces/rest/PricingController` | 3 個 REST 端點 |
| `domain/port/InMemoryPricingRuleRepository`（測試） | 埠之**完整**假實作（非 mock 回值） |
| `11-dev-pricing-seed.sql` | 補 `TIER` 維度（一般消費／預借現金利率之區分） |

### 7.2 自動化測試

`pricing-service` **45 測試 0 失敗 0 跳過**
（新增 `InterestCalculationServiceTest` 23、`FeeServiceTest` 5）。

### 7.3 實機端到端實測（真實 Oracle `CC_PRC`，非快取）

| # | 情境 | 實測結果 |
|---:|---|---|
| ① | `POST interest-calculations`（本金 52345，30 天） | `interestAmount=796`、`appliedRate=0.185`、`calcBasis=CONSTANT_PRINCIPAL`、`dayCountBasis=ACT_365` ✅ |
| ② | `GET fees` | 5 筆生效規則；**停用＋已過期之 `PRC-INT-GENERAL-LEGACY` 正確排除** ✅ |
| ③ | `GET interest-inquiries` | `796`，與 POST 一致 ✅ |
| ④ | 違約金＋預借現金拆分 | 利息 `960`（796＋164）、`lateFeeCount=3`（**由 5 期夾擠**）、`lateFeeAmount=90000`、CASH_ADVANCE 用 `0.2` ✅ |

### 7.4 錯誤路徑實測（驗證 `CR-004` 回歸守門）

| 情境 | 回應 |
|---|---|
| 缺 `X-Channel` | `400 CC-PLT-4001` |
| 未知 `calcMethod` | `400 CC-PLT-4003` |
| `TWO_CYCLE` 缺 `priorCycleAdb` | `400 CC-PLT-4003` |
| 非法 JSON | `400 CC-PLT-4002` |
| 不存在路徑 | `404 CC-PLT-4040` |
| 缺 `accountId` | `400 CC-PLT-4001` |
| 未知 `ruleType` | `400 CC-PLT-4003` |

> **7 項皆為 4xx，無一落入 500；服務日誌 ERROR 筆數＝0** ✅

---

## 8. 施行後新揭露之限制（誠實揭露）

1. ~~**`TIER` 欄位被用作計息子型**（`GENERAL`／`CASH_ADVANCE`）~~
   **✅ 已解除（2026-09-22，C-31）**：`PRICING_RULES` 已增設 `TX_TYPE` 欄位
   （`V2__pricing_rules_tx_type.sql`；可為 null，僅 `INTEREST` 規則具值；
   既有列依 `TIER` 回填），規則選取改以 `TX_TYPE` 篩選，`TIER` 回歸本義（客層級距）。
   單測 53 例 0 失敗（含 2 防回歸）；真機 e2e：清空 `TX_TYPE` → `CC-PRC-1001`、
   還原 → 20% 恢復。詳見 `SCS-006 1.2 §7.5`、`SDD-001 1.7 §3.11`。
2. **冪等鍵目前無從實作**：`POST interest-calculations` 為純函式、不寫入，
   故契約要求之 `X-Idempotency-Key` 僅接收而無副作用可重放。
3. **`CC-PRC-1001`（422「無適用利率」）被延伸用於「查無違約金規則」**——
   語意略有擴張（違約金非利率）。未新增錯誤碼以避免更動 `SDD-006 §3.4`；
   若日後認為應獨立成碼，另提 CR。
4. **未套用 `GRACE_PERIOD_DAYS`**：免息期之判定須有繳款狀態，本服務無該資料。
5. **`crossPeriodInterest` 恆為 0、`accumulatedInterest` ＝ 本期新增利息**——
   本服務不保存計息狀態。
6. **`FeeView.currency` 恆為 `TWD`**：`PRICING_RULES` 無幣別欄位。
7. **`FeeView.feeName`** 由 `RULE_TYPE` 對照產生（無名稱欄位）。

---

## 9. 修訂記錄

| 版本 | 日期 | 說明 |
|---|---|---|
| 1.0 | 2026-09-19 | 提出，盤點 4 處缺口，待核定 |
| **1.1** | 2026-09-19 | BEAM PMO 核定（四項全數授權我方設計）；新增 **§6 核定結果與定奪**、**§7 實作結果與實測證據**、**§8 施行後新揭露之限制**；`SCS-006` 升 1.1 |
| **1.2** | 2026-09-22 | **§8 #1 結案（C-31）**：`PRICING_RULES.tx_type` 增設（`V2__pricing_rules_tx_type.sql`），`TIER` 挪用解除；`SCS-006` 升 1.2、`SDD-001` 升 1.7 |
