# PSA 9 重評計算器 — 模型文件（v2）

頁面：`psa-9-regrade-calculator.html`
計算引擎：`js/regrade-model.js`（純函數，無 DOM、無隨機數）
私人資料庫：`js/regrade-db.js`
DOM 綁定：`js/regrade-ui.js`
測試：`node js/test/regrade-model.test.js`

這是 v1（POP 反推命中率 + κ 掃描 + Monte Carlo + Kelly 主判斷）的完整重構，不是文案修改。v1 的每一個被指出的問題（見下面「v1 → v2 差異摘要」）在 v2 的公式層面都已經改掉，而不是只換字眼。

---

## 1. 核心原則

1. **POP 只係弱先驗，唔入公式。** 頁面會顯示 Pop(10):Pop(9) 比例畀你參考卡款嘅整體評分難度，但 `p_base`（基礎重評率）永遠只可以嚟自私人重評紀錄資料庫。資料庫冇相關紀錄 = `insufficient_data` = 灰色「拒絕判斷」，唔會用 POP 頂替。
2. **一切機率調整用 log-odds，唔用百分比加減。** `LR_effective = exp(q × ln(LR_raw))`；`odds_adjusted = odds_base × ΠLR_effective`；`p = odds/(1+odds)`。
3. **未經驗證嘅因素預設冇影響。** 初評紀錄、廠房差異全部有一個「資料可信度 q」，q=0 令個 LR 變返 1.00，數學上完全冇影響（唔係人手判斷唔用，而係公式自動歸零）。
4. **廠房 LR 正式模型預設 1.00**，只有開咗「Experimental facility model」先會用 §5 嗰組暫定值（New Jersey/Japan 等）。
5. **冇任何隨機數。** 批次結果用 exact deterministic convolution（逐張卷積機率分佈），同一輸入刷新一百次答案完全一樣。
6. **未發生過嘅事唔當 0% 風險。** 拆殼／運輸損毀用 Beta-Binomial shrinkage + rule-of-three，0 次事故都會有一個 > 0 嘅 conservative rate。
7. **四色判斷，灰色係正式答案。** 綠／黃／紅／灰，灰色（資料不足）唔係「未計完」，係工具主動拒絕落判斷。

---

## 2. 公式對照（每個都有對應單元測試）

### 2.1 Odds / LR

```
odds_base = p_base / (1 - p_base)
LR_effective = exp(q × ln(LR_raw))        # q ∈ [0,1]，q=0 時 LR_effective 恰好 = 1
odds_adjusted = odds_base × Π(LR_effective, capped to [0.40, 3.00])
p_adjusted = odds_adjusted / (1 + odds_adjusted)
```

組合上限／下限（0.40–3.00）係為咗防止幾個有相關性嘅訊號（初評紀錄、原評廠房、重評目的地）疊加出一個假嘅精確度——呢個係 provisional 設計選擇，唔係數據推導出嚟。

**已驗證嘅例子**（p_base = 12.7%，見 `js/test/regrade-model.test.js` 第 3/3b/3c/4 項）：

| 情境 | 計算 | 結果 |
|---|---|---|
| 初評 10→9，LR=2.00，q=1.00 | odds=0.1455×2.00 | p≈22.5%，+9.8pp |
| 初評 9→9，LR=0.75，q=1.00 | odds=0.1455×0.75 | p≈9.8%，−2.9pp |
| 初評(LR2.00) + 原評 NJ(LR1.15) | odds×2.00×1.15 | p≈25.1%，+12.4pp |
| 原評 NJ，LR=1.15，q=0.35（cert range 推斷） | exp(0.35×ln1.15) | 有效 LR≈1.0501 |

### 2.2 Beta-Binomial shrinkage（p_base 嘅來源）

`betaPosterior(successes, failures, α₀=1, β₀=1)` 用標準 Lanczos lgamma + regularized incomplete beta（連分數法）計 CDF，bisection 反解 quantile。Prior 預設 Beta(1,1)（均勻），保證 mean/median 永遠唔會係 0% 或 100%。

輸出：`mean`、`median`、`p05`、`p95`、`ci90`。

### 2.3 p_base 優先序（`regrade-db.js: derivePBase`）

1. 同一卡款（card_name + card_number + set + language + release_year）
2. 同系列＋語言＋年份
3. 同語言嘅廣泛類別
4. 全部私人紀錄
5. 都冇 → `insufficient_data`

每一級都用嗰一級嘅 successes/failures 餵入 Beta 後驗。

### 2.4 損毀率（Rule of Three + Beta shrinkage）

```
observedRate = events / n
ruleOfThreeUpper = events===0 ? 3/n : null      # 只喺 0 次事故時有意義
conservativeRate = betaPosterior(events, n-events, priorMean×priorStrength, (1-priorMean)×priorStrength).p95
```

