【喵汪星球HarmonyOS 6.0】技术实战 09:健康症状库、关键词匹配与应急就医卡

前言

健康模块是养宠 App 里最敏感的一部分。用户发现宠物异常时,往往已经很焦虑。App 能做的是帮用户整理信息、识别明显风险、准备就医材料,而不是给出“诊断结论”。

喵汪星球的健康模块选择了一个克制的方案:本地症状库 + 关键词匹配 + 风险提示 + 就医信息卡。它不联网、不做诊断、不替代兽医,只帮助用户把信息组织得更清楚。

这篇会讲:

  • 如何设计症状内容模型
  • 如何做本地关键词匹配
  • 如何给未匹配输入做高风险兜底
  • 如何把宠物档案和当天记录聚合成就医卡
  • 健康类功能在文案和产品边界上的取舍

先说边界:健康模块不是问诊

项目里多处出现类似提示:

该功能仅供娱乐参考,症状比对不能替代兽医诊断或治疗建议

这个提示不是走形式,而是功能设计原则。

健康模块只做三件事:

第一,帮用户把“我观察到了什么”映射到可能相关的症状条目。

第二,提醒用户哪些情况需要尽快就医。

第三,把宠物档案、近期记录和准备清单整理成就医信息卡。

它不做处方、不推荐药、不说“就是某个病”。这类边界写清楚,产品才安全。

症状模型:内容必须结构化

项目中的症状文章模型:

interface SymptomArticle { title: string risk: string summary: string doctor: string observe: string checklist: string keywords: string[] }

字段分别对应页面信息块:

  • title:症状或疾病条目名称。
  • risk:风险等级,例如高风险、中风险、低中风险。
  • summary:常见表现。
  • doctor:什么情况下尽快就医。
  • observe:在家先观察哪些指标。
  • checklist:去医院前准备哪些材料。
  • keywords:用于本地关键词匹配。

示例条目:

{ title: '猫传腹', risk: '高风险', summary: '可能表现为持续发热、精神差、消瘦、腹围变大、胸腹水、呼吸费力或眼部神经症状。', doctor: '反复发热、腹部明显变大、呼吸费力、黄疸、眼睛异常或神经症状,建议尽快检查', observe: '体温曲线、体重变化、腹围、食欲、呼吸、眼睛和行动协调性', checklist: '体温记录、腹部照片、体重变化、血检和影像资料、发病时间线', keywords: ['猫传腹', '传腹', 'fip', '腹水', '胸水', '腹围大', '肚子大'] }

为什么不写成长文本?

因为用户在异常场景下没有耐心读大段内容。结构化字段能让页面直接分区展示,也方便后续导出就医卡。

健康页交互流程

页面的主流程如下:

```mermaid flowchart TD A["用户输入症状描述"] --> B["normalizeSymptomText 归一化"] B --> C["遍历症状库 keywords"] C --> D{"是否命中关键词"} D -->|是| E["选择分数最高的 SymptomArticle"] D -->|否| F["展示未匹配兜底建议"] E --> G["展示风险等级、表现、何时就医、观察指标、准备清单"] G --> H{"是否已建档"} H -->|是| I["生成就医信息卡"] H -->|否| J["引导添加宠物档案"] ```

这条链路的核心不是算法多复杂,而是每一步都要给用户明确反馈。

输入分析:空输入、未匹配、匹配成功都要处理

用户点击“分析”后:

private analyzeSymptomInput(): void { const value = this.symptomInput.trim() if (value.length === 0) { this.selectedSymptomIndex = -1 this.customHealthAdvice = '请输入观察到的症状,例如呕吐、腹泻、精神萎靡。' this.emergencyCardVisible = false this.saveState() return } const matchedIndex = this.matchSymptomIndex(value) if (matchedIndex < 0) { this.selectedSymptomIndex = -1 this.customHealthAdvice = '暂时识别不出来。请补充体温、食欲、饮水、排便排尿、精神状态和症状持续时间;如果已经精神差、不吃不喝、呼吸异常、抽搐、便血、尿不出或明显疼痛,请尽快就医。' } else { this.selectedSymptomIndex = matchedIndex const symptom = this.symptoms[matchedIndex] this.customHealthAdvice = '可能相关症状:' + symptom.title + '。匹配到:' + this.matchedKeywordText(value, symptom) + '。这只是本地症状比对,不能替代兽医诊断;请继续观察并按“何时就医”处理。' } this.emergencyCardVisible = false this.saveState() }

这个方法的质量在于兜底。

空输入时,不是报错,而是教用户怎么描述。

未匹配时,不说“没事”,而是提示补充体温、食欲、饮水、排便排尿、精神状态和持续时间,并列出高风险症状。

匹配成功时,用“可能相关症状”表达,并明确不能替代兽医诊断。

健康类功能最怕两个极端:一个是吓用户,一个是安慰过头。这里的处理相对稳。

关键词匹配:MVP 阶段要可解释

匹配算法很简单:

private matchSymptomIndex(value: string): number { const normalized = this.normalizeSymptomText(value) let bestIndex = -1 let bestScore = 0 for (let index = 0; index < this.symptoms.length; index++) { let score = 0 const keywords = this.symptoms[index].keywords for (let keyIndex = 0; keyIndex < keywords.length; keyIndex++) { if (normalized.indexOf(this.normalizeSymptomText(keywords[keyIndex])) >= 0) { score++ } } if (score > bestScore) { bestScore = score bestIndex = index } } return bestScore > 0 ? bestIndex : -1 }

归一化:

private normalizeSymptomText(value: string): string { return value.toLowerCase().replace(/\s/g, '') }

这不是智能诊断,但它有三个优点:

第一,完全本地运行,符合离线优先。

第二,可解释。用户能看到匹配到了哪些关键词。

