#互操作性

0 关注者 · 67 帖子

在医疗保健领域,互操作性是指在不同信息技术系统和软件应用程序之间进行通信、数据交换以及使用交换信息的能力。

文章 Lilian Huang · 十一月 6, 2025 7m read

Interoperability on Python (IoP) 是一个概念验证项目,旨在展示与 Python 优先方式相结合时 InterSystems IRIS Interoperability Framework 的强大功能。IoP 利用Embedded Python(嵌入式 Python,InterSystems IRIS 的一个功能)使开发者能够用 Python 编写互操作性组件,从而可以与强大的 IRIS 平台无缝集成。本指南专为初学者编写,全面介绍了 IoP、其设置以及创建第一个互操作性组件的操作步骤。 阅读完本文,您将能够清楚地了解如何使用 IoP 构建可扩缩、基于 Python 的互操作性解决方案。

0
0 13
文章 Kelly Huang · 十月 23, 2025 21m read

概述

快捷式医疗服务互操作资源 (FHIR) 是一个由 HL7 International 开发的标准化框架,旨在以灵活、对开发者友好且现代的方式促进医疗数据的交换。 它利用现代网络技术来确保在医疗保健系统间实现无缝集成与通信。

关键 FHIR 技术

  • 用于资源交互的 RESTful API
  • 用于数据表示的 JSON 和 XML
  • 用于安全授权和身份验证的 OAuth2

FHIR 围绕着称为资源的模块化组件构建,每个组件代表特定的医疗保健概念,包括:

  • 患者 – 受众特征和标识符
  • 观察数据 – 临床测量数据(例如,生命体征、实验室检测结果)
  • 诊疗 – 患者与医疗服务提供者的互动
  • 药物、过敏不耐受、健康状况等

资源单独定义,并且可以引用其他资源,以构成一个完善的数据模型。


InterSystems IRIS for Health:FHIR 支持

InterSystems IRIS for Health 是一个专为医疗保健行业设计的统一数据平台。 它包含对 HL7 FHIR 的原生支持。 它提供内置工具与服务,能够实现 FHIR 资源的存储、检索、转换和交换。IRIS 通过三大 FHIR 处理组件提升系统的互操作性:

1.FHIR 仓库服务器

IRIS 可以快速部署符合 FHIR 标准的服务器,并支持以下功能:

  • 完整的 FHIR 范式
  • 实现 FHIR RESTful API,包括搜索和查询参数
  • 导入并利用 FHIR 软件包和结构定义
  • 使用 FHIR 配置文件
  • 对 FHIR 资源进行原生 CRUD 操作
  • 以 JSON 或 XML 格式检索 FHIR 数据
  • 支持多种 FHIR 版本
  • FHIR SQL Builder 和批量 FHIR 处理功能

2. FHIR 装饰层

FHIR 装饰层是用于在现有架构(通常为非 FHIR 架构)之上提供符合 FHIR 标准的 API 接口的软件架构模式。 它还能简化医疗保健数据系统,包括电子健康记录系统 (EHR)、旧版数据库或 HL7 v2 消息存储库,无需将所有数据迁移到 FHIR 原生系统中。

此实现专门围绕 FHIR Interoperability Adapter 展开。

3. FHIR Interoperability Adapter

InterSystems IRIS for Health 具备高度灵活性和精细控制能力,可以实现 HL7 V2.x、C-CDA 等医疗消息标准与 FHIR 之间的双向转换​​(参见“消息转换示意图”)。 不过,部分 FHIR 实现需要使用专用的 FHIR 仓库服务器。 为支持此类场景,InterSystems IRIS for Health 内置了一套互操作性适配器工具包,无需使用 FHIR 服务器即可实现详细的消息转换。

此适配器能够处理来自外部系统的各种外部请求(例如, REST 或 SOAP API 请求),将这些请求转换为 FHIR 格式,然后路由到下游系统,且无需将数据持久存储在数据库中。

如有需要,该适配器也可以对数据进行转换并将其存储在数据库中。

它能有效提供外部接口层,使非 FHIR 数据库可以像 FHIR 服务器一样运行,从而实现无缝互操作性。

消息转换

SDA:摘要文档架构

摘要文档架构 (SDA) 是 InterSystems 推出的基于 XML 的中间格式,用于在 IRIS 和 HealthShare 产品内部表示患者数据。 利用这种功能强大的原生数据结构,您可以访问离散数据,并轻松实现多种数据格式之间的转换,包括 HL7 V2、CCDA、C32、HL7 FHIR 等。

SDA 结构

SDA(结构化数据架构)主要分为两个组成部分:

  1. 容器 – 包含一个或多个部分的顶层结构
  2. 部分 – 特定医疗元素(例如,患者、诊疗、过敏不耐受)的表示

容器

容器是 SDA 标准的顶层结构,其中包含多个部分(例如,患者、诊疗、过敏不耐受等)。

我们来深入探讨一下 SDA 的内部结构及其组成部分。

容器的类定义:

HS.SDA3.Container 类作为表示 SDA 文档的主要定义。 各个部分(如患者、诊疗)均定义为对象,并作为属性包含在此 class.Sections 中。

“部分”是指容器元素的独立片段,它以 IRIS 类定义的形式表示,并包含容器上的相关数据元素。

  1. 患者 – HS.SDA3.Patient
  2. 诊疗 – HS.SDA3.Encounter
  3. 过敏症 - HS.SDA3.Allergy

SDA 容器结构

以下 XML 结构表示整个 SDA 容器。

<Container><Patient/><Encounters/><Encounters/><AdvanceDirectives/></Container>

 

SDA 数据类型

FHIR 数据类型格式与 IRIS 标准数据类型不同。 因此,SDA 具有特定的自定义数据类型,与 %String、%Integer、%Stream 等标准属性相比,这些数据类型可以更有效地处理部分中的属性。 不过,标准属性也用于 SDA 部分中。

相应数据类型类也在 HS.SDA3* 软件包中定义:

  • HS.SDA3.Name
  • HS.SDA3.CodeTableDetail.Allergy
  • HS.SDA3.PatientNumber
  • HS.SDA3.TimeStamp

SDA 扩展

大多数情况下,SDA 具有充足的属性来管理和生成系统中传输的所有数据,以开发资源。 不过,如果您需要在实现过程中加入额外的数据,IRIS 提供了一种简单直接的方式可以帮助您轻松地将相应数据扩展到 SDA 扩展类中。

例如,HS.Local.SDA3.AllergyExtension 类定义是 HS.SDA3.Allergy 的扩展类。 您可以向此扩展类添加必要的数据元素,从而简化整个实现过程中的访问和操作流程。

下一步是创建容器对象。


创建容器对象

ClassMethod CreateSDAContainer()
{
<span class="hljs-keyword">set</span> SDAContainer = <span class="hljs-keyword">##class</span>(HS.SDA3.Container).<span class="hljs-built_in">%New</span>()

#<span class="hljs-comment">; create patient object</span>
<span class="hljs-keyword">set</span> patientSDA = <span class="hljs-keyword">##class</span>(HS.SDA3.Patient).<span class="hljs-built_in">%New</span>()
<span class="hljs-keyword">set</span> patientSDA.Name.FamilyName = <span class="hljs-string">"stood"</span>
<span class="hljs-keyword">set</span> patientSDA.Name.GivenName = <span class="hljs-string">"test"</span>
<span class="hljs-keyword">set</span> patientSDA.Gender.Code=<span class="hljs-string">"male"</span>
<span class="hljs-keyword">set</span> patientSDA.Gender.Description=<span class="hljs-string">"birth gender"</span>
#<span class="hljs-comment">; create Encounter 1</span>
<span class="hljs-keyword">set</span> encounterSDA  = <span class="hljs-keyword">##class</span>(HS.SDA3.Encounter).<span class="hljs-built_in">%New</span>()
<span class="hljs-keyword">set</span> encounterSDA.AccountNumber = <span class="hljs-number">12109979</span>
<span class="hljs-keyword">set</span> encounterSDA.ActionCode =<span class="hljs-string">"E"</span>
<span class="hljs-keyword">set</span> encounterSDA.AdmitReason.Code =<span class="hljs-string">"Health Concern"</span>
<span class="hljs-keyword">set</span> encounterSDA.AdmitReason.Description = <span class="hljs-string">"general health concern"</span>
#<span class="hljs-comment">; create Encounter 2</span>
<span class="hljs-keyword">set</span> encounterSDA1  = <span class="hljs-keyword">##class</span>(HS.SDA3.Encounter).<span class="hljs-built_in">%New</span>()
<span class="hljs-keyword">set</span> encounterSDA1.AccountNumber = <span class="hljs-number">95856584</span>
<span class="hljs-keyword">set</span> encounterSDA1.ActionCode =<span class="hljs-string">"D"</span>
<span class="hljs-keyword">set</span> encounterSDA1.AdmitReason.Code =<span class="hljs-string">"reegular checkup"</span>
<span class="hljs-keyword">set</span> encounterSDA1.AdmitReason.Description = <span class="hljs-string">"general health ckeckup"</span>
#<span class="hljs-comment">; set the patientSDA into the container.</span>
<span class="hljs-keyword">set</span> SDAContainer.Patient = patientSDA

#<span class="hljs-comment">; set multiple encounters into the container SDA</span>
<span class="hljs-keyword">do</span> SDAContainer.Encounters.Insert(encounterSDA)
<span class="hljs-keyword">do</span> SDAContainer.Encounters.Insert(encounterSDA1)

#<span class="hljs-comment">; convert the SDA object into an XML string.</span>
<span class="hljs-keyword">do</span> SDAContainer.XMLExportToString(.containerString)
<span class="hljs-keyword">write</span> containerString

}


SDA – XML 文档输出

<Container><Patient><Name><FamilyName>stood</FamilyName><GivenName>test</GivenName></Name><Gender><Code>male</Code><Description>birth gender</Description></Gender></Patient><Encounters><Encounter><AccountNumber>12109979</AccountNumber><AdmitReason><Code>Health Concern</Code><Description>general health concern</Description></AdmitReason><ActionCode>E</ActionCode></Encounter><Encounter><AccountNumber>95856584</AccountNumber><AdmitReason><Code>reegular checkup</Code><Description>general health ckeckup</Description></AdmitReason><ActionCode>D</ActionCode></Encounter></Encounters><UpdateECRDemographics>true</UpdateECRDemographics></Container>

 在上一节中,我们探讨了 SDA 及其组成部分。 我们还学习了如何通过 Cache ObjectScript 生成 SDA。

接下来,我们将使用互操作性生产(之前称为 Ensemble)生成 FHIR 资源或捆绑包。

在创建 FHIR 资源之前,我们来简单了解一下互操作性生产。

带 FHIR 适配器的互操作性生产

互操作性生产是一个集成框架,用于连接系统和开发应用程序,以轻松实现互操作性。 它通常分为 3 大组成部分:

  1. 业务服务 – 连接到外部系统,并接收外部系统发出的请求。
  2. 业务流程 – 接收其他业务主机发出的请求、根据您定义的业务逻辑处理请求,并转换相关数据。 会使用多个组件进行数据转换:
    1. BPL – 业务流程语言
    2. DTL – 数据转换语言
    3. BR – 业务规则
    4. 记录映射
  3. 业务操作 – 与外部系统相连,并向外部系统发送响应。

首先,我们来构建 FHIR 消息。

创建 FHIR 资源

存在两种类型的系统:FHIR 服务器和非 FHIR 服务器。 在我们的案例中,我们的目标是使用 FHIR Interoperability Adapter 生成 FHIR 资源,将非 FHIR 的InterSystems IRIS 数据库表示为符合 FHIR 标准的系统。

在本节中,我们将演示如何借助带 FHIR 适配器的 InterSystems IRIS for Health 互操作性工具包,通过存储在 IRIS 数据库中的自定义数据生成 FHIR 资源。

在此实现过程中,我们将创建以下类型的 FHIR 资源:

  1. 标准 FHIR 资源 – 使用内置的 FHIR 类,进行少量修改或不做修改。
  2. 自定义 FHIR 资源 – 涉及向 SDA 模型添加扩展,以及为 FHIR 资源创建自定义数据转换 (DTL)。

每个实现都将通过专用的业务主机启动。

业务服务

RESTful 业务主机负责接收外部系统发出的请求。 您可以根据特定集成要求(例如,HTTP、SOAP 或其他受支持的协议)配置合适的适配器。

接收到外部系统发送的请求后,工作流将立即使用自定义或旧版数据库中持久存储的数据生成相应的 FHIR 资源。

FHIR 业务流程

FHIR 消息生成流程主要包括两个步骤:

  1. 将自定义/专有数据转换为 SDA(将 HL7 版本 2.X 转换为 SDA,将 CCDA 转换为 SDA,等等)。
  2. 将数据元素添加到 SDA,如有需要,创建自定义 DTL。 以下步骤为可选步骤,是否执行取决于具体实现需求,例如自定义 FHIR 资源生成。
  3. 然后,借助 IRIS 内置流程将生成的 SDA 转换为 FHIR 资源。

结构化数据架构 (SDA) 格式充当中介,可以实现灵活的数据转换。 数据以 SDA 格式提供后,即可轻松映射到 FHIR 或其他医疗保健数据标准。

将自定义/专有数据转换为 SDA 格式
使用这种方式时,先创建持久或互操作性请求类,以便转换为 SDA 格式。 此过程涉及定义自定义患者类,用于将您的旧版或自定义数据库结构中的数据映射为符合 SDA 标准的对象。

利用自定义患者类可以提供极高的灵活性:

  • 简化对象处理和操作流程。
  • 以数据转换语言 (DTL) 实现清晰映射。
  • 轻松重用其他转换或业务逻辑层中的对象。

请求外部层的类以转换 SDA:

Class Samples.FHIRAdapt.CustomStorage.Patient Extends (Ens.Request,%JSON.Adaptor)
{
Property Name As%String;Property BirthDate As%String;Property Citizenship As%String;Property Religion As%String;Property PrimaryLanguage As%String;Property Married As%String;Property MRN As%String;
}

 

此请求类作为外部接口层,启动从您的数据库格式转换为 SDA 的流程。 SDA 对象创建完毕后,可以通过标准或自定义 DTL 映射无缝转换为所需 FHIR 资源:

  1. 添加 Samples.FHIRAdapt.CustomStorage.Patient(使用您的类定义)类作为转换的源类。
  2. 确定并选择适合映射的 SDA 目标类。 在本例中,HS.SDA3.Patient 是适合将自定义数据转换为 SDA 格式的类。

DTL 转换示例

