# 薪資計算器維護手冊

本手冊的版面與維護流程於 2026-10-21 更新，線上分析與伺服器資訊於 2026-11-10 補充；各類金融與統計資料的查核日期以頁尾來源表及 `data-refresh-runbook.md` 為準，不代表全部資料在當日重查。本工具以 `salary.html` 單檔頁面運作，可直接離線開啟；正式網域上的 Umami 操作統計例外見「版面與個資」。金融結果是估算，公開統計是有日期的快照，不是即時資料。

## 檔案與檢查入口

- `salary.html` 包含畫面、試算公式及公開統計快照。
- `salary*.test.js` 為十一組瀏覽器驗收：薪資、人口、退休、時間線、D3、排版、說故事、海外養老、工作異動、任務／保險／存股，以及 `salary-analytics.test.js` 線上分析事件。排版測試使用同源 iframe 驗證真實 375／1280／1440px，其他各組回傳實際通過的斷言數，歷史合計不是本輪重測證據。
- 第 5 站 05 章 Die with Zero 與第 7 站 `#stepWork`（換工作比較、特休與請假、離職與資遣費）的規格、法條與公式在 [`work-change-plan.md`](work-change-plan.md)。勞保、就保、健保本人負擔集中在 `payrollFor()`，第 3 站與換工作比較共用；改費率時只改這一處。
- 任務導向改版與保險功能的規格在 [`ux-redesign-plan.md`](ux-redesign-plan.md)。所得稅列舉扣除＝醫療＋捐款＋保險費；保險費＝本人勞保與健保 × 12（所得稅法第 17 條 115 年 8 月修正後不受金額限制）＋ min(24,000, 就保 × 12＋本人可列舉商業保險)＋每位家人 min(24,000, 該人可列舉商業保險)，投資型保單不計。改了這條規則要同步 `insuranceItemized()`、`policyDeductions()`、保險卡說明與 `salary-tasks.test.js`。
- 存股與股利卡（`renderStock()`）：一年股利＝市值 × 殖利率；所得稅差額＝`incomeTaxPlan` 有這筆股利與股利為 0 的稅額差（合併 8.5% 抵減與分開 28% 取低）；每次股利＝一年股利 ÷ 配息次數，達 20,000 元時補充保費＝一年股利 × 2.11%；試算表假設股價與殖利率不變、股利全數再投入，另加每月再投入。
- 退休支出加保費：保險卡有填「還要繳幾年」的保單，`retirementPlan({ premiums })` 在繳到的年齡前每年加上該年繳保費（今日幣值，偏保守）。Die with Zero 也計入（今天退休要有的錢、退休後每月最多能花、曲線）；FIRE 目標是永續提領，沒有加保費。
- Die with Zero 預設計入勞保年金與勞退（`#dwzPension`，和 02 章 `retirementPlan()` 同一套假設），取消勾選只算自己的存款；FIRE 的 Lean／Fat 夾在生活費兩側，達成年數試算到 100 歲。三種算法的差別寫在第 5 站開頭的比較卡（概念示意圖＋「計入」欄）。
- 第 6 站 `#stepAbroad` 的規格、公式與人物情境矩陣在 [`retirement-fire-plan.md`](retirement-fire-plan.md)，每個數字的來源在 [`retire-abroad-sources.md`](retire-abroad-sources.md)。國籍與在台居留身分在第 1 站，會改變第 5 站是否計入勞退與第 3 站自願提繳的所得稅處理。
- 視覺保留包浩斯幾何、雜誌排版與捷運站牌意象。刊頭在 `header.masthead`；封面、第 4 站、第 5 站數字由 `renderInfographics()` 從頁面內資料計算（`#coverStats`、`#stats4`、`#stats5`）。最低月薪預設為 29,500 元，退休生活費為 114 年家庭收支調查換算的 28,513 元，來源同頁標示。樣式集中在 `<style>`；修改時留意後面的同權重規則會覆蓋前面的規則，保留所有既有 id。
- 2026-10-21 第一站改為桌面「薪資輸入／薪資試算結果」兩欄，760px 以下單欄；個人檔案、人物範例與工時設定使用原生 `details` 預設收合。任務「去填」和「我的資料／改」先展開目的欄位再聚焦，無效工時設定會自動展開。任務側欄不再因薪資出結果而自動打開或閃動，改由結果卡連結引導。公式、欄位 id 與儲存契約不變。範圍及驗收見 [`ux-2026-10-21-plan.md`](ux-2026-10-21-plan.md)，實際結果見 [`CHANGELOG.md`](CHANGELOG.md)。
- 現行為七站：① `#stepStart` 輸入與任務、② `#storySection` 薪水圖解、③ `#stepDetails` 換算、中位數、勞健保、扣除、存股、保險與稅額明細及日期倒數、④ `#publicStatisticsSection` 台灣與鄰國數據、⑤ `#stepRetire` 算退休、⑥ `#stepAbroad` 海外養老、⑦ `#stepWork` 工作異動。`.steps` 與下一站只顯示已選任務所需內容；線色為 `--line-r/g/b/o/n/t/p`，資料色另行編碼。
- 第 4 站的 `renderVitals()` 讀 `publicHistory.vitals`，出生／死亡人形與逐年路線、`#vitalsYear` 均沿用封存原件；`renderNeighbors()` 比較台灣、中國、日本、韓國、美國、馬來西亞、泰國、越南八地，使用 `neighborPopulation` 的 UN WPP 2024。年份為 1950–2100，預設 2026，2023 年以後標推估，年齡組為 0–14／15–64／65 歲以上；車廂共用 `storyTrainCar()`。戶籍、WPP 及就業的母體不可混用。
- 扶養比與出生人數章節已移到第 4 站。第 5 站依序為生活費與資產、存款可以用到幾歲、一生時間軸、FIRE、Die with Zero，開頭有三種算法比較卡；計算、投保年資、勞退、生活費及通膨假設見下方「人口、時間線與退休」。退休欄位不持久保存，重載與清除回到預設；有繳費年限的保單會依既有模型計入退休支出。
- 「薪水的故事」（`#storySection`，`renderStory()`）只重新呈現既有結果，不新增公式：100 格＝年薪拆成留在手上、勞健保本人自付額 × 12、商業保險（`privatePremiums()`：壽險、醫療險、失能險年繳保費，美元用 `fx`、令吉用第 6 站匯率換成新台幣；全為 0 時文字與舊版相同）、所得稅，以 `percentCells()` 最大餘數法補足 100 格；「哪一天起賺的全是自己的」以（所得稅＋勞健保）÷ 年薪 × 當年天數無條件進位，由 `yearShareDate()` 換算日期，屬比喻，不是發薪或繳稅日；中位數章沿用個人檔案填完才顯示的規則與「比較口徑」選項，只呈現倍數，不是百分位；人口章使用 `publicStatistics.population` 與 `publicHistory.population`。兩個 helper 由 `verify_salary.py` 離線檢查。視覺採 Isotype 圖像：第 1 章 100 枚 1 元硬幣（黃銅＝留在手上、藍綠＝勞健保、磚紅＝所得稅，全區顏色語意一致）；第 4 章用月薪對照最低月薪 29,500 元（不用自訂分母的時薪對照最低時薪 196 元，兩者口徑不同，會把高於最低工資者算低）；每個鐘面＝最低工資者 1 小時，扇形＝假設工時相同時你賺到同樣金額所需分鐘（60 ÷ 倍數），最多畫 12 個；第 5 章人形以形狀與另一組年齡專用色（藍／灰／紫，不沿用金錢章節顏色）同時區分年齡組（小人形＝未滿 18 歲、持拐杖＝65 歲以上），填生日時其他組變淡；長者對應人數用實際人數計算。說故事區說明文字不小於 16px；第 2 章日期直接標在條上，條短時改靠左。2026-10-17 以三組年齡角色（16／35／70 歲）只看截圖作答三輪理解測驗並依結果修正，屬模擬審查，不等於真人使用者測試；另以使用者指定的四組情境（22～34 歲、月薪 33,000 至年薪 1,456,000）逐章核對數字。真人測試步驟見 `story-user-test.md`。
- `verify_salary.py` 檢查結構、語法、日期、退休門檻、人口及時間線；`--sources` 另核對官方數字與封存歷月原表。
- `fetch_population_series.py` 更新 `population-timeseries.json`，並同步 HTML 的 `publicHistory` 區塊。
- `archive_sources.py` 保留引用原件，`verify_archive_sources.py` 核對原名與檔案完整性；入口為 `sources/index.html`。
- [人口與就業來源](population-sources.md)、[歷月資料](population-timeseries.md)、[退休與高齡定義](retirement-sources.md)、[需求、任務提示詞與驗收計畫](project-plan.md)分別保存查核依據。
- 現用的專案維護技能為 [`.claude/skills/salary-data-maintenance/SKILL.md`](.claude/skills/salary-data-maintenance/SKILL.md)。下方 `salary-calculator-maintenance` 是較早的全域安裝草案，保留作歷史，不代表專案缺少維護技能。公式與資料來源以本手冊及 `salary.html` 為準。

