当前位置: 首页 > article >正文

如何把PV数据录入从“人肉战场“变成了全自动流水线

去年Q2,我们的药物警戒(Pharmacovigilance,PV)团队在做年度复盘,有人做了一张饼图。整个部门的工时分布:62%在录入数据,18%在核查录入的数据,只有20%在做真正的信号检测和风险分析。这张图在会议室里沉默了很久。我们公司同时跑着三十几个临床项目光是SUSAR(Suspected Unexpected Serious Adverse Reaction)的上报每周就有几十条。每条从PDF/传真/邮件里扒出来再填进安全数据库——MedDRA编码要对时间线要对项目编号要对E2B格式要合规——一个有经验的专员吗认认真真做也要2-3小时。出错了重做。监管要求更新了重做。我坐在那个会议室里看着这张饼图脑子里只有一个念头:这件事不应该由人来做。问题到底在哪里在开始写代码之前我们做了一件事:画了一张现实流程图。不是PPT里那种漂亮的示意图是真实观察PV专员工作一周之后一步一步还原出来的。大概是这样:这个流程每一步都依赖人的眼睛和判断但90%的步骤其实是规则明确的信息提取和格式转换。这就是自动化的切入点。技术选型:我们为什么没有选最新的内部开始讨论方案时有人提议直接上大模型让LLM自己解析报告、自己填字段。我拒绝了。原因很实际:PV数据是监管数据,有严格的audit trail要求。每一个字段的来源必须可追溯,每一步操作必须有日志。大模型的黑盒特性在这个场景下是监管风险。另外,PV系统的表单操作非常具体——点哪个按钮、怎么触发MedDRA查询——这些系统级交互LLM根本不理解。最终的技术栈:层次技术负责什么文档解析层UiPath Document Understanding 自定义ML模型从PDF/邮件中提取结构化字段规则引擎层自研Python规则库严重性判断、预期性匹配、项目级个性化规则RPA执行层UiPath Studio操控Argus界面完成录入质控层Python 正则字段完整性校验、逻辑一致性检查日志层ELK Stack全流程audit trail,满足21 CFR Part 11不是最酷的方案但是合规的、可落地的、三个月能上线的。核心代码:文档解析部分(脱敏版)这是整个系统最难的部分。AE报告格式千奇百怪:有些是结构化表单,有些是医生用Word写的叙述,有些是传真扫描件OCR之后满是噪声。我们用UiPath Document Understanding做了一个两阶段提取:第一阶段:分类器判断报告类型# ae_classifier.py (脱敏版)# 根据文档特征判断报告类型,走不同的提取模板importrefromenumimportEnumclassReportType(Enum):STRUCTURED_FORMstructured# 标准化表单(如MedWatch)NARRATIVE_TEXTnarrative# 叙述性文本LITERATUREliterature# 文献来源UNKNOWNunknownFORM_SIGNALS[rMedWatch,rCIOMS,rPatient Initials,rDate of Birth,rSuspect Drug,rIndication]NARRATIVE_SIGNALS[rDear.*Safety,radverse event,rside effect,rexperienced,rreported]defclassify_report(text:str)-ReportType: 基于关键词密度判断报告类型 生产环境中建议替换为训练好的分类模型 text_lowertext.lower()form_scoresum(1forpatterninFORM_SIGNALSifre.search(pattern,text,re.IGNORECASE))narrative_scoresum(1forpatterninNARRATIVE_SIGNALSifre.search(pattern,text,re.IGNORECASE))ifform_score3:returnReportType.STRUCTURED_FORMelifnarrative_score2:returnReportType.NARRATIVE_TEXTelifet alintext_lowerorjournalintext_lower:returnReportType.LITERATUREelse:returnReportType.UNKNOWN第二阶段:字段提取(针对叙述性文本)# ae_extractor.py (脱敏版)# 从叙述性AE报告中提取关键字段importrefromdataclassesimportdataclass,fieldfromtypingimportOptional,ListfromdatetimeimportdatetimedataclassclassAECase:# 患者信息(均为匿名化处理)patient_initials:Optional[str]Noneage:Optional[int]Noneage_unit:stryearssex:Optional[str]Noneweight_kg:Optional[float]None# 药品信息suspect_drugs:List[str]field(default_factorylist)dose:Optional[str]Noneroute:Optional[str]Noneindication:Optional[str]Nonestart_date:Optional[str]None# 不良反应ae_terms:List[str]field(default_factorylist)ae_onset_date:Optional[str]Noneae_outcome:Optional[str]Noneserious:Optional[bool]None# 上报信息reporter_type:Optional[str]Nonecountry:Optional[str]None# 提取置信度(用于QC标记)confidence_scores:dictfield(default_factorydict)classNarrativeExtractor: 从自由文本叙述中提取AE字段 注意:本版本为规则引擎,生产中建议结合NLP模型提升召回率 # 年龄匹配:支持多种表达方式AGE_PATTERNS[r(\d{1,3})[-\s]?year[-\s]?old,raged?\s(\d{1,3}),r(\d{1,3})\s*yo\b,rage[:\s](\d{1,3}),]# 性别匹配SEX_MAP{r\b(male|man|boy|him|his)\b:M,r\b(female|woman|girl|her|she)\b:F,}# 转归匹配OUTCOME_PATTERNS{recovered:rrecover|resolv|better|improved,recovering:rrecovering|improving,not recovered:rnot recover|ongoing|persist|continu,fatal:rdied|death|fatal|deceased|passed away,sequelae:rsequela|permanent|disability,unknown:runknown|unclear,}defextract(self,text:str)-AECase:caseAECase()text_normalizedself._normalize_text(text)case.age,case.age_unitself._extract_age(text_normalized)case.sexself._extract_sex(text_normalized)case.suspect_drugsself._extract_drugs(text_normalized)case.ae_termsself._extract_ae_terms(text_normalized)case.ae_outcomeself._extract_outcome(text_normalized)case.seriousself._assess_seriousness(text_normalized)case.countryself._extract_country(text_normalized)# 计算整体置信度case.confidence_scoresself._calc_confidence(case)returncasedef_normalize_text(self,text:str)-str:统一换行、去除多余空白textre.sub(r\r\n|\r,\n,text)textre.sub(r[ \t], ,text)returntext.strip()def_extract_age(self,text:str):forpatterninself.AGE_PATTERNS:mre.search(pattern,text,re.IGNORECASE)ifm:ageint(m.group(1))if0age120:# 合理性校验unitmonthsifage2andmonthintext.lower()elseyearsreturnage,unitreturnNone,yearsdef_extract_sex(self,text:str)-Optional[str]:forpattern,valueinself.SEX_MAP.items():ifre.search(pattern,text,re.IGNORECASE):returnvaluereturnNonedef_extract_drugs(self,text:str)-List[str]: 真实环境建议对接公司药品主数据库做字典匹配 此处为简化版本 drugs[]# 提取大写药品名(通常为INN或商品名)candidatesre.findall(r\b[A-Z][A-Za-z]{4,}\b,text)STOPWORDS{Patient,Subject,Report,Event,Study,Serious,Treatment,Hospital,Doctor}forwordincandidates:ifwordnotinSTOPWORDSandnotword.isupper():drugs.append(word)returnlist(dict.fromkeys(drugs))[:5]# 去重,最多返回5个def_extract_ae_terms(self,text:str)-List[str]: 生产环境应对接MedDRA字典API做精准匹配 此处为占位示例 ae_keywordsre.findall(r\b(nausea|vomiting|headache|rash|fever|fatigue|rdizziness|dyspnea|pain|swelling|pruritus|erythema)\b,text,re.IGNORECASE)returnlist(set(t.lower()fortinae_keywords))def_assess_seriousness(self,text:str)-bool: ICH E2A定义的严重性标准:死亡/危及生命/住院/致残/先天异常/重要医学事件 serious_indicators[rhospitali[sz],rlife.threatening,rdied|death|fatal,rdisabl,rcongenital,rimportant medical,rICU|intensive care,remergency,]text_lowertext.lower()returnany(re.search(p,text_lower)forpinserious_indicators)def_extract_outcome(self,text:str)-Optional[str]:text_lowertext.lower()foroutcome,patterninself.OUTCOME_PATTERNS.items():ifre.search(pattern,text_lower):returnoutcomereturnunknowndef_extract_country(self,text:str)-Optional[str]:# 真实版本维护一个国家名称词典,此处简化common_countries[United States,USA,Germany,France,Japan,China,United Kingdom,UK,Canada,Australia,Italy,Spain]forcountryincommon_countries:ifcountryintext:returncountryreturnNonedef_calc_confidence(self,case:AECase)-dict: 字段级置信度评估,用于QC决策: - HIGH: 自动提交 - MEDIUM: 人工复核后提交 - LOW: 退回人工处理 scores{}scores[age]HIGHifcase.ageelseLOWscores[sex]HIGHifcase.sexelseLOWscores[drugs]HIGHifcase.suspect_drugselseLOWscores[ae_terms]HIGHifcase.ae_termselseLOWscores[seriousness]HIGH# 规则引擎输出视为高置信high_countsum(1forvinscores.values()ifvHIGH)scores[overall](HIGHifhigh_count4elseMEDIUMifhigh_count2elseLOW)returnscores第三阶段:RPA写入(UiPath侧的配置逻辑,用Python伪代码说明)# argus_writer.py (脱敏版,对应UiPath Invoke Code活动)# 这部分在UiPath中实现,这里用Python说明逻辑classArgusWriter: 将提取的AECase写入Argus Safety数据库界面 实际通过UiPath RPA操控浏览器/客户端完成 def__init__(self,project_config:dict):# project_config包含该项目的特定规则:# 比如哪些AE需要走快速通道,report timeline要求等self.configproject_configdefwrite_case(self,case:AECase,session)-str: 返回生成的Case ID session为UiPath Browser Session对象 # 1. 只有高置信度的case才允许全自动提交ifcase.confidence_scores.get(overall)LOW:raiseLowConfidenceError(fCase置信度不足,转人工处理:{case.confidence_scores})case_idself._create_new_case(session)self._fill_patient_tab(session,case)self._fill_drug_tab(session,case)self._fill_event_tab(session,case)self._fill_narrative(session,case)# 项目特定逻辑:是否需要勾选额外的监管flagifself.config.get(eu_reporting_required):self._set_eu_flag(session)# MEDIUM置信度:保存草稿,推送QC队列;不直接提交ifcase.confidence_scores.get(overall)MEDIUM:self._save_draft(session)self._push_to_qc_queue(case_id)else:self._submit_for_review(session)self._write_audit_log(case_id,case)returncase_iddef_fill_patient_tab(self,session,case:AECase):点击Patient标签页,填写患者信息# UiPath: Click(Patient Tab)# UiPath: TypeInto(Age Field, str(case.age))# ... 以此类推passdef_write_audit_log(self,case_id:str,case:AECase):写入操作日志,满足21 CFR Part 11要求log_entry{timestamp:datetime.utcnow().isoformat(),case_id:case_id,action:AUTO_ENTRY,operator:RPA_BOT_v2,confidence:case.confidence_scores,fields_auto_filled:self._count_filled_fields(case),fields_manual_required:[],}# 写入ELK或合规日志系统self._send_to_audit_system(log_entry)上线之后发生了什么第一个月:机器人上线只敢处理MEDIUM/HIGH置信度的structured form。大概覆盖了35%的案例量。剩下65%还是人工。团队不太信任机器人每个case都要double check。这很正常。我们不急。第二个月:QC数据出来了。自动录入的case字段错误率2.1%人工录入历史基线是4.7%。机器人错得比人少。团队开始放心。第三个月:扩展到叙述性文本,覆盖率升到78%。剩下22%是质量极差的扫描件或者多语言混合文档,继续人工处理。到现在的数据:指标自动化前自动化后单案例平均处理时长2.1小时28分钟字段错误率4.7%2.1%专员加班频率每周3-4次基本消除人工覆盖率(需人工介入)100%22%那张曾经让人沉默的饼图现在长这样数据录入降到了21%信号检测和风险分析升到了58%。几个踩过的坑,送给同行坑一:MedDRA版本对齐问题MedDRA每年更新两次。自动化系统用的词典版本必须和数据库里的版本严格一致否则PT编码会对不上。我们上线第三天就发现了这个问题紧急加了版本锁定逻辑。坑二:过度追求自动化率项目初期KPI设的是自动化率90%“结果为了凑数字把一些本不该自动提交的低质量报告也推进去了被QC打回来了一批。后来把KPI改成自动提交后零退回率”反而质量和速度都上去了。坑三:变更控制(Change Control)没做好RPA机器人改了一个字段映射逻辑没有走正式的变更控制流程。被内审发现了。在制药行业系统变更必须有完整的文档和审批这一点不能偷懒。最后有人问我:以后PV专员会被取代吗不会。药物警戒的核心价值从来不是录入数据而是在海量的信号噪声里识别出真正的风险信号是在监管政策变动的时候做出正确的判断是在一个真实的病人可能因此受害的时候负起那份责任。这些事机器人做不了。我们做自动化是为了让那些聪明的人从重复劳动里解放出来去做只有人才能做的事。代码已脱敏处理生产环境中请根据实际系统进行适配。如有问题欢迎评论区交流。