Class Samples.FHIRAdapt.DTL.CustomDataToPatientSDA Extends Ens.DataTransformDTL [ DependsOn = (Samples.FHIRAdapt.CustomStorage.Patient, HS.SDA3.Patient) ]
{

Parameter IGNOREMISSINGSOURCE = 1;Parameter REPORTERRORS = 1;Parameter TREATEMPTYREPEATINGFIELDASNULL = 0; XData DTL [ XMLNamespace = "http://www.intersystems.com/dtl" ] { <transform sourceClass='Samples.FHIRAdapt.CustomStorage.Patient' targetClass='HS.SDA3.Patient' create='new' language='objectscript' > <assign value='$Piece(source.Name,",")' property='target.Name.GivenName' action='set' /> <assign value='$Piece(source.Name,",")' property='target.Name.FamilyName' action='set' /> <assign value='$Piece($Piece(source.Name,",",2)," ",2)' property='target.Name.MiddleName' action='set' /> <assign value='source.Citizenship' property='target.Citizenship' action='set' /> <assign value='"fullname"' property='target.Name.Type' action='set' /> <assign value='$Select(source.Married=1:"married",1:"single")' property='target.MaritalStatus.Code' action='set' /> </transform> }

}

在这一阶段,数据已成功转换为 SDA 文档,并准备好转换为 FHIR 资源。

在生成 FHIR 资源之前,应创建额外的支持 FHIR 资源作为此响应的组成部分。 此外,还需要在 FHIR 输出中包含自定义字段。 要支持这些自定义元素,必须将相应的属性纳入 SDA 结构。

此操作可以借助 SDA 扩展完成,SDA 扩展可以将准确、完整地生成 FHIR 资源所需的自定义数据元素包含在 SDA 结构中。

SDA 扩展

FHIR 遵循 80/20 法则,即核心 FHIR 规范负责处理约 80% 的常见医疗保健用例,而剩余的 20% 则通过自定义约束和扩展来解决。

为说明这一点,我们将使用自定义扩展创建一个 AllergyIntolerance 资源。

在 InterSystems IRIS 中正确实现扩展数据元素需要执行两个关键步骤:

  1. HS.SDA3.*******Extension 用于将额外的数据元素添加到各个 SDA 部分。 例如,类 HS.Local.SDA3.AllergyExtension 通过定义所需自定义属性对 HS.SDA3.Allergy 进行扩展。
  2. 由于预构建的 DTL 映射不包含这些自定义扩展,必须创建自定义 DTL 来相应地转换处理。

Allergy 扩展类

要在用于创建所需过敏症资源的 HS.Local.SDA3.AllergyExtension 类中构建所需字段,请使用以下代码行:

Class HS.Local.SDA3.AllergyExtension Extends HS.SDA3.DataType
{

Parameter STREAMLETCLASS = "HS.SDA3.Streamlet.Allergy";/// Mapped this property due to not being available in the SDA to FHIR conversionProperty Criticality As%String;/// Mapped this property due to not being available in the SDA to FHIR conversionProperty Type As%String(MAXLEN = ""); Storage Default { <Data name="AllergyExtensionState"> <Subscript>"AllergyExtension"</Subscript> <Value name="1"> <Value>Criticality</Value> </Value> <Value name="2"> <Value>Type</Value> </Value> </Data> <State>AllergyExtensionState</State> <Type>%Storage.Serial</Type> }

}

创建扩展只是完成了整个流程的一半,因为标准 DTL 未提供扩展字段的映射。 现在,我们需要构建自定义 DTL,以正确转换 FHIR 响应。


自定义 DTL 创建

在自定义 DTL 类之前,您需要为所有自定义 DTL 实现定义专用软件包。 为此,InterSystems 建议使用名为 HS.Local.FHIR.DTL 的软件包。

要为 Allergy 构建自定义 DTL,请先从现有的数据转换类
HS.FHIR.DTL.SDA3.vR4.Allergy.AllergyIntolerance 开始,该类用于处理从 SDA 到 FHIR 资源的转换。

首先,将此类复制到自定义包中,将其命名为

HS.Local.FHIR.DTL.SDA3.vR4.Allergy.AllergyIntolerance. 然后,将自定义扩展映射到 FHIR 资源生成流程中,以对其进行扩展。

例如,示例类 HS.Local.FHIR.DTL.FromSDA.Allergy 说明了如何便捷地映射 Allergy 扩展字段,同时从基类 HS.FHIR.DTL.SDA3.vR4.Allergy.AllergyIntolerance 继承所有其他映射。

示例自定义 DTL 映射如下图所示:

/// Transforms SDA3 HS.SDA3.Allergy to vR4 AllergyIntoleranceClass HS.Local.FHIR.DTL.SDA3.vR4.Allergy.AllergyIntolerance Extends Ens.DataTransformDTL [ DependsOn = (HS.SDA3.Allergy, HS.FHIR.DTL.vR4.Model.Resource.AllergyIntolerance), ProcedureBlock ]
{

XData DTL [ XMLNamespace = "http://www.intersystems.com/dtl" ] { <transform sourceClass='HS.SDA3.Allergy' targetClass='HS.FHIR.DTL.vR4.Model.Resource.AllergyIntolerance' create='existing' language='objectscript' > <assign value='source.Extension.Criticality' property='target.criticality' action='set' /> <assign value='source.Extension.Type' property='target.type' action='set' > <annotation>11/07/2023; ak; Added this set to populate type in AllergyIntolerance resource</annotation> </assign> </transform> }

}

为自定义 DTL 创建类软件包后(在自定义 DTL 软件包不存在的情况下),必须进行注册,以获得今后的 FHIR 数据转换结果。

set status = ##class(HS.FHIR.DTL.Util.API.ExecDefinition).SetCustomDTLPackage("HS.Local.FHIR.DTL")

 此外,您还可以通过调用类方法获取自定义 DTL 软件包详细信息(如果已定义)。

Write ##class(HS.FHIR.DTL.Util.API.ExecDefinition).GetCustomDTLPackage()


请求消息的流容器类

SDA 及其可选 SDA 扩展的设置,以及构建 SDA 的可选自定义 DTL 创建过程现已完成。 不过,现在必须将 SDA 对象转换为标准化的 Ens.StreamContainer,该容器专门用于 SDA 到 FHIR 的转换业务流程。

下面的简单步骤可以将 SDA 对象转换为 Ens.StreamContainer。

ClassMethod CreateEnsStreamContainer()
{
	set ensStreamCntr=""try {
        #; refer the CreateSDAContainer() method above#dim SDAContainer As HS.SDA3.Container = ..CreateSDAContainer()
		do SDAContainer.XMLExportToStream(.stream)
		#; Create Ens.StreamContainer is the default format for processing the SDA to FHIR processSet ensStreamCntr = ##class(Ens.StreamContainer).%New(stream)
	}
	catch ex {
		Write ex.DisplayString()
		set ensStreamCntr=""
	}
	return ensStreamCntr
}

 创建 SDA 的第一阶段已完成。 第二阶段(即生成 FHIR 资源)已由 InterSystems IRIS 处理。

下文介绍如何将 SDA 文档转换为 FHIR 资源。


SDA 转换为 FHIR

针对 FHIR 创建配置互操作性业务主机

生成 FHIR 的业务逻辑已完成。 现在,我们来配置互操作性生产设置:

  1. 设置入站服务,以接收外部系统发出的请求。
  2. 业务流程 – 这是创建 FHIR 资源的关键步骤。

业务流程实现

此业务流程侧重于 SDA 到 FHIR 的转换。 InterSystems IRIS 包含一套全面的内置业务流程,即 S.FHIR.DTL.Util.HC.SDA3.FHIR.Process,该流程可以促进 SDA 与 FHIR 消息的转换。 通过将生成的 SDA 文档发送到此业务流程,您可以接收以 JSON 响应形式发送的 FHIR 资源。

此流程支持两种类型的 FHIR 响应,具体取决于 SDA 输入。

  1. 捆绑包 – 如果整个 SDA 容器对象以 Ens.StreamConainter 形式发送,该流程会返回包含所有资源的 FHIR 捆绑包。
  2. 资源 – 如果个别 SDA 部分(例如,患者、诊疗、过敏症)以 Ens.StreamConainter 的形式发送,它会以捆绑包的形式发送对应的单个 FHIR 资源。

业务操作

FHIR 捆绑包现已准备就绪,可被返回给请求方或发送到外部系统。

生产设置:


业务服务类

该业务服务类负责处理外部系统发出的请求,以生成 FHIR。

  1. 一旦接收到请求,它就会使用现有逻辑创建 SDA。
  2. 该 SDA 随后会被转换为流对象。
  3. 此流会被转换为标准业务流程预期使用的格式。
  4. 最后,处理好的输入会被发送到业务流程。
Class Samples.Interop.BS.GenerateFHIRService Extends Ens.BusinessService
{

Parameter ADAPTER = "Ens.InboundAdapter";Property TargetConfigName As Ens.DataType.ConfigName [ InitialExpression = "HS.FHIR.DTL.Util.HC.FHIR.SDA3.Process" ]; Method OnProcessInput(pInput As%RegisteredObject, Output pOutput As%RegisteredObject) As%Status { #; create your SDA container object and export to streamdo..CreateSDAContainer().XMLExportToStream(.sdaStream)

#<span class="hljs-comment">; convert to the standard Ens.StreamContainer message format</span>
<span class="hljs-keyword">set</span> ensStreamCtnr = <span class="hljs-keyword">##class</span>(Ens.StreamContainer).<span class="hljs-built_in">%New</span>(sdaStream)

#<span class="hljs-comment">; send to the Business process</span>
<span class="hljs-keyword">do</span> <span class="hljs-built_in">..SendRequestSync</span>(<span class="hljs-built_in">..TargetConfigName</span>,ensStreamCtnr,.pOutput)
<span class="hljs-keyword">Quit</span> <span class="hljs-built_in">$$$OK</span>

}

ClassMethod CreateSDAContainer() As HS.SDA3.Container {

<span class="hljs-keyword">set</span> SDAContainer = <span class="hljs-keyword">##class</span>(HS.SDA3.Container).<span class="hljs-built_in">%New</span>()

#<span class="hljs-comment">; create patient object</span>
<span class="hljs-keyword">set</span> patientSDA = <span class="hljs-keyword">##class</span>(HS.SDA3.Patient).<span class="hljs-built_in">%New</span>()
<span class="hljs-keyword">set</span> patientSDA.Name.FamilyName = <span class="hljs-string">"stood"</span>
<span class="hljs-keyword">set</span> patientSDA.Name.GivenName = <span class="hljs-string">"test"</span>
<span class="hljs-keyword">set</span> patientSDA.Gender.Code=<span class="hljs-string">"male"</span>
<span class="hljs-keyword">set</span> patientSDA.Gender.Description=<span class="hljs-string">"birth gender"</span>
#<span class="hljs-comment">; create Encounter 1</span>
<span class="hljs-keyword">set</span> encounterSDA  = <span class="hljs-keyword">##class</span>(HS.SDA3.Encounter).<span class="hljs-built_in">%New</span>()
<span class="hljs-keyword">set</span> encounterSDA.AccountNumber = <span class="hljs-number">12109979</span>
<span class="hljs-keyword">set</span> encounterSDA.ActionCode =<span class="hljs-string">"E"</span>
<span class="hljs-keyword">set</span> encounterSDA.AdmitReason.Code =<span class="hljs-string">"Health Concern"</span>
<span class="hljs-keyword">set</span> encounterSDA.AdmitReason.Description = <span class="hljs-string">"general health concern"</span>
#<span class="hljs-comment">; set the patientSDA into the container.</span>
<span class="hljs-keyword">set</span> SDAContainer.Patient = patientSDA

#<span class="hljs-comment">; set encounters into the container SDA</span>
<span class="hljs-keyword">do</span> SDAContainer.Encounters.Insert(encounterSDA)
<span class="hljs-keyword">return</span> SDAContainer

} }


使用 ObjectScript 创建 SDA 到 FHIR 的转换

在上一个示例中,FHIR 资源是借助互操作性框架通过 SDA 生成的。 在本节中,我们将直接使用 ObjectScript 构建 FHIR 捆绑包。


通过 SDA 容器创建 FHIR 捆绑包

CreateSDAContainer 方法会返回 HS.SDA3.Container 类型的对象(我们在上文中提到过)。 在将此 SDA 容器传递给 TransformStream 方法之前,必须先将其转换为流。 TransformStream 方法随后会处理该流,并在 tTransformObj.bundle 中以 %DynamicObject 形式返回 FHIR 捆绑包。

ClassMethod CreateBundle(fhirVersion As%String = "R4") As%DynamicObject
{
	try {	
		Set SDAContainer = ..CreateSDAContainer()
		Do SDAContainer.XMLExportToStream(.stream)
		#; Should pass stream, not a container objectSet tTransformObj = ##class(HS.FHIR.DTL.Util.API.Transform.SDA3ToFHIR).TransformStream( stream, "HS.SDA3.Container", fhirVersion)
		return tTransformObj.bundle
	}
	catch ex {
		write ex.DisplayString()
	}
	return""
}


使用 SDA 部分创建 FHIR 捆绑包

使用此方式时,会直接在 ObjectScript 中声明 patientSDA。 此 SDA 对象随后会被传递到 TransformObject 方法,这个方法负责处理此 SDA 对象,并以 %DynamicObject 形式返回 FHIR 捆绑包。

ClassMethod CreatePatientResourceDirectSet()
{
	try {
		#; convert you're custom dataset into SDA by your DTLset patientSDA = ##class(HS.SDA3.Patient).%New()
		set patientSDA.Name.FamilyName = "stood"set patientSDA.Name.GivenName = "test"set patientSDA.Gender.Code="male"set patientSDA.Gender.Description="birth gender"#dim tTransformObj As HS.FHIR.DTL.Util.API.Transform.SDA3ToFHIR = ##class(HS.FHIR.DTL.Util.API.Transform.SDA3ToFHIR).TransformObject(patientSDA,"R4")
		set patinetBundle = tTransformObj.bundle
	}
	catch ex {
		write ex.DisplayString()
	}
	return patinetBundle
}

使用自定义 FHIR DTL 和 Allergy 扩展创建过敏症资源

  1. 直接在 SDA 对象中填充所有必需字段,包括自定义扩展字段。
  2. 您应当在 TransformObject 方法中将 FHIR 版本类型作为第二个参数提及(“R4”代表 Resource4 FHIR 消息)。
  3. 将完成的 SDA 对象传递给 FHIR 转换类,以生成 AllergyIntolerance FHIR 捆绑包。

注:过敏症资源的自定义扩展已定义,自定义 DTL 映射已注册。