在本目錄執行：

```sh
mise exec -- python verify_salary.py
mise exec -- python verify_salary.py --sources
mise exec -- python verify_archive_sources.py
open "salary.html"
```

第一個指令不取用網路。`--sources` 會連線戶政司、主計總處及必要的 TWCA 公開中繼憑證端點，比較快照與官方原始表，另以封存 ODS 核對完整歷月序列。驗證指令不修改快照，也不傳送使用者資料。

CLI 不執行瀏覽器互動測試。請在乾淨分頁開啟 `salary.html`，分別執行所有 `salary*.test.js` 的函式。使用 Firefox MCP 時，將函式運算式交給 `evaluate_script.function`，移除檔案末尾分號即可。成功結果包含 `passed` 計數。測試使用合成資料，會修改表單、測試頭像與複製文字，最後還原薪資設定並重載；勿在填有未保存資料的分頁執行。

若 Firefox 工具拒絕測試函式大小，保留測試內容，只移除縮排及行間空白，先用 Node `new Function` 驗證縮短後的函式語法，再交給工具。不要刪除測試案例來符合大小限制。一般瀏覽器主控台可直接執行未縮排處理的原檔。Firefox MCP 無法啟動時，可用 agent-browser（Chromium）的 `eval` 執行同樣的函式；版面測試的同源 iframe 在 Chromium 的 `file://` 下讀不到內容，需改用本機 `python -m http.server` 開啟。D3 測試的「附近點選命中最近資料點」以 1280×720 視窗為準，375px 下點距過密會命中相鄰點。