`priorMean` 預設 2%、`priorStrength` 預設 15（虛擬樣本數）——呢兩個係 configurable 嘅 provisional 假設，唔係實測數據，UI 同呢份文件都要講清楚。**`conservativeRate` 永遠 > 0**，就算 events=0 都係。

### 2.5 結果分佈（7 類）

`psa10, psa9, psa8, psa7orLower, noGradeAltered, crackDamage, shippingLoss`

`buildOutcomeDistribution(p10, shares)`：非 p10 嘅機率質量按你手動輸入嘅「下行結構佔比」比例分配，再摺入損毀率。永遠驗證總和 = 1（容許 1e-6 誤差），唔啱就 throw。

### 2.6 財務

```
profit_j(held)     = netSaleValue_j − currentNetSaleValueOfPSA9 − additionalCosts
profit_j(purchase) = netSaleValue_j − purchasePrice − additionalCosts
```

`centralEV`：中央機率分佈 × 中央（未打折）價格。
`conservativeEV`：保守機率（Beta p05 再套保守 LR）× 壓力價格（賣出價 −20%）。兩者都同時保守，唔淨係機率或者淨係價格。

打和 PSA10 機率／打和賣出價／打和買入價：喺固定其他變數嘅前提下，解一條線性方程（唔係「試出嚟」）。

### 2.7 批次結果（取代 Monte Carlo）

`exactBatchDistribution(outcomes, n)`：逐張卡做 discrete convolution，用 `Map<profit_cents, probability>` 逐步疊加。N ≤ 500（再大會 throw，避免瀏覽器卡死）。

由呢個 Map 度攞：`mean`、`p05`、`lossProbability`、`cvar95`（尾部 5% 嘅條件期望值）、`probAtLeastOnePSA10 = 1-(1-p10)^n`（閉式解，唔使 convolution）。

**Determinism 已驗證**：同一組 outcomes/n，計兩次，`Map` entries 完全一樣（見測試第 8 項）。

### 2.8 倉位（風險預算為主，Kelly 收埋）

```
max_cards = floor(min(
  availableCash / cashRequiredPerCard,
  batchLossBudget / max(0, -cvar95PerCard),
  worstCaseBudget / max(0, -worstCaseLossPerCard),
  concentrationLimit / cashRequiredPerCard
))
```

四個候選入面取最細嗰個，UI 會話你邊個係「binding constraint」。Kelly（`kellyFractionAdvanced`）只喺揀咗「進階／實驗性」先顯示，UI 有明文警告「假設你嘅機率估計啱」。

### 2.9 決策規則（四色，`M.decide`）

- **灰**：`dataStatus !== 'ok'` 或者 `fatalDataWarning` 或者 `inputsContradictory` → 直接拒絕判斷，其他條件唔使睇。
- **綠**：`conservativeProbability > breakEvenProbability` 同時 `conservativeEV > 0` 同時 `!exceedsRiskBudget` 同時樣本量夠（n ≥ 門檻）同時 90% CI 唔太闊。五項全過先算綠。
- **黃**：`centralEV > 0` 但保守情境已經蝕，或者樣本唔夠，或者廠房淨係靠 cert range 推斷，或者高度依賴 experimental LR。
- **紅**：`centralEV ≤ 0` 或者超出風險預算或者有 fatal defect flag。

---

## 3. 私人資料庫（`js/regrade-db.js` + `data/regrade-record.schema.json`）

- Schema：JSON Schema draft-07，29 個必填欄位（見 `regrade-record.schema.json`），**失敗、跌級、No Grade、損毀、未賣出嘅紀錄一定要保存**——schema 冇分「只存成功個案」嘅欄位。
- Import：JSON 或 CSV，`merge`（加入現有）或 `replace`（取代）。每筆紀錄都經 `validateRecord()` 驗證；`original_cert_number` 重複會被拒絕（列入 `rejected`，唔會靜默覆蓋）。
- Storage：瀏覽器 `localStorage`，key `grace_regrade_db_v1`。呢個係單機本地儲存，唔會自動同步去第二部機或者伺服器——要備份就用「匯出 JSON」。
- Export：JSON 或 CSV，隨時可以攞走全部紀錄。

## 4. 廠房 cert-range（`data/facility-cert-ranges.json`）

**呢個係範例／佔位檔案，唔係 PSA 官方資料。** 每個 range 有 `facility / cert_start / cert_end / effective_from / effective_to / source / confidence / notes / last_verified_at`。如果一個 cert number 跌入多過一個 range，`lookupFacilityByCert()` 回傳 `{status:'ambiguous', matches:[...]}`，UI 會顯示「廠房不確定」，唔會自己揀一個。

Cert-range 推斷嘅廠房喺 UI 永遠標「高可信推斷／低可信推斷」，唔會顯示做「已確認」——「已確認」呢個字眼淨係留畀 `order_record_confirmed` 嘅來源。

## 5. 測試

```bash
node js/test/regrade-model.test.js
```