ClassMethod CreateAllergyWithDTL()
{
	#; I already registered the "HS.Local.FHIR.DTL.SDA3.vR4.Allergy.AllergyIntolerance" for extension mapping
	#; fetch the data from the table/global and set it into AllergySDA directly.	set allerySDA = ##class(HS.SDA3.Allergy).%New()
	set allerySDA.Extension.Criticality = "critial"set allerySDA.Extension.Type = "t1"set allerySDA.Comments = "testing allergies"set allerySDA.AllergyCategory.Code="food"set allerySDA.AllergyCategory.Description="sea food"
	#; Set the required and additional properties in SDA, depending on your requirements.
	#; create a FHIR resource from the allergySDA with extension fields that uses a custom "HS.Local.FHIR.*" DTL#dim tTransformObj As HS.FHIR.DTL.Util.API.Transform.SDA3ToFHIR = ##class(HS.FHIR.DTL.Util.API.Transform.SDA3ToFHIR).TransformObject(allerySDA,"R4")
	Set patinetBundle = tTransformObj.bundle
}

FHIR 转换为 SDA

自定义数据、HL7 v2.x 或 CCDA 消息之前已被转换为 FHIR。 接下来的实现涉及将 FHIR 捆绑包或资源转换为 SDA 格式,随后可以存储在数据库中或转换为 CCDA 或 HL7 v2.x 格式。

JSON 或 XML 格式的 FHIR 资源接收自外部系统。 收到后,必须将资源转换为内部数据结构并存储在 IRIS 数据库中。

业务服务

可以根据要求通过 HTTP/REST 或其他任何入站适配器接收请求。


业务流程 – FHIR 转换为 SDA

InterSystems IRIS 接收到 FHIR 请求消息后,会提供全面的内置业务流程 (HS.FHIR.DTL.Util.HC.FHIR.SDA3.Process)。 此业务流程会将 FHIR 资源或捆绑包作为输入。 FHIR 输入只能是配置的 FHIR 版本。 此业务流程会将 FHIR 数据转换为 SDA3、将 SDA3 流转发给指定的业务主机、接收业务主机发送的响应,并返回 FHIR 响应。

请注意,您无法将收到的请求直接发送至业务流程。

请求输入应为以下类型:

  1. HS.FHIRServer.Interop.Request – 用于互操作性生产。
  2. HS.Message.FHIR.Request – FHIR 仓库服务器。

这意味着在发送请求之前,必须将请求转换为上述格式之一。

Creating Interop.Request

ClassMethod CreateReqObjForFHIRToSDA(pFHIRResource As%DynamicObject) As HS.FHIRServer.Interop.Request
{
             #; sample message set pFHIRResource = {"resourceType":"Patient","name":[{"use":"official","family":"ashok te","given":["Sidharth"]}],"gender":"male","birthDate":"1997-09-08","telecom":[{"system":"phone","value":"1234566890","use":"mobile"},{"system":"email","value":"tornado1212@gmail.com"}],"address":[{"line":["Some street"],"city":"Manipal1","state":"Karnataka1","postalCode":"1234561"}]}
<span class="hljs-keyword">set</span> stream = <span class="hljs-keyword">##class</span>(<span class="hljs-built_in">%Stream.GlobalCharacter</span>).<span class="hljs-built_in">%New</span>()
<span class="hljs-keyword">do</span> stream.<span class="hljs-keyword">Write</span>(pFHIRResource.<span class="hljs-built_in">%ToJSON</span>())
#<span class="hljs-comment">; create Quick stream </span>
<span class="hljs-keyword">set</span> inputQuickStream = <span class="hljs-keyword">##class</span>(HS.SDA3.QuickStream).<span class="hljs-built_in">%New</span>()
<span class="hljs-keyword">set</span> inputQuickStreamId = inputQuickStream.<span class="hljs-built_in">%Id</span>()
<span class="hljs-built_in">$$$ThrowOnError</span>( inputQuickStream.CopyFrom(stream) ) 
	
<span class="hljs-keyword">#dim</span> ensRequest <span class="hljs-keyword">as</span> HS.FHIRServer.Interop.Request = <span class="hljs-keyword">##class</span>(HS.FHIRServer.Interop.Request).<span class="hljs-built_in">%New</span>()

<span class="hljs-keyword">set</span> ensRequest.QuickStreamId = inputQuickStreamId

<span class="hljs-keyword">return</span> ensRequest

在 HS.FHIRServer.Interop.Request 消息创建完毕后,立即将其发送至业务流程,以将 FHIR 资源转换为 SDA 捆绑包。

生产设置:


业务服务类

该类通过 HTTP 请求接收 FHIR 资源流,将此流输入转换为标准流程预期使用的格式,即 HS.FHIRServer.Interop.Request,最后调用 FHIR 适配器流程类以生成 SDA。

Class Samples.Interop.BS.FHIRReceiver Extends Ens.BusinessService
{

Parameter ADAPTER = "EnsLib.HTTP.InboundAdapter";Property TargetConfigName As Ens.DataType.ConfigName [ InitialExpression = "HS.FHIR.DTL.Util.HC.FHIR.SDA3.Process" ]; Method OnProcessInput(pInput As%Stream.Object, Output pOutput As%Stream.Object) As%Status { set inputQuickStream = ##class(HS.SDA3.QuickStream).%New() set inputQuickStreamId = inputQuickStream.%Id() $$$ThrowOnError( inputQuickStream.CopyFrom(pInput) )

<span class="hljs-keyword">#dim</span> ensRequest <span class="hljs-keyword">as</span> HS.FHIRServer.Interop.Request = <span class="hljs-keyword">##class</span>(HS.FHIRServer.Interop.Request).<span class="hljs-built_in">%New</span>()
<span class="hljs-keyword">set</span> ensRequest.QuickStreamId = inputQuickStreamId

<span class="hljs-keyword">Do</span> <span class="hljs-built_in">..SendRequestSync</span>(<span class="hljs-built_in">..TargetConfigName</span>, ensRequest, .pOutput)

<span class="hljs-keyword">Quit</span> <span class="hljs-built_in">$$$OK</span>

}

}


使用 ObjectScript 通过 FHIR 资源创建 SDA

在上一个示例中,SDA 文档是借助互操作性框架通过 FHIR 生成的。 在本节中,我们将直接使用 ObjectScript 实现从 FHIR 到 SDA 的转换。

在您收到以请求形式发送到 IRIS 中的 FHIR 资源/捆绑包后,立即将 FHIR JSON 转换为 SDA 容器:

  1. 将 InterSystems %DynamicObject AKA JSON 转换为 %Stream 对象。
  2. 通过 HS.FHIR.DTL.Util.API.Transform.FHIRToSDA3 类执行 TransformStream 方法,这会以响应形式返回 SDA 容器对象。
///Simple, straightforward FHIR JSON resource to SDA conversionClassMethod CreateSDAFromFHIRJSON()
{
	try {
		; have to send as a stream, not a %DynamicObjectset patientStream = ##Class(%Stream.GlobalCharacter).%New()
		do patientStream.Write({"resourceType":"Patient","name":[{"use":"official","family":"ashok te","given":["Sidharth"]}],"gender":"male","birthDate":"1997-09-08","telecom":[{"system":"phone","value":"1234566890","use":"mobile"},{"system":"email","value":"tornado1212@gmail.com"}],"address":[{"line":["Some street"],"city":"Manipal1","state":"Karnataka1","postalCode":"1234561"}]}.%ToJSON())
		#dim SDAObj As HS.FHIR.DTL.Util.API.Transform.FHIRToSDA3 = ##class(HS.FHIR.DTL.Util.API.Transform.FHIRToSDA3).TransformStream(patientStream,"R4","JSON")
		set SDAContainer = SDAObj.container
	<span class="hljs-comment">; XML-based SDA output</span>
	<span class="hljs-keyword">write</span> SDAContainer.XMLExport()
}
<span class="hljs-keyword">catch</span> ex {
	<span class="hljs-keyword">write</span> ex.DisplayString()
}

}


FHIR XML 转换为 SDA 容器。

  1. 将 XML 转换为 %Stream 对象。
  2. 通过 HS.FHIR.DTL.Util.API.Transform.FHIRToSDA3 类执行 TransformStream 方法,这会以响应形式返回 SDA 容器对象。
/// Simple, straightforward FHIR XML resource to SDA conversionClassMethod CreateSDAFromFHIRXML()
{
	try {
		set patientXML = "<Patient xmlns=""http://hl7.org/fhir""><id value=""example""/><text><status value=""generated""/><div xmlns=""http://www.w3.org/1999/xhtml""><p>John Doe</p></div></text><identifier><use value=""usual""/><type><coding><system value=""http://terminology.hl7.org/CodeSystem/v2-0203""/><code value=""MR""/></coding></type><system value=""http://hospital.smarthealth.org""/><value value=""123456""/></identifier><name><use value=""official""/><family value=""Doe""/><given value=""John""/></name><gender value=""male""/><birthDate value=""1980-01-01""/></Patient>"set patientStream = ##Class(%Stream.GlobalCharacter).%New()
		do patientStream.Write(patientXML)
		#dim SDAObj As HS.FHIR.DTL.Util.API.Transform.FHIRToSDA3 = ##class(HS.FHIR.DTL.Util.API.Transform.FHIRToSDA3).TransformStream(patientStream,"R4","XML")
		set SDAContainer = SDAObj.container
	<span class="hljs-comment">; XML-based SDA output</span>
	<span class="hljs-keyword">write</span> SDAContainer.XMLExport()
}
<span class="hljs-keyword">catch</span> ex {
	<span class="hljs-keyword">write</span> ex.DisplayString()
}

}

按照上文详细介绍的步骤,您可以在数据与 FHIR 资源之间进行无缝转换。

其他内置的 FHIR 仓库和 FHIR 装饰选项是公开符合 FHIR 标准的系统、高效处理和存储 FHIR 资源的重要工具。

0
0 16
文章 Kelly Huang · 十月 23, 2025 15m read

img

在本节中,我们将探讨如何在 IRIS 中使用 Python 作为主要编程语言,在使用 Python 编写应用程序逻辑的同时仍能利用 IRIS 的强大功能。

使用方法 (irispython)

我们先来介绍官方操作方式,即使用 irispython 解释器。

您可以使用 irispython 解释器直接在 IRIS 中运行 Python 代码。 这样,您可以编写 Python 代码,并在 IRIS 应用程序的运行环境中执行相应代码。

什么是 irispython?

irispython 是位于 IRIS 安装目录 (<installation_directory>/bin/irispython) 下的 Python 解释器,用于在 IRIS 的运行环境中执行 Python 代码。

它的功能包括:

  • 设置 sys.path,以包含 IRIS Python 库和模块。
    • 此操作通过 <installation_directory>/lib/python/iris_site.py 中的 site.py 文件执行。
    • 如需了解详情,请参阅模块文章 Python 模块简介
  • 允许您导入 iris 模块,这是一个特殊模块,用于访问 IRIS 功能,例如实现任何 ObjectScript 类与 Python 的双向桥接。
  • 修复权限问题并动态加载 iris 内核库。

irispython 使用示例

您可以通过命令行运行 irispython 解释器:

<installation_directory>/bin/irispython

我们来运行一个简单的示例:

# src/python/article/irispython_example.py
import requests
import iris

def run():
    response = requests.get("https://2eb86668f7ab407989787c97ec6b24ba.api.mockbin.io/")

    my_dict = response.json()

    for key, value in my_dict.items():
        print(f"{key}: {value}")  # print message: Hello World

    return my_dict

if __name__ == "__main__":
    print(f"Iris version: {iris.cls('%SYSTEM.Version').GetVersion()}")
    run()

您可以使用 irispython解释器运行此脚本:

<installation_directory>/bin/irispython src/python/article/irispython_example.py

您将看到如下输出:

Iris version: IRIS for UNIX (Ubuntu Server LTS for x86-64 Containers) 2025.1 (Build 223U) Tue Mar 11 2025 18:23:31 EDT
message: Hello World

此例展示了如何使用 irispython 解释器在 IRIS 的运行环境中执行 Python 代码。

优点

  • Python 优先:您可以使用 Python 编写应用程序逻辑,这样,您可以利用 Python 的功能和库。
  • IRIS 集成:您可以轻松将 Python 代码与 IRIS 功能相集成。

缺点

  • 调试受限:在 irispython 中调试 Python 代码并不像在专用 Python 环境中那样简单直接。
    • 这并不是说无法进行调试,而是并不像在专用 Python 环境中那样简单。
    • 如需了解详情,请参阅补充部分
  • 虚拟环境:在 irispython 中为 Python 代码搭建虚拟环境比较困难。
    • 这并不是说无法搭建,只是操作起来比较困难。因为默认情况下,虚拟环境会查找名为 pythonpython3 的解释器,而 IRIS 中的情况并非如此。
    • 如需了解详情,请参阅补充部分

结论

总的来说,使用 irispython 解释器让您既可以利用 Python 编写应用程序逻辑,又能利用 IRIS 的强大功能。 不过,这种方式也存在调试和虚拟环境搭建方面的限制。

使用 WSGI

在本节中,我们将探讨如何使用 WSGI(Web 服务器网关接口)在 IRIS 中运行 Python Web 应用程序。

WSGI 是 Web 服务器与 Python Web 应用程序或框架之间的标准接口。 利用 WSGI,您可以在 Web 服务器环境中运行 Python Web 应用程序。

IRIS 支持 WSGI,这意味着您可以在 IRIS 中使用内置的 WSGI 服务器运行 Python Web 应用程序。

使用方法

要在 IRIS 中使用 WSGI,您需要创建 WSGI 应用程序,并向 IRIS Web 服务器注册此应用程序。

如需了解详情,请参阅官方文档

WSGI 使用示例

有关完整模板,请参见此处:iris-flask-example

优点

  • Python Web 框架:您可以使用流行的 Python Web 框架(如 Flask 或 Django)来构建 Web 应用程序。
  • IRIS 集成:您可以轻松将 Python Web 应用程序与 IRIS 功能相集成。

缺点

  • 复杂程度:构建 WSGI 应用程序会比直接在 Python Web 框架中使用 uvicorngunicorn 复杂一些。

结论

总的来说,在 IRIS 中使用 WSGI 让您既可以利用 Python 构建功能强大的 Web 应用程序,又能利用 IRIS 的功能。

DB-API

在本节中,我们将探讨如何使用 Python DB-API 与 IRIS 数据库进行交互。

Python DB-API 是 Python 中用于连接数据库的标准接口。 利用此接口,您可以执行 SQL 查询,并从数据库中检索结果。

使用方法

您可以使用 pip 进行安装:

pip install intersystems-irispython

随后,您可以使用 DB-API 连接 IRIS 数据库并执行 SQL 查询。

DB-API 使用示例

它的使用方法与其他所有 Python DB-API 相同,示例如下:

# src/python/article/dbapi_example.py
import iris

def run():
    # Connect to the IRIS database
# Open a connection to the server
    args = {
        'hostname':'127.0.0.1', 
        'port': 1972,
        'namespace':'USER', 
        'username':'SuperUser', 
        'password':'SYS'
    }
    conn = iris.connect(**args)

    # Create a cursor
    cursor = conn.cursor()

    # Execute a query
    cursor.execute("SELECT 1")

    # Fetch all results
    results = cursor.fetchall()

    for row in results:
        print(row)

    # Close the cursor and connection
    cursor.close()
    conn.close()
if __name__ == "__main__":
    run()

您可以使用任何 Python 解释器运行此脚本:

python3 /irisdev/app/src/python/article/dbapi_example.py

您将看到如下输出:

(1,)

优点

  • 标准接口:DB-API 提供用于连接数据库的标准接口,因此可以轻松切换不同的数据库。
  • SQL 查询:您可以使用 Python 执行 SQL 查询,并从数据库检索结果。
  • 远程访问:您可以使用 DB-API 连接到远程 IRIS 数据库。

缺点

  • 功能有限:DB-API 仅可通过 SQL 访问数据库,因此,您无法使用高级 IRIS 数据库功能,如执行 ObjectScript 或 Python 代码。

备选方案

还提供社区版 DB-API,参见此处:intersystems-irispython-community

该版本能更好地支持 SQLAlchemy、Django、langchain,以及其他使用 DB-API 的 Python 库。

如需了解详情,请参阅 补充部分

结论

总的来说,将 Python DB-API 与 IRIS 结合使用能够让您构建功能强大的应用程序,实现与数据库的无缝交互。

Notebook

现在,我们已了解如何在 IRIS 中使用 Python,接下来我们将探讨如何将 Jupyter Notebooks 与 IRIS 结合使用。

Jupyter Notebooks 是交互式编写和执行 Python 代码的绝佳方式,并且可与 IRIS 结合使用,以充分利用 IRIS 的功能。

使用方法

要在 IRIS 中使用 Jupyter Notebooks,您需要安装 notebookipykernel 这两个软件包:

pip install notebook ipykernel

然后,您可以创建新的 Jupyter Notebook 并选择 Python 3 内核。

Notebook 使用示例

您可以创建新的 Jupyter Notebook 并编写以下代码:

# src/python/article/my_notebook.ipynb
# Import the necessary modules
import iris
# Do the magic
iris.system.Version.GetVersion()

您可以使用 Jupyter Notebook 运行此 notebook:

jupyter notebook src/python/article/my_notebook.ipynb

优点

  • 交互式开发:利用 Jupyter Notebooks,您可以交互式编写和执行 Python 代码,非常适合数据分析和探索。
  • 丰富的输出:您可以直接在 Notebook 中显示丰富的输出,如图表和表格。
  • 文档:您可以在代码旁添加文档和说明。

缺点

  • 设置有难度:设置将 Jupyter Notebooks 与 IRIS 结合使用存在一定的难度,特别是对于内核配置而言。

结论

总的来说,将 Jupyter Notebooks 与 IRIS 结合使用可以交互式编写和执行 Python 代码,同时利用 IRIS 的功能。 不过,设置起来存在一定的难度,特别是对于内核配置而言。

补充部分

从本节开始,我们将探讨一些与在 IRIS 中使用 Python 相关的高级主题,例如远程调试 Python 代码、使用虚拟环境等。

以下大部分主题均未获得 InterSystems 的官方支持,但如果您要在 IRIS 中使用 Python,了解相关内容会提供很大的帮助。

使用原生解释器(无 irispython

在本节中,我们将探讨如何使用原生 Python 解释器代替 irispython 解释器。

这样一来,您可以直接使用虚拟环境,并使用您习惯的 Python 解释器。

使用方法

要使用原生 Python 解释器,您需要在机器本地安装 IRIS,并需要安装 iris-embedded-python-wrapper 软件包。

您可以使用 pip 进行安装:

pip install iris-embedded-python-wrapper

接下来,您需要设置一些环境变量指向 IRIS 安装目录:

export IRISINSTALLDIR=<installation_directory>
export IRISUSERNAME=<username>
export IRISPASSWORD=<password>
export IRISNAMESPACE=<namespace>

然后,您可以使用您的原生 Python 解释器运行 Python 代码:

python3 src/python/article/irispython_example.py
# src/python/article/irispython_example.py
import requests
import iris

def run():
    response = requests.get("https://2eb86668f7ab407989787c97ec6b24ba.api.mockbin.io/")

    my_dict = response.json()

    for key, value in my_dict.items():
        print(f"{key}: {value}")  # print message: Hello World

    return my_dict

if __name__ == "__main__":
    print(f"Iris version: {iris.cls('%SYSTEM.Version').GetVersion()}")
    run()

如需了解详情,请参阅 iris-embedded-python-wrapper 文档

优点

  • 虚拟环境:您可以将虚拟环境与原生 Python 解释器结合使用,从而可以更加轻松地管理依赖项。
  • 熟悉的工作流:您可以使用习惯的 Python 解释器,从而可以更轻松地与现有工作流相集成。
  • 调试:可以使用您喜欢的 Python 调试工具(如 pdbipdb)在 IRIS 中调试 Python 代码。

缺点

  • 设置的复杂程度:设置环境变量和 iris-embedded-python-wrapper 软件包可能会比较复杂,特别是对于初学者来说。
  • 未获官方支持:此方式未获 InterSystems 的官方支持,因此您可能遇到文档中未记录或不受支持的问题。

DB-API 社区版

在本节中,我们将探讨 GitHub 上提供的社区版 DB-API。

使用方法

您可以使用 pip 进行安装:

pip install sqlalchemy-iris

此代码将安装社区版 DB-API。

或使用特定版本:

pip install https://github.com/intersystems-community/intersystems-irispython/releases/download/3.9.3/intersystems_iris-3.9.3-py3-none-any.whl

然后,您可以使用 DB-API 连接 IRIS 数据库,并执行 SQL 查询或其他任何使用 DB-API 的 Python 代码,如 SQLAlchemy、Django、langchain、pandas 等。

DB-API 使用示例

它的使用方法与其他所有 Python DB-API 相同,示例如下:

# src/python/article/dbapi_community_example.py
import intersystems_iris.dbapi._DBAPI as dbapi

config = {
    "hostname": "localhost",
    "port": 1972,
    "namespace": "USER",
    "username": "_SYSTEM",
    "password": "SYS",
}

with dbapi.connect(**config) as conn:
    with conn.cursor() as cursor:
        cursor.execute("select ? as one, 2 as two", 1)   # second arg is parameter value
        for row in cursor:
            one, two = row
            print(f"one: {one}")
            print(f"two: {two}")

您可以使用任何 Python 解释器运行此脚本:

python3 /irisdev/app/src/python/article/dbapi_community_example.py

也可以使用 sqlalchemy:

from sqlalchemy import create_engine, text

COMMUNITY_DRIVER_URL = "iris://_SYSTEM:SYS@localhost:1972/USER"
OFFICIAL_DRIVER_URL = "iris+intersystems://_SYSTEM:SYS@localhost:1972/USER"
EMBEDDED_PYTHON_DRIVER_URL = "iris+emb:///USER"

def run(driver):
    # Create an engine using the official driver
    engine = create_engine(driver)

    with engine.connect() as connection:
        # Execute a query
        result = connection.execute(text("SELECT 1 AS one, 2 AS two"))

        for row in result:
            print(f"one: {row.one}, two: {row.two}")

if __name__ == "__main__":
    run(OFFICIAL_DRIVER_URL)
    run(COMMUNITY_DRIVER_URL)
    run(EMBEDDED_PYTHON_DRIVER_URL)

您可以使用任何 Python 解释器运行此脚本:

python3 /irisdev/app/src/python/article/dbapi_sqlalchemy_example.py

您将看到如下输出:

one: 1, two: 2
one: 1, two: 2
one: 1, two: 2

优点

  • 更好的支持:对 SQLAlchemy、Django、langchain 以及其他使用 DB-API 的 Python 库提供更好的支持。
  • 依托于社区:它由社区维护,这意味着随着时间的推移,可能会对其进行更新和改进。
  • 兼容性:它兼容官方 InterSystems DB-API,因此您可以在官方版与社区版之间轻松切换。

缺点

  • 速度:社区版的优化程度可能不如正式版高,某些场景下可能会导致速度变慢。

在 IRIS 中调试 Python 代码

在本节中,我们将探讨如何在 IRIS 中调试 Python 代码。

默认情况下,无法在 IRIS 中调试 Python 代码(在包含语言标签或 %SYS.Python 的 objectscript 中),但可以通过社区解决方案在 IRIS 中调试 Python 代码。

使用方法

先安装 IoP 基于 Python 的互操作性

pip install iris-pex-embedded-python
iop --init

此代码将安装 IoP 和新的 ObjectScript 类,以便您可以在 IRIS 中调试 Python 代码。

然后,您可以使用 IOP.Wrapper 类包装 Python 代码并实现调试。

Class Article.DebuggingExample Extends %RegisteredObject
{
ClassMethod Run() As %Status
{
    set myScript = ##class(IOP.Wrapper).Import("my_script", "/irisdev/app/src/python/article/", 55550) // Adjust the path to your module
    Do myScript.run()
    Quit $$$OK
}
}

然后,向 launch.json 文件添加以下配置,将 VsCode 配置为使用 IoP 调试器:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Python in IRIS",
            "type": "python",
            "request": "attach",
            "port": 55550,
            "host": "localhost",
            "pathMappings": [
                {
                    "localRoot": "${workspaceFolder}/src/python/article",
                    "remoteRoot": "/irisdev/app/src/python/article"
                }
            ]
        }
    ]
}

