# 把生理数据接回自己手上：与 AI 协作生理信息数据架构分享，你也可以自己来

> 个人生理日志如何对接 Google Health、Python、SQLite 与 Cloudflare？本文拆解五分钟步行检查、每日生理同步、三份公开数据契约与 AI 协作的实际边界。

- Author: Paul Kuo（郭曜郎）
- Date: 2026-10-21
- Pillar: 智能与秩序 / Intelligence & Order
- Tags: AI 协作, Google Health, Fitbit, Python, SQLite, Cloudflare
- Canonical: https://paulkuo.tw/zh-cn/articles/physiology-data-ai-collaboration/
- Language: Simplified Chinese (简体中文)
- Translation: AI-translated from the zh-Hant (Traditional Chinese) original
- Original (zh-Hant): https://paulkuo.tw/articles/physiology-data-ai-collaboration.md
- Site index for LLMs: https://paulkuo.tw/llms.txt

---

> 这个计划将本人授权的 Fitbit／Google Health 数据收回本机，经过日期整理、缺值处理与白名单检查，再公开指定的日级摘要。AI 协助设计、实现与复核；日常数据整理与页面摘要由固定程序规则执行。

## 为什么把自己的生理数据做成网站？

很多年前，我读了《身体调校圣经》（The 4-Hour Body）。从那时起，我就对掌握自己的身体数据有一股热情。对我来说，“没有数据就无法管理”这个想法，也适用于健康维护。

<figure style="max-width: 420px; margin: 2rem auto;">
  <img src="/images/articles/physiology-data-ai-collaboration/the-4-hour-body.jpg" alt="《身体调校圣经》（The 4-Hour Body）繁体中文版的书封照片" width="1108" height="1477" loading="lazy" decoding="async" />
  <figcaption>《身体调校圣经》（The 4-Hour Body）繁体中文版。照片由 Paul 提供。</figcaption>
</figure>

### 从《身体调校圣经》看数据管理

Tim Ferriss 在书中以自我实验探索饮食、训练与睡眠等议题。与这个网站最有关的，是其中几项测量与追踪观念。

