- 后端
- 前端
- 移动开发
【免费下载链接】SparkyFitness
SparkyFitness: Built for Families. Powered by AI. Track food, fitness, water, and health — together.
本文基于 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.ts | REST 端点、参数校验、照片上传与鉴权 |
| 服务 | 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:
- 直接指定
due_date; - 提供
lmp_date(末次月经首日)→EDD = LMP + 280 天; - 提供
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.4 | 23 | 指纹形成、声带发育 | 进入孕中期,常是最舒适的阶段 |
| 24 | 一穗玉米 | 30.0 | 600 | 肺发育、达到存活里程碑 | 可能即将进行糖筛 |
| 32 | 一块豆薯 | 42.4 | 1700 | 肺在练习呼吸、常转为头位 | 可开始准备分娩计划与待产包 |
| 40 | 一个小南瓜 | 51.2 | 3460 | 预产期已到,宝宝自有节奏 | 若未发动,医生会讨论后续方案 |
该表由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。
算法流程:
- 仅取最近 65 分钟内、已记录
ended_at的宫缩(nowMs - started_at <= 65 * 60 * 1000); - 按开始时间升序排列,计算平均时长(秒)与相邻平均间隔(分钟);
- 判定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、目标日当天或之前最新的体重、最近一次身高,进而:
- 计算孕前 BMI =
weight / (height/100)^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; - 以
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.
相关推荐
SparkyFitness Period & Cycle Hub 使用与原理指南:经期、备孕与孕期的一站式自托管健康追踪
SparkyFitness Period & Cycle Hub 使用与原理指南:经期、备孕与孕期的一站式自托管健康追踪 导读 本文以 SparkyFitnes
后端前端移动开发SparkyFitness 经期与周期中心五种追踪模式全面对比:Standard / TTC / Pregnancy / Postpartum / Menopause
SparkyFitness 经期与周期中心五种追踪模式全面对比:Standard / TTC / Pregnancy / Postpartum / Menopa
后端前端移动开发CANN 9.0.0-beta.1 发布计划解读:里程碑、测试轮次与特性追踪机制
CANN 9.0.0 beta.1 发布计划解读:里程碑、测试轮次与特性追踪机制 CANN 9.0.0 beta.1 是 CANN 9.x 系列的首个 Beta
文档CANN
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考