现在,您可以运行用于导入 Python 模块的 ObjectScript 代码,然后将 VsCode 中的调试器关联到端口 55550

您可以使用以下命令运行此脚本:

iris session iris -U IRISAPP '##class(Article.DebuggingExample).Run()'

然后,您可以在 Python 代码中设置断点,调试器将在这些断点处停止执行,以便您检查变量并单步执行代码。

远程调试实际运作视频(针对 IoP,但原理是相同的):

Python 代码中还提供回溯信息,这对调试非常有用。

启用回溯时:

Traceback enabled

禁用回溯时:

Traceback disabled

优点

  • 远程调试:您可以远程调试在 IRIS 中运行的 Python 代码,在我看来,这是一项革命性的功能。
  • Python 调试功能:您可以使用所有 Python 调试功能,例如断点、变量检查和单步执行代码。
  • 回溯:您可以看到 Python 代码中错误的完整回溯信息,这对调试非常有用。

缺点

  • 设置的复杂程度:设置 IoP 和调试器可能会比较复杂,特别是对于初学者来说。
  • 社区解决方案:该解决方案属于社区解决方案,因此可能不像官方解决方案那样稳定,文档也可能不完善。

结论

总的来说,在 IRIS 中调试 Python 代码可以通过 IoP 社区解决方案来实现,借助该解决方案,您可以使用 Python 调试器调试在 IRIS 中运行的 Python 代码。 不过,此解决方案需要执行一些设置操作,并可能不像官方解决方案一样稳定。

IoP(基于 Python 的互操作性)

在本节中,我们将探讨 IoP(基于 Python 的互操作性)解决方案,利用该解决方案,您可以使用 Python 优先的方式在 IRIS 中运行 Python 代码。

我开发这个解决方案已经有一段时间了,可以说它是我的心血之作,该解决方案尝试解决或改善我们在本系列文章中提到的所有问题。

IoP 的要点:

  • Python 优先:您可以使用 Python 编写应用程序逻辑,这样,您可以利用 Python 的功能和库。
  • IRIS 集成:您可以轻松将 Python 代码与 IRIS 功能相集成。
  • 远程调试:您可以远程调试在 IRIS 中运行的 Python 代码。
  • 回溯:您可以看到 Python 代码中错误的完整回溯信息,这对调试非常有用。
  • 虚拟环境:支持虚拟环境功能,因此您可以更加轻松地管理依赖项。

要详细了解 IoP,您可以查阅官方文档

然后,您可以阅读以下文章详细了解 IoP:

🐍❤️如您所见,通过 IoP 这一功能强大的方法,我们可以将 Python 与 IRIS 相集成,从而可以更轻松地开发和调试应用程序。

您无需继续使用 irispython,也不必手动设置 sys.path,而是可以使用虚拟环境,并且可以调试在 IRIS 中运行的 Python 代码。

结论

希望大家喜欢这一系列关于在 IRIS 中使用 Python 的文章。

如果您对这一系列的文章有任何疑问或反馈,请随时联系我。

祝您在 IRIS 中使用 Python 时一切顺利!

0
0 18
文章 Lilian Huang · 九月 19, 2025 5m read

image

您知道当您拿到验血结果时一切看起来都像天书的那种感觉吗? 这就是 FHIRInsight 要解决的问题。 它最初的理念是,医疗数据不应该令人恐惧或困惑 – 它应该是我们所有人都能使用的东西。 验血是健康检查中十分常见的检查,但说实话,大多数人都很难理解它们,有时甚至对不擅长实验室工作的医务人员来说也是如此。 FHIRInsight 希望整个过程能够变得更简单,信息更富有实用价值。

FHIRInsight logo

🤖我们为什么要构建 FHIRInsight

这一切都始于一个简单而有力的问题:

“为什么验血结果仍然很难读懂 — 有时甚至对医生来说也是如此?”

如果您看过化验结果,您可能会看到一大堆数字、隐晦的缩写和“参考范围”,这些可能适用于您的年龄、性别或身体状况,也可能不适用。 毫无疑问,它是一种诊断工具,但如果没有背景信息,它就变成了一个猜谜游戏。 即使是经验丰富的医疗保健专业人员有时也需要交叉参考指导方针、研究论文或专家意见才能理解所有内容。

这正是 FHIRInsight 的用武之地。

我们不只是为患者而构建,也为一线医护人员而构建。 为轮流值班的医生,为捕捉生命体征细微变化的护士,为每一位试图在有限的时间和巨大的责任下做出正确决定的医护人员而构建。 我们的目标是让他们的工作简单一点,将密集的临床 FHIR 数据转化为清晰、有用、以真正的医学科学为基础的东西, 讲人类语言的东西。

