☰
SparkyFitness Pregnancy Mode:孕期里程碑追踪与 5-1-1 宫缩监测的实现剖析
2026/10/10 8:15:12 网站建设 项目流程
  • 后端
  • 前端
  • 移动开发

【免费下载链接】SparkyFitness

SparkyFitness: Built for Families. Powered by AI. Track food, fitness, water, and health — together.

项目地址:https://gitcode.com/gh_mirrors/sp/SparkyFitness
点击查看免费下载

本文基于 docs/src/features/cycle-hub/pregnancy.md 展开,围绕 SparkyFitness 的 Pregnancy Mode(孕期模式)讲解其功能设计、数据模型与底层算法:从孕周计算、胎儿发育内容表,到胎动计数、宫缩计时器的 5-1-1 临床告警、IOM 孕期增重参考区间,再到照片日记与安全清单的权限模型。读完本文,你将掌握该模块"前端交互 — 后端服务 — 共享纯函数库"的完整调用链,并能对照源码理解每个功能背后的医学与工程依据。

一、模式定位:从备孕预测切换到孕期追踪

SparkyFitness 的 Cycle Hub 中,Pregnancy Mode 是女性健康模块的一个独立运行状态:当用户创建一条status = 'active'的怀孕记录后,应用的关注点会从生育力预测(TTC)整体切换到孕期里程碑追踪——包括孕周发育信息、胎动计数、宫缩计时、母体生命体征(体重/血压)、产检预约和各类检查清单。

在移动端入口 CycleHubScreen.tsx 中,该模式通过useCycleMode()切换,命中孕期模式后渲染PregnancyOverviewView(overview 与 tools 两个区块,见 CycleHubScreen.tsx),并由 PregnancySetupScreen.tsx 负责首次建档。前端孕期组件集中在 components/wellness/pregnancy/ 目录,包括BabyGrowthView、BumpPhotoJournal、FoodMedSafetySearch、PregnancyLogView、WeeklyChecklist、WombScene、WeekBanner等。

后端方面,孕期功能拥有独立的三层结构:

层级文件职责
路由routes/v2/pregnancyRoutes.tsREST 端点、参数校验、照片上传与鉴权
服务services/pregnancyService.ts概览聚合、预产期推导、照片文件安全
数据访问models/pregnancyRepository.ts六张表的 CRUD 与 SQL
纯函数库shared/src/cycle/pregnancy.ts 与 pregnancyContent.ts孕周/预产期/5-1-1/增重算法与静态内容表

其中"共享纯函数库"(@workspace/shared)被前后端与测试共同引用,保证同一套孕周与宫缩算法在服务端计算和客户端展示时完全一致。

二、Gestational Milestones:孕周、胎儿发育与每周检查清单

2.1 预产期与孕周计算(Naegele 法则)

预产期(EDD)有三种推导来源,由resolveDueDate按优先级选择,实现见 pregnancyService.ts:

  1. 直接指定due_date;
  2. 提供lmp_date(末次月经首日)→EDD = LMP + 280 天;
  3. 提供conception_date(排卵/受孕日)→EDD = 受孕日 + 266 天。

对应的纯函数实现位于 shared/src/cycle/pregnancy.ts:eddFromLmp直接调用addDays(lmp, 280),eddFromConception调用addDays(conception, 266)。280 天即临床上常说的"40 周从 LMP 起算",266 天是 280 减去约 14 天的排卵偏移,二者殊途同归。测试 gestation.test.ts 对两种算法都有断言。

孕周(Gestational Age)由gestationalAge(dueDate, onDay)计算,返回结构化对象:

interface GestationalAge { week: number; // 已完成周数(0-42) day: number; // 当前周内的第几天(0-6) totalDays: number; trimester: 1 | 2 | 3; daysRemaining: number; progress: number; // 0-1,跨整个 280 天孕期 }

其计算基准是"概念 LMP = dueDate − 280 天",与临床和主流 App 的周数口径一致;孕早期/中期/晚期的分界为第 13 周、第 27 周(week < 13 ? 1 : week < 27 ? 2 : 3),见 pregnancy.ts。测试用例覆盖了"LMP 当天为 week 0"、"第 24 周+2 天归入第二孕期"、"第 28 周起归入第三孕期"、"到期日 progress = 1" 四种边界。

2.2 周周发育数据表:Week 4–40

文档所述的 "Week-by-Week Baby Size & Tips"(胎儿大小对比、平均身长 cm、平均体重 g、每日发育提示、子宫插图)在代码中是一张纯静态内容表BABY_DEVELOPMENT,定义于 shared/src/cycle/pregnancyContent.ts,覆盖第 4 周到第 40 周共 37 条记录:

interface BabyWeek { week: number; comparison: string; // 水果/物品大小类比 lengthCm: number | null; // 平均身长 weightG: number | null; // 平均体重 wombScene: 8 | 20 | 36; // 子宫插画场景(就近取 8/20/36 周) babyBlurb: string; // 胎儿发育提示 momBlurb: string; // 母体变化提示 }

内容示例(摘录):

周数大小类比身长(cm)体重(g)胎儿提示母体提示
4一粒罂粟籽0.1—胚胎着床、胎盘开始形成可能刚错过月经,早孕激素上升
13一荚豌豆7.423指纹形成、声带发育进入孕中期,常是最舒适的阶段
24一穗玉米30.0600肺发育、达到存活里程碑可能即将进行糖筛
32一块豆薯42.41700肺在练习呼吸、常转为头位可开始准备分娩计划与待产包
40一个小南瓜51.23460预产期已到,宝宝自有节奏若未发动,医生会讨论后续方案

该表由babyWeek(week)按周精确命中查询。客户端 BabyGrowthView.tsx 借助WombScene根据wombScene字段选择 8/20/36 周三档宫腔插画,WeekBanner展示当前孕周横幅。测试断言babyWeek(8)为 "A raspberry"、babyWeek(24)为 "An ear of corn"、第 40 周存在,见 gestation.test.ts。

2.3 每周检查清单:模板 + 用户状态

文档提到的 "Weekly Checklists"(如糖耐测试、儿科医生调研等按孕周自动填充)由两部分组成:

  • 模板表CHECKLIST_TEMPLATES:12 条"周窗口"任务,定义于 pregnancyContent.ts,每条含key、weekStart、weekEnd与标题,例如:
{ key: 'first_appt', weekStart: 6, weekEnd: 10, title: 'Book your first prenatal appointment' }, { key: 'glucose_test', weekStart: 24, weekEnd: 28, title: 'Book your glucose screening test' }, { key: 'count_kicks', weekStart: 24, weekEnd: 40, title: 'Start counting fetal kicks daily' }, { key: 'birth_plan', weekStart: 30, weekEnd: 36, title: 'Draft your birth plan' }, { key: 'hospital_bag', weekStart: 32, weekEnd: 37, title: 'Pack your hospital bag' }, { key: 'pediatrician', weekStart: 34, weekEnd: 40, title: 'Choose a pediatrician' },

checklistForWeek(week)按week >= weekStart && week <= weekEnd过滤出当周应显示的任务。

  • 持久化状态表pregnancy_checklist_state:用户在 WeeklyChecklist.tsx 中的勾选、忽略(dismiss)与自定义任务通过upsertChecklistItem写入。服务端聚合时把模板与用户状态合并(template_key关联状态、custom_title为空则视为自定义项),同时过滤掉dismissed项并计算checklistProgress = { done, total },见 pregnancyService.ts。

2.4 Birth Plan / Hospital Bag

文档所述的 Birth Plan(分娩偏好:分娩环境、镇痛方式、新生儿护理,可导出与医疗团队共享)与 Hospital Bag(为 Mother / Partner / Baby 三类角色定制清单任务)在数据层同样落到pregnancy_checklist_state,通过custom_title区分自定义任务,按角色组织由客户端完成。这两块与上述每周清单共用同一套 upsert/列表接口。

三、Daily Tracking Tools:六块快速磁贴与 5-1-1 算法

孕期首页以六类磁贴(Quick Tiles)承载日常追踪,后端由GET /v2/pregnancy/overview一次性聚合返回。路由在 pregnancyRoutes.ts 中调用pregnancyService.getOverview(userId, today, date),其聚合内容包括:当前孕周与胎儿数据、合并后的清单、下一次产检、近 7 天胎动会话、生命体征快照(见 pregnancyService.ts)。

3.1 Kick Counter(胎动计数会话)

