◆ wport | pm_36

AI 新增職缺 — Prod 上線強化計畫

AI 匯入職缺(Universal Parser)

來源 doc/feature/pm_36/ai-job-import-hardening-plan.md

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 勾選;env AI_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 像合法職缺
  • scoperecruitment/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 主腳本不符 → 400 locale_content_mismatch(zh 站允許 EN/zh 並存白名單)
  • 驗收:locale=th + 中文 → 400;locale=zh + 英文 → 通過
  • owner:BE

W0-5. LLM provider 資料政策書面文件

  • why:驗收清單缺項;事後稽核需證據
  • scopedocs/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_usage log 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
  • scopeopossum circuit 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
  • scopetest/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 只有摘要,事件追查與客訴回放困難
  • scopeai_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-4
  • src/modules/ai-parser/services/wport-universal-parser.service.ts — W1-2 / W2-2 / W2-3
  • src/modules/ai-parser/services/llm-input-sanitizer.service.ts — W0-4
  • src/modules/ai-parser/prompts/job-extraction.prompt.ts — W2-2 / W3-6
  • 帳號刪除流程(W3-5)— 影響 users 模組

契約風格對齊檢查

  • 沿用 DataResponse / MsgResponse / handleCustomError pattern
  • API 欄位 snake_case;新表欄位命名對齊既有風格(*_at datetime、enc_* 加密 id)
  • 錯誤 code 走 i18n 路徑:error.ai_parser.* / error.ai_import.*

管理後台連動清單(彙整)

Wave項目W101-AMS 需做
W1-1成本治理建議加「公司 LLM 用量」查詢(可後置)
W2-3Audit 加密留痕必加 admin parse_job audit 查詢頁
W3-6Prompt registry必加 prompt 管理(建立/canary/rollback)介面

待你確認的決策點

  1. Wave 0 範圍:W0-1(同意勾選)需要 FE 配合,是否本週能拉到 FE 資源?若否是否退而求其次「先在 modal 顯示告知文字 + 接受即視為同意」(不存版本紀錄)?
  2. LLM provider 主備:目前流程是否已有 OpenAI + Anthropic 雙串?W1-2 才有得做;若只有單家,W1-2 改為「circuit breaker + 降級提示」不做 fallback
  3. Audit 加密 key 管理:用 AWS KMS 還是 env key?影響 W2-3 工時
  4. Eval golden set 來源:W2-1 30 筆 JD 從哪來?真實匿名 JD / 合成 / 公開招募網站?
  5. Wave 排序:上述順序合理嗎?特別是 W1-1 成本治理 vs W1-3 觀測,哪個更急?
  6. 管理後台:W2-3 / W3-6 需要 W101-AMS 同步開卡,是否現在就要通報 AMS team?

確認後我再依此開 PRD / API spec / DDL / Trello 卡。