FHIRInsight 不仅仅是解释化验结果。 它还:

  • 提供化验结果是轻度、中度还是重度的情境建议
  • 根据临床症状提出潜在病因和鉴别诊断
  • 提出下一步行动建议 — 是后续检查、转诊还是紧急护理
  • 利用 RAG(检索增强生成)拉取相关科学文章,为分析提供支持

想象一下,一位年轻的医生正在查看患者的贫血检查结果。 他们不需要在 Google 上搜索每一个异常值或翻阅医学期刊,而是收到一份报告,上面不仅总结了问题,还引用了最近的研究或世界卫生组织的指导方针来支持这一推理。 这就是将 AI针对精选研究的矢量搜索相结合的力量。

那患者呢?

他们再也不用盯着满屏的数字,猜想“胆红素 2.3 mg/dL”是什么意思,或者他们是否应该担心了。 他们会得到简单、周全的解释。 感觉更像是一场对话,而不是一份临床报告。 一些他们能真正理解的东西 — 并与他们的医生进行讨论,让人感觉更有准备,不那么焦虑。

因为这就是 FHIRInsight 的真正意义**:将复杂的医疗数据转化为清晰的见解**,帮助医疗保健专业人员和患者共同制定更好、更自信的决策

🔍 表象之下

当然,所有这些表面上的简单,背后可能由一些默默运行的强大技术提供支持。

以下是 FHIRInsight 的构建基础:

  • FHIR (Fast Healthcare Interoperability Resources) — 这是健康数据的全球标准。 它是我们接收化验结果、患者病史、受众特征和诊疗等结构化信息的方式。 FHIR 是医疗系统使用的语言,我们将这种语言翻译成人们可以真正使用的东西。
  • RAG(检索增强生成)的矢量搜索:FHIRInsight 通过在使用 InterSystems IRIS 原生矢量搜索的矢量数据库中建立科学 PDF 论文和可信 URL 索引,增强其诊断推理能力。 当化验结果看起来模棱两可或差别细微时,系统会检索相关内容来支持其建议,它不是从记忆库中进行检索,而是从真实的、最新的研究中进行检索。
  • 医学推理提示工程:我们对提示进行了微调,以指导 LLM 识别各种血液相关疾病。 无论是缺铁性贫血、凝血功能障碍、激素失衡还是自身免疫触发因素,提示都会引导 LLM 了解症状、检验室模式和可能病因的变化。
  • LiteLLM 集成:自定义适配器通过统一的接口将请求路由到多个 LLM 提供程序(OpenAI、Anthropic、Ollama 等),从而轻松实现回退、流式传输和模型切换。

无论您是查看 30 个患者图表的医生,还是想要理解数字含义的患者,都可以在几秒钟内将原始的化验数据转化为可解释、富有实用价值的医学见解

🧩 创建 LiteLLM 适配器:使用一个接口管理所有模型

在后台,FHIRInsight 的 AI 赋能报告由 LiteLLM 驱动,后者是一个出色的抽象层,可以使我们通过一个 OpenAI 风格的界面调用 100 多个 LLM(OpenAI、Claude、Gemini、Ollama 等)。

但是要想将 LiteLLM 集成到 InterSystems IRIS 中,需要比隐藏在业务操作中的 Python 脚本更持久、更能重复使用的东西。 所以,我们创建了自己的 LiteLLM 适配器

认识 LiteLLMAdapter

此适配器类可以处理您希望从一个强大的 LLM 集成中获得的所有东西:

  • 接受 promptmodeltemperature 等参数
  • 动态加载环境变量(例如,API 密钥)

为了将其嵌入我们的互操作性生产中,我们将其包装在一个专门的业务操作中:

  • 通过标准的 LLMModel 设置处理生产配置
  • 与 FHIRAnalyzer 组件集成,以实时生成报告
  • 作为未来任何需要访问 LLM 的组件的中心“AI 桥”

以下是简化的核心流程:

set response = ##class(dc.LLM.LiteLLMAdapter).CallLLM("Tell me about hemoglobin.", "openai/gpt-4o", 0.7)
write response

🧭 结语

当我们开始构建 FHIRInsight 时,我们的使命很简单**:让验血结果对每个人来说都更容易理解**。 不仅仅是患者,还有医生、护士、护理人员… 任何曾经盯着化验结果苦思冥想的人,“好吧,这到底是什么意思?”

我们都有过这样的经历。

通过融合 FHIR 的结构、InterSystems IRIS 的速度、LLM 的智能,以及通过矢量搜索实现真实医学研究的深度,我们创造了一个可以将令人困惑的数字转化成有意义的叙述的工具。 帮助人们对自己的健康做出更明智的决定,甚至可能及早发现一些被忽视的疾病。

但 FHIRInsight 不仅仅与数据相关。 它还与我们查看数据时的感受相关。 我们希望它给人一种清晰、支持和赋能的感觉。 我们希望这种体验… 有点像**“氛围编程”医疗保健** — 在智能的代码、优秀的设计和人类同理心方面达到最佳平衡点。

我们希望您能尝试它,打破它,质疑它,并帮助我们改进它。

告诉我们您接下来想看到什么。 更多条件? 更具可解释性? 更加个性化?

这只是一个开端 — 我们希望您能帮助塑造它的未来。

0
0 30
InterSystems 官方 Claire Zheng · 六月 19, 2025 4m read

互操作性用户界面现在包括可以在所有互操作性产品中使用的 DTL 编辑器生产配置应用程序的现代化用户体验。您可以在现代化视图与标准视图之间切换。所有其他互操作性屏幕仍采用标准用户界面。请注意,仅对这两个应用程序进行了更改,我们在下面确定了当前可用的功能。

要在升级前试用新屏幕,您可以点击这里,从我们的社区工具包网页中下载 2025.1 版:https://evaluation.intersystems.com/Eval/。请观看“学习服务”中的简短教程构建集成:一种新的用户体验,了解对这些屏幕进行的用户增强!

生产配置 - 配置任务简介
  • 生产配置:在以下版本的生产配置中受支持:
    • 创建/编辑/复制/删除主机
    • 停止/启动主机
    • 编辑生产设置
    • 停止/启动生产
  • 源代码控制集成:支持上述配置功能的源代码控制集成。
  • 分屏 视图:用户可以直接从“生产配置”屏幕打开“规则编辑器”和“DTL 编辑器”,在分屏视图中查看和编辑产品中包含的规则和转换。
  • 增强的筛选功能:使用顶部的搜索框,您可以搜索和筛选各种业务组件,包括多种类别、DTL 和子转换。 使用左侧边栏可以独立于主面板进行搜索,查看各种主机和类别中的搜索结果。
  • 批量编辑主机类别:通过从生产配置中添加主机,您可以为生产添加新类别或编辑现有类别。
  • 可展开路由器:可以展开路由器,内联查看所有规则、转换和连接。
  • 重新设计的主机连接:现在,在选择业务主机时,将呈现直接连接和间接连接,您可以查看消息能够采用的完整路径。 将鼠标悬停在任何出站或入站主机上可以进一步区分连接。如果开启仅显示连接的主机开关,将仅筛选所选主机及其连接。
DTL 编辑器 - DTL 工具简介
  • 源代码控制集成:支持源代码控制集成。
  • VS Code 集成:用户可以在其 VS Code IDE 中查看此版本的 DTL 编辑器。
  • 嵌入式 Python 支持:此版本的 DTL 编辑器现在支持嵌入式 Python。
  • DTL 测试:可以在此版本的 DTL 编辑器中使用 DTL 测试实用工具。
  • 切换面板布局:DTL 编辑器支持侧面到侧面和顶部到底部布局。 点击顶部功能区的布局按钮可以体验此功能。
  • 撤消/重做:用户可以使用撤消/重做按钮撤消和重做所有尚未保存为代码的操作。
  • “生成空段”参数:GENERATEEMPTYSEGMENTS 参数可用于为缺失的字段生成空段。
  • 子转换查看:用户可以点击眼睛图标,在新选项卡中打开子转换 DTL,查看子转换。
  • 滚动
    • 单独滚动:将光标放置在 DTL 的左右两部分(源和目标)其中之一的上方,并用滚轮或触控板垂直移动各段,可以单独滚动各个部分。
    • 联合滚动:将光标放置在图的中间,可以联合滚动源部分和目标部分。
  • 字段自动补全:自动补全适用于:“源”、“目标”和“条件”字段以及源类、源文档类型、目标类、目标文档类型。
  • 顺序编号:使用可视化编辑器,您可以打开和关闭查看每个段的序数和完整路径表达式的功能。
  • 轻松引用:当操作编辑器中的某个字段获得焦点时,在图形化编辑器中双击某个段会在操作编辑器中的当前光标位置插入相应的段引用。
  • 同步:点击可视化编辑器中的一个元素,可以在操作编辑器中高亮显示相应的行。
📣号召性用语📣

如果您有任何反馈,请通过以下途径提供给我们:

  • 跨所有互操作性的新功能:在 Ideas 门户中输入想法,或在 InterSystems Ideas 门户中参与其他想法。 对于新想法,请在您的帖子上添加“互操作性”标签或对列表中已提出的功能进行投票!
  • 💻跨所有互操作性的一般用户体验反馈:请在下面输入您的反馈或参与其他评论。
  • 🗒对现代化应用程序的建议/反馈(如上所述):请在下面输入您的反馈或参与其他评论。

请考虑利用 Global Masters 的机会,与团队进行互动,参与不公开的指导反馈会议,并获得积分! 点击此处,通过 Global Masters 报名参加这些会议。

如果您希望以私人形式提供任何其他反馈,请通过电子邮件将您的想法或问题发送至:ux@intersystems.com  
 

0
0 47
文章 Lilian Huang · 六月 3, 2025 2m read

IRIS 支持开箱即用的 CCDA 和 FHIR 转换,但访问和查看这些功能需要大量的时间设置和产品知识。IRIS Interop DevTools 应用程序旨在弥补这一差距,让实施人员能够立即进入并查看产品的内置转换功能。

除了 IRIS XML、XPath 和 CCDA 转换环境,Interop DevTools 软件包现在还提供:

  • FHIR-SDA 转换设置
  • SDA-FHIR 转换设置
  • 构建 FHIR 错误验证
  • 加载 FHIR 转换所需的内容

已经更新仪表板的外观和感触,看起来更加直观和用户友好。在 IRIS 中执行,以便充分利用环境,同时用户界面允许可见性、可重复性以及隔离修改和模块进行测试的能力。

以下是5个功能支持:

1. XPath 评估器: 根据输入 CCD 评估 XPath 并返回结果

2. CCDA 到 SDA 转换: 通过选定的基本 XSL 转换运行输入的 CCD,并显示 SDA 结果。

3. XSL 模板测试器: 针对输入 CCD 应用单个 XSL 模板,并显示生成的 CCD。

4. FHIR 到 SDA 转换: 在输入的 FHIR 资源或捆绑包上运行标准的 FHIR 到 SDA 转换,并显示 SDA 结果或 FHIR 验证错误响应。

5. 5. SDA 到 FHIR 转换: 在输入的 SDA 消息上运行标准的 SDA 到 FHIR 转换,并显示 FHIR 束结果。

0
0 79
文章 Jeff Liu · 五月 22, 2025 5m read

基于 XSLT 互联互通临床文档到 FHIR 资源转换

国家卫健委互联互通成熟度评测中的临床共享文档,作为医疗信息交换的重要载体,采用了XML标准的文档格式。随着医疗信息化的发展,FHIRFast Healthcare Interoperability Resources)作为新一代医疗信息交换标准,因其简洁性、灵活性和RESTful架构,逐渐成为医疗数据交换的理想选择。将共享文档文档转换为FHIR资源,能够有效促进不同医疗系统间的数据互通,提升医疗信息的利用价值。

XSLT(可扩展样式表语言转换)是一种用于将XML文档转换为其他XML文档或文本格式的语言。在医疗数据转换场景中,XSLT凭借其强大的XML处理能力,成为共享文档FHIR转换的理想工具。

我们知道共享文档文档是一种结构化的XML文档,通常包含以下主要部分:

- 文档头(Document Header):包含文档元数据,如文档类型、创建时间、作者等

- 临床数据部分(Clinical Sections):按章节组织的临床信息,如问题列表、用药记录、检查报告等

- 数据条目(Entries):具体的临床数据项,如诊断、药物、检验结果等

FHIR则采用了资源导向的设计理念,每个临床概念都被建模为独立的资源,通过RESTful API进行访问。FHIR资源具有以下特点:

0
0 57
文章 Jeff Liu · 五月 15, 2025 1m read

各位开发者,大家好!

或许您不得不实现一些场景,这些场景不需要 FHIR 仓库但需要转发 FHIR 请求、管理响应,并且可能运行转换或在两者之间提取一些值。 在这里,您会找到一些可以使用 InterSystems IRIS For HealthHealthShare Health Connect 实现的示例。

在这些示例中,我使用了具有 FHIR 互操作性适配器HS.FHIRServer.Interop.Request 消息的互操作性生产配置。

第一个场景从头开始构建 FHIR 请求(可以来自文件,也可以来自 SQL 查询),然后将其发送到外部 FHIR 服务。图片

下一个场景是一种 FHIR 传递,用于将请求和响应传递到外部 FHIR 仓库,另外还管理 OAuth 令牌。图片

最后一个场景包括接收 FHIR 请求,然后将其转发到外部 FHIR 服务,但会提取信息或更改其间的某些字段。图片

您将在 Open Exchange 应用程序中找到实现细节 :)

希望这对您有用!

0
0 65
InterSystems 官方 Michael Lei · 一月 7, 2025

我们已经有一段时间没有在开发者社区上发表关于嵌入式 Git 的文章了,我想借此机会更新一下今年我们完成的大量工作以及未来的工作计划。

背景信息

如果您要在 IRIS 上构建解决方案,并想要使用 Git,那就太棒了! 只需将 VSCode 与本地 Git 仓库结合使用,并将更改推送到服务器上即可,就是这么简单。

但在以下使用场景中该怎么办:

  • 您与其他开发者在共享的远程开发环境中合作,并想要避免因同时编辑同一文件而影响彼此的工作
  • 您使用位于管理门户中的编辑器来实现互操作性或业务智能,并希望对您的工作直接进行源代码控制,即使在本地容器中也希望如此
  • 您仍在使用 Studio 处理一些任务并且/或偶尔会再次从 VSCode 跳转回 Studio;或者您的团队还没有完全接受 VSCode,一些团队成员仍想使用 Studio,而其他人在使用 VSCode
  • 您同时在同一命名空间中处理多个独立的项目,例如使用 InterSystems Package Manager 定义的多个软件包,并想仅通过一个 isfs 编辑视图使用所有软件包(而不是在多个单独的项目中使用),同时在合适的本地 git 仓库中自动跟踪更改

