单次检测结果几乎无法提供任何信息。
你当前的心率是一个没有参照基准的事实。它高吗?与什么相比?那是基于一群与你不同的人、在与你不同的条件下、在与此刻不同的时间点得出的群体平均值。这种比较不仅毫无意义,甚至有害,因为它看似是信息。
真正有意义的是你——上周二的你。状态正佳的你。在那个改变一切的事情发生之前的你。
这需要两样我们此前不具备的东西:既能跨月进行比较的可靠记录,以及一种在数据不足时能识别出这一情况的评分机制。在 8 月的最后一周,我们同时构建了这两项功能。
在传输过程中得以保留的会话
会话即为一次记录:来自设备的信号被记录下来、保存,并在稍后被读取。
难点在于,这一过程的每一步都可能导致数据保真度下降。设备可能在录制中途断开连接;工作人员可能拖延操作;或者在写入文件和读取文件的版本之间,数据模式发生了变化。 六个月后,当你打开该会话时,数据与当时所见略有不同,而你却无从得知哪一组数据才是正确的。
因此 biosignal-session 该平台基于版本化的线协议和 Arrow 数据块构建,这些数据块在本地记录并保存在生成它们的设备上。会话的有效性完全取决于设备内部的数据,无需将任何数据传输至设备之外。
我们两人独立开发了这个项目,在不同的分支上同时进行。我们没有删除其中一个分支,而是将它们合并,并记录了哪些内容被移植以及原因。两个版本都解决了对方未解决的问题。
用事实证明,而非空谈
这里的大部分工作并不是编写记录器,而是要验证记录器是否能按其宣称的功能运行。
我们生成了一套黄金测试用例,并确保每个实现都符合这些用例。我们使用 pyarrow 和 pandas 读取 Arrow 数据块,因此该保证不仅限于我们自己的代码内部,而是跨语言通用的。我们执行了位精确的往返验证,并验证了基于属性的不变量。
我们编写了一个“恶意客户端”——一个故意在所有操作上都出错的模拟用户,它发现了主机中的两个真实漏洞。我们证明了录制过程中使用的内存不会随会话时长增加,因此四小时的会话与四分钟的会话消耗的内存相同。 我们将每个可恢复的故障与会话中的每个位置进行了交叉验证,因为会话开始时的掉线与第 90 分钟时的掉线属于不同的故障。我们确认,即使底层模式发生演变,数据块仍能被正确读取。
这就是为什么最终代码行数达到了六万行。其中大约一半是固定代码和测试代码。
六项评分,以及真实的零值
在会话之上是 biosignal-analytics该平台将记录数据转化为六个数值:测量质量、激活度、恢复力、专注度、准备度以及韧性。
每个指标的评分范围为 0 到 100。每个指标均可选择不显示,若不显示,其数值应为空,而非取一个“体面的中间值”。这一点比听起来更为重要。一个没有实际数据支撑的 50 分,本质上是一个伪装成测量结果的谎言,而下游的每个应用程序都会将其渲染为真实数据。
当评分未显示时,系统会说明原因:基线数据尚不充分;采集质量过低;未检测到激活;输入数据缺失;历史数据不足,无法进行纵向分析。应用程序可以显示原因而非具体数值,这样既更诚实又更有用,因为原因通常能告诉你该如何应对。
在底层,我们将脉搏和激活算法的实现迁移到了 Rust 语言,并通过 WebAssembly 将分析器暴露给浏览器,同时使用 Python 预言机来锁定 Rust 的计算结果,以确保两者结果一致。
返回基准线
最后一块拼图,正是其他所有部分存在的意义所在。
评分是基于滚动更新的个人基准线计算得出的,该基准线由您最近的几次使用记录构建而成,并按情境分组,因此早晨的读数会与您以往早晨的读数进行比较。系统对异常值处理得十分稳健,因此一个糟糕的夜晚不会拖累基准线。在积累足够的历史数据以进行有效比较之前,评分将不予显示。
这意味着第一周按设计本应保持低调。我们认为这是正确的。第三周一个值得信赖的数据,比第一天一个不可靠的数据更有价值。
当前进展
biosignal-session 以及 biosignal-analytics 已合并至 0.1.0 主分支,目前尚未发布到 npm。源代码位于 github.com/Elata-Biosciences/elata-bio-sdk 如果您想在发布版本定稿前阅读协议或测试用例,请
如果您正在基于此进行开发,我们最希望听到的问题是:这六项评分中,您会拒绝信任哪一项?以及需要满足什么条件,您才会改变主意。