選用 Bun 時，`mise exec -- bun run serve.ts` 會在 `http://localhost:3000` 提供靜態頁面；`mise exec -- bun test serve.test.ts` 檢查 allowlist、`HEAD` 與 `/healthz`，不執行薪資頁面互動。Docker build/run 指令見 `README.md`。

批次執行時，前一份測試的 `location.reload()` 可能尚未完成。先等 `document.readyState === 'complete'`，再等一次 `requestAnimationFrame`，確保 `pageshow` 的個資清除及生日上限初始化已執行；不要把重載競態當成產品錯誤。Firefox 工具接受 `async () => { ... }`，不要把開頭縮成 `async()=>`；函式上限 16,384 字元。

精確提示固定在圖表對話框上方，捲動後仍可見。版面測試不只查 `display`，也比對提示與對話框的 bounding rectangle；繪圖區附近點選另驗證最近資料點命中。真實觸控硬體仍需另外測。

每次改動至少確認：

1. 年薪 1,400,000、14 個月可反推月薪 100,000、時薪 444.44，預設分母為 225。
2. 未選最高學歷、性別或職務時，中位數圖不可開啟，但換算與稅額仍可計算。
3. 大學對應官方「專科及大學」，碩士及博士對應「研究所」。不提供性別或學歷時，不標示不存在的匹配組。
4. 租金、標準與列舉扣除、捐款上限、長輩免稅額及級距邊界的回歸測試通過。
5. 在實際 375px 內容寬度檢查 Portfolio、圖表及長文字，並確認深淺色與鍵盤操作。
6. 有效圖片可預覽，SVG、過大圖片或損壞檔案會拒絕。移除與重新載入後頭像應清空。
7. 重新載入後，個人資料及扣除欄位回到預設值；不能只看 localStorage，瀏覽器也可能自行回填表單。頁面使用 `pageshow` 清除這些欄位。
8. 退休欄位涵蓋未填、未來生日、生日當天、閏日及不同出生年次；重載後生日不得被瀏覽器回填。
9. 人口／就業圖不依賴個人薪資；只有真實月序列有折線，縮放縱軸須揭露。
10. 原件完整性檢查通過，新增引用都列入 manifest；失敗來源不能當成已下載。

## 公開統計來源與口徑

### 年薪中位數