在以上任何情况下,您都非常需要嵌入式源代码控制。 您可能听说过“服务器端源代码控制”或“源代码控制挂钩”这类叫法,它们的含义相同,都表示可在所有编辑器(包括 IDE 和图形编辑器)中对 IRIS 实例实现一致的源代码控制行为。 所有控制操作都是针对与您的 IRIS 实例位于同一位置的源代码控制仓库执行的,该仓库可能位于远程服务器上、您自己的机器上,甚至可能位于容器中。

如果您希望开始使用 Git 进行嵌入式源代码控制,可以先从嵌入式 Git (https://github.com/intersystems/git-source-control) 着手。 嵌入式 Git 不是 InterSystems 支持的产品,但得到了 InterSystems 内我的团队(应用程序服务团队)和广大用户社区的大力支持。 我们始终欢迎大家提出 PR 和 GitHub 议题,我们会监控开发者社区活动(特别是使用相对较新的“嵌入式 Git”标签)。

2024 年嵌入式 Git 工作回顾

2024 年初,我曾向已能轻松使用 Git 并愿意在必要时进行深入研究、更偏向技术型的用户推荐嵌入式 Git。 现在,我极力推荐所有人使用嵌入式 Git。 为了展示我们对此工具倾注的心血以及我们在 2024 年取得的飞跃,下方是过去几年的提交量图:

如果您想了解我们的最新动态,可以查看版本或我们的变更日志,但下面是对要点的总结:

  • 7 月,我们添加了“基本模式”(具体说明请参阅此处),其中的“同步”操作可以简化拉取/提交/变基/推送工作流,我们推荐大家在一般情况下使用该模式,尤其是那些不太熟悉 Git 的用户。
  • 在多个版本中,我们将 git 拉取和签出操作(实际上是任何修改 Git 仓库状态的操作)无缝同步回 IRIS,从而无需强制重新加载整个代码库便可确保所有内容均为最新状态。
  • 在我们的 9 月版本中,通过从管理门户下载 VSCode 工作空间文件,您可以非常轻松地建立 isfs 连接。
  • 在我们的 11 月版本中,可以通过扩展设置页面进行更多配置,无需在终端运行命令,并针对互操作性设置中的两种最常见(几乎一定会出现)的用例增加了智能合并冲突解决功能。
  • 除了以上工作之外,我们还解决了数十个小错误和实用性问题,简化了管理门户的导航,并增加了很多小功能,以简化互操作性用例以及跨多个开发者特定命名空间在共享远程实例上的协作(我们认为这是一种常用方式,并推荐大家使用)。

即将推出

下一个版本 (2.8.0) 预计在两周内发布,其中将包含我们历经数月开发出的“生产分解”功能。 互操作性生产通常以单个文件的形式进行源代码控制,由于一次只能由一人编辑生产,这会导致并发问题。 共享开发环境中还存在一些合并问题,这些问题几乎一定会出现,虽然我们可以通过智能化方式解决它们,但在某些分支工作流中,这些问题很可能会继续出现。 利用生产分解功能,每个业务主机(服务、进程或操作)在源代码控制中都会以单个文件的形式表示。 如果您有兴趣尝试此功能,请联系我们!

进入 2025 年,我们将继续保持嵌入式 Git 的发展势头,重点关注其他身份验证方法并支持其他常见部署模式。 您可以访问 2025 H1 里程碑了解我们的大致计划(其中包括几次版本发布;我们通常会每月发布次要版本,并根据需要发布补丁版本修复重要错误)。

如果您有兴趣深入了解开发细节/获取最新开发信息,欢迎加入我们每周五召开的利益相关者会议。 此会议也成为了嵌入式 Git 的一种“办公时间”。 请随时留言告诉我您的电子邮件地址,我会邀请您加入会议。

0
0 95
文章 Michael Lei · 九月 27, 2024 11m read

欢迎来到我的 CI/CD 系列的下一个章节,我们将探讨使用 InterSystems 技术和 GitLab 进行软件开发可以采用的几种方式。

今天,我们来谈谈互操作性。

问题

当您有一个有效的互操作性生产时,您有两个独立的流程:一个是处理消息的可以正常运行的生产流程,另一个是更新代码、生产配置和系统默认设置的 CI/CD 流程。

显然,CI/CD 流程会影响互操作性。 但问题是:

  • 更新期间究竟发生了什么?
  • 我们需要做些什么以在更新期间尽可能缩短或消除生产停机时间?

术语

  • 业务主机 (BH) – 互操作性生产的一个可配置元素:业务服务 (BS)、业务流程(BP、BPL)或业务操作 (BO)。
  • 业务主机作业 (Job) – 运行业务主机代码并由互操作性生产管理的 InterSystems IRIS 作业。
  • 生产 – 业务主机的互联集合。
  • 系统默认设置 (SDS) – 特定于安装 InterSystems IRIS 的环境的值。
  • 有效消息 – 当前正在由某个业务主机作业处理的请求。 一个业务主机作业最多只能有一条有效消息。 没有有效消息的业务主机作业处于空闲状态。

发生了什么?

我们从生产生命周期开始。

生产启动

首先,可以启动生产。 每个命名空间只能同时运行一个生产,通常而言(除非您真正知道自己在做什么以及为什么这样做),每个命名空间内只能运行一个生产。 不推荐在一个命名空间中于两个或多个不同的生产之间来回切换。 启动生产会启动生产中定义的所有已启用的业务主机。 某些业务主机启动失败不会影响生产启动。

提示:

  • 可以从系统管理门户或者调用以下代码来启动生产:##class(Ens.Director).StartProduction("ProductionName")
  • 通过实现 OnStart 方法可以在生产启动时(在启动任何业务主机作业之前)执行任意代码
  • 生产启动是一个可审核事件。 您始终可以在审核日志中查看是谁在何时启动的生产。

生产更新

在生产启动后,Ens.Director 会持续监视正在运行的生产。 生产存在两种状态:目标状态,在生产类和系统默认设置中定义;以及_运行状态_ – 当前运行的作业及其创建时应用的设置。 如果所需状态与当前状态相同,则一切正常;但如果有差异,就可以(也应该)更新生产。 通常,您会在系统管理门户的“生产配置”页面上看到一个红色的 Update 按钮。

更新生产意味着尝试使当前生产状态与目标生产状态匹配。

当您运行 ##class(Ens.Director).UpdateProduction(timeout=10, force=0) 以更新生产时,它会为每个业务主机执行以下操作:

  1. 将有效设置与生产/SDS/类设置进行比较
  2. 当且仅当 (1) 显示不匹配时,业务主机会被标记为过时并需要更新。

为每个业务主机运行此操作后,UpdateProduction 会构建一组更改:

  • 要停止的业务主机
  • 要启动的业务主机
  • 要更新的生产设置

然后,应用这些更改。

通过这种方式,“更新”设置而不更改任何内容不会导致生产停机。

提示:

  • 可以从系统管理门户或者调用以下代码来更新生产:##class(Ens.Director).UpdateProduction(timeout=10, force=0)
  • 默认的系统管理门户更新超时为 10 秒。 如果您知道处理消息需要更长时间,请调用 Ens.Director:UpdateProduction 并设置更长的超时。
  • 更新超时是一个生产设置,您可以将其更改为更大的值。 此设置适用于系统管理门户。

代码更新

UpdateProduction 不会使用过时的代码更新 BH。 这是一种以安全为导向的行为,但如果您想要在底层代码更改时自动更新所有正在运行的 BH,请按以下步骤操作:

首先,按以下方式加载和编译:

do $system.OBJ.LoadDir(dir, "", .err, 1, .load)
do $system.OBJ.CompileList(load, "curk", .errCompile, .listCompiled)

现在,listCompiled 将包含由于 u 标志而实际编译的所有条目(使用 git 差异来尽可能减小加载集)。 使用此 listCompiled 获取所有已编译类的 $lb:

set classList = ""
set class = $o(listCompiled(""))
while class'="" { 
  set classList = classList _ $lb($p(class, ".", 1, *-1))
  set class=$o(listCompiled(class))
}

然后,计算需要重启的 BH 的列表:

SELECT %DLIST(Name) bhList
FROM Ens_Config.Item 
WHERE 1=1
  AND Enabled = 1
  AND Production = :production
  AND ClassName %INLIST :classList

最后,在获取 bhList 后,停止并启动受影响的主机:

for stop = 1, 0 {
  for i=1:1:$ll(bhList) {
    set host = $lg(bhList, i)
    set sc = ##class(Ens.Director).TempStopConfigItem(host, stop, 0)
  }
  set sc = ##class(Ens.Director).UpdateProduction()
}

生产停止

生产可以停止,这意味着向所有业务主机作业发送关闭请求(如果存在,则在它们处理完有效消息后安全地关闭)。

提示:

  • 可以从系统管理门户或者调用以下代码来停止生产:##class(Ens.Director).StopProduction(timeout=10, force=0)
  • 默认的系统管理门户停止超时为 120 秒。 如果您知道处理消息需要更长时间,请调用 Ens.Director:StopProduction 并设置更长的超时。
  • 关闭超时是一个生产设置。 您可以将其更改为更大的值。 此设置适用于系统管理门户。
  • 通过实现 OnStop 方法可以在生产停止时执行任意代码
  • 生产停止是一个可审核事件,您始终可以在审核日志中查看是谁在何时停止的生产。

重要的一点是,生产是业务主机的总和:

  • 启动生产意味着启动所有已启用的业务主机。
  • 停止生产意味着停止所有正在运行的业务主机。
  • 更新生产意味着计算出过时的业务主机的子集,因此首先停止它们,然后立即重新启动。 此外,新增的业务主机只会启动,从生产中删除的业务主机只会停止。

这会将我们带到业务主机的生命周期。

业务主机启动

业务主机由相同的业务主机作业组成(根据池大小设置的值)。 启动业务主机意味着启动所有业务主机作业。 它们会并行启动。

单个业务主机作业的启动方式如下:

  1. 互操作性作业是一个将成为业务主机作业的新进程。
  2. 新进程注册为互操作性作业。
  3. 业务主机代码和适配器代码加载到进程内存中。
  4. 与业务主机和适配器相关的设置加载到内存中。 优先级顺序如下: a. 生产设置(覆盖系统默认设置和类设置)。 b. 系统默认设置(覆盖类设置)。 c. 类设置。
  5. 作业准备就绪并开始接受消息。

在完成 (4) 后,作业无法更改设置或代码,因此当您导入新的/相同的代码和新的/相同的系统默认设置时,它不会影响当前正在运行的互操作性作业。

业务主机停止

停止业务主机作业意味着:

  1. 互操作性命令作业停止接受更多消息/输入。
  2. 如果存在有效消息,业务主机作业具有一定的超时秒数来处理该消息(完成消息会结束 BO 的 OnMessage 方法,BS 的 OnProcessInput,BPL BP 的状态 S<int> 方法以及 BP 的 On* 方法)。
  3. 如果在超时前有效消息未被处理且 force=0,则生产更新会在该业务主机上失败(您会在 SMP 中看到一个红色的 Update 按钮)。
  4. 如果此列表中的任何一项为真,则表示停止成功:
    • 没有有效消息
    • 有效消息在 timeout 之前处理完
    • 有效消息在超时之前未处理完但 force=1
  5. 作业的互操作性取消注册并停止。

业务主机更新

业务主机更新意味着停止当前运行的业务主机作业并启动新作业。

业务规则、路由规则和 DTL

所有业务主机会在新版本的业务规则、路由规则和 DTL 可用时立即开始使用它们。 在这种情况下,不需要重启业务主机。

离线更新

不过,有时生产更新需要单个业务主机停机。

规则取决于新代码

考虑接下来的情况。 您有一个当前的路由规则 X,它根据任意标准将消息路由到业务流程 A 或 B。 在新提交中,您同时添加:

  • 业务流程 C
  • 新版本的路由规则 X,可以将消息路由到 A、B 或 C。

在此场景下,您不能先加载规则,然后再更新生产。 原因在于,新编译的规则会立即开始将消息路由到业务流程 C,而 InterSystems IRIS 可能尚未编译该规则,或者互操作性尚未更新以供使用。 在这种情况下,您需要禁用包含路由规则的业务主机,更新代码,更新生产,然后再次启用业务主机。

注:

  • 如果您使用生产部署文件更新生产,它会自动禁用/启用所有受影响的 BH。
  • 对于 InProc 调用的主机,编译会使调用方持有的特定主机的缓存失效。

业务主机之间的依赖关系

业务主机之间的依赖关系至关重要。 假设您有业务流程 A 和 B,其中 A 向 B 发送消息。 在新提交中,您同时添加:

  • 新版本的流程 A,可以在向 B 发送的请求中设置新属性 X
  • 新版本的流程 B,可以处理新属性 X

在此场景中,我们必须首先更新流程 B,然后更新流程 A。 您可通过以下两种方式之一完成此操作:

  • 在更新期间禁用业务主机
  • 将更新拆分为两步:首先,仅更新流程 B,然后在单独的更新中开始从流程 A 向流程 B 发送消息。

这个主题一个更具挑战性的变体是,新版本的流程 A 和流程 B 与旧版本不兼容,这需要业务主机停机。

队列

如果您知道更新后某个业务主机将无法处理旧消息,则需要确保在更新前该业务主机队列为空。 为此,请禁用所有向该业务主机发送消息的业务主机,并等到其队列变空。

BPL 业务流程中的状态更改

