AI 新增職缺 — Prod 上線強化計畫
把現有 8 項 finding(7 fixed / 1 partially)+ 進階向量發現(F-009~F-013)+ AI 應用通用議題,整理成可分批落地的工作清單。本份只列做什麼、為何做、驗收,不寫程式細節,等你確認後再轉 spec / 開卡 / 實作。
Wave 0 — 上線前必補(阻塞 prod,預估 3-5 工作天)
W0-1. F-006 後段:FE 揭露 + 同意紀錄
- why:raw_content 送第三方 LLM 屬個資法第 8 條告知義務,跨境傳輸需明示同意
- scope:DDL
user_ai_import_consents/ DTO 帶consent_version/ FE 首次匯入 modal 勾選;envAI_IMPORT_CONSENT_VERSION控版本,bump 後強制重勾 - 驗收:未帶 consent → 400;版本 bump 後舊使用者重勾;同意紀錄落 DB(user_id, company_id, version, ip, ua, ts)
- owner:BE + FE
- 管理後台連動:無需連動
W0-2. F-012:DTO byte-length validator
- why:char count vs utf8 byte count 不一致導致 quota 浪費 + retry 不豁免
- scope:自訂
@MaxByteLength(51200)decorator 取代@MaxLength(51200) - 驗收:CJK 49KB chars (~146KB utf8) → 400 不扣 quota;ASCII 50KB → 通過
- owner:BE
W0-3. F-013:FE 404 fallback 改錯誤頁
- why:直訪 placeholder enc_id 渲染空白 skeleton 像合法職缺
- scope:
recruitment/view-job/[encId]收 404/403 改顯式錯誤頁 +noindex - 驗收:placeholder enc_id 直訪 → 錯誤頁;爬蟲不收錄
- owner:FE
W0-4. F-011:locale ↔ content 語言一致性檢查
- why:locale=th + 中文 raw → 翻譯後寫入 i18n 表,跨語系污染
- scope:sanitizer 加
detectPrimaryScript(),與 locale 主腳本不符 → 400locale_content_mismatch(zh 站允許 EN/zh 並存白名單) - 驗收:locale=th + 中文 → 400;locale=zh + 英文 → 通過
- owner:BE
W0-5. LLM provider 資料政策書面文件
- why:驗收清單缺項;事後稽核需證據
- scope:
docs/compliance/llm-provider-data-policy.md(OpenAI/Anthropic 保留期、訓練使用、DPA 連結、最後核對日)+ 季度核對 cron 提醒 - 驗收:文件 commit;privacy policy 加跨境傳輸條款連結
- owner:BE + 法務
Wave 1 — 上線後 2 週內(1 sprint)
W1-1. 成本治理(B1)
- why:目前只有 request 次數限制,單次大 token 仍可燒成本
- scope:
llm_usagelog table(company_id, model, in/out tokens, cost_usd, ts)- Redis
llm:cost:daily:{company_id}INCRBYFLOAT,超LLM_DAILY_COST_LIMIT_USD_PER_COMPANY拒絕 - hourly cron:當日總 cost > 預警閾值 → 寄信 + Slack
- 驗收:mock 大量 token → 在 limit 處精確擋下;alert 觸發
- 管理後台連動:W101-AMS 加「公司 LLM 用量/成本」查詢頁(建議,可後置)
W1-2. Provider 故障處理(B2)
- why:LLM provider outage 時整個功能無 graceful degradation
- scope:
opossumcircuit breaker(primary OpenAI / fallback Anthropic);breaker open 時 placeholder 標failed_recoverable不扣 quota - 驗收:mock primary 連續 5xx → 自動切 fallback;30s 後半開試探
W1-3. 觀測指標 / SLO(B12)
- why:缺少 prod 黑盒指標,事件處理只能事後挖 log
- scope:OpenTelemetry / Prometheus 暴露 latency histogram、成功率、placeholder stuck、token usage、cost;Grafana dashboard + SLO(p95 < 30s、成功率 > 95%、stuck < 0.1%)+ alert
- 驗收:dashboard 可查;故意打掛 → alert 30s 內觸發
Wave 2 — 1 個月內(1 sprint)
W2-1. Eval / regression suite(B4)
- why:目前只有攻擊測試,沒有解析品質量化;prompt/model 改動無 gate
- scope:
test/eval/golden-jd-set/30 筆 JD + expected.json;CI gate(mean F1 < 0.85 block merge);prompt PR 必附 diff report - 驗收:baseline 落檔;改 prompt 後新分數 vs baseline 自動 diff
W2-2. Structured Output(B5)
- why:取代 prompt 約束 JSON,物理上限制 LLM 輸出 schema,F-002/F-004 攻擊面再縮
- scope:OpenAI
response_format: json_schema strict:true/ Anthropic tool use;zod schema → JSON Schema 自動轉 - 驗收:故意要求加額外欄位 → 仍只回 schema 內欄位;enum 違反 → provider reject
W2-3. Audit 留痕加密 90 天(B6)
- why:目前 logging 只有摘要,事件追查與客訴回放困難
- scope:
ai_parser_audit_log(jobbank_logs)AES-256-GCM 加密存 raw input + LLM output;KMS / env key;daily purge cron > 90 天;管理後台 admin 查詢介面 - 驗收:DB 直查無明文;admin 後台可解密;90 天後自動消失
- 管理後台連動:W101-AMS 需加「parse_job audit 查詢」頁(admin 限定)
Wave 3 — 季度規劃(依優先序)
W3-1. Model 版本鎖定 + 升版流程(B3)
- env 鎖完整 model id(含日期),不用 alias;新 model canary 10% → eval pass → 全量;PR 必附 regression report
- 驗收:
grep -r "gpt-4o-mini[^-]" src/為空
W3-2. 使用者反饋迴路(B7)
- placeholder→正式 job + 後續編輯的 diff 寫
ai_parse_feedback - 週報:高頻被改欄位(prompt 改善訊號)+ 同公司 7 天覆寫率 > 90% 異常標記
- 驗收:改職缺 → feedback 表有 row;週報自動寄
W3-3. i18n 一致性強化(B8)
- placeholder 寫
jobs_i18n強制source_locale = detectPrimaryScript(raw),其他語系欄位 NULL - 驗收:raw=中文 → 只有 zh row
W3-4. 配額 token bucket(B9)
- 加 minute / 10-min 兩層 rate limit(防 burst),疊在 daily 之上
- 驗收:1 秒內 6 次 → 第 6 次 429;間隔 12s 連 20 次 → 通過
W3-5. GDPR 刪除權級聯(B10)
- 帳號刪除流程級聯
ai_parser_audit_log+data_deletion_log留證 - 驗收:刪帳號後 audit log 無該 company row;deletion_log 有紀錄
W3-6. Prompt registry + A/B + rollback(B11)
- DDL
prompt_versions;prompt body 從 DB 讀 cache 5min;管理後台介面 canary/全量/rollback;每 parse_job 紀錄prompt_version_id - 驗收:改 prompt 不需 deploy;rollback < 1min;canary 流量分配正確
- 管理後台連動:W101-AMS 需加 prompt 管理頁
W3-7. F-010 補強:retry race row lock
- 並發 retry 用 row lock 或 unique partial index 防孤兒 parse_job
- 驗收:兩個並發 retry → 只有一個成功,另一個收
retry_in_progress
W3-8. F-009 / A13 補驗證 + 補測試
- 公開搜尋已過濾 PUBLISHED(code review 確認),補單元測試鎖死
- 企業內部列表保留 placeholder(PRD 設計),補註解說明
商業規則對齊檢查
- 與
business-context.md「企業模式-職缺管理」一致:placeholder 屬企業內部草稿狀態,不影響求職者搜尋面 - 與既有「配額/限流」風格一致:本計畫 W1-1 / W3-4 沿用 Redis 為主、in-memory fallback 的既有 pattern
- audience 標註:本計畫所有項目皆為企業模式 + 內部 admin(W2-3 / W3-6)
可能受影響的既有程式與 API
src/modules/jobs/services/jobs-ai-import.service.ts— W0-2 / W0-4 / W1-1 / W1-2 / W3-4src/modules/ai-parser/services/wport-universal-parser.service.ts— W1-2 / W2-2 / W2-3src/modules/ai-parser/services/llm-input-sanitizer.service.ts— W0-4src/modules/ai-parser/prompts/job-extraction.prompt.ts— W2-2 / W3-6- 帳號刪除流程(W3-5)— 影響 users 模組
契約風格對齊檢查
- 沿用
DataResponse/MsgResponse/handleCustomErrorpattern - API 欄位 snake_case;新表欄位命名對齊既有風格(
*_atdatetime、enc_*加密 id) - 錯誤 code 走 i18n 路徑:
error.ai_parser.*/error.ai_import.*
管理後台連動清單(彙整)
| Wave | 項目 | W101-AMS 需做 |
|---|---|---|
| W1-1 | 成本治理 | 建議加「公司 LLM 用量」查詢(可後置) |
| W2-3 | Audit 加密留痕 | 必加 admin parse_job audit 查詢頁 |
| W3-6 | Prompt registry | 必加 prompt 管理(建立/canary/rollback)介面 |
待你確認的決策點
- Wave 0 範圍:W0-1(同意勾選)需要 FE 配合,是否本週能拉到 FE 資源?若否是否退而求其次「先在 modal 顯示告知文字 + 接受即視為同意」(不存版本紀錄)?
- LLM provider 主備:目前流程是否已有 OpenAI + Anthropic 雙串?W1-2 才有得做;若只有單家,W1-2 改為「circuit breaker + 降級提示」不做 fallback
- Audit 加密 key 管理:用 AWS KMS 還是 env key?影響 W2-3 工時
- Eval golden set 來源:W2-1 30 筆 JD 從哪來?真實匿名 JD / 合成 / 公開招募網站?
- Wave 排序:上述順序合理嗎?特別是 W1-1 成本治理 vs W1-3 觀測,哪個更急?
- 管理後台:W2-3 / W3-6 需要 W101-AMS 同步開卡,是否現在就要通報 AMS team?
確認後我再依此開 PRD / API spec / DDL / Trello 卡。