- **先留下起点。** 以身体围度、体脂等记录建立基准，别只盯着体重。[原书测量章节](https://tim.blog/wp-content/uploads/2014/10/4-hour-body.pdf)
- **测量方法要一致。** 工具、算法与条件的差异，可能被误认为身体变化。[原书测量章节](https://tim.blog/wp-content/uploads/2014/10/4-hour-body.pdf)
- **让记录可以持续比较。** 书后资源链接到量化自我与自我实验的方法，将日常习惯与身体记录纳入追踪。[作者配套资源](https://tim.blog/gear/)
- **用“最小有效剂量”思考。** MED 指足以产生目标结果的最小干预量，核心是找出真正有作用的调整。[出版社节选](https://penguinrandomhouselibrary.com/book/?isbn=9780307463630)

我把这些观念延伸成本站的做法：保留来源与日期，使用一致定义，让记录能长期回看。这是数据架构上的借鉴，不是把书中案例当成本站健康效果的证明。

现在有了 AI，当年的期待更有机会落实。我想把这份热情，逐步做成能持续记录、整理与回看的数据架构。

我把自己的活动、睡眠与部分生理记录放上网站，想介绍的是这套系统如何运作。

网站上的一个数字，背后需要回答几件事：数据从哪里来？是哪一天？是否仍在累积？没有记录时怎么显示？哪些内容可以公开？

这些问题决定了一张图表能不能被正确理解，也决定了自动化流程遇到错误时，会不会继续把不确定的结果送上网站。

本文记录的是 2026 年 10 月这一版：通过 Google Health API、本机 Python 与 SQLite、Cloudflare KV／Worker，以及网站界面，建立持续更新的日级生理日志。它与[早期使用 Fitbit MCP、Apple Health 导出及部署脚本的探索](/zh-cn/articles/personal-health-data-infrastructure/)，属于不同阶段的架构。这次的重点放在数据来源、公开边界与失败处理。

<link rel="stylesheet" href="/styles/physiology-article-visuals-v1.css" />
<figure class="physio-visual" id="physio-data-atlas">
  <p class="physio-visual__notice">示意数据 · 非 Paul 实测</p>
  <div class="physio-atlas">
    <img src="/images/articles/physiology-data-ai-collaboration/physiology-data-atlas-unlabeled-v2.webp" alt="以睡眠柱状图、HRV 与静息心率趋势线及数据网格呈现日级记录的概念插图" width="1536" height="1024" loading="lazy" decoding="async" />
    <span class="physio-atlas__label physio-atlas__label--sleep" aria-hidden="true">睡眠</span>
    <span class="physio-atlas__label physio-atlas__label--hrv" aria-hidden="true">HRV</span>
    <span class="physio-atlas__label physio-atlas__label--rhr" aria-hidden="true">RHR</span>
  </div>
  <ul class="physio-visual__legend physio-visual__legend--atlas" aria-label="图例">
    <li><span class="physio-visual__swatch physio-visual__swatch--sleep" aria-hidden="true"></span>睡眠</li>
    <li><span class="physio-visual__swatch physio-visual__swatch--hrv" aria-hidden="true"></span>心率变异性（HRV）</li>
    <li><span class="physio-visual__swatch physio-visual__swatch--rhr" aria-hidden="true"></span>静息心率（RHR）</li>
  </ul>
  <figcaption>AI 生成的数据可视化插图：睡眠、心率变异性（HRV）与静息心率（RHR）的日级记录及趋势。所有图形均为人工示意，并非 Paul 的实测记录；阴影带不代表医学正常范围。</figcaption>
</figure>

## Google Health、Python 与网站怎么串起来？

整体流程分成采集、整理、发布与呈现四层。

```mermaid
flowchart TD
    A["Fitbit 设备与供应商同步"] --> B["Google Health API"]
    B -->|"本人授权的只读数据权限"| C["本机 Python 采集"]
    C --> D["私人 SQLite 与受限原始响应"]
    D --> E["日期整理、日级汇总、缺值与冲突处理"]
    T["本机 Takeout 一般历史"] --> E
    E --> F["批准公开白名单与 Node 验证"]
    F --> G["Cloudflare KV"]
    G --> H["Worker 只读端点"]
    H --> I["Astro／JavaScript 四语网站"]
    J["AI 协作：设计、程序、文案与复核"] -.-> C
    J -.-> I
```

图中的实线表示数据流；虚线表示搭建与维护协作，不是每次更新都把生理数据传给模型。Takeout 只用于一般历史指标，不参与 HRV、静息心率的个人参考带运算。

穿戴设备先把数据同步到供应商服务，本机程序再使用已授权的 Google Health 只读数据权限取得记录。程序核对账号与 Health identity，读取查询的所有分页，再将取得的数据写入本机数据库，原始 API 响应保存在受限目录。

这里的「只读」，指的是不写回 Google Health。本机数据整理与已授权的公开摘要发布，另有各自的写入流程。

数据定义也必须先确认。Google Health 区分日级、区间、单点等数据型别，并列出各型别所需权限；看起来相近的指标，不能直接当作相同数据使用。[Google Health 数据型别](https://developers.google.com/health/data-types)

公开端由另一段程序建立摘要，只接受批准字段。通过 Node 白名单验证后，摘要写入既有 Cloudflare KV，由 Worker 提供读取，网站再呈现图表与说明。一般数据更新走 KV，不需要每次重新部署网站。

## 每五分钟检查，实际更新哪些数据？

目前的排程设计有两种频率。

<p class="physio-table-hint" id="physio-table-1-hint">窄屏幕可左右滚动表格。</p>
<div class="physio-table-scroll" role="region" aria-label="数据更新频率与范围" aria-describedby="physio-table-1-hint" tabindex="0">
  <table>
    <caption>数据更新频率与范围</caption>
    <thead><tr><th scope="col">流程</th><th scope="col">检查目标</th><th scope="col">更新范围</th></tr></thead>
    <tbody>
      <tr><td>步行更新</td><td>每五分钟</td><td>今日与昨日的步数、距离日级累积值</td></tr>
      <tr><td>完整日同步</td><td>台北时间每日 09:00</td><td>回查最近七天，成功后更新其余批准摘要与独立生理区块</td></tr>
    </tbody>
  </table>
</div>

睡眠、HRV 与静息心率，不会跟着五分钟步行流程重新计算。

两条流程共用锁，避免同时采集与覆写。目前由 Codex 的排程唤起本机固定 Python 程序，数据整理与摘要计算则依程序规则执行。Mac 与 Codex 必须运行，网络和上游同步也会影响实际执行时间。因此，五分钟代表检查目标；设备尚未同步时，增加查询也不会产生新的记录。

公开数据则分成三份契约，端点位于 `api.paulkuo.tw`：

<p class="physio-table-hint" id="physio-table-2-hint">窄屏幕可左右滚动表格。</p>
<div class="physio-table-scroll" role="region" aria-label="公开数据端点与内容" aria-describedby="physio-table-2-hint" tabindex="0">
  <table>
    <caption>公开数据端点与内容</caption>
    <thead><tr><th scope="col">端点</th><th scope="col">内容</th></tr></thead>
    <tbody>
      <tr><td><code>/health-journal</code></td><td>一般日级摘要，以及今日一个固定的平均心率</td></tr>
      <tr><td><code>/health-journal/history</code></td><td>七项一般指标的日级历史，不含平均心率</td></tr>
      <tr><td><code>/health-journal/physiology</code></td><td>最近七天的睡眠、每日 HRV、静息心率与个人参考带</td></tr>
    </tbody>
  </table>
</div>

七项一般指标包含步数、距离、活动热量、睡着时长、睡眠平均血氧、主要睡眠呼吸频率与皮肤温度偏差。

平均心率与静息心率是不同指标。今日平均心率采截至数据更新时已记录的数据计算，在当日第一次远端发布尝试前固定，同日重试沿用同一值，五分钟步行更新也不重新计算。它不代表完整 24 小时的平均；没有可用的已验证当日值时，就保留空白。静息心率则在另一份已批准的生理摘要中呈现七日记录。

## 一张图表，怎么保留数据的限制？

我希望读者看到数字时，也能看见它成立的条件。

<figure class="physio-visual" id="physio-signals">
  <img src="/images/articles/physiology-data-ai-collaboration/physiology-signals-infographic.webp" style="height: auto; max-height: none; margin: 0;" alt="以人物轮廓及四个编号区块，示意睡眠、心率变异性、静息心率与步行活动数据" width="1536" height="1024" loading="lazy" decoding="async" />
  <ul class="physio-visual__legend physio-visual__legend--signals" aria-label="图例">
    <li><span class="physio-visual__number" aria-hidden="true">01</span>睡眠</li>
    <li><span class="physio-visual__number" aria-hidden="true">02</span>心率变异性（HRV）</li>
    <li><span class="physio-visual__number" aria-hidden="true">03</span>静息心率（RHR）</li>
    <li><span class="physio-visual__number" aria-hidden="true">04</span>步行活动</li>
  </ul>
  <figcaption style="margin-top: 0.75rem; font-size: 0.88rem; line-height: 1.6; color: var(--text-secondary);">AI 生成的概念信息图：01 睡眠、02 心率变异性（HRV）、03 静息心率（RHR）、04 步行活动。波形与图形均为示意，未使用 Paul 的实测数据。</figcaption>
</figure>

步数与距离依台北日期汇整，今天仍在累积；睡眠归在醒来日，夜间指标保留供应商日期。睡眠在数据储存与 API 层保留分钟，界面则换成小时，让阅读比较直觉。

缺值维持空白或 `null`，不补成零；来源冲突也保留空白。旧数据保留原日期，不换上今天的标签。历史导出与近期 API 数据依来源规则整并，避免重复加总。

生理区块放在页面前方，用一句话、三项依据与七日图表，呈现睡眠、每日 HRV 与静息心率。HRV 使用每日平均 RMSSD 原值；静息心率使用包含睡眠数据的 `WITH_SLEEP` 定义。[Google Health 数据点定义](https://developers.google.com/health/reference/rest/v4/users.dataTypes.dataPoints)

个人参考带采用每个评估日前 28 天的同定义 API 数据，至少需要 21 个有效日，计算中位数及第 25 至 75 百分位。评估当天不算进去，也不混入未验证的 Takeout 数据。有效记录不足时，就显示「建立基准中」。

这个参考带描述近期记录的分布。落在带内不代表健康，落在带外也不能用来诊断。页面不估算剩余能量，也不据此判断运动安全。

### HRV 与 RHR，怎么与生活经验对照？

**HRV 看的是心跳间隔的变化。** 它与自主神经对心跳的调节有关，不是每分钟跳几下。日常追踪的用途，是把自己的趋势与睡眠、压力及生活习惯放在一起看，而不是拿单次数值与别人排名。[Google Health 的 HRV 说明](https://support.google.com/googlehealth/answer/14237938?hl=en)

**RHR 看的是休息时的心跳频率。** 体温、情绪、疼痛与药物等因素，都可能影响心率；数值升高不会直接告诉我原因。[美国心脏协会的心率说明](https://www.heart.org/en/health-topics/high-blood-pressure/the-facts-about-high-blood-pressure/all-about-heart-rate-pulse)

我自己有很明显的观察：只要喝酒，HRV 就往下掉；身体不舒服、没睡好或生病时，RHR 则会明显往上升。这是我在自己记录中看到的现象，不代表每个人都会有相同反应。

<!-- Personal-observation animation: illustrative art and fictional example values, no measured data. -->
<figure class="physio-cartoon">
<link rel="stylesheet" href="/styles/physiology-observation-cartoon-v2.css" />
<div class="physio-cartoon__heading"><span class="physio-cartoon__kicker">个人观察 × 生理信号</span><p>我的身体小剧场</p></div>
<input class="physio-cartoon__toggle" id="physio-cartoon-motion" type="checkbox" checked aria-describedby="physio-cartoon-caption" />
<label class="physio-cartoon__control" for="physio-cartoon-motion"><span class="physio-cartoon__switch" aria-hidden="true"></span><span>播放动画</span><span class="physio-cartoon__playing" aria-hidden="true">播放中</span><span class="physio-cartoon__paused" aria-hidden="true">已暂停</span><span class="physio-cartoon__reduced" aria-hidden="true">依系统设置静态显示</span></label>
<p class="physio-cartoon__notice" id="physio-cartoon-data-note">示意数据，非 Paul 实测</p>
<fieldset class="physio-cartoon__modes" aria-describedby="physio-cartoon-data-note"><legend>切换前后对照</legend><div><input class="physio-cartoon__mode physio-cartoon__mode--reference" id="physio-cartoon-reference" name="physio-cartoon-example" type="radio" /><label for="physio-cartoon-reference">对照示意</label><input class="physio-cartoon__mode physio-cartoon__mode--scenario" id="physio-cartoon-scenario" name="physio-cartoon-example" type="radio" checked /><label for="physio-cartoon-scenario">变化示意</label></div></fieldset>
<div class="physio-cartoon__stage">
  <div class="physio-cartoon__scene physio-cartoon__scene--hrv">
    <p class="physio-cartoon__situation"><span class="physio-cartoon__value--reference">HRV · 对照示意</span><span class="physio-cartoon__value--scenario">喝酒后的 HRV</span></p>
    <div class="physio-cartoon__portrait" role="img" aria-label="帅气成年男性卡通，拿着红酒；胸前浮标呈现 HRV 示意值"><img class="physio-cartoon__sprite" src="/images/articles/physiology-data-ai-collaboration/physiology-observation-cartoon.webp" alt="" aria-hidden="true" width="1536" height="1024" loading="lazy" decoding="async" /><div class="physio-cartoon__body-data" aria-hidden="true"><span class="physio-cartoon__body-heart"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21S2 15 2 8.5C2 3 9 2 12 7c3-5 10-4 10 1.5C22 15 12 21 12 21Z" /></svg></span><span class="physio-cartoon__body-kind">HRV · 示意值</span><span class="physio-cartoon__body-number"><span class="physio-cartoon__value--reference">68</span><span class="physio-cartoon__value--scenario">42</span><small>ms</small></span></div></div>
    <div class="physio-cartoon__reading"><span class="physio-cartoon__value--reference">HRV</span><span class="physio-cartoon__value--scenario">HRV 下降</span><span class="physio-cartoon__arrow physio-cartoon__value--scenario" aria-hidden="true">↓</span></div>
    <div class="physio-cartoon__data-reading"><span class="physio-cartoon__state-name physio-cartoon__value--reference">对照示意</span><span class="physio-cartoon__state-name physio-cartoon__value--scenario">变化示意</span><strong><span class="physio-cartoon__value--reference">68</span><span class="physio-cartoon__value--scenario">42</span><small>ms</small></strong><span class="physio-cartoon__data-note">示意值，非 Paul 实测</span></div>
    <div class="physio-cartoon__signal"><div class="physio-cartoon__signal-label">节拍间隔示意</div><svg class="physio-cartoon__intervals" viewBox="0 0 192 50" aria-hidden="true"><path class="physio-cartoon__rail" d="M10 40H182" /><g class="physio-cartoon__value--reference"><path d="M12 15v22" /><path d="M40 15v22" /><path d="M77 15v22" /><path d="M103 15v22" /><path d="M144 15v22" /><path d="M180 15v22" /></g><g class="physio-cartoon__value--scenario"><path d="M12 15v22" /><path d="M46 15v22" /><path d="M79 15v22" /><path d="M113 15v22" /><path d="M146 15v22" /><path d="M180 15v22" /></g><circle class="physio-cartoon__scan" cx="12" cy="40" r="3" /></svg><p class="physio-cartoon__signal-explainer physio-cartoon__value--scenario">间隔的差异变小</p></div>
  </div>
  <div class="physio-cartoon__scene physio-cartoon__scene--rhr">
    <p class="physio-cartoon__situation"><span class="physio-cartoon__value--reference">RHR · 对照示意</span><span class="physio-cartoon__value--scenario">没睡好・不舒服时的 RHR</span></p>
    <div class="physio-cartoon__portrait" role="img" aria-label="同一位帅气成年男性卡通披着毯子；胸前浮标呈现 RHR 示意值与跳动图案"><img class="physio-cartoon__sprite" src="/images/articles/physiology-data-ai-collaboration/physiology-observation-cartoon.webp" alt="" aria-hidden="true" width="1536" height="1024" loading="lazy" decoding="async" /><div class="physio-cartoon__body-data" aria-hidden="true"><span class="physio-cartoon__body-heart"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21S2 15 2 8.5C2 3 9 2 12 7c3-5 10-4 10 1.5C22 15 12 21 12 21Z" /></svg></span><span class="physio-cartoon__body-kind">RHR · 示意值</span><span class="physio-cartoon__body-number"><span class="physio-cartoon__value--reference">60</span><span class="physio-cartoon__value--scenario">78</span><small>bpm</small></span></div></div>
    <div class="physio-cartoon__reading"><span class="physio-cartoon__value--reference">RHR</span><span class="physio-cartoon__value--scenario">RHR 上升</span><span class="physio-cartoon__arrow physio-cartoon__value--scenario" aria-hidden="true">↑</span></div>
    <div class="physio-cartoon__data-reading"><span class="physio-cartoon__state-name physio-cartoon__value--reference">对照示意</span><span class="physio-cartoon__state-name physio-cartoon__value--scenario">变化示意</span><strong><span class="physio-cartoon__value--reference">60</span><span class="physio-cartoon__value--scenario">78</span><small>bpm</small></strong><span class="physio-cartoon__data-note">示意值，非 Paul 实测</span></div>
    <div class="physio-cartoon__signal"><div class="physio-cartoon__signal-label">心跳节奏示意</div><div class="physio-cartoon__rhythm" aria-hidden="true"><span class="physio-cartoon__rhythm-line"></span><span class="physio-cartoon__rhythm-dot"></span><span class="physio-cartoon__rhythm-ring"></span></div><p class="physio-cartoon__signal-explainer physio-cartoon__value--scenario">节奏变快</p></div>
  </div>
</div>
<figcaption id="physio-cartoon-caption">切换两种示意，可看见角色的数值浮标、节拍间隔与心跳节奏联动。所有数字与信号均为教学示意，并非 Paul 的实测数据、正常范围或诊断，也不代表每个人都有相同反应。可暂停动画，并支持减少动态设置。</figcaption>
</figure>
<!-- End personal-observation animation. -->

这样的观察也有研究可对照。一项 2026 年、由 WHOOP 资助的穿戴设备回顾性研究，观察到饮酒与夜间 HRV 降低、夜间 RHR 升高有关。这是观察性关联，不能替我某一次的变化确定原因。[原始研究](https://journals.plos.org/digitalhealth/article?id=10.1371/journal.pdig.0001284)

对我来说，这两项数据的价值，是让生活经验多一份可以回看的记录。同一定义、相近测量条件与自己的长期趋势，比追逐单日高低更值得关注。身体的感受与症状，也不能被图表上的数值取代。

## AI 在这个计划中做了什么？

我的角色是提出需求、决定公开范围与确认成果。AI 协助拆解数据流程、撰写程序、整理四语文案，并检查日期、缺值与公开字段等规则。独立复核则用来找出原先说明或实现中容易忽略的问题。

目前页面上的一句话摘要，由预先写好的文案与固定判断规则产生。每次更新，不需要把整批生理原始记录交给模型重新解读。

这让数据来源、计算规则与画面结果可以逐一对照，也比较容易重现错误。是否完成，仍须看程序检查与实际读回结果。

数据边界也需要说清楚。在这套管线中，下载的原始 API 响应与本机凭据档保存在受限本机路径，公开层只接收批准摘要。Google／Fitbit 上游仍持有来源数据；Codex 协作中的提示、程序片段与工具输出，则可能进入 AI 服务处理。本机执行不等于整个过程完全离线。

这份成果也让我看见，与 AI 协作时，需求本身需要写得足够具体。「把数据接起来」之外，还必须定义哪些值能公开、失败时停在哪里，以及用什么证据确认完成。这也是我在[智能与秩序](/zh-cn/ai/)这个主题里持续关注的人机协作问题。

## 成果，要连失败时的行为一起看

这套系统已把私人采集、日级摘要与网站呈现接起来，也把发布条件写进程序。查询不完整时，不进入新的摘要发布流程。

步行与独立生理区块在上传结果不明时，会保留固定候选，先读回验证，再决定是否重试。一般日级摘要则固定当日平均心率，发布后核对公开内容。

目前一般摘要的 current 与 history 分别写入两个 KV key，不能宣称原子更新；部分上传成功时，两者可能短暂不同版。这是需要如实交代的工程限制。

发布后的验证会读回公开端点，比对内容哈希与 `no-store` 响应标头。这是在确认读回内容与 HTTP 缓存政策，不代表全球所有地点同时取得新值。Cloudflare KV 采最终一致性，不同地点仍可能有传播延迟。[Cloudflare KV 一致性说明](https://developers.cloudflare.com/kv/concepts/how-kv-works/)

目前可以确认的成果，是数据流程与呈现方式已经建立，公开范围有明确限制，错误也有停止、验证与通知机制。这些工程成果，不能延伸成健康改善、诊断准确度或节省工时的证明。

我想让[这份生理日志](/zh-cn/health/)上的每个数字，都能回答同一个问题：它怎么来，又有哪些事情还不知道？
