職涯諮詢客戶 8 維度 JSON Schema v1.0
建立日期:2026-05-25 | 作者:Tim + Claude | 狀態:草稿(待 L210 實作驗證) 用途:為 SYS-10 LINE Bot Phase 4 個人化諮詢打底;定義客戶資料結構、KV 儲存規格、自動化機制
一、用途與範圍
本文件服務對象
| 對象 | 用途 |
|---|---|
| L210 實作者 | Phase 4 Claude Tool Use 客戶資料萃取的完整規格 |
| 諮詢完整輸出 SKILL | Stage 1 KV 寫入的欄位定義依據 |
| Tim | 了解哪些客戶資料會被收集與如何使用 |
明確不在範圍內
- LINE Bot 的 Gemini/Claude prompt 設計(→ L210)
- 諮詢完整輸出 SKILL 的程式碼修改(→ L210)
- line-webhook.js 的程式碼修改(→ L210)
個人化用途示例(讓 L210 知道為何收集這些資料)
career_status.employment_status = "求職中"
→ LINE Bot 回應求職類問題時,主動提供履歷/面試資源,不問「你現在有在找工作嗎?」
pain_pattern.p_type_primary = "P3"
→ 涉及面試問題時,優先調用 six-consulting-frameworks.md P3 相關框架
service_progress.follow_up_date 距今 < 7 天
→ 對話開頭自然帶出:「你上次提到 [next_milestone],有進展嗎?」
engagement_cycle.session_count = 1(首次諮詢後)
→ 使用較引導性的語氣,提供更多背景說明
salary_expectation.target_monthly_ntd 已知
→ 討論薪資議題時,直接對應目標數字,不需重新詢問
二、完整 JSON Schema
{
"schema_version": "1.0",
"metadata": {
"client_id": "C-YYYYMM-XXX",
"line_user_id": "Uxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"last_updated": "YYYY-MM-DD",
"consent_at": null,
"consent_revoked_at": null,
"data_deletion_requested_at": null
},
"dimensions": {
"career_status": {
"employment_status": "在職|求職中|待業|自由接案|創業中",
"current_role": "string",
"company_size": "新創(<50)|中小(50-500)|大型(500+)|外商|政府/學術",
"tenure_current_role_years": 0
},
"job_seeking_intent": {
"intent_level": "主動求職|被動觀望|暫不考慮",
"timeline_months": 0,
"trigger": "薪資|發展|環境|興趣|被裁|其他"
},
"work_experience_years": {
"total_years": 0,
"main_function": "string"
},
"industry_target": {
"current_industry": "string",
"target_industry": "string",
"open_to_cross_industry": true
},
"salary_expectation": {
"current_monthly_ntd": 0,
"target_monthly_ntd": 0,
"negotiation_readiness": "已有底線|模糊|未思考"
},
"engagement_cycle": {
"last_contact": "YYYY-MM-DD",
"session_count": 0,
"preferred_channel": "LINE|Meet|Email"
},
"pain_pattern": {
"p_type_primary": "P1|P2|P3|P4|P5|P6",
"p_type": ["P1"],
"pain_description": "string"
},
"service_progress": {
"active_service": "S4|S6|D1|W6|其他",
"current_phase": "string",
"next_milestone": "string",
"follow_up_date": "YYYY-MM-DD"
}
}
}
欄位說明
metadata
| 欄位 | 類型 | 說明 |
|---|---|---|
schema_version |
string | 固定 "1.0";schema 升版時用於 migration 判斷 |
client_id |
string | CRM ID(格式:C-YYYYMM-XXX);SKILL 觸發 key |
line_user_id |
string | null | LINE User ID(格式:Uxxxxxxxx);可選,初始化後填入 |
last_updated |
date | Stage 1 或 Stage 2 最後寫入日期 |
consent_at |
date | null | 客戶同意個人化服務的日期;未同意則不啟動個人化 |
consent_revoked_at |
date | null | 客戶撤回同意的日期;撤回後停止 Stage 2 更新 |
data_deletion_requested_at |
date | null | 客戶請求刪除資料的日期;L210 需在 24 小時內執行 DEL |
dimensions.pain_pattern(修正說明)
p_type_primary:主要類型(單值),Stage 1 從逐字稿判定p_type:陣列,允許複數(如 P3 + P6 混合型)pain_description:自由文字,補充 P-type 無法涵蓋的細節- P-type 定義來源:
knowledge/methodology/career-problem-patterns.md- P1 履歷無效率 / P2 方向不清晰 / P3 面試表現差
- P4 薪資不到位 / P5 職場困境 / P6 轉職焦慮
三、自動化難度標記
| 維度 | 欄位 | 難度 | 來源 |
|---|---|---|---|
| career_status | employment_status, current_role | 🟡 | 逐字稿(Stage 1);LINE 訊息偶爾可偵測 |
| career_status | company_size, tenure_current_role_years | 🔴 | 幾乎只能從 Meet 逐字稿取得 |
| job_seeking_intent | intent_level, timeline_months, trigger | 🟡 | Stage 1 主要來源;Stage 2 偵測信號弱但可補充 |
| work_experience_years | total_years, main_function | 🔴 | 逐字稿(Stage 1);LINE 訊息極少提及 |
| industry_target | current_industry, target_industry | 🟡 | Stage 1 主要;Stage 2 偶可偵測 |
| industry_target | open_to_cross_industry | 🟡 | Stage 1 |
| salary_expectation | current/target_monthly_ntd | 🔴 | 逐字稿(Stage 1);LINE 訊息偶爾提及數字 |
| salary_expectation | negotiation_readiness | 🟡 | Stage 1 |
| engagement_cycle | last_contact | 🟢 | 全自動:每次 LINE webhook → 更新 |
| engagement_cycle | session_count | 🟢 | 全自動:Stage 1 執行 +1 |
| engagement_cycle | preferred_channel | 🟡 | Stage 1 判定 |
| pain_pattern | p_type_primary, p_type | 🟡 | Stage 1;後續 LINE 訊息可補強信號 |
| pain_pattern | pain_description | 🔴 | Stage 1 |
| service_progress | active_service, current_phase | 🟡 | Stage 1;LINE 訊息中提到課名可偵測 |
| service_progress | next_milestone | 🟡 | Stage 1 |
| service_progress | follow_up_date | 🟢 | Stage 1 設定;可以 LINE 訊息觸發更新 |
🟢 全自動(LINE Bot webhook)| 🟡 半自動(Stage 1 主導,Stage 2 補充)| 🔴 純 Stage 1(需逐字稿)
四、Stage 1 vs Stage 2 欄位權限
Stage 1(諮詢完整輸出 SKILL — 深度寫入)
觸發:每次 Tim 執行 諮詢完整輸出 [客戶代號] SKILL
可更新:所有欄位(完整覆蓋,包含 🔴 欄位)
機制:4 步驟寫入(見 Section 六)
Stage 2(LINE Bot webhook — 輕量更新)
觸發:客戶每次傳 LINE Bot 訊息(前提:consent_at 已設定) 僅可更新以下欄位:
| 可更新欄位 | 偵測方式 |
|---|---|
engagement_cycle.last_contact |
每次訊息自動更新(無需 AI 萃取) |
engagement_cycle.preferred_channel |
每次訊息確認為 LINE |
job_seeking_intent.intent_level |
Pattern B + C 偵測(見 Section 五) |
job_seeking_intent.timeline_months |
Pattern C 偵測 |
salary_expectation.current_monthly_ntd |
Pattern B 數字偵測 |
salary_expectation.target_monthly_ntd |
Pattern B 數字偵測 |
service_progress.next_milestone |
Pattern D 偵測 |
service_progress.follow_up_date |
Pattern D 偵測 |
Stage 2 禁止更新:work_experience_years.*、pain_pattern.*、career_status.company_size(需逐字稿確認,LINE 訊息不可靠)
五、Stage 2 Gemini 偵測框架
完整 Gemini prompt 設計 → L210 實作時定義。本節提供偵測分類框架供 L210 參考。
| Pattern | 偵測目標 | 觸發信號範例 | 更新欄位 |
|---|---|---|---|
| A — 求職狀態信號 | employment_status 變化 | 「我最近辭職了」「剛被裁員」「現在在找工作」 | career_status.employment_status, job_seeking_intent.intent_level |
| B — 薪資數字 | 薪資期望值 | 「希望薪資 6 萬」「現在月薪 4 萬 5」「目標年薪 80K」 | salary_expectation.current/target_monthly_ntd |
| C — 時程表達 | 求職時程 | 「希望 3 個月內找到」「年底前想換工作」「下個月要面試」 | job_seeking_intent.timeline_months, follow_up_date |
| D — 服務進度更新 | 服務進展 | 提到課名、諮詢預約、階段完成 | service_progress.next_milestone, follow_up_date |
| E — 互動(自動) | 最後接觸時間 | 每次訊息(無需 AI 偵測) | engagement_cycle.last_contact |
萃取失敗處理:Pattern A-D 無法信心確定時(低置信度)→ 靜默跳過,不更新 KV
六、KV Key 結構
Upstash Redis 命名空間:
client:{crmId}:profile ← 最新 profile(主要讀取 key)
client:{crmId}:profile:{YYYY-MM-DD} ← 歷史備份(每次 Stage 1 寫入前保存)
mapping:{lineUserId} ← lineUserId → crmId 對照表
範例:
client:C-202604-001:profile = {...完整 JSON...}
client:C-202604-001:profile:2026-05-25 = {...Stage 1 執行前備份...}
mapping:U8a9b2c3d4e5f6a7b8c9d0e1f2a3b4c5 = C-202604-001
TTL 規則:
client:*:profile:無 TTL(永久保存)client:*:profile:*(歷史備份):TTL 365 天mapping:*:無 TTL(永久保存)- 若 consent_revoked_at 或 data_deletion_requested_at 已設定 → 執行
DEL清除所有相關 key
七、Stage 1 寫入機制(Bash curl SOP)
前提:Tim 本機需有以下環境變數(與 SYS-10 Vercel 相同的 Upstash 實例):
KV_REST_API_URL=https://XXXXX.upstash.io
KV_REST_API_TOKEN=YYYYY
Stage 1 完整寫入流程(4 步驟)
# Step 1:讀取現有 profile(GET)
CURRENT=$(curl -s "$KV_REST_API_URL/get/client%3A${CLIENT_ID}%3Aprofile" \
-H "Authorization: Bearer $KV_REST_API_TOKEN" | jq -r '.result // empty')
# Step 2:備份舊版本(SET with TTL 365 days)
TODAY=$(date +%Y-%m-%d)
BACKUP_KEY="client:${CLIENT_ID}:profile:${TODAY}"
curl -s -X POST "$KV_REST_API_URL/pipeline" \
-H "Authorization: Bearer $KV_REST_API_TOKEN" \
-H "Content-Type: application/json" \
-d "[[ \"SET\", \"${BACKUP_KEY}\", $(echo $CURRENT | jq -R '.'), \"EX\", \"31536000\" ]]"
# Step 3:合併新資料與舊資料(Python merge)
# (由 SKILL 中的 Python 腳本執行 JSON merge)
# Step 4:寫入最新 profile(使用 pipeline 避免 URL 長度問題)
NEW_PROFILE=$(python3 -c "import json,sys; print(json.dumps(json.load(sys.stdin)))" < new_profile.json)
curl -s -X POST "$KV_REST_API_URL/pipeline" \
-H "Authorization: Bearer $KV_REST_API_TOKEN" \
-H "Content-Type: application/json" \
-d "[[ \"SET\", \"client:${CLIENT_ID}:profile\", ${NEW_PROFILE} ]]"
⚠️ 使用 pipeline(POST body)而非
/set/{key}/{value}路徑格式,避免大 JSON 觸發 URL 長度限制
mapping 建立(初始化時執行)
# 建立 lineUserId → crmId 對照(由 L210 SKILL 整合)
curl -s -X POST "$KV_REST_API_URL/pipeline" \
-H "Authorization: Bearer $KV_REST_API_TOKEN" \
-H "Content-Type: application/json" \
-d "[[ \"SET\", \"mapping:${LINE_USER_ID}\", \"${CLIENT_ID}\" ]]"
八、初始化與同意機制
初始化流程
Tim 執行諮詢完整輸出 SKILL [客戶代號]
→ SKILL 提示:「此客戶是否有 LINE Bot 帳號?(可選,格式:Uxxxxxxxx,從 LINE Bot Vercel log 查詢)」
→ Tim 輸入 lineUserId(或跳過)
→ 若輸入 → 建立 mapping:{lineUserId} → crmId
→ 寫入 client:{crmId}:profile(含 consent_at: null)
客戶下次傳 LINE 訊息(首次 mapping 命中)
→ LINE Bot 偵測到 mapping 存在但 consent_at = null
→ 發送同意請求訊息(⚠️ 訊息文字 → LEG 審查後定稿,見 Section 九)
→ 客戶回覆同意 → LINE Bot 設定 consent_at = 今日日期
→ 個人化功能啟動
lineUserId 取得方式
- 從 SYS-10 Vercel log 查詢(客戶曾傳訊才有記錄)
- 若客戶從未傳訊給 LINE Bot → lineUserId 不存在 → 跳過 mapping 建立
- 此情況 client profile 仍會建立,但個人化功能對此客戶無效
個人化啟動條件(LINE Bot 每次收訊時檢查)
mapping:{lineUserId} 存在 → 取得 crmId
AND client:{crmId}:profile 存在
AND metadata.consent_at ≠ null
AND metadata.consent_revoked_at = null
→ 啟動個人化(讀取 profile 注入 Gemini/Claude prompt)
→ 同時執行 Stage 2 輕量更新(async,先回覆後更新)
以上任一條件不符 → 靜默降級為通用回覆(不告知客戶,不報錯)
九、法律合規注意事項(⚠️ L210 實作前必讀)
PDPA 個資法合規要求
| 項目 | 規範 | 實作要求 |
|---|---|---|
| 同意告知 | 個資法第 8 條 | 客戶首次啟用個人化前,必須明確告知「收集哪些資料、用途為何」 |
| 撤回同意 | 個資法第 3 條 | 客戶傳「退出個人化」或類似指令 → 設定 consent_revoked_at,停止 Stage 2 |
| 刪除請求 | 個資法第 11 條 | 設定 data_deletion_requested_at → 24 小時內 DEL 所有 client:* 和 mapping:* key |
| 同意訊息文字 | ⚠️ 待 LEG 審查 | 禁止在 LEG 審查完成前設定 consent_at;不得在客戶未明確回覆前視為同意 |
⛔ 同意訊息設計(L210 實作前必做):
- Tim 草擬同意告知文字
- 送 LEG 部門審查(legal/CLAUDE.md 路由)
- 確認客戶觸發動作(回覆「同意」/ 點選快速回覆按鈕)
- 確認後才實作 consent_at 設定邏輯
Stage 2 資料處理聲明
Stage 2 使用 Gemini API 分析 LINE 訊息內容以萃取客戶職涯資訊。同意訊息必須包含此說明。
十、Fallback 規則(L210 實作規範)
| 失敗情境 | 處理方式 |
|---|---|
| Upstash GET timeout / 錯誤 | 靜默降級為通用回覆,不報錯給客戶 |
| mapping:{lineUserId} 不存在 | 靜默降級,繼續通用服務 |
| consent_at 未設定 | 靜默降級,發送同意請求(若尚未發送) |
| Stage 2 Gemini 萃取失敗 | 跳過 KV 更新,不影響回覆 |
| Stage 2 Gemini 置信度低 | 跳過 KV 更新(不寫入不確定資料) |
| Stage 2 寫入失敗 | fail-open:仍返回回覆,背景記錄 console.error |
| Stage 1 寫入失敗 | 報告 Tim(非背景忽略),需手動重試 |
核心原則:Stage 2 任何環節失敗 = 靜默降級,不影響客戶收到回覆
十一、Schema 版本管理
Migration 策略(v1.0 → v1.x)
- 新增欄位:讀取時若欄位不存在,L210 使用
null或預設值,不報錯 - 移除欄位:舊 KV 記錄中保留舊欄位直到下次 Stage 1 覆蓋(不影響功能)
- 重大結構改變(v1.x → v2.0):需建立 migration script,重寫所有 client:*:profile key
未來整合項目(Phase 5+)
- SYS-03 診斷整合:官網診斷結果的 P-type 流入 schema → Phase 5 待規劃
- 知識庫健康儀表板:SYS-07 顯示已個人化客戶數、consent 率 → Phase 6 待規劃
- 管理端讀取工具:Tim 可查詢特定客戶的 profile 現況 → L210 可納入
十二、L210 實作清單(本文件的下游任務)
在 L210 實作前需完成:
- LEG 審查:同意訊息文字定稿(阻塞項,L210 不可在此完成前實作 consent_at 設定)
- 環境設定:Tim 本機
.env新增KV_REST_API_URL+KV_REST_API_TOKEN(與 Vercel 相同值) - SYS-10 程式碼:line-webhook.js 新增 profile 讀取邏輯 + Stage 2 萃取 + consent 流程
- 諮詢完整輸出 SKILL:新增 LINE User ID 輸入提示 + Stage 1 KV 寫入步驟
- Stage 2 Gemini prompt:依 Section 五偵測框架設計 5 個 Pattern 的 prompt + 置信度門檻
- Fallback 測試:模擬 Upstash 失敗、Gemini 失敗,確認靜默降級正常運作
來源:10 輪嚴謹設計自查(2026-05-25)|前置任務:tasks.md L209 | 後置任務:tasks.md L210