3 Commits
Author SHA1 Message Date
laoyang 72dc6c0006 家谱关系推导:把「只记了一头」的边补成双向(修用户报的 4 处少人)
用户指出 4 个具体错误,逐条核对线上真实数据后确认**全部是算法缺陷**:
  ① 杨永华只有一位家长(母亲那边整条线缺失)
  ② 洪博为的孩子是空的 —— 她是杨永全的配偶,杨永全的孩子就是她的
  ③ 洪勇、武竹芳的配偶都是空的 —— 两人明明是同一个孩子的父母
  ④ 杨景焕 / 杜浩洋 / 杜若桐 都只有一位家长

根因:本系统的设计是「只存相邻两代之间的边,其余全部推导」(见 Kinship 头注释),
但家人手工录入时只会填**最直接的那一条**:
  · 给几个孩子都填了「父亲 = 某人」,从没点过「母亲」→ 孩子只有一位家长;
  · 填了孩子父母两位,却忘了把这两位再标一次「夫妻」→ 两位家长各自孤零零。
而 graph() 只做了「parent 边反推 children」的**单向**推导,缺了两条互补推导。

修复(都在 graph() 里,内存视图,不动 relations 表):
  推导① 同一个孩子的两位家长 ⇒ 认定他们是夫妻
  推导② 孩子只有一位家长、且该家长**恰好只有一位配偶** ⇒ 把配偶补成另一位家长
         (有多个配偶时不猜;迭代到稳定,最多 4 轮)

配套修复(实测发现的回归,必须一起上):
  推导① 会把「已解除的配偶」凭共同孩子**配回去** —— 用户点"解除配偶"刷新又出现。
  解法:解除配偶时把这一对记进 settings 的 `kin_undone_pairs`(不动表结构),
       推导①跳过这些对;重新 addSpouse 时抹掉标记。
  验证:初始有配偶 → 解除后为空(不再被推回)→ 重新加回恢复。

验证:
· _fc_verify_derive.py(新):用**线上真实那 10 条边**做夹具,逐条断言用户报的 4 处
  + 11 条「不许过度推导」守卫(祖父母不被推成夫妻、孩子家长不多不少、无自环…)
  全绿;反向验证 2 处注入(关推导①/关推导②)各变红 6/8 条且退出码非 0。
· e2e 549/0、家谱可视化 24/0、家谱版面 10/0、桌面几何 368/0、移动端 516/0、
  交互 97/0、升级专测 123/0。
· **线上真机验证**:修复后重新取线上 data-datum,4 处全部正确 ——
  洪博为子女=杨景焕、洪勇⇄武竹芳、杨景焕双亲=杨永全+洪博为、
  杜浩洋/杜若桐双亲=杨永华+杜元青、杨永华双亲=杨培振+刘巧莲。
· 部署脚本加 2 条断言锁住推导逻辑。
2026-10-02 22:08:13 +08:00
laoyang 809443d429 v7:家谱树与名册三处口径统一(树上的人自动互相可见)
用户反馈:我把一个人设成配偶、又设了我们的孩子,配偶登录后首页能看到
孩子、家谱也能看到,名册里却看不到——没分享的人却在家谱上看得到,
口径打架。

口径由用户拍板:**以每个人为中心上下三代那棵家谱树(= 首页「我的家人」
同一棵)里的人,自动进他名册(source=kin,默认归公共「家人」组);
树外的人三处都不可见。**

- PersonRoster::syncKinForFamily():整树同步(补缺、撤多余;已存在的
  self/shared/seeded 行绝不碰,用户改过的分组/顺序保留)。幂等。
- 同步挂在事件上:建边 / 解边 / 开通账号 / 结构升级回填。
- Relations::addParent/addSpouse 建边后同步;remove/detach 解边后同步。
- 名册不许单独移走 kin 来源的人(服务端拦截 + 人话提示 + 卡片隐藏
  移走按钮改提示语):他在首页/家谱照样看得见,单藏名册只会回到口径
  打架;想不再看到他,解除关系即自动移出。
- 名册卡片给 kin 来源标「家谱」徽标(来历可辨)。
- Schema v7:widenRosterSourceEnum(⚠️ CREATE TABLE IF NOT EXISTS 不会
  更新已存在表的枚举,v6 老库不扩宽则 strict 模式下插 kin 整条被拒、
  且被容错吞掉——e2e 的 v6 旧库当场抓出)+ 按已有关系回填 kin 条目。

验证:e2e 309/0、升级专测 82/0、部署静态空跑 78/0、桌面几何 0 失败、
交互 97/0、移动几何 180/0;线上探针 ver=7、enum 含 kin、无升级告警。
2026-09-30 16:12:28 +08:00
laoyang 1669ab48d0 五项家庭化改造:以个人为中心 · 家谱 · 三种角色 · 附件存储可配置 · 独立页样式
1. 独立页(登录/安装/错误)套上 HTML 骨架 —— 修「首页为什么没有样式」:
   新增 templates/layout-bare.php,View::render 对独立页也走完整 <head> + CSS。
2. 以个人为中心:Kinship::label() 现算称呼、Auth::homeTitle() 出「X的家书」;
   同一批人在不同登录者眼里自动变成公公婆婆 / 岳父岳母(不存一次性称谓)。
3. 附件存储可配置:Storage 门面(local / 阿里云 OSS),库里只存相对键,
   云端失败自动回退本地;设置页可选驱动并填 OSS 参数(照 MovieLog 的方案)。
4. 三种角色:admin / member / guest,访客只读、写操作一律 403;
   accounts.role 枚举扩容,老库靠 Schema::ensure() 自动升级。
5. 家谱:只存相邻两代的边(parent / spouse),其余称呼全部推导 ——
   兄弟姐妹不存边(共同父母 + birth_rank 排行推长子/次女、哥哥/妹妹),
   认不清的人用虚拟人物 is_placeholder 占位,关系天然双向。

顺带修掉本地 e2e 挖出的 8 个线上级 bug(性别写死、长幼方向反、关系方向静默写反、
birth_rank 不从表单取回、隐式可空参数致响应头失效、无文件字段被误判为上传失败、
layout 变量撞车覆盖 $me、世代 0 显示成「下 0 代」)。

验证:kinship 单测 37/0 · HTTP e2e 81/0 · 老库升级专测 14/0 ·
反向验证 11/11(把每个修复改回旧写法,对应断言都会变红)。
2026-09-30 08:13:07 +08:00