本可视化已更新至 V1 重新设计(V2,2026-08-03)。
旧版(v0.1 基线:
company/position/job_family/tag.namespace='skill'/coffee_chat/LaTeX 简历)已全面作废。
权威文本见本站顶层:数据模型 V2、术语表、ADR 0009–0021。
旧草稿保留于 archive/v0.1-baseline(仅供追溯,请勿照此实现)。
0这份文档在说什么
只讲一件事:东西是怎么从「你随手发一段话/一张截图」变成「库里干净、能查、能复用、可主动提醒你的数据」的。
你发东西→
原样存档
永不丢→ AI 拆解→ 你过目确认→ 正式入库
行为记 event
状态物化 fact→ 随时调用
含主动提醒
永不丢→ AI 拆解→ 你过目确认→ 正式入库
行为记 event
状态物化 fact→ 随时调用
含主动提醒
最关键的两步:①「你过目确认」挡住脏数据;②「双真相落库」——行为进
event(append-only 不可变),状态进 fact(带 valid_from→valid_to),两者经 fact.source_event_id 双向可追,谁都赖不掉。已经拍板的四个核心定论
| 议题 | 结论(V2) | 为什么 |
|---|---|---|
| 真相怎么存 | 双真相:event(行为)+ fact(状态) | "发生了什么"和"现在是什么状态"是两类东西,混在一起没法查时间线也没法纠错 |
| 技能库怎么建 | 共享词表 skill + 我的投影 user_skill + JD 需求 position_requirement | "Excel 全库只定义一次";三种"技能"严格拆开,AI 技能包不算技能 |
| 个人经历放哪 | 单独建表 experience,仅存口语稿 raw_input_md | 经历是"简历素材"要反复挑用;AI 优化稿按岗位存入简历,不污染源稿 |
| 和 Alfred 的对话 | 双方都存,带时间戳;chat_thread/chat_segment/chat_message | 你要的是一条像微信一样能回看、且能调出原话的时间线 |
1核心范式变化(v0.1 → V2)
这一节是"最新版到底是什么"的锚点。下面每一行都是已被 ADR 接受的决定。
| 旧(v0.1,已作废) | 新(V2,当前权威) | ADR |
|---|---|---|
position | job_posting | 0009 |
position.job_family | occupation(独立职位大类表) | 0009 |
company | organization(带 kind 枚举,泛化机构) | 0017 |
tag.namespace='skill' | skill 表 + user_skill + position_requirement | 0010、0011 |
event(带 kind 的时间轴表) | event(行为真相,append-only)+ 强类型 interaction/interview | 0010 |
coffee_chat 概念 | interaction(kind 枚举:coffee_chat/meal/hangout/call/networking_event/info_session/referral_talk) | 0010 |
position.location(字符串) | location 实体 + location_alias,job_posting.location_id 外键 | 0015 |
experience_skill 关联表 | experience_source(来源)+ user_skill + document_version_fact | 0010 |
document_version 存 LaTeX 源码 | 存结构化 Markdown 主信息 + template_ref + checksum | 0014 |
| 编排:Action 原语直写库 | LangGraph 管状态/中断,写库仍走 Action 原语 | 0012 |
一句话记住 V2:行为用
event、状态用 fact;公司叫 organization、岗位叫 job_posting、职位大类叫 occupation;技能是共享词表 + 我的投影 + JD 需求三件套;简历是 Markdown 套模板;咖啡聊天泛化成 interaction;系统会主动 nudge 你。2名词对照表
口语说法 → 系统里的叫法(V2)。带 新建 的是 V1 重新设计新增的。
| 你平时说的 | 系统里的名字(V2) | 一句话解释 |
|---|---|---|
| 公司 / 学校 / 机构 | organization 改名 | 泛化机构,一条一个,带 kind |
| 岗位 / 职位 | job_posting 改名 | 某机构 + 某 occupation + 某 location 的一次招聘 |
| 职位大类 | occupation 新建 | software engineer / psychologist,跨公司可比 |
| 地点 | location + location_alias 新建 | 结构化地点 + 别名(HK / 香港 → 同一行) |
| 技能(词表) | skill 新建 | 共享受控词表,"Excel 全库只定义一次" |
| 我的技能 | user_skill 新建 | skill 的私有投影,带 proficiency 1–5 |
| 岗位要求 | position_requirement 已有 | JD 每条要求一行,kind 区分技能/资质/经历 |
| 我的经历 | experience + experience_source 新建 | 仅存口语稿;多段语音挂到同一条 |
| 简历 / 求职信 | document_asset + document_version 已有 | Markdown 主信息 + 模板引用 + checksum |
| 简历里的 bullet | document_version_fact 新建 | bullet → fact 软引用,支撑溯源 footnote |
| 投递 | application + application_evidence 新建 | 多模态凭证(截图/确认邮件)挂这里 |
| 面试 | interview 已有 | 每轮一条 |
| 咖啡聊天/约饭/宣讲会 | interaction 新建 | 泛化接触记录,kind 区分 |
| 我和 Alfred 的聊天 | chat_thread/chat_segment/chat_message 新建 | 双方消息,带时间戳、可分段 |
| 我的记忆 / 偏好 | memory 新建 | 收容强类型表之外的残余 |
| 系统主动提醒 | nudge_rule + nudge 新建 | 与手设 reminder 分表 |
| 行为日志 | event 语义变更 | append-only 行为真相,不再是时间轴表 |
| 当前状态快照 | fact 新建 | 从 event + 规则物化,带有效期 |
3核心实体与唯一标识
每个实体靠什么"认出自己",撞车了怎么办。
| 实体 | 唯一键 | 长什么样 | 冲突了怎么办 |
|---|---|---|---|
| 机构 | slug | bytedance | 先查 slug,再按别名模糊匹配;命中就复用 |
| 岗位 | fingerprint | hash(机构id + 标准职位名 + 地点) | 同岗从两个渠道来 → 只留一条,来源追加 |
| 职业大类 | slug | software-engineer | 共享词表,跨用户可比 |
| 技能 | slug + skill_alias | excel / 别名 excel-数据处理 | 归一化后仍不命中 → 建 proposed 等你确认 |
| 岗位要求 | position_id + 原文哈希 | — | 重复解析只留一条 |
| 经历 | slug | hku-psy-ra-2025 | 你自己命名,重名拒绝 |
| 简历版本 | (asset_id, version_no) + checksum | cv-clinical-psy #7 | 内容没变就不新建版本(全局复用,不限最新版) |
| 投递 | id(软约束:岗位+投递日) | — | 同岗二投合法,但会提醒"3 月投过" |
| 聊天消息 | id + sent_at | — | 重发用幂等键挡掉 |
| 附件 | sha256 | — | 同截图传两次只存一份 |
三个"两层结构"在 V2 怎么处理
① 职位大类
独立表 · occupation
大类 = occupation,细分 = job_posting.title。
职业大类跨公司可比,"所有要 SWE 的岗位"必须能一键查出来,所以独立成表。
② 技能
共享词表 + 别名
不再用 parent_id 两级树。技能是平的共享词表;相似说法靠 skill_alias 反查合并("商务写作"→excel 这种跨类不强行层级)。
③ 经历
一层就够
经历表本身是平的,仅存口语稿;"这段经历证明了什么能力"靠 user_skill 与简历 document_version_fact 体现,不污染源稿。
4技能三义分离 + 共享词表
整个设计里最容易废掉的一点 —— "技能"这个词在本项目里有三种完全不同的意思,必须拆开。
| 三种"技能" | 落点 | 说明 |
|---|---|---|
| AI 技能包(Skill 目录) | 文件系统里的 skills/*/ | Alfred 会做的一件事(拆职位、记笔记…),不是简历里的能力 |
| 共享技能词表 | skill 新建 | "Excel"全库只定义一次;skill_alias 做同义合并 |
| 我的技能 | user_skill 新建 | skill 的私有投影,带 proficiency 1–5 |
| JD 所需技能 | position_requirement(kind=skill) 已有 | 挂在具体岗位下,可溯源到 JD 原句 |
为什么必须拆:"AI 技能包"是代码概念,"我会 Excel"是私人事实,"这个岗要 Excel"是岗位需求——三者 JOIN 才能回答"我能投哪些要 Excel 的岗""我的技能还差什么",混在一起谁都查不了。
AI 抽 JD 技能的四步法
- 抽取 把 JD 句子压成技能短语:
"熟练使用 Excel 进行数据透视分析" → Excel - 归一 套同义词表 +
skill_alias:
"Microsoft Excel" / "excel" / "EXCEL 技能" → excel - 匹配 拿结果去
skill表比对(slug + 别名 + 模糊):
命中 → 直接复用(99% 的情况) - 提议 不命中才新建
proposed技能,等你确认 →approved/merged/rejected
关键约束:
proposed 状态的技能不参与任何统计和筛选,它只是候选。即使 AI 提了一堆垃圾,也污染不了你的正式技能库。JD 要求分三类,全进 position_requirement,用 kind 区分
| kind | 含义 | 例子 | 挂 skill_id |
|---|---|---|---|
skill | 能力 | 英语商务写作、Excel 数据透视、CBT 咨询技术 | ✅ 挂 |
qualification | 学历 / 证书 / 专业背景 | 心理学硕士、注册心理师执照 | ❌ 不挂 |
experience | 经历 / 年限 | 2 年以上咨询实习经验 | ❌ 不挂 |
三大匹配查询(靠 JOIN 实现)
① 哪些岗位要我的技能 X
user_skill ⋈ skill ⋈ position_requirement② 我凭技能 X 能投哪些岗
同上反向过滤
job_posting③ 我的技能 vs 岗位要求
matched / gap 缺口分析
技能确认流程
flowchart LR
P(["AI 提议的新技能"]) --> Q{"你怎么看"}
Q -->|"确实是新技能"| A["approved
进共享词表"]
Q -->|"跟已有的重复"| M["merged
并入已有技能
说法进 skill_alias"]
Q -->|"这不算技能"| R["rejected
下次不再提"]
Q -->|"先放着"| P
A --> USE(["可用于筛选/匹配/统计"])
M --> USE
5经历库与简历生成
- 你说:给字节的数据分析岗改简历,用经历 1、3、5
- Alfred 定位机构 + 岗位(没有就先建)
- 从
experience取出你点名的那几条(仅存口语稿raw_input_md) - 缺口比对:岗位要求 vs 我的
user_skill→「这岗要 SPSS,你选的三条没体现,要不要加经历 4」 - 生成 Markdown 草稿(结构化主信息,编译时套
resume_asset.base_template) - 你确认 / 让它改
- 定稿 → 存
document_version(Markdown +template_ref+checksum) - 编译 PDF(复用模板,不必存源码)
- AI 优化稿按岗位存入
document_version_experience.rendered_bullets,并可软引用fact
关键设计:记住「这版用了哪几条经历」+ bullet 可溯源
document_version_experience | |
|---|---|
document_version_id | 哪一版简历 |
experience_id | 用了哪条经历 |
order_no | 在简历里排第几 |
rendered_bullets | 这版实际怎么措辞的(同一段经历,投研究岗和投咨询岗写法不一样) |
简历可解释(ADR-0014):每条 bullet 经
document_version_fact 软引用到 fact。渲染时拼 footnote 标注出处;校验器阻断"无源 bullet"——简历里出现任何无法溯源到经历/事实的句子都会被拦下。版本管理规则(基于全局 checksum 复用)
不可变
每次定稿 = 一个新版本,老版本永远不改
checksum 复用
生成新简历时算主信息 checksum,与同 asset 下任意历史版本比对,命中则直接复用该版(不限于最新版);否则新建
based_on_id
指向用户显式选择的父版本(非自动,避免误继承)
必填摘要
每版必须有「这版改了什么」,AI 自动生成,你可以改
延后编译
只在你确认后才编译 PDF,草稿阶段不浪费时间
经历 ≠ bullet。
experience 只存你的口语原话("港大做 RA,跟量表评估相关那段");AI 把它优化成的简历措辞,存在简历版本的 rendered_bullets 里,不污染源稿、随岗位不同。6投递 · 面试 · 接触
投递(多模态凭证)
"我投了"不只是语音——截图(投递成功页/确认邮件)、文字、转发的确认邮件同样受理。截图是投递凭证,不是普通附件。
| 记录什么 | 存哪(V2) |
|---|---|
| 投了哪家哪个岗 | application.job_posting_id |
| 用的哪份简历/求职信 | resume_version_id / cover_letter_version_id |
| 投递渠道、时间 | channel / submitted_at |
| 网页表单填了什么 | submission_detail_md |
| 内推人是谁 | referrer_person_id |
| 投递凭证截图/确认邮件 | application_evidence(application_id, attachment_id, role) 新建 |
| 投递"行为"本身 | 同时写一条 event(APPLICATION_SUBMITTED) |
凭证时间优先取截图内的时间戳;截图是证据而非附件,所以走专门的
application_evidence 关系表,不和普通 attachment 混。面试(每轮一条,强类型)
| 记录什么 | 存哪 |
|---|---|
| 轮次、类型、时间 | interview |
| 问了什么、我怎么答的 | interview.questions |
| 面试官是谁 | interview_interviewer 新建 |
| 面试感受 | feedback_md + feeling |
| "发生面试"行为 | 写 event(INTERVIEW_SCHEDULED) 等 |
接触记录(泛化为 interaction)
旧
coffee_chat 太窄(约饭/出去玩/线上通话/宣讲会都不是 coffee chat)。改为 interaction(kind=coffee_chat|meal|hangout|call|networking_event|info_session|referral_talk),多人场景用 interaction_person 关系表。其"发生"行为写 event(INTERACTION_HAPPENED)。投递状态流转
stateDiagram-v2
[*] --> draft
draft --> submitted
submitted --> ack_received
submitted --> ghosted : 长期无回应
ack_received --> oa_pending
oa_pending --> oa_done
ack_received --> interviewing
oa_done --> interviewing
interviewing --> offer
interviewing --> rejected
offer --> accepted
offer --> declined
ghosted --> interviewing : 突然回复
7聊天 · 记忆 · 主动提醒
聊天时间线 chat_thread / chat_segment / chat_message
chat_message 新建
sent_at
时间戳,时间线排序靠它
role
user(你发的) / assistant(Alfred 回的)
media_kind
text / voice / image / link / mixed
content_md
显示文本(语音就是转写、图片就是描述)
segment_id
所属语义段落(AI 检测主题漂移自动开新段)
thread_id
所属对话线程
分层结构
chat_thread
每用户一个常驻对话分组/归档
chat_segment
段内语义聚合;可重命名/合并/归档;存
summary_mdchat_message
双方全量原话 + sequence +
segment_id 外键化;保留软丢弃为什么分三层
长对话能按主题检索与回收上下文,而不是一整坨线性流
有意的冗余:
chat_message.content_md 把语音转写冗余存一份,时间线是读得最频繁的东西,多存一份文本换秒开,划算。记忆 memory + 双真相
强类型表为真相,
memory 仅收容残余。无法归入强类型表的非结构化内容(kind = fact/preference/intent/context)才进 memory。
而你说的每句话原话都进 chat_message,日后能调出;AI 提炼的 fact/memory 是可重算的派生物,错了对原话无损。
主动提醒:reminder vs nudge
| 类型 | 来源 | 例子 |
|---|---|---|
reminder | 你手设的提醒 | "周五提醒我改简历" |
nudge_rule + nudge | 系统算出来的提醒 | 截止日临近未投、投递后静默 14 天、面试后 1 天未发感谢信、技能缺口、简历陈旧、命中率低、经历空洞、follow-up 逾期、offer 期限、每周复盘 |
分表不混:你手动设的
reminder 和系统主动生成的 nudge 是两套机制。nudge 必须有去重与冷却(cooldown_days / dismissed_at),同一件事不重复烦你。主推送通道 = 后端邮件 + 每日聚合。8数据流图 · Data Flow Diagram
数据从哪来、经过谁、存到哪去(V2 命名)。
Level 0 · 整体
flowchart LR
U(["你"])
subgraph ALFRED["Alfred 系统"]
direction TB
A1["接收与存档"]
A2["AI 拆解(路由→抽取→归一)"]
A3["预览确认与入库"]
A4["调用与生成"]
end
LLM(["AI 模型"])
WEB(["外部网页 / 搜索"])
DB[("数据库")]
U -- "文字/语音/图片/链接" --> A1
A1 -- "原始内容 + event 行为" --> DB
A1 -- "归一化文本" --> A2
A2 <-- "问答" --> LLM
A2 -- "抓正文/查背景" --> WEB
A2 -- "预览卡片" --> U
U -- "改一改 + 确认" --> A3
A3 -- "正式写入(fact 物化)" --> DB
U -- "给这个岗改简历" --> A4
DB -- "经历/技能/岗位要求" --> A4
A4 -- "简历草稿" --> U
Level 1 · 上传 / 拆解 的内部数据流
flowchart TB
IN(["你发的内容"])
N1["归一化
链接抓正文 / 截图OCR / 语音转写"]
S1[("attachment
原件+转写")]
S2[("ingest_request
原始输入")]
S3[("chat_message
你说的这句")]
R1["路由:这是什么类型"]
E1["抽取:拆成结构化字段"]
K1["技能归一:匹配共享词表 skill"]
P1{"预览卡片
等你确认"}
W1["写入实体(event + fact)"]
S4[("organization / job_posting")]
S5[("position_requirement")]
S6[("skill 提议区")]
S7[("experience / note / memory")]
S8[("application / interaction")]
S9[("chat_message
Alfred 的回复")]
S10[("event 行为日志")]
BG["背景搜集
只看不存"]
IN --> N1
IN --> S2
IN --> S3
N1 --> S1
N1 --> R1
R1 --> E1
E1 --> K1
K1 --> P1
E1 --> BG
BG -.->|"只展示"| P1
P1 -->|"确认"| W1
P1 -->|"丢弃"| X(["不入库"])
W1 --> S4
W1 --> S5
W1 --> S6
W1 --> S7
W1 --> S8
W1 --> S9
W1 --> S10
Level 1 · 简历生成的数据流
flowchart LR
Q(["给X机构Y岗改简历
用经历1/3/5"])
D1[("position_requirement
这个岗要什么")]
D2[("experience
我的经历素材")]
D3[("user_skill
我会什么")]
M["缺口比对
要求 vs 我的技能"]
G["Markdown 生成 + 套模板"]
P{"你确认"}
V[("document_version
Markdown + checksum")]
L[("document_version_experience
用了哪几条经历")]
F[("document_version_fact
bullet 溯源 fact")]
PDF[("PDF 文件")]
Q --> M
D1 --> M
D2 --> M
D3 --> M
M -->|"提示缺口"| Q
M --> G
G --> P
P -->|"再改改"| G
P -->|"定稿"| V
V --> L
V --> F
V --> PDF
9流程图 · Flow Diagram
上传 / 拆解 主流程(含所有分支)
flowchart TD
START(["你发了链接/截图/语音"]) --> SAVE["立即存档原件
此刻起内容不会丢"]
SAVE --> NORM["归一成纯文本"]
NORM --> OK1{"文本拿到了吗"}
OK1 -->|"否"| FAIL1["标记 partial
告诉你抓取失败
可手动补内容"]
OK1 -->|"是"| ROUTE["判断类型"]
ROUTE --> ISJOB{"是岗位信息吗"}
ISJOB -->|"否"| OTHER["走别的技能
笔记 / 经历 / 接触 / 记忆"]
ISJOB -->|"是"| EXTRACT["AI 拆解字段"]
EXTRACT --> CO{"识别到机构了吗"}
CO -->|"否"| PLACE["建占位机构
标记待补"]
CO -->|"是"| MATCH["按 slug + 别名匹配已有 organization"]
PLACE --> FP
MATCH --> FP["算岗位指纹"]
FP --> DUP{"这个岗已经存过了吗"}
DUP -->|"是"| MERGE["复用已有岗位
追加新来源"]
DUP -->|"否"| NEW["准备新建岗位"]
MERGE --> SK
NEW --> SK["技能归一与匹配"]
SK --> SKHIT{"技能库里有吗"}
SKHIT -->|"有"| REUSE["直接复用"]
SKHIT -->|"没有"| PROPOSE["建成待确认技能"]
REUSE --> CARD
PROPOSE --> CARD["生成预览卡片"]
CARD --> BG["顺手搜背景信息
只展示不入库"]
BG --> REVIEW{"你看一眼"}
REVIEW -->|"改字段"| CARD
REVIEW -->|"丢弃"| DROP(["不入库
原始内容仍留档"])
REVIEW -->|"确认"| WRITE["写入数据库(event + fact)"]
WRITE --> REM{"有截止日期吗"}
REM -->|"有"| MKREM["自动建提前提醒 nudge/reminder"]
REM -->|"没有"| REPLY
MKREM --> REPLY["Alfred 回话
写进聊天时间线"]
REPLY --> END(["完成"])
简历生成流程(Markdown)
flowchart TD
S(["给某机构某岗改简历"]) --> PICK{"指定用哪几条经历了吗"}
PICK -->|"指定了"| GET["取出指定经历"]
PICK -->|"没指定"| REC["按岗位要求自动推荐经历"]
REC --> CONFIRM1{"这几条行吗"}
CONFIRM1 -->|"换一批"| REC
CONFIRM1 -->|"可以"| GET
GET --> GAP["比对岗位要求
找出没覆盖到的能力"]
GAP --> WARN{"有明显缺口吗"}
WARN -->|"有"| TELL["提示你
要求SPSS但选的经历没体现"]
WARN -->|"没有"| GEN
TELL --> GEN["生成 Markdown 草稿 + 套模板"]
GEN --> LOOK{"你看草稿"}
LOOK -->|"改措辞/换经历"| GEN
LOOK -->|"定稿"| SAVEV["存新版本
记录用了哪几条经历"]
SAVEV --> CK{"checksum 和历史版一样吗"}
CK -->|"一样"| REUSEV["复用已有版本"]
CK -->|"不一样"| PDF["编译 PDF"]
PDF --> FACT["bullet 软引用 fact 建索引"]
FACT --> DONE(["可用于投递"])
从上传到投递的完整闭环
flowchart LR
A(["发现岗位"]) --> B(["解析入库"])
B --> C(["写评价与准备思路"])
C --> D(["生成定制简历"])
D --> E(["投递 + 凭证 evidence"])
E --> F(["跟踪状态"])
F --> G(["面试记录"])
G --> H(["复盘写进记忆"])
H -.->|"经验反哺"| C
G -.->|"新技能被问到"| I(["补进技能词表"])
I -.-> D
10ER 图 · 实体关系(V2)
拆成三张看,避免一张图糊成一团。
10.1 摄入 · 对话 · 双真相
erDiagram
CHAT_THREAD ||--o{ CHAT_SEGMENT : "分段"
CHAT_THREAD ||--o{ CHAT_MESSAGE : "含"
CHAT_MESSAGE ||--o{ ATTACHMENT : "含(方向化)"
CHAT_MESSAGE ||--o{ EVENT : "触发行为"
EVENT ||--o| FACT : "物化为状态"
CHAT_THREAD {
string id PK
string user_id FK "私有"
string title
string status "active archived"
}
CHAT_SEGMENT {
string id PK
string thread_id FK
string title
string status "active archived merged"
text summary_md
string user_id FK
}
CHAT_MESSAGE {
string id PK
string thread_id FK
string segment_id FK
timestamp sent_at "时间戳"
string role "user 或 assistant"
string media_kind "text voice image link"
text content_md "显示文本"
json attachment_ids
string reply_to_id FK
string status "sent processing done failed"
bool discarded "软丢弃"
}
ATTACHMENT {
string id PK
string media_kind
string sha256 UK "同文件不重复存"
string storage_uri
text transcription "语音转写"
text vision_caption "图片描述"
}
EVENT {
string id PK
string type "FACT_DECLARED APPLICATION_SUBMITTED INTERACTION_HAPPENED SKILL_EXTRACTED INTERVIEW_SCHEDULED RESUME_GENERATED CUSTOM"
string actor_user_id FK "私有"
timestamp occurred_at
string source_type "conversation import api"
string source_ref_id
json payload
}
FACT {
string id PK
string kind "skill_declaration relationship attribute custom"
timestamp valid_from
timestamp valid_to NULL "纠错时封口"
string subject_ref FK
string object_ref FK
string proficiency NULL
string source_event_id FK "双向追溯"
string owner_user_id FK "私有"
}
10.2 机构 · 岗位 · 职业 · 地点 · 技能
erDiagram
ORGANIZATION ||--o{ JOB_POSTING : "发布"
OCCUPATION ||--o{ JOB_POSTING : "属于"
LOCATION ||--o{ JOB_POSTING : "位于"
LOCATION ||--o{ LOCATION_ALIAS : "别名"
SKILL ||--o{ SKILL_ALIAS : "别名"
OCCUPATION }o--o{ USER_SKILL : "跨用户可比"
SKILL ||--o{ USER_SKILL : "技能定义"
SKILL ||--o{ POSITION_REQUIREMENT : "JD所需"
JOB_POSTING ||--o{ POSITION_REQUIREMENT : "要求"
ORGANIZATION {
string id PK
string name
string slug UK "唯一标识"
string kind "company school club ngo government fund open_source_office other"
json aliases "别名用于去重"
}
OCCUPATION {
string id PK
string slug UK
string name "software-engineer 等"
}
LOCATION {
string id PK
string country
string city
string district
string admin_code
}
LOCATION_ALIAS {
string id PK
string location_id FK
string alias_slug "香港 HK HongKong → 同行"
}
SKILL {
string id PK
string slug UK "excel"
string name "显示名"
string kind "hard soft language tool domain"
string status "proposed approved merged rejected"
string merged_into_id FK
}
SKILL_ALIAS {
string id PK
string canonical_skill_id FK
string alias_slug "反查合并键"
string source "agent manual"
}
POSITION_REQUIREMENT {
string id PK
string position_id FK
string kind "skill qualification experience"
string skill_id FK "仅能力类才有"
text text "要求原文(可溯源 JD)"
string importance "must nice"
json source_sentences
}
USER_SKILL {
string id PK
string user_id FK "私有"
string skill_id FK
int proficiency "1到5"
}
JOB_POSTING {
string id PK
string organization_id FK
string fingerprint UK "去重指纹"
string occupation_id FK "职位大类"
string location_id FK "替代 location 字符串"
string title "细分职位名"
timestamp posted_at
timestamp deadline_at "投递截止→驱动提醒"
string apply_url
json required_documents "要交的材料"
string source_kind
int interest_level
text personal_comment_md
}
10.3 经历 · 文档 · 投递 · 面试 · 接触
erDiagram
EXPERIENCE ||--o{ EXPERIENCE_SOURCE : "来源"
EXPERIENCE ||--o{ DOCUMENT_VERSION_EXPERIENCE : "被引用"
DOCUMENT_ASSET ||--o{ DOCUMENT_VERSION : "版本链"
DOCUMENT_VERSION ||--o{ DOCUMENT_VERSION_EXPERIENCE : "用了哪几条"
DOCUMENT_VERSION ||--o{ DOCUMENT_VERSION_FACT : "溯源fact"
FACT ||--o{ DOCUMENT_VERSION_FACT : "被引用"
JOB_POSTING ||--o{ APPLICATION : "投递"
APPLICATION ||--o{ INTERVIEW : "轮次"
APPLICATION ||--o{ APPLICATION_EVIDENCE : "凭证"
ATTACHMENT ||--o{ APPLICATION_EVIDENCE : "截图/确认邮件"
PERSON ||--o{ INTERVIEW_INTERVIEWER : "面试官"
INTERVIEW ||--o{ INTERVIEW_INTERVIEWER : ""
PERSON ||--o{ INTERACTION_PERSON : "参与"
INTERACTION ||--o{ INTERACTION_PERSON : "多人"
EXPERIENCE {
string id PK
string user_id FK "私有"
string slug UK
string title
string kind "internship project research award"
string org_name
date started_on
date ended_on
text raw_input_md "仅存口语稿,不存优化稿"
}
EXPERIENCE_SOURCE {
string id PK
string experience_id FK
string source_type "voice text screenshot"
string source_ref_id "指向 attachment / chat_message"
}
DOCUMENT_ASSET {
string id PK
string slug UK
string kind "resume 或 cover_letter"
string name
string base_template "模板引用"
}
DOCUMENT_VERSION {
string id PK
string asset_id FK
int version_no
string checksum "内容没变就不新建"
text markdown_main "结构化Markdown主信息"
string template_ref "套用模板"
string based_on_id FK "用户显式选父版"
text change_summary_md "这版改了什么"
string compiled_pdf_url
}
DOCUMENT_VERSION_EXPERIENCE {
string document_version_id FK
string experience_id FK
int order_no
json rendered_bullets "实际措辞(按岗不同)"
}
DOCUMENT_VERSION_FACT {
string id PK
string document_version_id FK
string fact_id FK
text bullet_text "软引用 fact,支撑 footnote"
}
APPLICATION {
string id PK
string user_id FK
string job_posting_id FK
string status "投递状态机"
string resume_version_id FK
string cover_letter_version_id FK
timestamp submitted_at
text submission_detail_md
string referrer_person_id FK
}
APPLICATION_EVIDENCE {
string id PK
string application_id FK
string attachment_id FK
string role "screenshot confirmation_email"
}
INTERVIEW {
string id PK
string application_id FK
int round_no
string kind
timestamp scheduled_at
json questions "问了什么我怎么答"
text feedback_md
}
INTERACTION {
string id PK
string user_id FK
string kind "coffee_chat meal hangout call networking_event info_session referral_talk"
timestamp occurred_at
text summary_md
}
V2 已移除旧
entity_link「万能连线」。强关系一律用外键强类型表(上面 ER 即全部);弱关联走受控关系词 + 专门关系表(如 interaction_person、interview_interviewer、application_evidence),不再有任意两实体自由连线的一张总表。11潜在问题 Brainstorm
先替你踩坑。按风险高低排。
高风险 · 不处理一定出事
① 技能库爆炸 / 语义碎片
三个月后技能表 400 条,其中 300 条是同义重复;或"Excel/商务写作"被拆成两个毫无关系的技能
对策 AI 只能提议不能直接入库;
proposed 不参与筛选;skill_alias 反查合并,合并时旧说法自动进别名;共享词表全库唯一
② 截图 / OCR 认错关键字段
公司名认成"字节眺动",截止日期认成去年
对策 强制走预览卡片;AI 推算出来的字段("本周五"→ 具体日期)在卡片上高亮标注
③ 经历和笔记 / 记忆混为一谈
想往简历里加东西时,翻遍笔记也找不到那段实习怎么写的
对策 经历单表只存口语稿;AI 优化稿在简历版本里;非结构残留进
memory。判断标准一句话——能写进简历的是经历,其余是笔记/记忆
④ 简历版本 / bullet 无法溯源
收到面试通知,不知道当初投的是哪版;简历里出现 AI 瞎编、无来源的能力
对策
resume_version_id 硬关联 + 记录用了哪几条经历 + 每版必填改动摘要;document_version_fact 软引用 fact,校验器阻断无源 bullet中风险 · 会不舒服但不致命
| 问题 | 对策 |
|---|---|
| ⑤ 同岗位重复入库 | 指纹先过中英归一表;不确定时卡片上问"这是不是和已有的 XX 同一个岗" |
| ⑥ 聊天时间线越滚越长 | chat_segment 按主题分段、可归档;分页加载;纯闲聊可标记归档 |
| ⑦ 你的评价被塞进 JD 描述里 | prompt 已明确"主观评价只进 personal_comment";再加校验——描述字段出现第一人称就打回 |
| ⑧ 背景搜集的信息想留下来 | 默认不存,卡片上给「存为笔记/记忆」按钮,你点了才存 |
| ⑨ 系统提醒烦人 | nudge 带 cooldown_days / dismissed_at 去重冷却;与手设 reminder 分表 |
| ⑩ 投递凭证 vs 普通附件混了 | 专用 application_evidence 关系表挂凭证,时间优先取截图内时间戳 |
低风险 · 记着就行
⑪ 材料清单没人管
required_documents + 生成 checklist 提醒⑫ 同岗二次投递
不阻止,但提示"你 3 月投过这个岗"
⑬ 文件越存越多
PDF 本身不大;真占地方的是截图,sha256 去重已解决大半
⑭ 旧 event 数据迁移
旧
event.kind 内容迁到 interaction/interview 强类型表;event 改行为真相12实现清单(与现有代码的差距)
要新建的表(按依赖顺序)
| 表 | 作用 | 优先级 |
|---|---|---|
occupation | 职位大类,跨公司可比 | P0 |
job_posting | 替代 position(加 occupation_id / location_id / required_documents / posted_at) | P0 |
skill + skill_alias | 共享技能词表 + 别名合并 | P0 |
position_requirement | 替代 position.requirements JSON 字段 | P0 |
user_skill | 我的技能投影(proficiency) | P0 |
experience + experience_source | 经历库(仅口语稿)+ 来源 | P0 |
fact + event | 双真相(行为 + 状态) | P0 |
chat_thread / chat_segment / chat_message | 分层对话时间线 | P1 |
document_version_experience + document_version_fact | 简历用了哪几条经历 + bullet 溯源 | P1 |
interaction + interaction_person | 泛化接触记录 | P1 |
application_evidence | 投递多模态凭证 | P1 |
nudge_rule + nudge | 系统主动提醒 | P1 |
memory | 残余记忆收容 | P1 |
要改 / 语义变更的地方
| 位置 | 改什么 |
|---|---|
models/tables.py | position→job_posting、company→organization;加 occupation_id/location_id;document_version 改 Markdown 主信息 + template_ref + checksum |
models/enums.py | 加 SkillStatus/SkillKind/RequirementKind/ExperienceKind/MemoryKind/ThreadKind/OccupationStatus |
skills/extract_job/skill.yaml | skills 升级为带 evidence/importance 对象;requirements 加 kind;add_tags 换成 upsert_skill + link_requirement |
services/executor.py | 新增 upsert_skill/link_requirement/upsert_experience/record_application/create_memory/upsert_user_skill |
| 编排 | LangGraph 管状态/中断(ADR-0012),写库仍走 Action 原语(dry_run + commit) |
| 接口层 | 技能确认/合并、经历 CRUD、聊天时间线分页、记忆检索、技能匹配查询、投递/面试分别提交 |
旧
docs/DATA_MODEL.md(v0.1)把技能表述为「namespace='skill' 的 tag」。已被 DATA_MODEL_V2.md 取代——tag 是自由标签,扛不住"共享词表 + 提议确认 + 别名合并"这三件事。旧草稿保留于 archive/v0.1-baseline/,请勿照此实现。建议的落地顺序
- 建
skill+skill_alias+position_requirement+user_skill,改 extract_job 技能 ← 上传链路立刻变干净 - 加预览确认机制(dry_run + 确认接口) ← 挡住脏数据
- 双真相落地:
event+fact写入路径 ← 可审计、可纠错 - 建
experience+experience_source表 + 录入你现有的经历 ← 为简历生成铺路 - 简历生成链路(Markdown +
document_version_experience+document_version_fact) chat_thread/chat_segment/chat_message时间线 +memoryinteraction+application_evidence+nudge_rule/nudge(主动提醒引擎)
13还需要你后续拍板的问题
不影响现在动工,但迟早要定。
技能要不要多级
V2 用平的共享词表 +
skill_alias 反查,放弃强制两级树。若日后真要层级,可加 parent_id 自引用,不破坏现有模型。我的整体熟练度
现在
user_skill.proficiency 1–5 已能表达。要不要再分"自评/岗位要求"两套,先不加。背景搜集完全不存会后悔吗
已给折中:默认不存 + 一键存为笔记/记忆。
CL 和 PS 要分开吗
document_asset.kind 加枚举值即可。经历的中英双语措辞
现在 AI 优化稿在
document_version_experience.rendered_bullets 里,按岗位生成,天然支持双语。nudge 推送通道
V2 先定后端邮件 + 每日聚合;小程序/微信通道留
channel 字段未来扩展。