21 個測試全過，覆蓋 spec §19 要求嘅全部 16 項（odds 轉換、LR=1 不變、初評 LR 計算、confidence shrinkage、q=0 完全冇影響、p 近 0/1 唔爆、機率總和=1、determinism、批次總機率=1、0 事故唔當零風險、held/purchase opportunity cost 唔同、冇數據回 insufficient_data、cert range 重疊回 ambiguous、combined LR cap、conservative EV 用下界機率+不利價格、break-even 唔會同實際機率混淆）。

---

## 6. v1 → v2 差異摘要

| 檔案 | 變化 |
|---|---|
| `psa-9-regrade-calculator.html` | 完整重寫（原檔備份喺 `backups/psa-9-regrade-calculator.pre-v2-*.html`）。四色判斷、7 種結局、私人資料庫 UI、初評／廠房輸入、風險預算倉位取代 Kelly-first、exact batch 取代 Monte Carlo。 |
| `js/regrade-model.js` | 新增。全部核心公式（odds/LR、Beta 後驗、rule-of-three、7-結局分佈、財務、break-even、exact convolution、CVaR、風險預算倉位、四色決策）。 |
| `js/regrade-db.js` | 新增。私人紀錄 schema 驗證、import/export、p_base 優先序查詢。 |
| `js/regrade-ui.js` | 新增。純 DOM 綁定層，唔含任何計算邏輯。 |
| `data/facility-cert-ranges.json` | 新增。範例 cert-range 對應表，明確標示未經官方核實。 |
| `data/regrade-record.schema.json` | 新增。JSON Schema。 |
| `js/test/regrade-model.test.js` | 新增。21 個純函數測試，`node` 直接跑。 |

移除嘅講法（唔再出現喺頁面）：「命中率由 POP 反推」「POP 係客觀重評成功率」「κ＝1 係冇假設嘅主判斷」「未發生過就填 0」「主判斷不使用估數」「10,000 次模擬」「Kelly 倉位」做主要建議。

---

## 7. 仍未解決嘅資料限制（老實列出）

1. **Hierarchical Bayesian logistic regression 未實作。** Spec §9 建議嘅正式模型（多特徵 + interaction + shrinkage prior）需要一個真正嘅回歸求解器（IRLS 或 MCMC），喺冇任何真實私人數據餵入之前寫呢個都係做假動作。而家用嘅係 spec 都承認係 fallback 嘅 Beta-Binomial shrinkage，已經達到「唔會出現 0%/100% 確定性」呢個要求，但冇捕捉到卡款×廠房、初評×廠房呢啲 interaction。
2. **結構性下行佔比（share9/8/7/noGrade）係手動輸入，未有自動由私人資料庫推導。** Spec 冇強制要求自動化呢步，但理想情況應該由 `derivePBase` 順便計埋呢組比例。而家你要自己揀。
3. **廠房 LR 冇 published 嘅保守壓力範圍。** Spec §2 有畀初評 LR 嘅 stress range（1.25–3.00 / 0.50–1.00），但廠房 LR 冇。V2 嘅保守情境處理方式：cert-range 推斷或者低可信度來源嘅廠房 LR，喺 conservative 分支直接歸 1.00（唔畀佢幫你），只有 q≥0.85 先用返個 point value。呢個係文件化嘅工程判斷，唔係規格數據。
4. **運輸損毀嘅回收價硬編碼做 $0。** 冇畀用戶輸入保險賠償或者殘值回收嘅欄位。
5. **私人資料庫淨係本機 `localStorage`。** 冇雲端同步，換機／清瀏覽器數據會清晒——用戶要自己記得定期匯出備份。
6. **`combineLR` 嘅上下限（0.40/3.00）同 rule-of-three prior（mean=2%, strength=15）全部係 provisional 常數**，寫死喺 `regrade-model.js` 頂部，冇獨立設定檔——如果要跟住真實數據校準，呢兩組數要人手改碼，未做成 UI 可調（除咗損毀率同批次張數上限已經開放做輸入）。
7. **Cert-range JSON 入面嘅三條 range 係編造嘅範例**（`placeholder_example`），唔可以直接當真用嚟斷廠房。用之前一定要換成你自己驗證過嘅對應表。

## 8. 點樣加入真實歷史數據

1. 送評紀錄整理成 `data/regrade-record.schema.json` 嘅格式（每張卡一行，包括失敗個案）。
2. 喺頁面「私人重評紀錄資料庫」貼上 JSON 或者上載檔案，揀 `merge`。
3. 卡款身份（卡名／卡號／系列／語言／年份）要同私人紀錄入面嘅欄位完全對得上，先會行去第一優先級（tier 1）。
4. 如果你有可靠嘅 cert-number → 廠房對應表，取代 `data/facility-cert-ranges.json`，並將每條 range 嘅 `source`／`confidence`／`last_verified_at` 填實。
5. 隨時撳「匯出決策審計 JSON」保存嗰一刻嘅全部輸入、公式輸出同 `model_version`，方便日後覆核。
