来源:
notes/documents/01_flomo_同步方案_数据映射与改造清单.mdflomo 同步方案:数据映射与改造清单
结论
flomo 不是「一个来源」,而是三种形态混在一起,所以要拆成三条流分别落位:
- 短记(<200 字,376 条,73%)→ 事件流,进日视图和飞书「每日记录」,这是主干;
- 长文(≥500 字,53 条)→ 正式笔记,按类型落进
notes/,再走既有的飞书文档投影; - 附件(32 个)→ 本地文件,阶段一只在正文里留路径引用。
四条判断依据来自对已抓取数据的实测:
- 515 条里 376 条短于 200 字,随手记是绝大多数,把它们都写成笔记会把
notes/变成碎片垃圾场。 - 只有 73 条(14%)带标签,32 个标签里 14 个只出现过一次——不能靠标签分流。
- 日视图对正文有 120 字截断(
scripts/daily_log_view.py:83),长文如果只进事件就看不全,这是长文必须升级为笔记的直接理由。 - 事件表在
(source, source_record_id)上有唯一索引(scripts/event_store.py:193),flomo 的slug天然就是幂等键,不用再造 fingerprint 机制。
另外:不新建飞书表。2026-09-22 那版方案提议加「flomo 收件箱」,但 ADR-0007 已经把 Base 收敛成一套表,正确做法是把 flomo 当成既有表里的一个「来源」取值,而不是再开一张表。
一、实测数据形态
长度分档
| 档位 | 条数 | 占比 | 去向 |
|---|---|---|---|
| 纯附件无正文 | 15 | 3% | 事件(附件型) |
| <50 字 | 217 | 42% | 事件 |
| 50–200 字 | 159 | 31% | 事件 |
| 200–500 字 | 71 | 14% | 事件;个别可晋升 |
| 500–2000 字 | 41 | 8% | 笔记候选 |
| ≥2000 字 | 12 | 2% | 笔记候选 |
标签
- 有标签 73 条(14%),无标签 442 条(86%);
- 32 个标签,两级标签 50 条、一级 23 条;14 个标签只出现一次;
- 高频:
经济学/老徐常识看世界(13)、经济学/投资体系(6)、日记/202501(6)、人生(5)、工作(4)。
来源与年份
- 来源:android 266、web 184、wechat 59、ios 4、register 1、incoming_webhook 1;
- 年份:2024 年 185、2025 年 170、2026 年 160。
长文内部还要再分层
53 条长文不是同一种东西,实测至少四类,长度不能作为唯一判据:
- 读书/文章摘录(微信读书格式:
◆+原文:+来自微信读书); - 自己写的长文:公众号草稿、投研分析、年度总结(如《我的 2024》、理想芯片那篇);
- 代码与技术片段:
main.py、patreon_api_scraper.py、export.sql、class PositionTracker:等 6 条; - AI 转贴:
(由于技术原因,联网搜索暂不可用)之类。
后两类不该进 notes/ 的知识区,应落 notes/projects/ 或直接留事件。
二、memo → 事件
字段映射
| flomo | 事件 | 规则 |
|---|---|---|
slug |
source_record_id |
配合 source='flomo' 作幂等键 |
created_at |
occurred_at |
转成带 +08:00 的 ISO 时间 |
| 正文首行 | title |
跳过纯标签行,取第一个非标签行前 60 字 |
| 正文其余 | body |
无其余时 body = title(遵守 ADR-0005) |
content(HTML) |
Markdown | 复用 flomo_export.py 的转换器 |
tags |
event_tags |
原样保留,不做归一化 |
files |
阶段一:正文里的相对路径;阶段二:attachments 字段 |
|
deleted_at |
status='withdrawn' |
不物理删除,遵守 ADR-0002 |
updated_at 变化 |
新 revision | 内容不变则不新建 revision |
标题推导的两个坑
- flomo 把标签留在正文里,很多 memo 第一行就是
#经济学/老徐常识看世界。推导标题必须先跳过纯标签行,否则 14 组「首行相同」的假重复会全部误判。 - 标签有时贴在正文行首(
#标签 正文…),需要把行首的#xxx剥掉再取标题。
event_type 判定
沿用现有六个取值,不新增类型:
- 命中「要做 / 记得 / 待办 / TODO」→
task; - 命中「复盘 / 反思 / 我应该」→
reflection; - 标签命中
经济学/*或正文含股票、基金、持仓 →investment; - 长文类 →
note; - 其余 →
life。
敏感级别
敏感级别与发布面(2026-09-26 二次修正)
sensitivity 不是内容属性,而是部分视图的可见性开关,而且它管的范围比名字听起来窄得多。
把四个目的地逐个查过一遍之后的实际矩阵:
| 目的地 | 取合规则 | sensitivity 参与吗 |
|---|---|---|
本地日视图 logs/daily_log.md |
全部事件 | 参与:private 的正文被换成 [敏感记录已隐藏](scripts/daily_log_view.py:74) |
| 飞书 Base「每日记录 V2」 | query_events() |
参与:private 跳过,台账记 skipped / private event |
| 飞书日历 · 事件线 | calendar_enabled=1 且有发生时间 |
不参与 |
| 飞书日历 · 笔记线 | status='active' 且 calendar_enabled=1 且有落点 |
不参与 |
| 网页日程(Neon) | selectProjectable():事件线额外要求 internal/public;笔记线不过滤 |
事件线参与,笔记线不参与 |
| 飞书文档(正式笔记) | status='active' |
不参与(ADR-0010 明确笔记线不做过滤) |
| 博客 | 没有闸门:scripts/publish_private_blog_data.js:204 全量读日视图,笔记导入 andywu1998.github.io/scripts/import_personal_assistant_notes.py:76 全量 rglob("*.md") |
不参与 |
三条必须记住的结论:
- private ≠ 不发。
scripts/note_cli.py:220给正式笔记写死calendar_enabled=True, 所以库里 126 篇 private 笔记照样进了飞书日历和飞书文档。 - 日历真正的闸门是
calendar_enabled,不是 sensitivity。取合规则在scripts/calendar_entries.py, 两条线都只判calendar_enabled加时间/落点,一个 sensitivity 条件都没有。 ADR-0010 里那句「日程线保留 active + 非 private」描述的是收敛前网页那一侧的口径, 现已是web/scripts/lib/daily-projection.mjs:60的selectProjectable()。 - 上一版「对齐现有语料所以默认 internal」的论证偏弱:库里 298 条事件全是
internal, 不是你做过隐私决策,而是写入路径的副产物——capture_event按"internal" if publish_enabled else "private"推导,而/note把publish_enabled写死为 true。
flomo 的默认值(2026-09-26 定稿:三个渠道全开)
analysis 的字段口径已按 .scratch/channel-switches/spec.md 换成渠道开关,
sensitivity 正在被删除,feishu_base_enabled / feishu_docs_enabled 是新增字段。
| 字段 | /note 基线 |
flomo 取值 | 理由 |
|---|---|---|---|
feishu_base_enabled |
true | true | 短记要能进飞书 Base 检索 |
calendar_enabled |
true | true | 用户 2026-09-26 裁决:三个渠道都要 true |
publish_enabled |
true | true | 同上 |
feishu_docs_enabled |
true | 不适用 | 短记走事件线,不是正式笔记 |
两个后果,记录在案(用户已拍板,此处不再论证):
- 515 条带时间的短记会一次性灌进飞书日历。 上一版建议
calendar_enabled=false正是为了避开这一点, 现按决定执行。若事后发现日历过载,逐条关开关即可,撤回语义见.scratch/channel-switches/issues/04-base-columns.md。 publish_enabled今天还不是真闸门。 博客两条管道都是全量读、不看这个字段 (见上表「博客」一行的实测)。要让它有意义必须先完成.scratch/channel-switches/issues/05-blog-gate.md;在那之前,flomo 短记进不进博客不取决于这个值。
原来那条「按主题白名单降级为 private」的设计随之作废——要排除某类内容,改为关掉对应渠道开关,或者干脆不导入。
这个字段在哪定义、什么含义(2026-09-26 文档审计)
sensitivity 只有代码定义,没有任何文档定义它:
| 位置 | 内容 |
|---|---|
scripts/event_store.py:24 |
SENSITIVITY_LEVELS = frozenset({"private", "internal", "public"}) |
events / event_revisions 建表 |
CHECK (sensitivity IN ('private','internal','public')),默认 'private' |
notes / note_revisions |
同名字段、同 CHECK |
scripts/capture_event.py:57 |
推导规则 "internal" if publish_enabled else "private" |
web/backend/src/lib/schema.ts:75 |
Neon 投影表的 sensitivity 列 |
| 飞书 Base | 「每日记录 V2」的「敏感级别」字段 |
CONTEXT.md、README.md、docs/agents/ 里一条定义都没有,这本身就是缺口。
三个取值的实际行为(按各目的地的过滤器反推,不是按名字):
private:本地日视图隐藏正文、飞书 Base 跳过、网页日程事件线排除;但飞书日历、飞书文档、博客照发;internal:以上全部放行;public:与 internal 行为完全相同。全仓库唯一按 sensitivity 取合的地方 (web/scripts/lib/daily-projection.mjs:64)写的是=== 'internal' || === 'public', 没有任何地方单独判public——它是预留值,目前没有约束力。
错误认知的来源(2026-09-26 写错那次):
- 直接来源:
web/docs/adr/0011第 11 行与.scratch/web-multimodule-shell/spec.md:135的 「只投影status=active且sensitivity ∈ {internal, public};private 一律不上网」。 这两句的语境是网页投影,「不上网」容易被读成「不外流」;ADR-0011 第 42–43 行才把范围收窄到日线。 - 更上游的设计意图:
.scratch/personal-assistant-event-model/spec.md:46写的是 「so that private content is not sent to an inappropriate destination」,.scratch/daily-log-sqlite-migration/spec.md:60同义。原意是「不发给不合适的去处」, 被读成了「不发给任何去处」。 - 代码里的耦合也助长了这个印象:
capture_event.py:57把sensitivity直接由publish_enabled推导。 - 反证一直摆着:
docs/adr/0010明说「笔记线不做 sensitivity 过滤」,理由是 106/109 篇笔记本就是 private。
建议补的三件事(需要你点头):
CONTEXT.md加一条术语,把真实语义写死——它是目的地可见性开关,不是内容分级;- 修
web/docs/adr/0011的措辞,把「private 不上网」限定成「private 不上网页日程」; - 决定
public要不要有实际语义。没有就删掉,否则又是一个名不副实的值。
三、memo → 正式笔记
晋升条件(满足任一,且先排除代码/AI 转贴)
- 正文字数 ≥ 500;
- 命中摘录特征:
◆+原文:+来自微信读书; - 标签命中创作白名单(
小红书创作、经济学/投资体系观察名单); - 人工指定
flomo promote <slug>。
content_type 映射
| 形态 | content_type | 落点 |
|---|---|---|
| 微信读书 / 文章摘录 | source_extract |
notes/books/<书名>/ 或 notes/topics/<主题>/ |
| 自己写的长文(公众号草稿、投研分析) | article |
notes/topics/<主题>/ |
| 方法、清单、模板 | method |
notes/topics/<主题>/ |
| 年度/阶段复盘 | review |
notes/topics/ |
| 代码与技术片段 | 不晋升 | 留事件,或 notes/projects/ |
| 同主题多篇合并 | synthesis |
二期 |
溯源
sources表加source_kind='flomo',为每个被晋升的 memo 注册一条来源资料;note_sources关联笔记与来源;- 笔记正文首行保留
<!-- flomo-source:<slug> -->,对齐 ADR-0002 的导入标记思路,便于反查和重跑。
四、附件
- 已落盘
exports/flomo/attachments/(32 个:25 张图 + 7 段录音),扩展名按文件头嗅探; - 阶段一:事件正文里用 Markdown 相对路径引用,日视图保留一行,不上飞书和网页;
- 阶段二:给事件加结构化附件字段后再打通展示;
- 注意:附件 URL 是 OSS 签名地址,几天后失效,本地副本才是长期资产。
五、去重
- flomo 内部:实测 4 组精确重复(8 条),其中 2 组时间戳完全相同,是网页端重复提交;
- 14 组「首行相同」里大部分是标签行造成的假重复,剥掉标签行后按正文比较;
- 与既有事件:515 条里只有 1 条与现有事件文本完全相同(2026-09-25 23:11:32 那条,飞书机器人也记过一遍)。以后会变多,因为在 flomo 和飞书两头记的人是你自己;
- 规则:规范化文本完全相同、且时间差在 24 小时内 → 标记「疑似重复」进待确认清单,不自动合并。ADR-0005 的教训就是自动归一化制造过无意义 revision。
六、个人助理要做哪些改造
阶段一:零 schema 变更,先跑通闭环
- 新增
scripts/import_flomo_memos.py,契约对齐import_feishu_daily_log.py:--dry-run为默认,--apply才落库;- 幂等:
find_by_source('flomo', slug),已存在则比对内容,变了才update_event; - 支持编辑与
deleted_at(转withdrawn),从不删除本地记录; - 状态文件
config/flomo_sync_state.json,记录游标与 slug 映射。
scripts/flomo_export.py补增量能力:--since用updated_at游标,避免每次拉全量。- 分流清单
inbox/flomo-promote.md:列出长文候选与判定理由,你确认后再建笔记,不自动批量建。 - 定时器:
systemd --usertimer,每 30 分钟拉一次(与飞书导入器同样的模式)。 - 术语落地:
CONTEXT.md补一条「flomo 采集源」定义,避免和「本地事实源」混用。 - 令牌失效处理:接口返回未授权时重新登录,令牌继续放
~/.flomo-token.json(权限 600)。 - 给
note_cli.py加日历开关:create_note写死calendar_enabled=True(note_cli.py:220) 且没有--no-calendar参数,意味着 flomo 长文一旦晋升成笔记,就会自动出现在飞书日历上。 晋升流程要么加开关,要么建完立刻改回 false。
阶段一的代价:图片只能以路径形式出现在正文里,飞书和网页看不到图。
阶段二:要给数据模型动刀的部分
- 事件加附件字段:
events/event_revisions增加attachments TEXT(JSON:id、type、本地路径),daily_log_view渲染缩略引用。迁移要点:schema_metadata版本 +1,现有 298 条事件填空串,投影状态不受影响。 - 飞书表:复用「每日记录 V2」和「内容资产 V2」,
来源字段是纯文本,新增flomo取值不需要改表结构。飞书 Base 会跳过 private 事件(代码行为,见上一节),但 flomo 默认是 internal,所以事件会正常投影。 - 网页端:
web/backend/src/lib/schema.ts里事件表没有来源枚举,加flomo只需确认展示层是否要区分来源筛选(ADR-0014 要求 DDL 以schema.ts为唯一来源,真要加字段必须同步改这里)。
明确不做
- 不建「flomo 收件箱」新表(推翻 09-22 那版提议,理由见 ADR-0007);
- 不回写 flomo(
CONTEXT.md已定义采集源不是同步目标); - 不做自动模糊合并;
- 不把 515 条全部笔记化。
七、验收标准
- 阶段一:重复执行导入不产生新 revision;日视图出现带
<!-- source:flomo:slug -->的条目且正文可见;关掉渠道开关的条目按开关语义退出对应目的地(日视图永远显示全文);--dry-run输出与人工抽检一致。 - 阶段二:带附件事件在日视图能看到图;迁移后 298 条既有事件零变化。
八、需要你定的三件事
- 515 条短记要不要全量进日视图?现有事件 298 条,全量导入后时间线会翻近三倍且以 flomo 为主。三个选项:全量导入(推荐,历史最完整)、只导 2026 年、或按来源分开成两个视图。
- 公众号草稿类长文放哪?
notes/topics/还是notes/projects/。 - 图片有没有必要进飞书?不做就是阶段一,做就要排阶段二。