  • 数据表:pregnancy_kick_sessions,字段含started_at、ended_at、kick_count、kick_times(数组)。
  • 接口:POST /kicks/start开新会话(kick_count = 0、kick_times = '{}');PUT /kicks/:id更新计数、追加kick_times并可选结束会话;GET /kicks列出全部历史会话。
  • 校验边界:kick_count取值范围 0–1000,kick_times为 ISO8601 时间字符串数组,见 pregnancySchemas.ts。
  • 概览中仅取最近 7 个会话(listKickSessions(userId, 7))用于展示趋势。

3.2 Contraction Timer + 5-1-1 Alert(宫缩计时与临床告警)

宫缩记录由pregnancy_contractions表存储(started_at、ended_at、intensity1–5),接口为POST /contractions、PUT /contractions/:id、GET /contractions。真正的临床判断在共享纯函数contractionStats中完成,见 shared/src/cycle/pregnancy.ts。

算法流程:

  1. 仅取最近 65 分钟内、已记录ended_at的宫缩(nowMs - started_at <= 65 * 60 * 1000);
  2. 按开始时间升序排列,计算平均时长(秒)与相邻平均间隔(分钟);
  3. 判定5-1-1模式需要同时满足四项:
const isFiveOneOne = recent.length >= 6 && // 至少 6 次宫缩 avgIntervalMin <= 5.5 && // 平均间隔 ≈ ≤5 分钟 avgDurationSec >= 45 && // 平均时长 ≈ ≥1 分钟(留 15s 容差) spanMin >= 50; // 首尾跨度 ≈ ≥1 小时(留 10min 容差)

即"宫缩间隔 ≤5 分钟、每次持续 ≥1 分钟、且持续 ≥1 小时"的临床就诊指征。之所以在 5 / 60 / 60 的标称值上分别留出 5.5 分钟、45 秒、50 分钟的容差,是为了在数据录入有少量误差时仍能可靠触发告警,避免漏报。服务端getContractionAnalysis会取最近 2 小时数据交给该函数分析,见 pregnancyService.ts。

测试 gestation.test.ts 构造了 12 次"每 5 分钟一次、每次 60 秒、跨度约 55 分钟"的宫缩序列断言isFiveOneOne === true,并用仅 1 次稀疏宫缩验证不会误报。当判定命中时,客户端(如 PregnancyOverviewView.tsx 与 PregnancyLogView.tsx)展示警告横幅,建议联系医疗提供方。

3.3 Bump Photo Journal(孕肚照片日记)

文档所述"上传、浏览、删除每周孕肚照片"对应pregnancy_photos表与四组接口:

  • POST /photos(multipart 上传,经 checkInPhotoUpload.ts 校验图片魔数与扩展名)
  • GET /photos?pregnancy_id=...(列表,按week ASC, entry_date ASC排序)
  • GET /photos/file/:id(取图片字节,仅本人可访问)
  • DELETE /photos/:id(删除行与磁盘文件)

磁盘布局(见 pregnancyRoutes.ts):

uploads/pregnancy/{userId}/{pregnancyId}/w{week}-{timestamp}.{ext}

例如uploads/pregnancy/u_abc/preg_123/w20-1720000000000.jpg。路径中的pregnancyId必须是 UUID(路由层用UUID_RE正则强制校验),从源头阻断../../../etc之类的路径穿越。

隐私设计值得注意:pregnancy_photos是"仅本人"的生殖健康数据,从公共静态目录/uploads中排除,只能通过鉴权路由GET /photos/file/:id获取字节。服务层getPhotoFile在返回文件前依次校验:记录存在性 → 归属权(WHERE user_id = $1 AND id = $2)→ 路径未逃逸 uploads 根目录(resolveUploadPathWithinRoot)→ 文件确实在磁盘上;任一环节失败统一返回 404,不向调用方暴露具体原因(pregnancyService.ts)。删除时同样先校验路径再unlink,即使文件已缺失也以数据库行为准返回成功(pregnancyService.ts)。

3.4 Weight & BP Tracking(体重与血压)

体重(IOM 指南):getOverview通过check_in_measurements表取三个值——末次经期日(LMP)之前最早的体重作为prePregnancyWeight、目标日当天或之前最新的体重、最近一次身高,进而:

  1. 计算孕前 BMI =weight / (height/100)^2;
  2. 调用共享函数weightGainRange(prePregnancyBmi, week, fetusCount)得到基于 IOM(美国医学研究院)指南的累计增重区间,分类依据孕前 BMI:<18.5 偏瘦(12.5–18 kg)、<25 正常(11.5–16 kg)、<30 超重(7–11.5 kg)、≥30 肥胖(5–9 kg);fetusCount >= 2时按双胎指导放宽区间(如正常 BMI 为 17–25 kg),见 shared/src/cycle/pregnancy.ts;
  3. 以weightDelta = latestWeight - prePregnancyWeight与区间比较,输出within_range / below_range / above_range三态状态。

实现细节:孕早期(≤13 周)按约 2 kg 匀速增长,13 周后线性插值到足月目标,确保第 1 周前返回null(测试见 gestation.test.ts)。孕前体重未记录时跳过该评估。

血压:getLatestBpCustomMeasurement在custom_categories中按blood_pressure(含别名)定位自定义类别,再取custom_measurements中目标日及以前的最新一次value,见 pregnancyRepository.ts。

3.5 Prenatal Vitamin Toggle(产前维生素打卡)

pregnancies表记录两个可选用药引用:prenatal_medication_id与supplement_medication_id(如铁剂)。概览聚合时,服务端通过getMedicationName取药名、getMedicationLogStatus检查目标日是否已有medication_entries记录,从而返回loggedToday布尔值与对应 entry id(pregnancyService.ts)。前端在 vitals 卡片上提供快速打卡按钮,点击即写入当日的用药记录。

3.6 Appointments & Scans(产检预约)

预约数据存入通用表health_appointments(scheduled_at、appointment_type、title、location、notes、outcomeJSONB)。接口:POST /appointments、PUT /appointments/:id、GET /appointments?upcoming=true(仅未来)、DELETE /appointments/:id。APPOINTMENT_TYPES枚举预置了 checkup / ultrasound / glucose_test / bloodwork / specialist / class / other 七类(shared/src/cycle/pregnancy.ts)。概览中的nextAppointment取升序排列后的第一条未来记录。

3.7 Food & Medication Safety(食物与用药安全)

文档提到的"Safe / Caution / Avoid"三级风险分类在 shared/src/cycle/pregnancyContent.ts 中实现为两张静态表:

  • FOOD_SAFETY(20 条):如 Cooked salmon(safe)、Tuna canned light(caution,每周约 2 份并优选 light 罐装)、Swordfish / raw sushi / unpasteurized soft cheese / alcohol(avoid)、Coffee(caution,约 200 mg/天 咖啡因)、Herbal tea(caution);
  • MED_SAFETY(10 条):如 Acetaminophen(safe,一线镇痛)、Ibuprofen(caution,孕晚期尤其避免)、Isotretinoin(avoid,致畸)、Prenatal vitamin(safe,推荐全程补充叶酸/铁/DHA)。

每条记录含aliases别名数组与note风险说明。lookupSafety(query, list)对名称与别名做双向包含匹配(pregnancyContent.ts),matchMedSafety还能从"药柜名"(如 "Advil 200mg")匹配到对应条目;测试断言 "sushi" 命中 avoid、matchMedSafety('Advil 200mg')命中 caution(gestation.test.ts)。前端搜索组件为 FoodMedSafetySearch.tsx。

四、Physical Wellness Roadmap:孕产健身的前瞻规划

文档明确说明:Prenatal Workouts 与 Pelvic Floor(盆底肌)专项训练属于Planned Roadmap(计划中),专门的孕期核心/盆底训练内容推迟到未来的健身计划 backlog中实现。当前孕期模式并未接入专项训练计划,读者在评估该模块能力边界时应以此为准。

五、数据模型、权限与 API 速查

5.1 涉及的表与 RLS 策略

表用途
pregnancies孕期主档(due_date、basis、fetus_count、status、medication 引用、notes)
pregnancy_kick_sessions胎动会话
pregnancy_contractions宫缩记录
pregnancy_photos孕肚照片(file_path 不对外返回)
pregnancy_checklist_state检查清单状态(模板项+自定义项)
health_appointments通用产检预约

权限方面,db/rls_policies.sql 对四张孕期表统一执行create_owner_policy(Tier 1,仅创建者本人可读写),与文档"家庭共享"主基调形成鲜明对比:孕期数据(尤其照片)是 owner-only,绝不通过家庭成员委托或共享机制暴露。这一点在路由注释中有明确说明(pregnancyRoutes.ts):不同于可委托的 check-in 照片,孕期照片路由故意不挂checkPermissionMiddleware,防止家庭成员经由权限授权拿到孕肚照片。

5.2 API 端点总览(前缀/api/v2/pregnancy)

方法路径说明
GET/current当前 active 孕期
POST/创建孕期(due_date/lmp_date/conception_date至少其一)
GET/overview?date=YYYY-MM-DD聚合概览(孕周、清单、下次产检、胎动、体征)
PUT / DELETE/:id更新(可含 status/ended_on/outcome)/ 删除
POST/kicks/start开启胎动会话
PUT/kicks/:id更新 kick_count / kick_times / ended
GET/kicks全部会话
POST/contractions记录宫缩
PUT/contractions/:id更新 ended_at / intensity
GET/contractions近 2 小时宫缩 + 5-1-1 统计
POST/photos上传孕肚照片(multipart,pregnancy_id 必须为 UUID)
GET/photos照片列表(按周/日期排序)
GET/photos/file/:id本人取照片字节(鉴权,公共 /uploads 不可达)
DELETE/photos/:id删除照片行与文件
GET/PUT/POST/checklist查询 / upsert 清单项(completed/dismissed/custom)
POST/appointments新建产检预约
PUT/appointments/:id更新预约
GET/appointments?upcoming=true预约列表
DELETE/appointments/:id删除预约

路由注册顺序有讲究:PUT /:id与DELETE /:id必须放在所有具名路由之后,否则 Express 会把PUT /checklist这类单段路径当成:id吞掉并在 UUID 校验处报 400(见 pregnancyRoutes.ts 注释)。

5.3 请求体校验要点(zod)

