Project

General

Profile

CR #206 » CR-005_定價服務契約缺口.md

內容更新:內部稱謂去識別化(2026-10-18) - Redmine Admin, 10/18/2026 05:32 AM

 

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 表格載明本端點為「利率查詢」,
故定位為唯讀試算——不寫入、不計違約金、不拆分預借現金;
與 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
    (1-1/1)