相关文章:

如何把PV数据录入从“人肉战场“变成了全自动流水线

去年Q2,我们的药物警戒(Pharmacovigilance,PV)团队在做年度复盘,有人做了一张饼图。 整个部门的工时分布:62%在录入数据,18%在核查录入的数据,只有20%在做真正的信号检测和风险分析。 这张图在会议室里沉默了很久。 我们公司同时跑着三十几个临床项目,光是SUSAR(Sus…...

LD2450毫米波雷达Arduino库:协议抽象与嵌入式鲁棒通信

1. 项目概述LD2450_Radar 是一款专为 HiLink LD2450 24GHz 毫米波人体存在雷达模块设计的轻量级 Arduino 兼容库。该库并非简单封装串口收发,而是面向嵌入式工程师实际开发场景构建的协议抽象层 状态管理器 数据流处理器三位一体解决方案。其核心价值在于&#xf…...

sguard_limit终极指南:轻松限制腾讯游戏ACE-Guard系统资源占用,提升游戏性能

sguard_limit终极指南:轻松限制腾讯游戏ACE-Guard系统资源占用,提升游戏性能 【免费下载链接】sguard_limit 限制ACE-Guard Client EXE占用系统资源,支持各种腾讯游戏 项目地址: https://gitcode.com/gh_mirrors/sg/sguard_limit 你是…...

V7K 数据收集

V7000的故障数据收集1. 通过GUI图形管理界面Download Support Package是基本的数据收集方式 选择Troubleshooting菜单下面的“Support” 注意: 微码6.3下,应选择Settings菜单下面的“Support”。点击“Download Support Package” 有4个选项可…...

如何高效查询数据库中一对多关联的多项选择字段

本文讲解如何通过规范化数据库设计与标准 sql 关联查询,准确获取农民注册信息及其多个专业领域(多对一/一对多关系),解决因表结构不合理导致的查询失败问题。 本文讲解如何通过规范化数据库设计与标准 sql 关联查询,准…...

2026螺杆空压机性价比品牌榜:7.5-500KW选型指南

在工业制造领域,压缩空气被誉为“第四大能源”,而螺杆空压机作为其核心动力源,其选型直接关系到企业的生产成本、运行效率和长期发展。面对市场上琳琅满目的品牌和型号,如何从7.5KW到500KW的广阔功率区间内,挑选出兼具…...

FISCO BCOS节点扩容实战指南:从原理到操作全解析

引言:为什么需要节点扩容? 在区块链网络运营过程中,“如何在不中断服务的情况下扩展网络处理能力?”“能否实现节点的平滑扩容与缩容?”“怎样确保新节点快速同步数据?”——这些问题困扰着许多区块链运维人员。FISCO BCOS作为金融级联盟链平台,提供了完善的节点扩容机…...

OpenClaw技能市场挖掘:百川2-13B量化模型十佳实用技能推荐

OpenClaw技能市场挖掘:百川2-13B量化模型十佳实用技能推荐 1. 为什么需要关注量化模型适配技能? 去年冬天,当我第一次在本地部署百川2-13B量化模型时,发现一个有趣的现象:同样的技能在不同模型上的表现差异巨大。有些…...

qt+vlc实现解码h264/h265裸码流播放

一 概述本文章实现了对h264/h265裸码流的解码播放功能,主要是一个基于VLC实现的H.264/H.265裸流解码播放类。主要功能包括:1)通过OpenStream接口打开流并指定显示窗口;2)使用InputStream接口输入裸流数据;3)通过CloseStream关闭流。核心实现采…...

