哮喘控制测试评分表数字化:ACT/C-ACT计分规则与工程化实现
2026/9/18 15:16:23 网站建设 项目流程

简介:这是一份专为4-12岁儿童设计的哮喘控制测试评分表(ACT C-ACT),用于评估哮喘症状控制水平及当前治疗方案的成效。量表包含五个核心问题,依次覆盖日常胸闷或呼吸困难天数、急救药物使用频率、夜间憋醒次数、日常活动受限程度以及对整体控制情况的主观评价,每题0-5分,总分25分。评分结果按三档划分:25分为控制良好,20-24分为基本控制,低于20分为未有效控制,并附有明确的结果解读,医生可据此判断是否需要调整用药方案,家长也能直观掌握孩子的病情变化。这份评分表既适合儿科、呼吸科门诊随访,也适用于家庭定期自测,长期使用可构建动态监控体系,及时发现症状波动并降低急性发作风险。资源为单份PDF文档,约1.09MB,下载后即可打印使用;目前已有183人学习下载,是哮喘儿童长期健康管理的实用工具。

1. 哮喘控制测试评分表(ACT C-ACT):先分清两份表再谈打分

一份跟踪哮喘控制水平的评分表,数字化时工程师常被“ACT 和 C-ACT 是不是同一份表”绊住。ACT 面向 12 岁以上,5 题,总分 5 到 25;C-ACT 面向 4 到 11 岁,7 题,总分 0 到 27。两者都是分值越高越好,但题量、量尺、年龄分界完全不同。

做电子病历、随访 App、临床数据采集时,这份 PDF 就是需求文档。常见做法是先翻译成数据字典和计分函数,再接进随访流程。后文按“规则—建模—对接—验证”推进,重点放在没写进 PDF、但必踩的坑。

适合读的人:给呼吸科做随访系统的工程师、CDMS 里做量表采集的数据工程师、审校计分逻辑的临床 IT 支持。

2. ACT与C-ACT的计分规则:题项权重、回顾窗口与判定阈值

2.1 ACT的5题等权设计:症状频率与急救药使用各占一问

ACT 一共 5 道题,全部采用 1 到 5 分等距刻度,5 分代表“完全没有/控制很好”,1 分代表“几乎一直有/控制很差”。这里选等权加总不是统计建模的结果,而是刻意保持临床可用性:医生不需要查权重表,把 5 个数字相加即可。5 道题共享同一个回顾窗口“过去 4 周”:前两题问活动受限(工作、学习、家务)和气短频率,第三题问夜间憋醒,第四题问急救药使用频率,第五题让患者对整体控制水平做一次自评。

需要特别注意的是第四题的分值方向。急救药(如沙丁胺醇)用得越多,控制越差,所以这道题的选项顺序与前三题相反:选“完全没有”拿 5 分,选“每天 3 次以上”拿 1 分。实现时最容易犯的错不是求和,而是把第四题的选项文本复用成“从不/偶尔/经常”这类与症状题相同的文案,导致分值方向反了。我的建议是每一题都单独配选项标签数组,不共用一份文案,从源头防止方向错位。

提示:中文版 ACT 的选项措辞各题并不完全一致,第四题和第五题的刻度方向也与其他题不同,建模阶段逐题登记最稳妥。

2.2 C-ACT的两段式量尺:儿童自评0-3分与家长填报0-5分

C-ACT 为 4 到 11 岁儿童设计,7 道题分成两段。前 4 题由儿童自己回答,配合脸谱图,每题 0 到 3 分;后 3 题由家长或主要照护者回答,每题 0 到 5 分,总量程 0 到 27。它并不是把 ACT 改个年龄就完事,而是两套不同量尺拼出来的复合问卷。

儿童段的回顾窗口刻意缩短为“今天”“运动时”这些具体场景,因为 4 到 11 岁孩子很难稳定回答“过去四周有多频繁”这类抽象问题;家长段则保留四周回顾,用天数档位来量化症状暴露。工程上这两段必须分开建模:儿童题用四个大按钮配表情图,家长题用六档频次单选,不要合并成一张 7 题全用同一量尺的表单。年龄越界是另一个常见问题,C-ACT 不覆盖 4 岁以下儿童,接口层遇到 age < 4 或 age >= 12 都应直接返回“请使用对应年龄段量表”。