  • 创建/更新孕期:due_date_basis限定['lmp', 'conception', 'manual', 'scan'];fetus_count为 1–4 的整数;日期字段必须是YYYY-MM-DD(isDayStringrefine);
  • 宫缩intensity1–5 整数;清单week0–45;预约scheduled_at为 ISO datetime。 完整 schema 见 pregnancySchemas.ts。

六、测试与质量保障

孕期模块的算法正确性由 tests/gestation.test.ts 系统覆盖,另有两个专项测试文件:

  • pregnancyRoutes.test.ts:路由层行为验证;
  • pregnancyPhotoService.test.ts:照片服务的路径安全与文件操作验证。

加上 uploadsStaticMount.test.ts 对公共静态挂载的约束断言,共同构成"算法(共享库)→ 服务(聚合与安全)→ 路由(鉴权与校验)"三层测试防线。任何对孕周公式、5-1-1 阈值或 IOM 增重区间的改动都会先被这些测试拦截。

七、小结

SparkyFitness 的 Pregnancy Mode 是一套以"纯函数共享库 + 静态内容表"为核心、前后端严格分工的孕期追踪实现:@workspace/shared承载可被测试与前后端共同引用的孕周/预产期/5-1-1/增重算法,pregnancyContent.ts提供离线可用的胎儿发育与安全知识库,服务端负责聚合与照片等敏感资源的路径安全,而 RLS 层则把孕期数据锁定为 owner-only。对希望复用或扩展该模块的开发者,建议从 shared/src/cycle/pregnancy.ts 与 shared/src/cycle/pregnancyContent.ts 入手理解领域逻辑,再对照 pregnancyService.ts 与 pregnancyRepository.ts 追踪数据流。

说明:孕期相关功能属于健康追踪工具而非医疗建议,文中涉及的任何临床参考(5-1-1、IOM 增重、食物/用药分级)均以提醒用户咨询医疗提供方为最终口径。

  • 后端
  • 前端
  • 移动开发

【免费下载链接】SparkyFitness

SparkyFitness: Built for Families. Powered by AI. Track food, fitness, water, and health — together.

项目地址:https://gitcode.com/gh_mirrors/sp/SparkyFitness
点击查看免费下载
上一篇:K9s v0.32.6 维护版深度解析:插件生态扩展、Jump to Owner 增强与稳定性修复
下一篇:Boto3 配置 S3 存储桶 CORS:get_bucket_cors 与 put_bucket_cors 实战指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询