git restore --source 提交id 和 git reset --hard 提交id 有什么区别

这两个命令长得像、都能“回到过去”,但核心逻辑、操作范围、安全性天差地别,一句话先点破: ✅ git restore --source 提交id:文件级操作,只恢复文件内容,不删提交历史、不改动分支,安全无风险 …...

macOS一键部署OpenClaw:Phi-3-vision-128k-instruct多模态体验教程

macOS一键部署OpenClaw:Phi-3-vision-128k-instruct多模态体验教程 1. 为什么选择OpenClawPhi-3组合 上周我在整理团队项目文档时,突然意识到一个痛点:每次收到同事发来的截图和文字混合内容,都需要手动复制粘贴到笔记软件里分类…...

嵌入式c语言——关键字4

typedef 给数据类型起个别名,使得对程序的可读性更高吗,同时和#define不一样typedeff是关键字,对已经存在的数据类型取别名。 在编译阶段处理,会进行类型检查,只能在定义的作用域内使用。 define是预处理指令&#xff…...

xpath爬取网页图片

# 1. 导入需要的工具包 import requests # 用来发送网络请求,爬取网页 from lxml import etree # 用来解析网页,提取图片 import os # 用来创建文件夹,保存图片 import time # 用来延时,防止爬太快被封# 2. 设置图片保存的位置…...