首先,简单介绍一下 BPL BP 的运作方式。 在您编译 BPL BP 后,会在与完整 BPL 类同名的软件包中创建两个类:

  • Thread1 类包含方法 S1、S2、... SN,对应于 BPL 中的活动
  • Context 类包含所有上下文变量,以及 BPL 将执行的下一个状态(即 S5

此外,BPL 类是持久类,可以存储当前正在处理的请求。

BPL 的工作方式是在 Thread 类中执行 S 方法并相应地更新 BPL 类表、Context 表和 Thread1 表,其中一条“正在处理”的消息是 BPL 表中的一行。 请求处理完后,BPL 会删除 BPL、ContextThread 条目。 由于 BPL BP 是异步的,通过在 S 调用之间保存信息并在不同请求之间切换,一个 BPL 作业可同时处理多个请求。 例如,BPL 处理一个请求,直至其到达 sync 活动 – 等待来自 BO 的回答。 它会将当前上下文保存到磁盘中,同时将 %NextState 属性(位于 Thread1 类中)设置为响应活动 S 方法,并在 BO 回答前继续处理其他请求。 BO 回答后,BPL 会将上下文加载到内存中,并执行与 %NextState 属性中保存的状态对应的方法。

现在,当我们更新 BPL 时会发生什么? 首先,我们需要检查是否至少满足以下两个条件之一:

  • 更新期间,上下文表为空,这意味着没有正在处理的有效消息。
  • 新状态与旧状态相同,或者新状态是在旧状态之后添加的。

如果至少满足一个条件,我们就可以开始更新。 要么没有需要更新后 BPL 处理的更新前请求,要么状态是在结束时添加的,这意味着旧请求也可以进入其中(假设更新前请求与更新后 BPL 活动和处理兼容)。

但是,如果您有正在处理的有效请求,而 BPL 更改了状态顺序,该怎么办? 理想情况下,如果您可以等待,则禁用 BPL 调用方并等待队列为空。 验证上下文表是否也为空。 请记住,队列只显示未处理的请求,而上下文表存储正在处理的请求,因此您可能会遇到一个非常繁忙的 BPL 显示零队列大小的情况,这是正常的。 之后,禁用 BPL,执行更新并启用所有之前禁用的业务主机。

如果无法实现(通常是在 BPL 非常长的情况下,例如,我记得我更新过一个花了大约一周时间处理请求的 BPL,或者更新窗口太短),请使用 BPL 版本控制

或者,您也可以编写一个更新脚本。 在此更新脚本中,将旧的后续状态映射到新的后续状态并在 Thread1 表上运行,以便更新的 BPL 可以处理旧请求。 当然,在更新期间必须禁用 BPL。 也就是说,这是一种极为罕见的情况,通常您不必这样做,但如果您需要这样做,方法就是如此。

结论

为了将底层代码更改后实现生产所需的操作数降到最低,互操作性实现了一种复杂的算法。 每次 SDS 更新时,调用具有安全超时的 UpdateProduction。 对于每次代码更新,您都需要决定一种更新策略。

通过使用 git 差异减少编译的代码量有助于缩短编译时间,但利用自身“更新”代码并重新编译它或使用相同的值“更新”设置不会触发或要求进行生产更新。

更新和编译业务规则、路由规则和 DTL 可使它们在未进行生产更新的情况下立即可用。

最后,生产更新是一项安全操作,通常不需要停机。

链接

在本文撰写期间,@James MacKeith@Dmitry Zasypkin@Regilo Regilio Guedes de Souza 提供了宝贵的帮助,作者对此深表感谢。

0
0 117
公告 Claire Zheng · 九月 11, 2024

HI 各位开发者们,

📅2024年9月23日🕑14:00-15:30🕞,InterSystems将举办线上研讨会,点击🔔此处🔔报名参会。

此次研讨会以“面向未来的数据平台——InterSystems IRIS五大亮点提速数据潜力挖掘与AI应用”为主题,帮助您了解InterSystems IRIS数据平台的五大亮点:

  • 使用InterSystems IRIS for Health进行FHIR开发
  • 使用Python进行互操作Production开发
  • InterSystems IRIS列存储
  • InterSystems IRIS外部表(Foreign Table)
  • InterSystems IRIS向量和基于向量检索的患者相似度匹配
2
0 115
文章 Michael Lei · 八月 6, 2024 2m read

开发新的互操作性Production时,最初在Production中添加设置是很自然的做法。

不过,一旦要将Production从开发环境移动到测试或其他环境,你就会发现 HTTP 服务器、IP 地址和/或端口之类的设置都需要更改。 为了避免这些设置在后续重新部署时被覆盖,必须将这些设置从Production得设置中移动到系统默认设置(System Default Settings)。

虽然系统默认设置可以手动创建,但是当生产中有大量业务组件时会难以处理。 因此,@Wietze Drost 让我开发一个工具自动执行此流程,通过筛选表达式指定哪些设置必须创建为系统默认设置。

  • 这个表达式可以定义为“*:HTTPServer,SSLConfig*”,其中“\*”表示“为任何主机类名”。 冒号后面是需要移动的设置列表。 所以,这个表达式的意思是“为所有名为 HTTPServer 和 SSLConfig 的设置创建或更新系统默认设置”。
  • 可以定义多个筛选表达式,用分号分隔,例如 "*:HTTPServer,SSLConfig;FullClassName2:xxx,yyy"

根据他的请求,我编写了名为 GetSettingsFromProduction 的类方法,

1
0 95
文章 Jingwei Wang · 六月 23, 2024 8m read

低代码挑战

想象一下那个场景。您正在 Widgets Direct 愉快地工作,这是互联网上首屈一指的小部件和小部件配件零售商。您的老板有一些毁灭性的消息,一些客户可能对他们的小部件不太满意,我们需要一个帮助台应用程序来跟踪这些投诉。为了让事情变得有趣,他希望代码占用非常小,并挑战您使用 InterSystems IRIS 以少于 150 行代码交付应用程序。这可能吗?

免责声明:本文记录了一个非常基本的应用程序的构建,为了简洁起见,省略了安全性和错误处理等细节。该应用程序仅供参考,不得用于任何生产应用。本文使用IRIS 2023.1作为数据平台,并非所描述的所有功能在早期版本中都可用

第 1 步 - 定义数据模型

我们首先定义一个新的干净的命名空间 - 带有代码和数据数据库。虽然所有内容都可以位于 1 个数据库中,但将它们拆分以便于数据刷新。

我们的帮助台系统需要 3 个基本类:一个 Ticket 对象,它可以包含用于记录员工顾问 UserAccount 和客户联系人 UserAccount 之间交互的操作。让我们用一些基本属性来定义它们:

0
0 127
文章 Michael Lei · 二月 18, 2024 11m read

1. IRIS RAG Demo

IRIS RAG Demo

这是 IRIS 与 RAG(检索增强生成)示例的一个简单演示。 后端是使用 IRIS 和 IoP用 Python 编写的,LLM 模型是 orca-mini 并由 ollama 服务器提供。 前端是用 Streamlit 编写的聊天机器人。

    1. IRIS RAG 演示](#1-iris-rag-demo)
    • 1.1. 什么是 RAG](#11-what-is-rag)
    • 1.2. 如何工作?
    • 1.3. 安装演示](#13-installation-the-demo)
    • 1.4. 使用方法
    • 1.5. 演示如何运行](#15-演示如何运行)
      • [1.5.1. 前端](#151-前端)
      • 1.5.2. 后台
        • [1.5.2.1. 业务服务](#1521-业务服务)
        • [1.5.2.2. 业务流程](#1522-业务流程)
        • [1.5.2.3. LLM 操作](#1523-the-llm-operation)
        • 1.5.2.4. 矢量操作](#1524-the-vector-operation)
    • 1.6. 一般性说明](#16-一般性说明)

1.1. 什么是 RAG?

RAG 是 Retrieval Augmented Generation(检索增强生成)的缩写,它带来了使用带有知识库的 LLM 模型(GPT-3.5/4、Mistral、Orca 等)的能力。

为什么它很重要? 因为它允许使用知识库来回答问题,并使用 LLM 来生成答案。

例如,如果你直接向 LLM 询问**"grongier.pex 模块是什么?"**,它将无法回答,因为它不知道这个模块是什么(也许你也不知道🤪)。

但是,如果你向 RAG 提出同样的问题,它就能回答,因为它会使用知识库,知道 grongier.pex 模块是什么,从而找到答案。

既然你已经知道什么是 RAG,那就让我们来看看它是如何工作的。

1.2. 它是如何工作的?

首先,我们需要了解 LLMS 的工作原理。LLMS 经过训练,可以根据前一个单词预测下一个单词。因此,如果你给它一个句子,它就会尝试预测下一个词,以此类推。很简单吧?

要与 LLM 交互,通常需要给它一个提示,它就会生成句子的其余部分。例如,如果你给它一个提示 "什么是 grongier.pex 模块?

很抱歉,我对您提到的 Pex 模块并不熟悉。能否请您提供有关它的更多信息或上下文?

好的,不出所料,它不知道什么是 grongier.pex 模块。但如果我们给它一个包含答案的提示呢?例如,如果我们提示``什么是 grongier.pex 模块?它是一个可以让你做 X、Y 和 Z 的模块。`",它就会生成剩下的句子,看起来就像这样:

grongier.pex 模块是一个可以让你执行 X、Y 和 Z 的模块。

好了,现在它知道什么是 grongier.pex 模块了。

但如果我们不知道 grongier.pex 模块是什么呢?我们怎样才能给它一个包含答案的提示呢? 这就需要知识库了。

RAG

RAG 的整个思路是使用知识库找到上下文,然后使用 LLM 生成答案。

为了找到上下文,RAG 将使用一个**知识库。

1.3.安装演示

只需克隆存储库并运行“docker-compose up”命令即可。

git clone https://github.com/grongierisc/iris-rag-demo
cd iris-rag-demo
docker-compose up

⚠️ 一切都是本地的,没有任何东西发送到云端,所以请耐心等待,可能需要几分钟才能开始。

1.4.用法

演示开始后,您可以在 http://localhost:8051 访问前端。

![Frontend](https://github.com/grongierisc/iris-rag-demo/blob/master/misc/iris_chat.png?raw=true)

你可以提出有关「综合注册资讯系统」的问题,例如:

  • 什么是grongier.pex模块?

![Question](https://github.com/grongierisc/iris-rag-demo/blob/master/misc/without_rag.png?raw=true)

正如你所看到的,答案不是很好,因为 LLM 不知道什么是 grongier.pex 模块。

现在,让我们尝试使用 RAG:

上传“grongier.pex”模块文档,它位于“docs”文件夹中,文件“grongier.pex.md”。

并问同样的问题:

  • 什么是grongier.pex模块?

![Question](https://github.com/grongierisc/iris-rag-demo/blob/master/misc/with_rag.png?raw=true)

正如你所看到的,答案要好得多,因为 LLM 现在知道什么是 grongier.pex 模块。

您可以在日志中看到详细信息:

转到管理门户, http://localhost:53795/csp/irisapp/EnsPortal.ProductionConfig.zen?$NAMESPACE=IRISAPP&$NAMESPACE=IRISAPP&,然后单击“消息”选项卡。

首先,您将看到发送到 RAG 进程的消息:

![Message](https://github.com/grongierisc/iris-rag-demo/blob/master/misc/trace_query.png?raw=true)

然后是知识库(向量数据库)中的搜索查询:

![Message](https://github.com/grongierisc/iris-rag-demo/blob/master/misc/trace_result_vector.png?raw=true)

最后,发送给 LLM 的新提示:

![Message](https://github.com/grongierisc/iris-rag-demo/blob/master/misc/trace_new_query.png?raw=true)

1.5.这个Demo如何工作?

该演示由 3 个部分组成:

  • 前端,用 Streamlit 编写
  • 后端,用 Python 和 IRIS 编写
  • 知识库 Chroma 向量数据库
  • LLM,Orca-mini,由 Ollama 服务器提供服务

1.5.1.前端

前端是用 Streamlit 编写的,它是一个简单的聊天机器人,可让您提出问题。

这里没什么花哨的,只是一个简单的聊天机器人。

import os
import tempfile
import time
import streamlit as st
from streamlit_chat import message

from grongier.pex import Director

_service = Director.create_python_business_service("ChatService")

st.set_page_config(page_title="ChatIRIS")


def display_messages():
    st.subheader("Chat")
    for i, (msg, is_user) in enumerate(st.session_state["messages"]):
        message(msg, is_user=is_user, key=str(i))


def process_input():
    if st.session_state["user_input"] and len(st.session_state["user_input"].strip()) > 0:
        user_text = st.session_state["user_input"].strip()
        with st.spinner(f"Thinking about {user_text}"):
            rag_enabled = False
            if len(st.session_state["file_uploader"]) > 0:
                rag_enabled = True
            time.sleep(1) # help the spinner to show up
            agent_text = _service.ask(user_text, rag_enabled)

        st.session_state["messages"].append((user_text, True))
        st.session_state["messages"].append((agent_text, False))


def read_and_save_file():

    for file in st.session_state["file_uploader"]:
        with tempfile.NamedTemporaryFile(delete=False,suffix=f".{file.name.split('.')[-1]}") as tf:
            tf.write(file.getbuffer())
            file_path = tf.name

        with st.spinner(f"Ingesting {file.name}"):
            _service.ingest(file_path)
        os.remove(file_path)

    if len(st.session_state["file_uploader"]) > 0:
        st.session_state["messages"].append(
            ("File(s) successfully ingested", False)
        )

    if len(st.session_state["file_uploader"]) == 0:
        _service.clear()
        st.session_state["messages"].append(
            ("Clearing all data", False)
        )

def page():
    if len(st.session_state) == 0:
        st.session_state["messages"] = []
        _service.clear()

    st.header("ChatIRIS")

    st.subheader("Upload a document")
    st.file_uploader(
        "Upload document",
        type=["pdf", "md", "txt"],
        key="file_uploader",
        on_change=read_and_save_file,
        label_visibility="collapsed",
        accept_multiple_files=True,
    )

    display_messages()
    st.text_input("Message", key="user_input", on_change=process_input)


if __name__ == "__main__":
    page()

💡 我只是在用 :

_service = Director.create_python_business_service("ChatService")

来创建一个前后端之间的绑定.

ChatService 只是互操作性生产中的简单业务服务BS。

1.5.2.后端

后端是用 Python 和 IRIS 编写的。

它由3个部分组成:

  • 业务服务BS
    • 前端的入口点
  • 业务流程BP
    • 如果需要,在知识库中执行搜索
  • 拖曳业务运营BO
    • 一个用于知识库
      • 摄取文档
      • 搜索文档
      • 清除文件
    • 一个用于LLM大模型
      • 生成答案

1.5.2.1.业务服务BS

业务服务是一个简单的业务服务,它允许:

  • 上传文件
  • 提出问题
  • 清除向量数据库
from grongier.pex import BusinessService

from rag.msg import ChatRequest, ChatClearRequest, FileIngestionRequest

class ChatService(BusinessService):

    def on_init(self):
        if not hasattr(self, "target_chat"):
            self.target_chat = "ChatProcess"
        if not hasattr(self, "target_vector"):
            self.target_vector = "VectorOperation"

    def ingest(self, file_path: str):
        # build message
        msg = FileIngestionRequest(file_path=file_path)
        # send message
        self.send_request_sync(self.target_vector, msg)

    def ask(self, query: str, rag: bool = False):
        # build message
        msg = ChatRequest(query=query)
        # send message
        response = self.send_request_sync(self.target_chat, msg)
        # return response
        return response.response

    def clear(self):
        # build message
        msg = ChatClearRequest()
        # send message
        self.send_request_sync(self.target_vector, msg)

基本上,它只是操作和过程之间的传递。

1.5.2.2.业务流程

业务流程是一个简单的过程,允许在需要时搜索知识库

from grongier.pex import BusinessProcess

from rag.msg import ChatRequest, ChatResponse, VectorSearchRequest

class ChatProcess(BusinessProcess):
    """
    the aim of this process is to generate a prompt from a query
    if the vector similarity search returns a document, then we use the document's content as the prompt
    if the vector similarity search returns nothing, then we use the query as the prompt
    """
    def on_init(self):
        if not hasattr(self, "target_vector"):
            self.target_vector = "VectorOperation"
        if not hasattr(self, "target_chat"):
            self.target_chat = "ChatOperation"

        # prompt template
        self.prompt_template = "Given the context: \n {context} \n Answer the question: {question}"


    def ask(self, request: ChatRequest):
        query = request.query
        prompt = ""
        # build message
        msg = VectorSearchRequest(query=query)
        # send message
        response = self.send_request_sync(self.target_vector, msg)
        # if we have a response, then use the first document's content as the prompt
        if response.docs:
            # add each document's content to the context
            context = "\n".join([doc['page_content'] for doc in response.docs])
            # build the prompt
            prompt = self.prompt_template.format(context=context, question=query)
        else:
            # use the query as the prompt
            prompt = query
        # build message
        msg = ChatRequest(query=prompt)
        # send message
        response = self.send_request_sync(self.target_chat, msg)
        # return response
        return response

这真的很简单,它只是向知识库发送一条消息来搜索文档。

如果 知识库 返回文档,则会使用文档内容作为提示,否则会使用查询作为提示。

1.5.2.3.LLM 操作

LLM 操作是一个简单的操作,可以生成答案。


class ChatOperation(BusinessOperation):

    def __init__(self):
        self.model = None

    def on_init(self):
        self.model = Ollama(base_url="http://ollama:11434",model="orca-mini")

    def ask(self, request: ChatRequest):
        return ChatResponse(response=self.model(request.query))

这一步也很简单,它只是向 LLM 发送一条消息来生成答案。

1.5.2.4.Vector 操作

向量操作是一个简单的操作,允许摄取文档、搜索文档和清除向量数据库。


class VectorOperation(BusinessOperation):

    def __init__(self):
        self.text_splitter = None
        self.vector_store = None

    def on_init(self):
        self.text_splitter = RecursiveCharacterTextSplitter(chunk_size=1024, chunk_overlap=100)
        self.vector_store = Chroma(persist_directory="vector",embedding_function=FastEmbedEmbeddings())

    def ingest(self, request: FileIngestionRequest):
        file_path = request.file_path
        file_type = self._get_file_type(file_path)
        if file_type == "pdf":
            self._ingest_pdf(file_path)
        elif file_type == "markdown":
            self._ingest_markdown(file_path)
        elif file_type == "text":
            self._ingest_text(file_path)
        else:
            raise Exception(f"Unknown file type: {file_type}")

    def clear(self, request: ChatClearRequest):
        self.on_tear_down()

    def similar(self, request: VectorSearchRequest):
        # do a similarity search
        docs = self.vector_store.similarity_search(request.query)
        # return the response
        return VectorSearchResponse(docs=docs)

    def on_tear_down(self):
        docs = self.vector_store.get()
        for id in docs['ids']:
            self.vector_store.delete(id)
        
    def _get_file_type(self, file_path: str):
        if file_path.lower().endswith(".pdf"):
            return "pdf"
        elif file_path.lower().endswith(".md"):
            return "markdown"
        elif file_path.lower().endswith(".txt"):
            return "text"
        else:
            return "unknown"

    def _store_chunks(self, chunks):
        ids = [str(uuid.uuid5(uuid.NAMESPACE_DNS, doc.page_content)) for doc in chunks]
        unique_ids = list(set(ids))
        self.vector_store.add_documents(chunks, ids = unique_ids)
        
    def _ingest_text(self, file_path: str):
        docs = TextLoader(file_path).load()
        chunks = self.text_splitter.split_documents(docs)
        chunks = filter_complex_metadata(chunks)

        self._store_chunks(chunks)
        
    def _ingest_pdf(self, file_path: str):
        docs = PyPDFLoader(file_path=file_path).load()
        chunks = self.text_splitter.split_documents(docs)
        chunks = filter_complex_metadata(chunks)

        self._store_chunks(chunks)

    def _ingest_markdown(self, file_path: str):
        # Document loader
        docs = TextLoader(file_path).load()

        # MD splits
        headers_to_split_on = [
            ("#", "Header 1"),
            ("##", "Header 2"),
        ]

        markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
        md_header_splits = markdown_splitter.split_text(docs[0].page_content)

        # Split
        chunks = self.text_splitter.split_documents(md_header_splits)
        chunks = filter_complex_metadata(chunks)

        self._store_chunks(chunks)

如果文档太大,那么向量数据库将无法存储它们,因此我们需要将它们拆分为块。

如果文档是 PDF,那么我们将使用“PyPDFLoader”来加载 PDF,否则我们将使用“TextLoader”来加载文档。

然后,我们将使用“RecursiveCharacterTextSplitter”将文档拆分为块。

最后,我们将块存储到向量数据库中。

如果文档是 Markdown,那么我们将使用“MarkdownHeaderTextSplitter”将文档拆分为块。我们还使用标题将文档拆分为块。

1.6.A. 总 论

所有这些都可以通过“langchains”来完成,但我想向你展示如何使用互操作性框架来做到这一点。并让每个人都更容易理解它是如何工作的。

1
0 587
文章 Qiao Peng · 一月 31, 2024 21m read

InterSystems IRIS、Health Connect和上一代的Ensemble提供了优秀的互操作架构,但即便有低代码开发能力,很多开发者还是希望能用自己的技术栈语言在InterSystems的产品上开发互操作产品。

考虑到互操作产品本身的开放性要求和各个技术栈背后庞大的生态价值,InterSystems IRIS和Health Connect提供了Production EXtension (PEX)架构,让开发者使用自己的技术栈语言来开发互操作解决方案。目前PEX支持Java、.net、Python。

这里我们介绍使用Java利用PEX进行互操作产品的开发。

一 InterSystems IRIS上使用Java开发的基础

在进入PEX主题前,需要简单介绍一下Java在InterSystems IRIS上开发的各种技术选项,因为PEX也是以这些技术选项为基础的。

0
1 274
文章 Qiao Peng · 十二月 7, 2023 5m read

TCP作为OSI 7层的传输层的通信协议,其使用上不像更上层的通信协议那么方便,因为TCP操作的不是数据包,它操作的是数据流。因此有多种将TCP数据流“解释”为数据包(消息)的方法。

InterSystems IRIS提供了多种TCP适配器,用于不同的“解释”,例如EnsLib.TCP.FramedInboundAdapter使用特定的首尾字符做为分隔、EnsLib.TCP.CountedInboundAdapter使用固定的长度进行分隔...

同时,InterSystems IRIS提供了多种开箱即用的TCP业务服务和业务操作,方便接入和发送TCP数据。这里我们介绍常见的使用特定的首尾字符做为分隔的TCP业务服务和业务操作。

1. 通用TCP业务服务和业务操作

EnsLib.TCP.Framed.PassthroughService和EnsLib.TCP.Framed.PassthroughOperation是一组使用特定的首尾字符做为分隔TCP数据流的通用业务服务和业务操作。EnsLib.TCP.Framed.PassthroughService业务服务会将TCP数据封装在Ens.StreamContainer发送给业务流程或业务操作;而EnsLib.TCP.Framed.PassthroughOperation业务操作发送并接收Ens.StreamContainer类型的数据。

0
0 222
文章 Nicky Zhu · 四月 27, 2021 6m read

根据IDC的报道,超过80%的信息是基于NoSQL的,尤其是文本文件。当数字服务或应用程序不能处理所有这些信息时,企业就会遭受损失。为了面对这一挑战,可以使用OCR技术。OCR使用机器学习和/或训练的图像模式将图像像素转化为文本。这一点很重要,因为许多文件被扫描成PDF格式的图像,或者许多文件中包含有文本的图像。因此,OCR是一个重要的步骤,可以从文件中获得所有可能的数据。

为了实现OCR,可以使用开源解决方案Google Tesseract,这是Python和Java社区中最流行的解决方案。Tesseract支持100多个习语,并且可以用新的模型进行训练以识别车牌、验证码等等。Tesseract是在C++中创建的,可以通过Java套用Tess4J构成一个中介层来使用它。下面的代码展示了调用过程。

2
0 443
文章 Kelly Huang · 七月 12, 2023 4m read

在本文中,我将分享我们在 2023 年全球峰会技术交流室中提出的主题。我和@Rochael Ribeiro  

借此机会,我们就以下话题进行探讨:

  • 用于快速 API 的开放交换工具
  • 开放API规范
  • 传统与快速 Api 开发
  • 复合 API(互操作性)
  • 规范优先或 API 优先方法
  • API 治理和监控
  • 演示(视频)

用于快速 API 的开放交换工具

当我们谈论快速现代 API 开发(Rest / json)时,我们将使用两个 Intersystems Open Exchange 工具:

第一个是用于快速开发 API 的框架,我们将在本文中详细介绍。

https://openexchange.intersystems.com/package/IRIS-apiPub

第二种是使用 Swagger 作为用户界面,用于 IRIS 平台上开发的 Rest API 的规范和文档,以及它们的使用/执行。其运行的基础是开放 API 规范 (OAS) 标准,如下所述:

https://openexchange.intersystems.com/package/iris-web-swagger-ui

 

什么是开放 API 规范 (OAS)?

它是全球范围内用于定义、记录和使用 API 的标准。在大多数情况下,API 甚至在实现之前就已经设计好了。我将在下一个主题中详细讨论它。

0
0 139
文章 Jingwei Wang · 七月 7, 2023 2m read
    主机和备机为什么总是切换,会不会是与仲裁机连接的心跳检测出问题了

    查看镜像状态 打开管理门户 - ‘系统操作’ - ‘镜像监视器’
    如遇到主备切换,可以通过消息日志查看切换原因 打开管理门户 - ‘系统操作’ - ‘系统日志’ - ‘消息日志’      

    基础问题怎么查询

    通过管理门户查看日志 打开管理门户 - ‘系统操作’ - ‘系统日志’ - ‘消息日志’
    通过监控告警日志 告警日志文件:<intall-dir>/mgr/alerts.log文件      

    Production所有组件设置重试间隔、故障超时是什么意思

    重试间隔:与回复代码操作配合使用,当发生回复代码操作中设置的回复发生时,按照重试间隔设置的时间对外部业务系统再次访问。
    故障超时:发生故障时继续传递消息的总秒数,可以与回复代码操作配合使用。      

    有队列出现怎么办

    查看队列的方式 打开管理门户 - ‘Interoperability’ - ‘监视器’ - ‘队列’ 首先,查看队列计数是否是在降低,及时有队列,但是队列在正常下降,此状态为正常情况。 但是,如果队列持续升高,没有任何下降趋势,查看产生队列的相应组件的日志,查看是否是平台内部有异常出现。如果平台内部没有异常,查看第三方下游业务系统是否能正常通信。      

    如何设置重发

0
0 276
文章 Zhang Fatong · 七月 5, 2023 2m read

预测平台入场服务器配置

平台服务器入场配置预测应用,以java连接iris并使用其IntegratedML技术完成对医院门诊量,服务数,消息数,消息保存时间等数据分析,可以在医院集成平台入场前,对医院入场平台所需的服务器配置进行预测,为客户提供便利。

模拟数据

平台服务器入场配置预测应用 使用IntegratedML技术,通过JDBC连接IRIS,并可使用业务流程流转消息。先导入数据,然后初始化模型,对模型进行训练,然后输入相应的条件对服务器配置进行预测。 ImageImageImageImage

操作步骤

    1. 在IRIS中导入代码并进行编译

Image

    1. 启动 Production

Image

  • 3.运行jar包(修改配置文件)
  1. 输入正确的IP端口和命名空间名称 ImageImage

Image

6
0 188
文章 Weiwei Gu · 六月 28, 2023 5m read

1. 区块链

当我写这篇文章时,比特币的价格还不到其成功顶峰时期的五分之一。因此,当我开始向某人讲述我的区块链经历时,我听到的第一句话是毫不掩饰的怀疑:“现在谁需要这个区块链东西?”

没错,区块链炒作已经减弱。然而,它所基于的技术将继续存在并将继续在特定领域使用。互联网通常提供大量描述这些技术的一般用法的材料

0
0 150
文章 Claire Zheng · 六月 8, 2023 1m read

医院管理者、信息中心、业务骨干,每个角色都有独特的业务诉求,InterSystems IRIS医疗版互联互通套件专为国内用户设计,可助力医院用户释放数据价值,推动应用创新!截至目前,InterSystems技术已助力150余家医院通过四级及以上医院信息互联互通标准化成熟度评测,其中,五级乙等用户近30家。

0
0 162
文章 Michael Lei · 六月 8, 2023 2m read

嗨社区!

想与您分享我在Telegram中使用GPT创建“我自己的”聊天的练习

这个应用需要用到 Open Exchange 上的两个组件:@Nikolay SolovyevTelegram Adapter@Kurro LopezIRIS Open-AI 

因此,通过此示例,您可以在 Telegram 中使用 ChatGPT 设置自己的聊天。

让我们看看如何让它发挥作用!

0
0 189
文章 Claire Zheng · 六月 6, 2023 1m read

当数据资产已经成为医院的核心资产,如何有效利用数据核心资产,持续挖掘互联互通平台和各种数据中心的价值,成为医疗信息化工作的重中之重。欢迎扫码注册(或点击链接注册)观看多位大咖针对这一话题的解读。

0
0 128
文章 Claire Zheng · 五月 23, 2023 1m read

实现医疗系统之间的互操作性是一项艰巨的挑战。随着FHIR日渐普及,对FHIR标准的兼容如今已成为许多国家医疗信息系统的全国性强制性要求。FHIR标准最有价值的一方面就是所有内容可被机器读取,使得FHIR应用跨机构使用成为可能,帮助您距离“语义互操作性”更近一步。

0
0 211
文章 Claire Zheng · 五月 23, 2023 1m read

不同系统在“对话”时候,如何让它们明白“带黑色条纹的白马”和“带白色条纹的黑马”是同一种动物(斑马)呢?FHIR是HL7国际(HL7 International)为满足当前医疗数据环境中的互操作性需求而创建的医疗数据标准,如今已成为许多国家医疗信息系统的全国性强制性要求。本视频介绍了FHIR的设计理念和关键构成,包括FHIR资源、FHIR Profile、FHIR扩展等。

0
0 165