第三,内容库可控。新增一个症状条目,只需要补内容和关键词,不会引入黑盒判断。

MVP 阶段,我更推荐这种可解释的规则系统,而不是一上来就接一个无法解释的模型。

展示命中的关键词

为了避免用户觉得结果是“凭空猜的”,项目会拼出命中的关键词:

private matchedKeywordText(value: string, symptom: SymptomArticle): string { const normalized = this.normalizeSymptomText(value) let text = '' let count = 0 for (let index = 0; index < symptom.keywords.length; index++) { const keyword = symptom.keywords[index] if (normalized.indexOf(this.normalizeSymptomText(keyword)) >= 0) { if (count > 0) { text += '、' } text += keyword count++ } } return text }

比如用户输入“肚子大腹水”,页面提示“匹配到:腹水、肚子大”。这会让本地匹配结果更可信,也方便用户检查自己输入是否准确。

页面结构:风险等级必须醒目

健康页核心卡片包含:

Text(this.currentSymptom().title) Text('根据输入关键词本地比对') Text(this.currentSymptom().risk) Text(this.currentSymptom().summary) this.InfoRow('何时就医', this.currentSymptom().doctor) this.InfoRow('观察指标', this.currentSymptom().observe) this.InfoRow('准备清单', this.currentSymptom().checklist)

风险等级使用不同颜色:

Text(this.currentSymptom().risk) .fontColor( this.currentSymptom().risk === '高风险' || this.currentSymptom().risk === '需就医' ? Theme.emergency : Theme.warning ) .backgroundColor( this.currentSymptom().risk === '高风险' || this.currentSymptom().risk === '需就医' ? Theme.emergencySoft : Theme.warningSoft )

健康功能里,“高风险”不能藏在段落里。它应该是用户扫一眼就能看到的状态。

就医信息卡:把档案和记录变成现场沟通材料

症状详情页可以生成就医卡:

if (this.emergencyCardVisible && this.hasPetProfile) { Column({ space: Theme.spaceS }) { Text('就医信息卡') Text(this.emergencyPetSummary()) Text('症状:' + this.currentSymptom().title + ' · ' + this.currentSymptom().risk) Text(this.emergencyRecentRecordText()) } }

宠物摘要:

private emergencyPetSummary(): string { if (!this.hasPetProfile) { return '尚未添加宠物档案' } return this.selectedPetName + ' · ' + this.petMeta() }

最近记录:

private emergencyRecentRecordText(): string { const records = this.recordsForSelectedDay() if (records.length === 0) { return '今天还没有补充记录,建议记录食欲、饮水、排便排尿和精神状态。' } return records[0].title + ':' + records[0].detail }

应急携带:

private emergencyKitText(): string { return '带上近期照片/视频、叫声录音、疫苗驱虫记录、发病时间线和正在使用的药品。' }

这个功能的价值在于“帮用户和医生沟通”。很多人一着急就忘记描述时间线、饮食饮水、排便排尿、疫苗驱虫记录。就医卡能把 App 里已有的数据组织起来。

为什么健康模块要绑定宠物档案

如果没有宠物档案,症状库也能查,但就医卡价值会低很多。

有档案后,就医卡能带上:

  • 名字
  • 品种或类型
  • 年龄阶段
  • 体重
  • 性别和绝育状态
  • 疫苗信息
  • 驱虫信息
  • 过敏史
  • 慢性病史

这些信息看似基础,但在实际就医沟通中非常重要。宠物档案不是孤立页面,它在健康场景里会变成应急信息的一部分。

未匹配输入的高风险兜底

我认为健康模块最值得保留的是这段未匹配文案:

如果已经精神差、不吃不喝、呼吸异常、抽搐、便血、尿不出或明显疼痛,请尽快就医。

为什么?

因为关键词库再怎么扩,也不可能覆盖用户所有描述。用户可能输入“它不对劲”“一直趴着”“叫得很惨”。匹配不到时,App 不能给用户安全错觉。

所以未匹配并不等于低风险。未匹配只是说明本地知识库暂时识别不了,仍然要提示用户观察高风险信号。

后续可以怎么升级

当前症状库写在 ArkTS 数组里。后续可以迁移:

resources/rawfile/symptoms.json -> KnowledgeRepository -> SymptomService -> HealthPage

还可以增加:

  • 同义词词典,例如“拉稀”和“腹泻”。
  • 高风险关键词强规则,例如“尿不出”“抽搐”“呼吸困难”直接置顶。
  • 多症状组合匹配,例如“呕吐 + 腹泻 + 幼猫 + 未免疫”提高风险提示。
  • 最近 7 天记录自动摘要。
  • 就医卡导出图片或 PDF。
  • 症状内容版本号,方便后续更新。

但无论怎么升级,都要保留“不替代兽医诊断”的边界。

这部分最容易踩的坑

第一,健康内容不能写成诊断结论。尽量使用“可能相关”“建议尽快检查”“不能替代诊断”。

第二,未匹配时不能说“没问题”。要提示补充信息和高风险情况。

第三,风险等级必须醒目。用户异常场景下不会细读长文。

第四,症状库要结构化。否则后续就医卡、搜索、导出都很难做。

第五,不要让就医卡只展示静态清单。它要聚合宠物档案和近期记录,才有真实价值。

本篇小结

健康模块的核心不是算法炫技,而是稳:

  • 用结构化症状库承载内容。
  • 用本地关键词匹配保证离线可用。
  • 用命中关键词增强可解释性。
  • 用高风险兜底处理未匹配输入。
  • 用就医信息卡聚合档案和记录。
  • 用克制文案守住产品边界。

下一篇是系列收尾:主题系统、暗色模式、多设备适配和发布前体验打磨。一个 App 能不能像正式产品,很多时候就看最后这些细节。