LeetCode 删除无效的括号:python 题解匆

这个代码的核心功能是:基于输入词的长度动态选择反义词示例,并调用大模型生成反义词,体现了 “动态少样本提示(Dynamic Few-Shot Prompting)” 与 “上下文长度感知的示例选择” 的能力。 from langchain.prompts impo…...

一文学习 工作流开发 BPMN、 Flowable参

一、什么是requests? requests 是一个用于发送HTTP请求的 Python 库。 它可以帮助你: 轻松发送GET、POST、PUT、DELETE等请求 处理Cookie、会话等复杂性 自动解压缩内容 处理国际化域名和URL 二、应用场景 requests 广泛应用于以下实际场景: …...

Windows安卓应用运行新方案:轻量级安卓环境搭建与实践指南

Windows安卓应用运行新方案:轻量级安卓环境搭建与实践指南 【免费下载链接】APK-Installer An Android Application Installer for Windows 项目地址: https://gitcode.com/GitHub_Trending/ap/APK-Installer 在数字化办公与多设备协同的时代,用户…...

WarcraftHelper终极指南:如何让经典魔兽争霸III在现代电脑上完美运行

WarcraftHelper终极指南:如何让经典魔兽争霸III在现代电脑上完美运行 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 还在为魔兽争霸III在…...