[主計總處 113 年全年總薪資中位數統計結果](https://www.stat.gov.tw/News_Content.aspx?n=3703&s=235514)的附表 3，標題為「工業及服務業全年總薪資中位數－全體受僱員工按特性別分」。

[原始 ODS](https://ws.dgbas.gov.tw/001/Upload/463/relfile/10980/235514/表3　工業及服務業全年總薪資中位數－全體受僱員工按特性別分.ods)。

- 統計年度為 2024 年，即民國 113 年。
- 母體包括本外國籍全時與部分工時受僱員工，不能混入同份新聞稿的「本國籍全時」數字。
- 全年總薪資包括經常性與獎金等非經常性薪資。
- 取原表 `113年` 欄。原單位為萬元，乘 10,000 後存入 `salaryMedians.annual`。原表精度為 0.1 萬元。

### 經常性月薪中位數

[主計總處 114 年全年薪資統計新聞稿](https://www.dgbas.gov.tw/News_Content.aspx?n=3602&s=235863)的附表 6，標題為「工業及服務業受僱員工每人每月經常性薪資－按受僱員工個人特性別分」。

[原始 ODS](https://ws.dgbas.gov.tw/001/Upload/463/relfile/10980/235863/表6.ods)。

- 快照取 `114年Ⓟ` 整年列，不取 `12月Ⓟ` 列。這是 2025 年整年指標的初步值，不是 2026 即時資料。
- 母體同樣為全體受僱員工，含本外國籍全時及部分工時。
- 原表單位為元。總計、男性、女性及四組學歷的中位數分別位於零起算欄位 2、4、6、8、10、12、14。
- 相鄰欄位是平均數。更新時必須先核對表頭，不能只抄欄位位置。
- `Ⓟ` 是初步值，`Ⓡ` 是修正值。這份固定快照不是最新修正版的保證。

### 主管與一般人員

[勞動部 114 年職類別薪資調查](https://www.mol.gov.tw/1607/1632/1633/90814/)提供職類平均數。主管及監督人員平均月薪為 81,000 元，期間為 2025 年 7 月；平均年薪為 1,317,000 元，期間為 2024 年。

此調查只涵蓋全時員工。其全年薪資所得採扣繳資料定義，不包括加班費及免稅額度內伙食津貼等，與主計總處全年總薪資不完全相同。頁面僅將主管平均數列為旁註，不放入中位數長條。

目前使用的主計總處快照沒有主管／一般人員的分層中位數，也沒有性別 × 學歷 × 職務聯合分組。選職務會改變適用性提示，不會偽造職務校正後的中位數。一般人員亦不等於「全體員工扣掉主管」後可直接計算的中位數。

### 查詢與擷取指令

有 `agwctl` 的環境可先查詢工具介面，再取得來源正文。工具名稱可能變動，先以實際 inventory 為準。

```sh
agwctl tools search tavily --limit 5
agwctl tools describe tavily-mcp_tavily_search
agwctl tools describe tavily-mcp_tavily_extract
agwctl call tavily-mcp_tavily_extract --arg 'urls=["https://www.stat.gov.tw/News_Content.aspx?n=3703&s=235514","https://www.dgbas.gov.tw/News_Content.aspx?n=3602&s=235863"]' --arg extract_depth=advanced
```

擷取工具適合讀公告與下載連結，數字核對仍使用原始 ODS。搜尋摘要不能作為平均數、中位數或統計母體的唯一證據。

2026-10-15 查核時，部分政府站點省略 TWCA 中繼憑證。`verify_salary.py` 只對這個下載流程補入公開的 `Cyber_SSL_2023.crt`，並相容 Python 3.13 以後對舊憑證延伸欄位的嚴格檢查。CA 簽章、信任根與主機名稱驗證仍開啟。不要改用 `curl -k`、關閉 CA 驗證或替使用者全域安裝憑證來迴避失敗。若來源換證或連結改變，先查證錯誤再更新流程。

## 計算公式與名詞

### 薪資

- 年薪月數是本工具的換算假設，不是統計資料的固定倍數。年薪＝月薪 × 年薪月數，反推月薪＝年薪 ÷ 年薪月數。
- 預設每日工時 7.5 小時，每月分母＝30 × 7.5＝225 小時。可關閉自動分母後改填實際工時。
- 月薪換算時薪＝月薪 ÷ 每月分母。全年攤提時薪＝年薪 ÷ 12 ÷ 每月分母。兩者均不是公司正式加班費算法。
- 經常性薪資通常包含本薪與按月固定給付，不含非經常性獎金。全年總薪資含非經常性薪資，不能用經常性月薪中位數乘 12 冒充其年薪中位數。
- 中位數是排序中間的位置，不等於平均數。各分組中位數不能直接平均、相減或相乘推導聯合分組中位數。
- 圖表顯示「你的薪資為該組中位數的幾倍」，倍數＝個人薪資 ÷ 參考中位數。摘要的高低差＝倍數 − 1，再乘 100%。2 倍等於高出 100%，3 倍等於高出 200%，所以差額百分比可以超過 100%；這不是贏過幾成的人，也不是第幾百分位。
- 學歷沿用官方四組。專科與大學同組，碩士與博士同組。頁面不能提供未公布的獨立學位中位數。

### 年度顯示與日期倒數

- UI 以民國年優先，括號保留西元年。`taiwanYear` 使用「民國年＝西元年 − 1911」。民國 115 年是 2026 年；反向換算則加 1911。
- `salaryMedians` 的 `year` 存西元整數，再由同一函式產生顯示名稱。原始 ODS 欄名維持民國年，來源核對腳本依 `year - 1911` 找欄位，不能把資料年度當成目前年度。
- `TAX_YEAR` 代表本頁稅制適用的所得年度，不是自動取得的今年。調整稅制時應一併核對它與所有法定金額，不能只換年份文字。
- 年底目標為本機今年 12 月 31 日。年終獎金預設同日，可調整，並非公司保證發放日。
- 繳稅目標預設為所得年度隔年的 5 月 31 日。[國稅局一般規則](https://www.ntbt.gov.tw/multiplehtml/5c3e2eac3d544364afb57e9afdba26d4)是 5 月 1 日至 31 日，末日遇假日或休息日延至下一工作日。本頁不自動取得延期公告，需依當年度公告手動調整日期。
- 原生日期欄位使用西元日期，旁邊顯示換算後的民國日期。選擇日期只供本次頁面使用。
- `dateDay` 驗證實際有效日期。`countdownForDate` 取本機年月日，再轉成 UTC 日序計算日數，避免夏令時間讓一天變成 23 或 25 小時。
- 進度比例＝本年 1 月 1 日至今天的日數 ÷ 本年 1 月 1 日至目標日的日數，限制於 0～100%。這是已經過的時間，不是剩餘天數比例。
- 目標當天顯示「就是今天」，過期顯示「已過 N 天」，不把使用者指定的過期日期靜默改成隔年。每分鐘更新，無外部服務或排程後端。
- `verify_salary.py` 的離線檢查也涵蓋民國換算、年底、繳稅、跨年、閏日、日期錯誤及夏令時間附近的日數案例。

### 版面與個資

內容最寬 1440px（`.wrap`），桌面採三欄卡片，1200px 以下兩欄，760px 以下單欄。第 3 站所得稅卡跨兩欄、欄位兩兩並排（扣除明細跨兩列）；寬於 1200px 時「其他所得與扣除項目」預設展開，和所得稅卡並排。大數字資訊條 `.stat-strip dd` 以 `clamp(30px, 3.6vw, 60px)` 縮放，單位字用 `small`，長數字要寫成「60,000 美元」這種數字加單位的形式，不要寫成「US$60,000」。Portfolio 與日期倒數在上方獨立顯示。薪資輸入、換算、中位數、保費、扣除與稅額各自成卡。長說明保留在原生 `details`，不能用隱藏、裁切或刪除必要限制來壓縮畫面。

七種薪資及四種人口／就業分類圖使用固定 D3.js 7.9.0。分類項目之間沒有連續關係，只畫長條，不提供折線；部分占整體的圖（年薪組成、保費、稅額占比、扣除明細、人口與就業比例）另畫一條 100% 堆疊長條 `#chartStack`，顏色與下方各列相同。長條／時間折線切換只出現在時間線。D3 原件、授權與來源記錄在 `vendor/d3/`、`d3-sources.md`，同一份程式碼內嵌 HTML，單檔仍可離線。三種真實歷月線的橫軸是民國年月，縱軸縮放須明說，長條仍由 0 起算。滑鼠、鍵盤及點選可讀時間、群組、數字與單位提示；月資料可選起訖年月，無效範圍不保留舊圖。詳見 `d3-refactor-plan.md`。

頭像只接受 2 MB 以內可解碼的 PNG、JPEG 或 WebP，以 Blob URL 本機預覽；更換、移除、離頁時撤銷 URL。姓名以 `textContent` 顯示。個人條件、姓名、圖片、生日及扣除資料不寫入 localStorage，並由 `pageshow` 清除瀏覽器回填。生日僅供年齡與退休倒數，不改變所得稅模型的本人身分假設。

### 線上操作統計與錄影

只有 `twlife.fyi` 與 `jp4.curl.rs` 會動態載入 `https://umami.doge.rs/script.js` 和 `https://umami.doge.rs/recorder.js`，網站 ID 為 `54e90682-e850-4ad7-9036-3f06ffc2425e`。`navigator.doNotTrack === '1'` 或 `localStorage.getItem('umami.disabled')` 為非空值時，不載入兩支腳本；`localhost`、本機檔案及測試也不載入。Umami tracker 限定上述網域，並排除網址 query 與 hash。

自訂事件的 data 送固定 `target` 控制項 ID／DOM 路徑、最近的 section ID，以及程式列出的 `chart`、`publicChart`、`timeline`、`persona`、`place`、`scenePlace`、`scenarioPick`、`railTask`、`need`、`field`、`removePolicy` 資料鍵。`button_click` 另有中文 `button_name`，來源限於固定 ID 名稱表、初始化時快取的固定圖表／關閉標籤，或建立動態按鈕時由程式指定的安全名稱。事件不讀私人輸入、動態結果、剪貼簿內容、連結網址或檔名，也不呼叫 `identify`。Umami 標準 payload 仍含頁面網址、標題、來源網址、語言與螢幕尺寸；tracker 排除 query／hash，recorder 的網址 metadata 沿用目前網址。載入前最多在記憶體排隊 100 筆；追蹤錯誤不影響試算。

委派事件包括 `button_click`、`action_click`、`control_change`、停止輸入 600 ms 後送出的 `control_input`、`control_focus`、`control_invalid`、`clipboard_action`（只記 `copy`、`cut` 或 `paste` 操作名稱）、`details_toggle`、`dialog_close`、`key_action`、`slider_action`、`scroll` 與 `scene_change`。`scene_change` 在背景風景換地點或路線圖換站時送出，data 只有程式定義的地點代碼 `place`（`TW`、`JP`、`MY`、`TH`、`VN`、`SG`）與站別 id `station`；滑鼠移動造成的視角變化不記錄。`btnBird` 的固定名稱是「切換俯視角」。背景裡的標籤與對話泡泡是程式內的固定字串，不會進入事件資料。鍵盤事件只記 Enter、Escape、方向鍵、Home、End；捲動每秒最多一筆，只記方向及 0～100 的頁面深度。`visibilitychange` 隱藏頁面或 `pagehide` 時，會補送尚待處理的輸入與捲動事件。
自訂事件在追蹤器啟用且可連線時全量送出，不受錄影抽樣率影響；使用者停用或瀏覽器／網路擋掉追蹤器時不送。2026-11-10 唯讀核對的 Umami 後台設定為 `replayEnabled=true`、`heatmapEnabled=true`、`sampleRate=0.15`、`heatmapSampleRate=0.15`、`maskLevel=moderate`、`blockSelector` 空值、`maxDuration=300000` ms；回放與熱圖各抽樣 15%。頁面不管理這些後台設定，本次也未修改。

錄影以 `body.rr-mask` 遮蔽文字；`#profileDetails`、`#quickAnswer`、`#personaStatus`、`#myData`、`#taskList`、`#storySection`、`#stepDetails`、`#publicStatisticsSection`、`#stepRetire`、`#stepAbroad`、`#stepWork`、`#chartDialog` 及所有 `input`、`select`、`textarea` 都標記 `rr-block`。目前載入的 recorder bundle 預設支援 `rr-mask`／`rr-block`，`maskAllInputs: true` 且 `recordCanvas: false`；維護時先核對現役 bundle，不假設未文件化的選項。

輸入遮蔽不會遮住動態結果文字、ARIA 屬性或 SVG 資料，因此必須整區排除含有個人資料或試算結果的章節，且不能只依靠 `maskAllInputs`。排除區域在回放中顯示遮蔽塊，允許的操作仍由上述事件記錄。

2026-11-10 的 twlife HTTPS 實測分兩層：Cloudflare 對瀏覽器提供 Let's Encrypt `YE2`，origin 的 cert-manager Google ACME 憑證為 Google Trust Services `WR1`；兩端網域與信任鏈均通過。Cloudflare 目前另在 edge 注入 Web Analytics beacon。本輪保留既有 zone 設定，原始碼只載入指定 Umami；完整性檢查以 origin SHA-256 為準，公開 HTML 另明列 beacon 轉換後核對，其餘公開檔案直接核對。

### 2026 年所得稅

[115 年免稅額、扣除額及級距公告](https://www.mof.gov.tw/singlehtml/384fb3077bb349ea973e7fc6f13b6974?cntId=34b463dc8f1b49f29d440d92a6fd5139)適用 2026 年所得、2027 年申報。

[稅務入口網扣除額手冊](https://www.etax.nat.gov.tw/etwmain/tax-info/understanding/tax-saving-manual/national/individual-income-tax/k2Jbgrp)提供租金、醫療、捐贈及標準／列舉規則，但其中金額範例年度是 114 年。頁面採 115 年公告金額，更新時要分開確認規則與年度金額。

[臺北國稅局免稅額與扶養條件](https://www.ntbt.gov.tw/multiplehtml/f4e3899a499b4f05821af5a5afadd2f5)說明直系尊親屬資格與年齡加成，亦須注意其內文範例年度。

本工具限定本人單身未滿 70 歲、全年居住者、只有本頁薪資，以及符合條件且沒有應合併申報所得的受扶養長輩。

- 本人免稅額為 101,000 元。扶養 60～69 歲直系長輩每人 101,000 元，70 歲以上每人 151,500 元。需確實扶養、符合資格且未重複申報。未涵蓋未滿 60 歲但無謀生能力的特殊情況。
- 一般捐款採計＝min(合規公益捐款，年薪 × 20%)，上限向下取整元。對政府／國防勞軍的合規現金捐款核實列舉，不套用一般捐款 20% 上限。政治獻金、私校等特殊類別未涵蓋。
- 合規醫藥及生育費須先扣保險給付與補助。美容、保健及非醫療養護費不計。金額應有合規院所單據。
- 本頁列舉合計＝醫療＋採計一般捐款＋合規政府／勞軍現金捐款。一般扣除額＝max(136,000，本頁列舉合計)，兩者擇一。即使選標準扣除，租金特別扣除仍可另外適用。
- 勞退自願提繳：每月工資 6% 內的自願提繳不計入提繳年度薪資所得課稅（《勞工退休金條例》第 14 條）。本頁取第 5 站的自願提繳比例，`voluntaryPension`＝round(比例 × min(月薪，150,000)) × 12，薪資所得＝max(0，年薪 − voluntaryPension)。以月薪近似月提繳工資。
- 薪資特別扣除額＝min(薪資所得，227,000)。
- 儲蓄投資特別扣除額＝min(存款利息，270,000)，只用於金融機構存款利息；股利不適用，分離課稅利息不要填（所得稅法第 17 條、第 17-3 條、第 14-1 條）。
- 股利（所得稅法第 15 條）：`incomeTaxPlan()` 同時算兩種，取稅額較低者，同額時取合併。合併計稅：股利併入所得淨額，稅額＝taxForIncome(淨額) − min(round(股利 × 8.5%)，80,000)，可為負數，代表可退稅（稅務入口網問答）。分開計稅：股利不併入，另加 round(股利 × 28%)，且不得扣房屋租金特別扣除（所得稅法第 17 條第 3 項）。
- 租金候選扣除額＝min(每月已扣補助自付租金 × 租屋月數，180,000)。需使用者確認境內自住等資格。
- 先減去租金候選額，若試算所得淨額仍超過 1,380,000 元，表示適用 20% 以上邊際稅率，租金整項不採計。不是先用未扣租金的稅率排除。選合併計稅時，試算所得淨額含股利。基本所得額等其他排富情形不在本模型範圍。
- 所得淨額＝max(0，薪資所得 ＋ 存款利息 ＋ 合併計稅的股利 − 免稅額 − 一般扣除 − 薪資特別扣除 − 儲蓄投資特別扣除 − 合規租金扣除)。
- 邊際稅率及累進差額依 `taxBands` 查表。稅額＝max(0，floor(所得淨額 × 稅率 − 累進差額))，由 `taxForIncome` 共用計算。
- 實效稅率＝max(0，所得稅) ÷（年薪＋利息＋股利），不是邊際稅率；沒有利息與股利時標示「占年薪比例」。
- 稅額圖的 `drawTaxSteps()` 以所得淨額為橫軸畫邊際稅率階梯與平均稅率（稅額 ÷ 淨額）虛線，未畫入股利抵減與分開計稅。
- 新增項目省稅額＝同樣的利息與股利、只用本人免稅額、標準扣除及薪資特別扣除、不計勞退自願提繳的基準稅額 − 本次稅額。各項省稅效果有交互作用，不能把每項扣除額直接當成退稅，也不能把各項單獨試算省稅額相加。

本頁未計基本生活費差額、長照、身心障礙、配偶、股利與存款利息以外的所得、保險列舉、股利以外的稅額抵減、基本稅額及已扣繳稅額。扶養長輩時尤其可能高估稅額，應在畫面及複製結果保留限制。健保本人無眷屬的估算不會因稅務扶養人數自動改變。

### 勞健保級距

[健保署 2026 分級與保費表](https://www.nhi.gov.tw/ch/cp-19418-9eefb-2576-1.html)、[勞保局 2026 分擔金額表](https://www.bli.gov.tw/0102606.html)是來源。投保級距依本頁月薪或年薪反推月薪歸級，找第一個不低於月薪的投保金額。超過最高級距仍採最高級距。

本頁健保本人負擔＝round(月投保金額 × 5.17% × 30%)；勞保普通事故＝round(月投保薪資 × 11.5% × 20%)；就保＝round(月投保薪資 × 1% × 20%)。只處理一般全時受僱者、本人無眷屬。股利與利息的補充保費只做提示：單次給付達 20,000 元時扣 2.11%（單次上限 1,000 萬元），頁面以全年合計 × 2.11% 標示為「全部都是達門檻單筆」時的上限估算（[健保署實務手冊 115.01 版](https://www.nhi.gov.tw/ch/dl-64814-5f26c2b955c24ebbbbf1479877ecfb7e-1.pdf)）。獎金等其他補充保費未計。勞保沿用健保前 11 級，維護時必須重新確認兩份表仍一致，不能只更新一份。

### 人口、時間線與退休

- 人口採民國 115 年 10 月底戶籍登記：總數 23,204,000 人，未滿 18 歲 3,157,240 人，65 歲以上 4,823,709 人，占 20.79%，屬超高齡社會。當月出生 8,035、死亡 17,393，自然減少 9,358 人；不含遷徙。
- 就業採民國 115 年 9 月官方原表，戶籍 11,640 千人、常住 12,204 千人；戶籍男女小計因千人獨立捨入比總數少 1 千人，不調平。國籍比例仍缺完整同母體分組，不以差額猜測。
- 時間線人口為民國 114/09～115/10 共 14 個月；戶籍及常住就業各為民國 115/01～115/09 共 9 個月。教育固定最高已取得學歷，不用含在學／肄業的另一分組替換。
- 更新指令為 `mise exec -- python fetch_population_series.py`，會重抓程式指定期間及來源，覆寫 JSON 並只替換 HTML 的 `publicHistory` 資料區塊。變更期間或官方表網址時，先更新程式範圍、錨點、主卡快照、來源文件及測試；這不是自動尋找最新月份的服務。
- 年齡為本機日期的曆日周歲估算；退休進度＝出生日起已經過天數 ÷ 至目標年齡生日總天數，上下限 0～100%。未填／未來生日不算；一般日期進度仍從今年元旦起算。
- 第 5 站資產：現金與存款＋國內股票基金＋海外資產（選美元時乘匯率），先扣緊急預備金＝每月必要支出 × 月數（預設 6 個月，屬假設），剩下的才是退休試算的存款。每月必要支出留白時，依使用者年齡對應主計總處 114 年家庭收支調查附表 3 的經濟戶長年齡組，每戶消費支出 ÷ 每戶人數 ÷ 12（`spendingByAge`）。退休生活費預設 28,513 元＝114 年全國平均每戶消費支出 920,397 ÷ 2.69 ÷ 12。來源與限制見 `retirement-sources.md`；`verify_salary.py` 會用封存原檔核對。
- 匯率：頁面內 `fx` 為中央銀行新台幣兌美元銀行間收盤匯率快照，`verify_salary.py` 會用封存的 CBC CSV 核對。「取得最新匯率」是使用者觸發的匯率連網操作，與上述線上統計分開：只在按下時 GET `https://open.er-api.com/v6/latest/USD`，不帶 cookies、referrer 或金額，驗證 `result`、`rates.TWD` 介於 1～1,000 與更新時間後才採用，失敗沿用快照；結果只留在本次頁面。該服務條款要求標示 Rates By Exchange Rate API 並禁止轉存，所以不能把它的匯率寫回快照。
- 一生時間軸（`renderLifeline()`）：0～100 歲標出出生、現在、開始投保、勞保滿 15 年、勞保年金請領年齡、退休與存款用完（年報酬 4%）。退休標記是 `role=slider`，可拖動或用方向鍵、Home、End 調整 50～75 歲，放開才寫回 `#retireAge` 並重算。下方三個試算各只改一個條件：晚 1 年退休、每月多存 5,000 元、自願提繳 6%（含今年所得稅變化）。
- 勞退新制一般年齡門檻 60 歲；勞保老年年金為出生年 `min(65, max(60, 西元出生年－1897))`，另須確認年資與退保。2/29 目標年無生日則不推定精確請領日；特殊及未來修法不計。法規與例外見 `retirement-sources.md`。

### 原始來源封存

執行 `mise exec -- python archive_sources.py` 後，以 `mise exec -- python verify_archive_sources.py` 檢查。程式收集 HTML、維護／來源文件、歷月 JSON 的官方來源與公開下載參數。RIS 每個月份各存原始 ODS，不把動態下載入口當成附件內容。

檔案依下載日期／政府網域／原網址路徑分類，保留回應檔名或 URL basename 與原始格式；同名內容變更時使用新版本子目錄，不修改檔名或覆蓋舊內容。manifest 記錄來源、GET／POST、非敏感表單參數、MIME、位元組、SHA-256、含時區時間與錯誤。同日檔案僅在雜湊及原名驗證一致時重用。

目前封存狀態應以 `sources/manifest.json`、`sources/index.html` 及 `verify_archive_sources.py` 的實際輸出為準。2026-10-21 驗證為 186 份原件完整、19 筆既有來源下載失敗；不要沿用早期「51 個請求、48 份成功、3 個失敗」的數字。部分官方站對 CLI 回 403 時，可用正常瀏覽器同源 fetch（省略 cookies／登入憑證）取得原始回應，再核對 SHA-256 並標記傳輸方式；不得用 DOM、Reader 或截圖冒充原件。

封存不包含原站外部圖片、CSS 或互動服務，不能宣稱原站完整離線。不得為下載關閉 CA／主機驗證、接受瀏覽器憑證例外，或保存 session cookies／CSRF 到 metadata。


## 更新流程與完成條件

每半年的全面查核照 [`data-refresh-runbook.md`](data-refresh-runbook.md)：資料總表、清洗規則、已知的坑與查核紀錄都在那裡。

1. 在修改前查看 `git status --short`，保留既有修改；建立時間戳備份並比對一致，不覆蓋舊備份，也不把備份加入 Git。
2. 記錄官方來源標題、公布日期、統計年度、母體、薪資定義、單位及初步／修正標記。先確認有沒有新的資料，再決定是否更新快照。
3. 更新中位數時，從原始表逐項核對全體、男女與四組學歷。同步修改 `salaryMedians` 的數值、年度、來源網址、原始表網址、查核日及測試。修正既有年度亦須更新標記。
4. 更新稅額時，核對公告適用所得年度與申報年度。同步免稅額、扣除額、稅率級距、累進差額、租金排富分界、股利抵減率與上限、分開計稅稅率、儲蓄投資特別扣除額、補充保費費率與門檻、畫面說明、複製內容及測試。手冊與新聞稿範例年度可能不同。
5. 若新增主管中位數或交叉分組，先取得相符定義與母體的官方資料，再改動比較功能。僅取得平均數不能解除「沒有職務中位數」提示。
6. 執行 CLI 與瀏覽器回歸測試，確認手機、深淺色、頭像與資料清除。比對最終差異只涉及本次需求。
7. 回報實際通過的檢查及未驗證項目。CLI 語法通過不能宣稱瀏覽器互動通過，ODS 數值相同也不能代表資料仍是最新。

## 可安裝的維護 skill 定義

2026-10-18 起，維護 skill 放在專案內 [`.claude/skills/salary-data-maintenance/SKILL.md`](.claude/skills/salary-data-maintenance/SKILL.md)，在這個目錄開 Claude Code 時會載入；踩過的坑記在 [`lessons-learned.md`](lessons-learned.md)。下方是較早的全域安裝版定義，保留作歷史，內容以專案內 skill 為準。

安裝狀態目前為受阻。工具回覆 `The managed-skills root is a symlink; refusing to operate outside the managed directory.`。需先由環境管理者確認 managed skill 路徑的支援方式，再使用受控工具安裝；不要手動寫入 symlink 目標來繞過檢查。

以下是待安裝內容，並非已啟用的 skill。

```markdown
---
name: salary-calculator-maintenance
description: 維護薪資 Portfolio、官方薪資／人口時間線、稅務、退休門檻與來源封存，或檢查公式與回歸測試時使用。
---

# 薪資計算器維護

1. 讀取 `salary-maintenance.md`、`project-plan.md` 及相關來源文件；以同組 salary.html、verify_salary.py、population-timeseries.json 定位專案，不硬編碼個人路徑。
2. 檢查工作目錄與版本狀態。無 Git 時先建立時間戳備份並驗證一致。
3. 更新來源時核對年度、母體、平均數／中位數、單位及初步／修正標記。稅務需另核對所得年度與申報年度。
4. 只修改本次需求及直接影響的畫面、公式、複製結果、測試與手冊。維持單檔離線，不新增遠端頭像、追蹤或個資持久化。
5. 執行 `mise exec -- python verify_salary.py`；查核來源時加 `--sources`。更新歷月資料執行 `fetch_population_series.py`，再執行 `archive_sources.py` 與 `verify_archive_sources.py`。不關閉 CA 或主機驗證。
6. 在乾淨分頁執行所有 `salary*.test.js` 函式。Firefox MCP 可用 evaluate_script.function，移除末尾分號；過大時只去縮排／行間空白並先驗證語法，不刪案例。僅用合成資料。
7. UI 改動需確認實際 375px 內容寬度、深淺色、鍵盤操作、頭像驗證與重載後個資清除。Firefox 視窗有最小寬度時，用同源 iframe 並讀取 innerWidth 驗證。
8. 保留資料邊界：經常性月薪與全年總薪資分開，性別與學歷不冒充交叉分組；現有主管資料是平均數，不能冒充中位數；倍數及差額百分比不是人口排名。
9. 稅額維護保留標準／列舉擇一、試扣租金後判斷排富、免稅額與扣除額不等於退稅，以及未計基本生活費差額等限制。
10. 時間線須核對月份、母體及單位，JSON 與 HTML 同步；缺值不插值，戶籍與常住不相減猜國籍。退休只判年齡門檻，不承諾給付。
11. 第 6 站依 `retirement-fire-plan.md` 的人物情境矩陣維護：新增國籍、地點或資產類型時，同步 `places`、路線卡規則、`retire-abroad-sources.md` 的來源列、`FOREIGN_OFFICIAL_HOSTS` 與 `salary-abroad.test.js` 的代表人物。稅負看稅務居住地不看國籍；美股預扣、券商年底退回（只限基金公布的 QII／短期資本利得）、居住國課稅與遺產稅分開呈現。簽證、稅制每次更新都要重新查核官方原文。
12. 比對最終差異並同步文件。語法、原件、瀏覽器行為、資料新鮮度分開回報；完成所有可執行檢查，並列明外部限制。
```
