-- 到诊派生字段落 patient_profiles ——「上次到诊 / 上次医生 / 偏好医生」
--
-- 【为什么要物化】跨全池的筛选没有不物化的:否则「上次到诊在 3-6 个月」这个条件
--   要对 2,500 万行 patient_facts 做 GROUP BY patient_id HAVING max(occurred_at) …,交互式查询扛不住。
--   画像筛选也一样物化了 —— 它的"柱子"就叫 persona_features,只是立得早、又用 JSONB 做成了
--   免 DDL 的扩展点,所以加维度时感觉不到成本。这三个字段不属于画像,没有现成柱子可搭。
--
-- 【为什么落 profiles 而不是 patients】profile 本来就装派生 / 摄入属性(获客渠道、转介绍统计),
--   patients 是主数据。派生的归派生,主表不被越搞越宽。
--
-- 【为什么不是画像特征】2026-07-29 第一版做成 persona 特征 visit_recency,当天被否:
--   它会混进患者详情的画像标签抽屉、并喂给画像摘要 LLM。
--   「上次到诊 2022-06-19」是原始事实;画像该是「重要价值 / 流失客 / 禁忌」这类判断。
--   (那版一行都没写进生产就撤了,无需清理。)
--
-- 【⭐ 存日期不存分档 —— 不需要任何定时任务】
--   存 `0_3m` 这种分档,时间流逝自己就会错(今天 2 个月、下月变 3 个月)→ 得配到期重扫机制;
--   存日期则**只有患者又来了才会变**,而"又来了"必然先写到诊 fact → 推进事实水位 →
--   触发该患者画像重算 → 这三列顺路刷新。触发时机与已有机制天然重合,自洽,零额外调度。
--   0-3 / 3-6 / … 的区间在**查询时**按 now 现算(visitRecencyRange),语义永远对得上今天。
--
-- 【索引】profile 是 1:1 表(PK = patient_id),行数 = 患者数,不需要 host/tenant 前缀
--   (筛选先经 patients 的 host/tenant 圈定,再 join 过来)。
--   · last_visit_at   → 区间查询    · last_visit_doctor / preferred_doctor → 等值查询
--     ⭐ 是**等值**不是模糊:前端在 /plans/doctors 的名单上做客户端模糊,选中后发精确姓名。
--     改成后端 ILIKE '%x%' 索引立刻失效、退回全表扫(见 2026-07-29「圈人 20 秒」事故)。
--
-- 【存量】新列全 NULL,等下一轮画像重算填。在那之前:卡片"上次到诊"留空、三个筛选筛不到人、
--   医生名单接口返回空数组(前端显示「暂无医生名单」)。刻意**不做**"取不到就现算"的兜底 ——
--   那会让卡片展示值与筛选口径再次分叉,正是本次返工要消灭的问题。
ALTER TABLE "patient_profiles"
  ADD COLUMN IF NOT EXISTS "last_visit_at"     date,
  ADD COLUMN IF NOT EXISTS "last_visit_doctor" text,
  ADD COLUMN IF NOT EXISTS "preferred_doctor"  text;

CREATE INDEX IF NOT EXISTS "patient_profiles_last_visit_at_idx"     ON "patient_profiles" ("last_visit_at");
CREATE INDEX IF NOT EXISTS "patient_profiles_last_visit_doctor_idx" ON "patient_profiles" ("last_visit_doctor");
CREATE INDEX IF NOT EXISTS "patient_profiles_preferred_doctor_idx"  ON "patient_profiles" ("preferred_doctor");