3小时搞定OpenClaw飞书机器人:Phi-3-mini-128k-instruct对话集成

3小时搞定OpenClaw飞书机器人:Phi-3-mini-128k-instruct对话集成 1. 为什么选择OpenClaw飞书Phi-3-mini组合 上周三下午4点,我正在为团队周会纪要焦头烂额时,突然想到:能不能让AI自动把飞书会议录音转成结构化纪要?经…...

Zookeeper分布式协调

一、总览图1、定义 ZooKeeper 集群协调器 它是一个分布式协调服务,专门为分布式应用提供一致性、可靠性的协调功能,解决分布式环境下的数据同步、配置管理、状态监控等问题。 2、部署3、数据模型 Znode树结构:采用层级化的命名空间&#xff…...

【C++可变模板参数】

C11 可变模板参数总结:搞懂参数包、包扩展和 emplace1. 为什么 C11 需要可变模板参数? 在 C11 之前,如果我们想写一个“参数个数可变、参数类型也可变”的函数,基本只能靠: 写很多重载或者用 ...(C 风格可变…...

广州邮科选型指南:挑选可调电源必须关注的四个核心参数

在电子工程师的工作台上,有一种设备兼具了灵活性与智能保护——它就是可调稳压恒流开关电源。这种电源不仅是供电工具,更像是懂得自我保护的"智能能源管家"。它如何同时实现稳压与恒流?传统电源往往只能固定输出,而这类…...

我用 LocalClaw 记忆系统管理项目知识:上下文永不丢失,问一句就能找到任何历史决策

LocalClaw官网:https://www.localclaw.me 前言:项目知识去哪了 我们团队有个老项目,30万行代码,5年历史。 上周我改一个功能,问同事:“当初为什么这样设计?” 他说:“不知道&…...

常见的服务器

常见的服务器 目录 [ 一、塔式服务器(Tower Server)](#%E4%B8%80%E3%80%81%E5%A1%94%E5%BC%8F%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%88Tower%20Server%EF%BC%89) [ 二、机架式服务器(Rack Server)](#%E4%BA%8C%E3%80%81%E6%9C%BA%E6…...

codex解决中文乱码

根源似乎不在codex的编码上,我设置了全局指令还是错误,现在观察到根源应该在控制台,参考文章: codex解决中文乱码问题-CSDN博客 Codex 中文乱码问题全链路解决方案(Windows 11)_codex 乱码-CSDN博客 原因…...

Java全核心-阿里大厂面试-Gemini版

完善更新中......一、Java 核心基础1、Java 四大引用与 ThreadLocal 深度拷问【核心连环炮】面试官:说一下 Java 的四大引用及其实际业务场景?面试官:ThreadLocal 为什么要用弱引用?不用行不行?面试官:既然…...

OpenClaw技能市场挖掘:百川2-13B-4bits量化版适配插件精选

OpenClaw技能市场挖掘:百川2-13B-4bits量化版适配插件精选 1. 为什么需要专门适配百川模型的技能? 去年冬天第一次尝试用OpenClaw对接百川2-13B模型时,我遇到了一个典型问题:虽然模型本身运行良好,但很多现成的技能模…...

AI基础设施权力更迭:AWS Bedrock 凭什么在 2026 年让开发者集体“倒戈”?

声明:本文由AI编辑生成,内容仅供参考。文中涉及的行业判断、平台能力分析、商业趋势推演与产品价值描述,均基于公开资料、通用观察及示意性表达整理,不构成任何商业承诺、采购建议、投资建议或服务保证。实际产品能力、接口支持范…...

golang如何理解值类型和引用类型_golang值类型与引用类型区别详解

<p>Go所有传参均为值传递&#xff0c;但“值”取决于类型底层&#xff1a;基础类型传数据副本&#xff0c;slice/map/chan传含指针的header副本&#xff0c;修改元素会影响原变量&#xff1b;需改变量本身&#xff08;如重置slice header&#xff09;时才必须传* T。<…...

Spring Boot 4.0 Agent-Ready架构的7个隐秘陷阱:90%团队在第4步就触发JVM元空间泄漏

第一章&#xff1a;Spring Boot 4.0 Agent-Ready架构的演进本质与企业级定位Spring Boot 4.0 并非简单版本迭代&#xff0c;而是面向可观测性、运行时可塑性与平台协同能力重构的范式跃迁。其核心突破在于将 Java Agent 集成从“可选插件”升维为“原生架构契约”&#xff0c;使…...

2026 安全生产精选:五款巡检软件实用清单,隐患排查与闭环管理轻松上手

安全生产是企业发展的核心防线&#xff0c;而巡检巡查则是守护这道防线的关键动作。无论是餐饮门店的消防安全检查、工厂车间的设备点检&#xff0c;还是建筑工地的隐患排查&#xff0c;传统的纸质记录和人工巡查方式正逐渐暴露出效率低、易造假、难追溯的问题。今天为大家整理…...