2.3 判读阈值与最小临床重要差异:切点写进常量而不是if分支

两份表的判读阈值经常被记混。标准判定:ACT 总分 20 到 25 为控制良好,16 到 19 为控制不佳,5 到 15 为控制很差;成年人 ACT 的最小临床重要差异(MCID)约为 3 分,即两次随访相差 3 分以上,才谈得上控制水平真的发生变化。C-ACT 以 20 分及以上为控制良好,19 分及以下为控制不佳,其 MCID 的共识度不如 ACT 高,临床上更常看连续多次的轨迹。

实现时不要把 20、16 这些数字散落在 if 分支里,建议收敛成一张只读常量表:

export const ACT_SCORING = { min: 5, max: 25, wellControlledFrom: 20, // 20-25 控制良好 notWellControlledFrom: 16, // 16-19 控制不佳,15 及以下控制很差 } as const; export const CACT_SCORING = { min: 0, max: 27, wellControlledFrom: 20, // 20-27 控制良好,19 及以下控制不佳 } as const;

常量化有两个直接好处:一是院内临床路径如果采用“二分判读”,即只区分控制与未控制,改动只落在一处;二是研究项目试用新切点时,不会因为魔法数字散落而漏改。需要提醒的是,任何切点调整都必须由临床确认,工程角色不要自行“优化”阈值。两份表的完整差异可以收敛成一张表放进设计文档:

项目ACTC-ACT
适用年龄12 岁及以上4-11 岁
题数57(儿童 4 + 家长 3)
每题量尺1-5儿童 0-3,家长 0-5
总分范围5-250-27
判读切点≥20 控制良好;16-19 控制不佳;≤15 控制很差≥20 控制良好;≤19 控制不佳

表里最容易被记错的是 C-ACT 的判定方向:它和 ACT 一样是分数越高越好,如果哪套系统的文案写成“分数越低控制越好”,那就是把量表语义整个搞反了。

3. 把PDF评分表落成可计算的数据:抽题、建模与计分函数

3.1 先用pdfplumber把PDF抽成文本,锁定题序与选项值

我一般把这份 PDF 当“标准答案”而不是宣传页处理:先抽取文本,确认题号、选项顺序和分值说明,再动手建模。抽取用 Python 的 pdfplumber 最直接,几行就能看到整份表单的文本结构:

import pdfplumber with pdfplumber.open("哮喘控制测试评分表(ACT C-ACT).pdf") as pdf: for page in pdf.pages: text = page.extract_text() if text: print(page.page_number, text)

说明:extract_text() 按视觉顺序输出纯文本,能直接看到“第1题…第5题”的题序和“由孩子回答/由家长回答”这类标注。如果这份 PDF 是扫描图而非文本层,extract_text() 会返回空串,这时先确认有没有带文本层的电子版,再决定是否走 OCR,不要一开始就往 OCR 方向投入。抽完文本我一般再核对三件事:每题的回顾窗口是“今天”还是“过去四周”、选项个数是不是 5 或 6 档、题目是否标注了作答人身份。

3.2 数据字典先行:题号、归属、量尺与控件类型一一对应

建模阶段先建数据字典,把题号、归属、量尺、控件类型对齐,而不是直接开数据库表。常见做法的字典表长这样:

item_id归属量尺选项数回顾窗口说明
act_1患者1-55过去4周活动受限频率
act_2患者1-55过去4周气短频率
act_3患者1-55过去4周夜间憋醒频率
act_4患者1-55过去4周急救药频率,方向相反
act_5患者1-55过去4周整体控制自评
cact_1-cact_4儿童0-34今天/平时表情图,自评
cact_5-cact_7家长0-56过去4周白天症状/喘息/夜醒天数

字段命名用 act_ 和 cact_ 前缀,不要用 q_1 到 q_7 这种顺序编号,否则后续分析时两份表会混进同一个字段集合。控件类型上,儿童题建议用四个大按钮配表情图渲染,家长题用单选列表或下拉即可;两份表都禁止提交空值,这一点要落到前端的提交拦截和后端的校验两层。

3.3 计分函数保持单一入口:范围校验优先于求和

计分逻辑必须只有一个入口,不要让前端算完把总分传过来。常见做法是在共享模块放纯函数,传答案对象,返回总分和判定级别,后端再做一次幂等校验:

type Answers = Record<string, number>; export function scoreACT(a: Answers) { const vals = ['act_1','act_2','act_3','act_4','act_5'] .map(id => a[id]); if (vals.some(v => !Number.isInteger(v) || v < 1 || v > 5)) { throw new Error('ACT 答案越界,分值必须在 1-5'); } const total = vals.reduce((s, v) => s + v, 0); const level = total >= ACT_SCORING.wellControlledFrom ? 'well' : total >= ACT_SCORING.notWellControlledFrom ? 'notWell' : 'veryPoor'; return { total, level }; }

参数说明:a 是形如 { act_1: 5, act_2: 4 } 的答案映射;函数先做类型和范围校验再求和,非法输入直接抛异常,而不是悄悄转成默认值。缺失值策略上,量表类工具一般直接判“本次作答无效”而不是均值插补:临床判定依赖总分,缺一项总分就失去判读意义。如果研究项目确实要处理缺失,也必须单独记录 missing_reason,绝不能返回一个看似完整的总分。

C-ACT 的计分函数把两段分开校验再相加,避免一段合法掩盖另一段越界:

export function scoreCACT(child: Answers, parent: Answers) { const childVals = ['cact_1','cact_2','cact_3','cact_4'] .map(id => child[id]); const parentVals = ['cact_5','cact_6','cact_7'] .map(id => parent[id]); if (childVals.some(v => !Number.isInteger(v) || v < 0 || v > 3) || parentVals.some(v => !Number.isInteger(v) || v < 0 || v > 5)) { throw new Error('C-ACT 答案越界,儿童题 0-3,家长题 0-5'); } const total = [...childVals, ...parentVals] .reduce((s, v) => s + v, 0); return { total, level: total >= CACT_SCORING.wellControlledFrom ? 'well' : 'notWell', }; }

说明:儿童段 0 到 3、家长段 0 到 5 是两个独立校验域,分开做才能准确定位是哪一段出错。年龄分支放在调用方处理:小于 12 岁走 C-ACT,12 岁及以上走 ACT,4 岁以下直接拒绝,这份表不能覆盖学龄前低龄儿童。

4. 让评分表跑进随访流程:接口契约、SQL趋势与数据质量

4.1 接口契约带上版本号与填表人身份,避免跨版混算

评分表落库时,要区分问卷版本和作答记录:PDF 表单会发生修订,同一个 item_id 在不同版本里可能对应不同选项文本,不记版本就会把新旧两版的分数加在一起。一个比较完整的作答请求长这样:

{ "form_version": "act-cact-cn-2024", "patient_id": "P10086", "age": 9, "filled_by": "caregiver", "answered_at": "2025-06-01T09:30:00+08:00", "answers": { "cact_1": 3, "cact_2": 2, "cact_3": 3, "cact_4": 3, "cact_5": 4, "cact_6": 3, "cact_7": 4 } }

字段说明:age 不能省,服务端要用它选择量表并做年龄校验;filled_by 记录是孩子、家长还是医护代填,因为 C-ACT 后 3 题必须由家长作答,医护代填会引入偏差;answered_at 用带时区的 ISO8601,避免随访时间跨时区被算错。服务端拿到请求后先做一次年龄分支判断:age 小于 12 强制要求走 C-ACT 字段集,age 不在 4 到 99 直接拒绝。

4.2 用窗口函数SQL跟踪连续随访,找出评分恶化记录

随访产品最常见的需求是判断患者到底在变好还是变坏。多次作答不是等间隔的,我一般不在应用层逐条比较,而是在 SQL 里用 LAG 把上一次总分并排取出,一次查出变化量:

WITH scored AS ( SELECT patient_id, answered_at, total_score, LAG(total_score) OVER ( PARTITION BY patient_id ORDER BY answered_at ) AS prev_score FROM asthma_scores WHERE form_version = 'act-cact-cn-2024' ) SELECT patient_id, answered_at, total_score, prev_score, total_score - prev_score AS delta FROM scored WHERE total_score - prev_score <= -3 AND answered_at >= NOW() - INTERVAL '6 months' ORDER BY answered_at DESC;

说明:LAG 取同患者上一次的总分,delta 为负且绝对值大于等于 3 时,按成人 ACT 的 MCID 约定视为有临床意义的恶化,这类记录应主动推送而不是等医生自己翻历史;首次作答的 prev_score 为 NULL,会自然被过滤,不会误判为恶化。form_version 条件保留的原因在于,换版后新旧分数不一定可比,过滤版本能避免把不同版本的总分放进同一个对比窗口;等未来真换版时,这条条件会自动切断新旧数据的可比性。

4.3 三条数据质量红线:重复提交、年龄跳变与版本漂移

评分表类功能的数据质量问题,通常集中在重复提交、年龄跳变和版本漂移三处,处理方式可以直接落地成规则:

风险表现处理方式
重复提交同一天同一患者多条总分记录用 patient_id + answered_at + form_version 做业务唯一约束,保留最新一条并写 dedup 标记
年龄跳变同一患者上次走 C-ACT,这次走 ACT不自动改判,记录 age_at_submit,由临床决定切换时机
版本漂移换版后总分整体抬高或降低换版前做对拍测试,新旧分数差在耐受范围内再开放入口

第一条最容易被忽视:患者或家长在 App 里连点两次提交,就会生成两条看似正常的记录。去重不能只靠数据库自增主键,因为两次请求的时间戳会有毫秒级差异,必须靠业务键做唯一约束。年龄跳变的处理原则是“记录而不猜测”:系统可以提示“该患者已满 12 岁,下次随访建议改用 ACT”,但不要自动把历史 C-ACT 分数折算成 ACT,两份表结构不同,折算没有临床依据。版本漂移则必须在换版前完成对拍,而不是上线后靠用户反馈被动发现。

5. 上线前的验证技巧:用对拍样本和边界用例锁死计分

评分表功能最怕的不是逻辑复杂,而是“看起来对了”。我建议在进 CI 之前准备两类样本:临床对拍样本直接照抄典型病历的作答与总分;边界用例把所有选项压到最小和最大值,验证总分落在量程端点。临床对拍至少要覆盖三档:全选最优,ACT 应为 25 分,C-ACT 应为 27 分;全选最差,ACT 为 5 分,C-ACT 为 0 分;再补一组中间值,比如 ACT 前三题 4 分、后两题 3 分,总分 18 分。

用代码写死期望值,让每次改动都跑一遍:

const cases = [ { answers: { act_1:5, act_2:5, act_3:5, act_4:5, act_5:5 }, expect: 25 }, { answers: { act_1:1, act_2:1, act_3:1, act_4:1, act_5:1 }, expect: 5 }, { answers: { act_1:4, act_2:4, act_3:4, act_4:3, act_5:3 }, expect: 18 }, ]; cases.forEach((c, i) => { const r = scoreACT(c.answers); if (r.total !== c.expect) { throw new Error(`ACT 用例 ${i + 1} 失败:期望 ${c.expect},实际 ${r.total}`); } });

这组用例进 CI 后,任何一次表单改版、选项重排、或者有人把量尺从 1-5 改成 0-4,都会在 PR 阶段直接报错。C-ACT 还需要单独补一组跨段用例:儿童段合法但家长段越界时,scoreCACT 必须抛异常,而不是返回 27 以内的某个数字;反向同理。除了固定用例,还可以加一条性质测试:随机生成合法答案,断言总分永远是整数且落在量程内,这能兜住手工样本覆盖不到的排列组合。最后把用例数量和覆盖的阈值档位写进 README,临床同事复核时直接看那一节就能确认计分没有被悄悄改过。

本文还有配套的精品资源,点击获取

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

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

立即咨询