发布新帖

Pesquisar

摘要
· 五月 1

InterSystems開発者コミュニティのダイジェスト、4月 2025

4月 2025開発者コミュニティ ニュースレターへようこそ。
全体統計
17 件の新しい投稿が公開 (4月 月):
 12件の新着記事
 5の新しい発表
6 人の新メンバーが参加 (4月 月)
1,490 件の投稿が公開 (全期間)
319 人のメンバーが参加 (全期間)
トップ記事
二要素認証について
Tomoko Furuzono順
今月のトップ作家
記事
#InterSystems IRIS
IRISから外部のWebAPIを呼び出したとき、レスポンス内の全角文字が化ける
Hiroshi Sato順
IRIS for MacにODBCアクセスする方法 その1
Hiroshi Sato順
IRIS for MACにODBCでアクセスする方法その2
Hiroshi Sato順
二要素認証について
Tomoko Furuzono順
ロードバランサやプロキシサーバ経由でアクセスしている接続元クライアントIPを取得する方法
Tomoko Furuzono順
インストールマニフェストを利用したアプリケーションのデプロイ
Tomoko Furuzono順
全角/半角混在文字列から指定された幅の文字列を取得する方法
Tomoko Furuzono順
サーバのIPアドレスやマシン名をオブジェクトスクリプトで取得する
Tomoko Furuzono順
Webゲートウェイの設定値をプログラムで変更する
Tomoko Furuzono順
Iris における Django アプリケーションの実行
Toshihiko Minamoto順
メソッドの実装に使用する言語を明示するーLanguage(メソッド・キーワード)の指定について
Tomoko Furuzono順
ADO.NET Managed Provider を使用してクラスクエリを実行し、結果セットを取得するサンプル
Megumi Kakechi順
お知らせ
4月, 2025Month at a GlanceInterSystems Developer Community
文章
· 四月 30 阅读大约需 17 分钟

OpenAPI Suite - Parte 1

Olá Comunidade,

Gostaria de apresentar meu último pacote OpenAPI-Suite. Este é um conjunto de ferramentas para gerar código ObjectScript a partir de uma especificação OpenAPI versão 3.0..  IEm resumo, estes pacotes permitem:

  • Gerar classes de servidor. É bem parecido com o código gerado por ^%REST mas o valor adicionado é o suporte à versão 3.0.
  • Gerar classes de cliente HTTP.
  • Gerar classes de produção de cliente (business services, business operation, business process, Ens.Request, Ens.Response).
  • Uma interface web para gerar e baixar o código ou gerar e compilar diretamente no servidor.
  • Converter especificações das versões 1.x, 2.x para a versão 3.0.

Visão Geral

O OpenAPI Suite é dividido em vários pacotes e utiliza diferentes bibliotecas da comunidade de desenvolvedores e também serviços REST públicos. Você pode ver no esquema abaixo todos os pacotes que foram desenvolvidos, e as bibliotecas e serviços web utilizados:

Observação: Em caso de problemas ao usar serviços REST públicos, é possível iniciar uma instância Docker do serviço de conversão e validação.

O que cada pacote faz?

O OpenAPI Suite foi projetado em diferentes pacotes para facilitar a manutenção, melhoria e extensão futura. Cada pacote tem um papel. Vamos dar uma olhada!

openapi-common-lib

Isto contém todo o código comum aos outros pacotes. Por exemplo openapi-client-gen e openapi-server-gen aceitam a seguinte entrada para uma especificação OpenAPI:

  • URL
  • Caminho de arquivo
  • %Stream.Object 
  • %DynamicObject
  • Formato YAML
  • Formato JSON
  • OpenAPI versão1.x, 2.x, 3.0.x.

No entanto, apenas uma especificação 3.0.x em um %DynamicObject pode ser processada. O código para a transformação está localizado neste pacote. Ele também contém várias utilidades.

swagger-converter-cli

É uma dependência do openapi-common-lib. Este é um cliente HTTP que utiliza o serviço REST públicoconverter.swagger.iopara converter OpenAPI versão 1.x ou 2.x para a versão 3.0.

swagger-validator-cli

É também uma dependência do openapi-common-lib, mesmo que seu nome seja "validator", ele não é usado para validar a especificação. O  converter.swagger.iofornece o serviço "parse" que permite simplificar a estrutura de uma especificação OpenAPI. Por exemplo: criar uma definição para uma "nested object definition" e substituí-la por um "$ref". Isso reduz o número de casos a serem tratados no algoritmo de geração de código.

openapi-client-gen

Este pacote é dedicado à geração de código do lado do cliente para ajudar os desenvolvedores a consumir serviços REST.

Ele inclui um cliente HTTP simples ou um cliente de Produção(business services, process, operation, Production classes). Originalmente criado para suportar OpenAPI 2.x, foi completamente refatorado para suportar a versão 3.x.

openapi-server-gen

O oposto do openapi-client-gen, é dedicado à geração de código do lado do servidor. Não há interesse na versão 2.0 da especificação porque o ^%RESTexiste, mas o objetivo deste pacote é o suporte à versão 3.0.  

openapi-suite

Ele reúne todos os pacotes acima e fornece uma API REST para:

  • Gerar o código e compilar o código na instância IRIS.
  • Gerar código sem compilar para download apenas.

Uma interface web também é fornecida para consumir esta API REST e, assim, explorar as funcionalidades do OpenAPI Suite.

E as bibliotecas?

Aqui estão algumas das bibliotecas existentes no DC que foram úteis neste desenvolvimento:

objectscript-openapi-definition

Uma biblioteca útil para gerar classes de modelo a partir de uma especificação OpenAPI. Esta é uma parte muito importante deste projeto e eu também sou um contribuidor.

ssl-client

Permite criar configurações SSL. Usado principalmente para criar um nome de configuração "DefaultSSL" para requisições HTTPS.

yaml-utils

No caso de especificação em formato YAML, esta biblioteca é usada para converter para o formato JSON. Essencial neste projeto. A propósito, foi desenvolvida inicialmente para testar a especificação YAML com a versão 1 do openapi-client-gen.

io-redirect

Esta é uma das minhas bibliotecas. Ela permite redirecionar a saída de "write" para um stream, arquivo, global ou variável string. É usada pelo serviço REST para manter um rastreamento dos logs. É inspirada nesta postagem da comunidade.

Instalação IPM

Para instalar a suíte, seu melhor amigo é oIPM (zpm)Existem muitos pacotes e dependências, e usar o IPM é definitivamente conveniente.

zpm "install openapi-suite"
; opcional
; zpm "install swagger-ui"

 

Instalação Docker

Não há nada de especial, este projeto utiliza o intersystems-iris-dev-template.

git clone git@github.com:lscalese/openapi-suite.git
cd openapi-suite
docker-compose up -d

Se você tiver um erro ao iniciar o Iris, talvez seja um problema de permissão no arquivo iris-main.log.

Você pode tentar:

touch iris-main.log && chmod 777 iris-main.log

Observação: Adicionar permissão de leitura e escrita (RW) para o usuário irisowner deve ser suficiente.

Como Usar

O OpenAPI-Suite fornece uma interface web para "Gerar e baixar" ou "Gerar e instalar" código.

A interface está disponível em http://localhost:52796/openapisuite/ui/index.csp (*adapte com o seu número de porta, se necessário).

É muito simples, preencha o formulário:

  1. Nome do pacote da aplicação: este é o pacote usado para as classes geradas. Deve ser um nome de pacote inexistente.
  2. Selecione o que você deseja gerar: Cliente HTTP, Produção Cliente ou Servidor REST.
  3. Selecione o namespace onde o código será gerado. Isso faz sentido apenas se você clicar em "Install On Server"; caso contrário, este campo será ignorado.
  4. Nome da Aplicação Web é opcional e está disponível apenas se você selecionar "Servidor REST" para gerar. Deixe vazio se você não quiser criar uma Aplicação Web relacionada à classe de despacho REST gerada.
  5. O campo Especificação OpenAPI pode ser uma URL apontando para a especificação ou uma cópia/cola da própria especificação (neste caso, a especificação deve estar em formato JSON).

Se você clicar no botão "Download Only", o código será gerado e retornado em um arquivo XML, e então as classes serão excluídas do servidor. O namespace usado para armazenar temporariamente as classes geradas é o namespace onde o OpenAPI-Suite está instalado (por padrão IRISAPP se você usar uma instalação Docker).

No entanto, se você clicar no botão "Install On Server", o código será gerado e compilado, e o servidor retornará uma mensagem JSON com o status da geração/compilação do código e também os logs.

Por padrão, este recurso está desabilitado. Para habilitá-lo, basta abrir um terminal IRIS e:

Set ^openapisuite.config("web","enable-install-onserver") = 1

Explore a API REST do OpenAPI-Suite

O formulário CSP utiliza serviços REST disponíveis emhttp://localhost:52796/openapisuite.

Abra o swagger-ui http://localhost:52796/swagger-ui/index.html  e explore http://localhost:52796/openapisuite/_spec 

Este é o primeiro passo para criar uma aplicação front-end mais avançada com o framework Angular.

Gere código programaticamente

Claro, não é obrigatório usar a interface do usuário. Nesta seção, veremos como gerar o código programaticamente e como usar os serviços gerados.

Todos os trechos de código também estão disponíveis na classe dc.openapi.suite.samples.PetStore.

 

cliente HTTP

Set features("simpleHttpClientOnly") = 1
Set sc = ##class(dc.openapi.client.Spec).generateApp("petstoreclient", "https://petstore3.swagger.io/api/v3/openapi.json", .features)

 

O primeiro argumento é o pacote onde as classes serão geradas, portanto, certifique-se de passar um nome de pacote válido. O segundo argumento pode ser uma URL apontando para a especificação, um nome de arquivo, um stream ou um %DynamicObject. "features" é um array, atualmente apenas os seguintes subscritos estão disponíveis:

simpleHttpClientOnly: se for 1, apenas um cliente HTTP simples será gerado; caso contrário, uma produção também será gerada (comportamento padrão).

compile: se for 0, o código gerado não será compilado. Isso pode ser útil se você quiser gerar código apenas para fins de exportação. Por padrão, compile = 1.

Abaixo está um exemplo de como usar o serviço "addPet" com o cliente HTTP recém-gerado:

Set messageRequest = ##class(petstoreclient.requests.addPet).%New()
Set messageRequest.%ContentType = "application/json"
Do messageRequest.PetNewObject().%JSONImport({"id":456,"name":"Mittens","photoUrls":["https://static.wikia.nocookie.net/disney/images/c/cb/Profile_-_Mittens.jpg/revision/latest?cb=20200709180903"],"status":"available"})
  
Set httpClient = ##class(petstoreclient.HttpClient).%New("https://petstore3.swagger.io/api/v3","DefaultSSL")
; MessageResponse será uma instância de petstoreclient.responses.addPet
Set sc = httpClient.addPet(messageRequest, .messageResponse)
If $$$ISERR(sc) Do $SYSTEM.Status.DisplayError(sc) Quit sc
  
Write !,"Http Status code : ", messageResponse.httpStatusCode,!
Do messageResponse.Pet.%JSONExport()

 

 
Click to show generated classes.

 

Produção de cliente HTTP

Set sc = ##class(dc.openapi.client.Spec).generateApp("petstoreproduction", "https://petstore3.swagger.io/api/v3/openapi.json")

O primeiro argumento é o nome do pacote. Se você testar a geração de código de um cliente HTTP simples e de uma produção de cliente, certifique-se de usar um nome de pacote diferente. O segundo e o terceiro seguem as mesmas regras do cliente HTTP.

Antes de testar, inicie a produção através do portal de gerenciamento usando este comando:

Do ##class(Ens.Director).StartProduction("petstoreproduction.Production")

Abaixo está um exemplo de como usar o serviço "addPet", mas desta vez com a produção gerada:

Set messageRequest = ##class(petstoreproduction.requests.addPet).%New()
Set messageRequest.%ContentType = "application/json"
Do messageRequest.PetNewObject().%JSONImport({"id":123,"name":"Kitty Galore","photoUrls":["https://www.tippett.com/wp-content/uploads/2017/01/ca2DC049.130.1264.jpg"],"status":"pending"})
; MessageResponse será uma instâncoa de petstoreclient.responses.addPet
Set sc = ##class(petstoreproduction.Utils).invokeHostSync("petstoreproduction.bp.SyncProcess", messageRequest, "petstoreproduction.bs.ProxyService", , .messageResponse)
Write !, "Take a look in visual trace (management portal)"
If $$$ISERR(sc) Do $SYSTEM.Status.DisplayError(sc)
Write !,"Http Status code : ", messageResponse.httpStatusCode,!
Do messageResponse.Pet.%JSONExport()

Agora, você pode abrir o rastreamento visual para ver os detalhes:

As classes geradas nos pacotes model, requests e responses são bem semelhantes ao código gerado para um cliente HTTP simples. As classes do pacote requests herdam de Ens.Request, e as classes do pacote responses herdam de Ens.Response. A implementação padrão da operação de negócio é muito simples, veja este trecho de código:

Class petstoreproduction.bo.Operation Extends Ens.BusinessOperation [ ProcedureBlock ]
{

Parameter ADAPTER = "EnsLib.HTTP.OutboundAdapter";
Property Adapter As EnsLib.HTTP.OutboundAdapter;
/// Implementar operationId : addPet
/// post /pet
Method addPet(requestMessage As petstoreproduction.requests.addPet, Output responseMessage As petstoreproduction.responses.addPet) As %Status
{
    Set sc = $$$OK, pHttpRequestIn = ##class(%Net.HttpRequest).%New(), responseMessage = ##class(petstoreproduction.responses.addPet).%New()
    $$$QuitOnError(requestMessage.LoadHttpRequestObject(pHttpRequestIn))
    $$$QuitOnError(..Adapter.SendFormDataArray(.pHttpResponse, "post", pHttpRequestIn, , , ..Adapter.URL_requestMessage.%URL))
    $$$QuitOnError(responseMessage.LoadFromResponse(pHttpResponse, "addPet"))
    Quit sc
}
...
}
}

 

Serviço REST HTTP do lado do servidor

Set sc = ##class(dc.openapi.server.ServerAppGenerator).Generate("petstoreserver", "https://petstore3.swagger.io/api/v3/openapi.json", "/petstore/api")

O primeiro argumento é o nome do pacote para gerar as classes. O segundo segue as mesmas regras do cliente HTTP. O terceiro argumento não é obrigatório, mas se presente, uma aplicação web será criada com o nome fornecido (tenha cuidado ao fornecer um nome de aplicação web válido).

A classe petstoreserver.disp (dispatch, despacho, %CSP.REST class) parece um código gerado por ^%REST, realizando muitas verificações para aceitar ou rejeitar a requisição e chamando a implementação do ClassMethod de serviço correspondente em petstoreserver.impl. A principal diferença é o argumento passado para o método de implementação, que é um objeto petstoreserver.requests. Exemplo:

Class petstoreserver.disp Extends %CSP.REST [ ProcedureBlock ]
{

Parameter CHARSET = "utf-8";
Parameter CONVERTINPUTSTREAM = 1;
Parameter IgnoreWrites = 1;
Parameter SpecificationClass = "petstoreserver.Spec";
/// Processar request post /pet
ClassMethod addPet() As %Status
{
    Set sc = $$$OK
    Try{
        Set acceptedMedia = $ListFromString("application/json,application/xml,application/x-www-form-urlencoded")
        If '$ListFind(acceptedMedia,$$$LOWER(%request.ContentType)) {
             Do ##class(%REST.Impl).%ReportRESTError(..#HTTP415UNSUPPORTEDMEDIATYPE,$$$ERROR($$$RESTContentType,%request.ContentType)) Quit
        }
        Do ##class(%REST.Impl).%SetContentType($Get(%request.CgiEnvs("HTTP_ACCEPT")))
        If '##class(%REST.Impl).%CheckAccepts("application/xml,application/json") Do ##class(%REST.Impl).%ReportRESTError(..#HTTP406NOTACCEPTABLE,$$$ERROR($$$RESTBadAccepts)) Quit
        If '$isobject(%request.Content) Do ##class(%REST.Impl).%ReportRESTError(..#HTTP400BADREQUEST,$$$ERROR($$$RESTRequired,"body")) Quit
        Set requestMessage = ##class(petstoreserver.requests.addPet).%New()
        Do requestMessage.LoadFromRequest(%request)
        Set scValidateRequest = requestMessage.RequestValidate()
        If $$$ISERR(scValidateRequest) Do ##class(%REST.Impl).%ReportRESTError(..#HTTP400BADREQUEST,$$$ERROR(5001,"Invalid requestMessage object.")) Quit
        Set response = ##class(petstoreserver.impl).addPet(requestMessage)
        Do ##class(petstoreserver.impl).%WriteResponse(response)
    } Catch(ex) {
        Do ##class(%REST.Impl).%ReportRESTError(..#HTTP500INTERNALSERVERERROR,ex.AsStatus(),$parameter("petstoreserver.impl","ExposeServerExceptions"))
    }
    Quit sc
}
...
}

Como você pode ver, a classe de despacho chama “LoadFromRequest” e “RequestValidate” antes de invocar o método de implementação. Esses métodos possuem uma implementação padrão, mas o gerador de código não consegue cobrir todos os casos. Atualmente, os casos mais comuns são tratados automaticamente como parâmetros em "query" (consulta), "headers" (cabeçalhos), "path" (caminho) e no corpo (body) com os tipos de conteúdo “application/json”, “application/octet-stream” ou “multipart/form-data”. O desenvolvedor precisa verificar a implementação para checar/completar se necessário (por padrão, o gerador de código define $$$ThrowStatus($$$ERROR($$$NotImplemented)) para casos não suportados).

 

 
Exemplo de classe de requisição:

Assim como no uso de ^%REST, a classe "petstoreserver.impl" contém todos os métodos relacionados aos serviços que o desenvolvedor precisa implementar.

Class petstoreserver.impl Extends %REST.Impl [ ProcedureBlock ]
{

Parameter ExposeServerExceptions = 1;
/// Implementação do serviço para post /pet
ClassMethod addPet(messageRequest As petstoreserver.requests.addPet) As %Status
{
    ; Implemente seu serviço aqui
    ; Retorne {}
    $$$ThrowStatus($$$ERROR($$$NotImplemented))
}

...
}

 

Breve descrição dos pacotes gerados:

Nome do Pacote \ Nome da Classe

Tipo

Descrição

petstoreclient.model


petstoreproduction.model

Client-side e server-side

Ele contém todos os modelos. Essas classes estendem %JSON.Adaptor para facilitar o carregamento de objetos a partir de JSON.

Se uma produção for gerada, essas classes também estenderão %Persistent.

petstoreclient.requests

 


petstoreproduction.requests

Client-side e server-side

Objeto usado para inicializar facilmente %Net.HttpRequest. Existe uma classe por operação definida na especificação.

Se a produção for gerada, essas classes estenderão Ens.Request.

Nota: A implementação desta classe é diferente se for gerada para propósitos do lado do servidor ou do lado do cliente. No caso do lado do cliente, todas as classes contêm um método "LoadHttpRequestObject" permitindo carregar um "%Net.HttpRequest" a partir das propriedades desta classe.

Se as classes forem geradas para propósitos do lado do servidor, cada classe contém um método "LoadFromRequest" para carregar a instância a partir do objeto "%request".

petstoreclient.responses


petstoreproduction.responses

Client-side e server-side

É o oposto de petstoreclient.requests. Ele permite manipular a resposta de um %Net.HttpRequest.

Se a produção for gerada, essas classes estenderão Ens.Response.

petstoreclient.HttpClient

Client-side

Contém todos os métodos para executar requisições HTTP; existe um método por operação definida na especificação OpenAPI.

petstoreproduction. bo.Operation

Client-side

A classe de Operação possui um método por operação definida na especificação OpenAPI.

petstoreproduction.bp

Client-side

Dois processos de negócio padrão são definidos: síncrono e assíncrono.

petstoreproduction.bs

Client-side

Contém todos os serviços de negócio vazios para implementar.

petstoreproduction.Production

Client-side

Configuração de produção.

petstoreserver.disp

Server-side

Classe de despacho %CSP.REST.

petstoreserver.Spec

Server-side

Esta classe contém a especificação OpenAPI em um bloco XData.

petstoreserver.impl

Server-side

Ele contém todos os métodos vazios relacionados às operações definidas na especificação OpenAPI. Esta é a classe (que estende %REST.Impl) onde os desenvolvedores precisam implementar os serviços.

Status de desenvolvimento

O OpenAPI-Suite ainda é um produto muito novo e precisa ser mais testado e então aprimorado. O suporte ao OpenAPI 3 é parcial, mais possibilidades poderiam ser suportadas.

Os testes foram realizados com a especificação públicahttps://petstore3.swagger.io/api/v3/openapi.json e outras duas relativamente simples. Obviamente, isso não é suficiente para cobrir todos os casos. Se você tiver alguma especificação para compartilhar, ficaria feliz em usá-las para meus testes.

Acredito que a base do projeto é boa e ele pode evoluir facilmente, por exemplo, ser estendido para suportar AsyncAPI.

Não hesite em deixar feedback.

Espero que você goste desta aplicação e que ela mereça seu apoio para o concurso de ferramentas para desenvolvedores.

Obrigado por ler.

讨论 (0)1
登录或注册以继续
文章
· 四月 30 阅读大约需 8 分钟

Using vector search for duplicate patient detection

I recently had to refresh my knowledge of the HealthShare EMPI module and since I've been tinkering with IRIS's vector storage and search functionalities for a while I just had to add 1 + 1.

For those of you who are not familiar with EMPI functionality here's a little introduction:

Enterprise Master Patient Index

In general, all EMPIs work in a very similar way, ingesting information, normalizing it and comparing it with the data already present in their system. Well, in the case of HealthShare's EMPI, this process is known as NICE:

  • Normalization: all texts ingested from interoperability production are normalized by removing special characters.
  • Indexing: indexes are generated from a selection of demographic data to speed up the search for matches.
  • Comparison: the matches found in the indexes are compared between demographic data and weights are assigned based on criteria according to the level of coincidence.
  • Evaluation: the possibility of linking patients is evaluated with the sum of the weights obtained.

If you want to know more about HealthShare Patient Index you can review a serie of articles that I wrote some time ago here.

What is the challenge?

While the setup process to obtain the possible links is not extremely complicated, I wondered... Would it be possible to obtain similar results by making use of the vector storage and search functionalities and saving the weight adjustment steps? Let's get to work!

What do I need to implement my idea?

I will need the following ingredients:

  • IRIS for Health to implement the functionality and make use of the interoperability engine for HL7 messaging ingestion.
  • Python library to generate the vectors from the demographic data, in this case it will be sentence-transformers.
  • Model for generating the embeddings, in a quick search on Hugging Faces I have chosen all-MiniLM-L6-v2.

Interoperability Production

The first step will be to configure the production in charge of ingesting the HL7 messaging, transforming it into messages with the most relevant demographic data of the patient, generating the embeddings from said demographic data and finally generating the response message with the possible matches.

Let's take a look at our production:

As you can see, it couldn’t be simpler. I have a Business Service HL7FileService that retrieves HL7 messages from a directory (/shared/in), then sends the message to the Business Process EMPI.BP.FromHL7ToPatientRequestBPL where I will create the message with the patient’s demographic data and finally we will send it to another BP called EMPI.BP.VectorizationBP where the demographic information will be vectorized and the vector search will be performed that will return a message with all the possible duplicate patients.

As you can see, theBP FromHL7ToPatientRequesBPL is very simple:

We transform the HL7 message into a message that we have created to store the demographic data that we have considered most relevant.

Messages between components

We have created two specific type of messages: 

EMPI.Message.PatientRequest

Class EMPI.Message.PatientRequest Extends (Ens.Request, %XML.Adaptor)
{

Property Patient As EMPI.Object.Patient;
}

EMPI.Message.PatientResponse

Class EMPI.Message.PatientResponse Extends (Ens.Response, %XML.Adaptor)
{

Property Patients As list Of EMPI.Object.Patient;
}

This type message will contain a list of "possible" duplicated patients.

Let's see the definition of EMPI.Object.Patient class:

Class EMPI.Object.Patient Extends (%SerialObject, %XML.Adaptor)
{

Property Name As %String(MAXLEN = 1000);
Property Address As %String(MAXLEN = 1000);
Property Contact As %String(MAXLEN = 1000);
Property BirthDateAndSex As %String(MAXLEN = 100);
Property SimilarityName As %Double;
Property SimilarityAddress As %Double;
Property SimilarityContact As %Double;
Property SimilarityBirthDateAndSex As %Double;
}

Name, Address, Contact and BirthDateAndSex are the properties in wich we are going to save the most relevant patient demographic data.

Now let's see the magic, the embedding generation and the vector search in the production.

EMPI.BP.VectorizationBP

Embedding and vector search

With the PatientRequest received we are going to generate the embeddings using a method in Python:

Method VectorizePatient(name As %String, address As %String, contact As %String, birthDateAndSex As %String) As %String [ Language = python ]
{
    import iris
    import os
    import sentence_transformers

    try :
        if not os.path.isdir("/iris-shared/model/"):
            model = sentence_transformers.SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")            
            model.save('/iris-shared/model/')
        model = sentence_transformers.SentenceTransformer("/iris-shared/model/")
        embeddingName = model.encode(name, normalize_embeddings=True).tolist()
        embeddingAddress = model.encode(address, normalize_embeddings=True).tolist()
        embeddingContact = model.encode(contact, normalize_embeddings=True).tolist()
        embeddingBirthDateAndSex = model.encode(birthDateAndSex, normalize_embeddings=True).tolist()

        stmt = iris.sql.prepare("INSERT INTO EMPI_Object.PatientInfo (Name, Address, Contact, BirthDateAndSex, VectorizedName, VectorizedAddress, VectorizedContact, VectorizedBirthDateAndSex) VALUES (?,?,?,?, TO_VECTOR(?,DECIMAL), TO_VECTOR(?,DECIMAL), TO_VECTOR(?,DECIMAL), TO_VECTOR(?,DECIMAL))")
        rs = stmt.execute(name, address, contact, birthDateAndSex, str(embeddingName), str(embeddingAddress), str(embeddingContact), str(embeddingBirthDateAndSex))
        return "1"
    except Exception as err:
        iris.cls("Ens.Util.Log").LogInfo("EMPI.BP.VectorizationBP", "VectorizePatient", repr(err))
        return "0"
}

Let's analyze the code:

  • With sentence-transformer library we are getting the model all-MiniLM-L6-v2 and saving it into the local machine (to avoid further connections by Internet).
  • With the model imported it allows to us to generate the embeddings for the demographic fields using the encode method.
  • Using IRIS library we are executing the insert query to persist the embeddings for the patient.

Searching duplicated patients

Now we have the patients recorded with the embeddings generated from the demographic data, let's query it!

Method OnRequest(pInput As EMPI.Message.PatientRequest, Output pOutput As EMPI.Message.PatientResponse) As %Status
{
    try{
        set result = ..VectorizePatient(pInput.Patient.Name, pInput.Patient.Address, pInput.Patient.Contact, pInput.Patient.BirthDateAndSex)
        set pOutput = ##class(EMPI.Message.PatientResponse).%New()
        if (result = 1)
        {
            set sql = "SELECT * FROM (SELECT p1.Name, p1.Address, p1.Contact, p1.BirthDateAndSex, VECTOR_DOT_PRODUCT(p1.VectorizedName, p2.VectorizedName) as SimilarityName, VECTOR_DOT_PRODUCT(p1.VectorizedAddress, p2.VectorizedAddress) as SimilarityAddress, "_
                    "VECTOR_DOT_PRODUCT(p1.VectorizedContact, p2.VectorizedContact) as SimilarityContact, VECTOR_DOT_PRODUCT(p1.VectorizedBirthDateAndSex, p2.VectorizedBirthDateAndSex) as SimilarityBirthDateAndSex "_
                    "FROM EMPI_Object.PatientInfo p1, EMPI_Object.PatientInfo p2 WHERE p2.Name = ? AND p2.Address = ?  AND p2.Contact = ? AND p2.BirthDateAndSex = ?) "_
                    "WHERE SimilarityName > 0.8 AND SimilarityAddress > 0.8 AND SimilarityContact > 0.8 AND SimilarityBirthDateAndSex > 0.8"
            set statement = ##class(%SQL.Statement).%New(), statement.%ObjectSelectMode = 1
            set status = statement.%Prepare(sql)
            if ($$$ISOK(status)) {
                set resultSet = statement.%Execute(pInput.Patient.Name, pInput.Patient.Address, pInput.Patient.Contact, pInput.Patient.BirthDateAndSex)
                if (resultSet.%SQLCODE = 0) {
                    while (resultSet.%Next() '= 0) {
                        set patient = ##class(EMPI.Object.Patient).%New()
                        set patient.Name = resultSet.%GetData(1)
                        set patient.Address = resultSet.%GetData(2)
                        set patient.Contact = resultSet.%GetData(3)
                        set patient.BirthDateAndSex = resultSet.%GetData(4)
                        set patient.SimilarityName = resultSet.%GetData(5)
                        set patient.SimilarityAddress = resultSet.%GetData(6)
                        set patient.SimilarityContact = resultSet.%GetData(7)
                        set patient.SimilarityBirthDateAndSex = resultSet.%GetData(8)
                        do pOutput.Patients.Insert(patient)
                    }
                }
            }
        }
    }
    catch ex {
        do ex.Log()
    }
    return $$$OK
}

Here is the query. For our example we have included a restriction to get patients with a similarity bigger than 0.8 for all the demographics but we could configure it to tune the query.

Let's see the example introducing the file messagesa28.hl7 with HL7 messages like these:

MSH|^~\&|HIS|HULP|EMPI||20241120103314||ADT^A28|269304|P|2.5.1
EVN|A28|20241120103314|20241120103314|1
PID|||1220395631^^^SERMAS^SN~402413^^^HULP^PI||FERNÁNDEZ LÓPEZ^JOSÉ MARÍA^^^||19700611|M|||PASEO JUAN FERNÁNDEZ^183 2 A^LEGANÉS^CÁDIZ^28566^SPAIN||555749170^PRN^^JOSE-MARIA.FERNANDEZ@GMAIL.COM|||||||||||||||||N|
PV1||N

MSH|^~\&|HIS|HULP|EMPI||20241120103314||ADT^A28|570814|P|2.5.1
EVN|A28|20241120103314|20241120103314|1
PID|||1122730333^^^SERMAS^SN~018565^^^HULP^PI||GONZÁLEZ GARCÍA^MARÍA^^^||19660812|F|||CALLE JOSÉ MARÍA FERNÁNDEZ^281 8 IZQUIERDA^MADRID^BARCELONA^28057^SPAIN||555386663^PRN^^MARIA.GONZALEZ@GMAIL.COM|||||||||||||||||N|
PV1||N
DG1|1||T001^TRAUMATISMOS SUPERF AFECTAN TORAX CON ABDOMEN, REG LUMBOSACRA Y PELVIS^||20241120|||||||||||^CONTRERAS ÁLVAREZ^ENRIQUETA^^^Dr|

MSH|^~\&|HIS|HULP|EMPI||20241120103314||ADT^A28|40613|P|2.5.1
EVN|A28|20241120103314|20241120103314|1
PID|||1007179467^^^SERMAS^SN~122688^^^HULP^PI||OLIVA OLIVA^JIMENA^^^||19620222|F|||CALLE ANTONIO ÁLVAREZ^51 3 D^MÉRIDA^MADRID^28253^SPAIN||555638305^PRN^^JIMENA.OLIVA@VODAFONE.COM|||||||||||||||||N|
PV1||N
DG1|1||Q059^ESPINA BIFIDA, NO ESPECIFICADA^||20241120|||||||||||^SANZ LÓPEZ^MARIO^^^Dr|

MSH|^~\&|HIS|HULP|EMPI||20241120103314||ADT^A28|61768|P|2.5.1
EVN|A28|20241120103314|20241120103314|1
PID|||1498973060^^^SERMAS^SN~719939^^^HULP^PI||PÉREZ CABEZUELA^DIANA^^^||19820309|F|||AVENIDA JULIA ÁLVAREZ^253 1 A^PERELLONET^BADAJOZ^28872^SPAIN||555705148^PRN^^DIANA.PEREZ@YAHOO.COM|||||||||||||||||N|
PV1||N
AL1|1|MA|^Polen de gramineas^|SV^^||20340919051523
MSH|^~\&|HIS|HULP|EMPI||20241120103314||ADT^A28|128316|P|2.5.1
EVN|A28|20241120103314|20241120103314|1
PID|||1632386689^^^SERMAS^SN~601379^^^HULP^PI||GARCÍA GARCÍA^MARIO^^^||19550603|M|||PASEO JOSÉ MARÍA TREVIÑO^153 3 D^JEREZ DE LA FRONTERA^MADRID^28533^SPAIN||555231628^PRN^^MARIO.GARCIA@GMAIL.COM|||||||||||||||||N|
PV1||N

In this file, all the patients are different so the result of the operation will be of this type:

The only match is the patient himself, let's introduce the hl7 messages from the messagesa28Duplicated.hl7 with duplicated patients:

As you can see the code has detected the duplicated patient with minor differences in the name (Maruchi is an affectionate nickname for María and Mª is the short way) , well, this case is oversimplified but you can get and idea about the capabilities of the vector search to get duplicated data, not only for patients but any other type of info.

Next steps...

For this example I have used a common model to generate the embeddings but the behaviour of the code would be improved with a fine tuning using nicknames, monikers, etc.

Thank you for your attention!

讨论 (0)1
登录或注册以继续
问题
· 四月 30

Serialised classes, exports and development environments

We have classes in a Production environment that are causing us some issues - example attached.

When we export them from the production environment the XML contains a snippet like the following:

<UDLText name="T">
<Content><![CDATA[
//Property any As list Of %XML.String(XMLNAME = "any", XMLPROJECTION = "ANY") [ SqlFieldName = _any ];

]]></Content>
</UDLText>

When imported and compiled into an Ensemble instance this class works as expected.

When viewed/edited in a development environment we run into issues - the presentation is similar with both Studio and VS Code.

On first viewing in Studio the class source code displays like this:

If you use the extension in VS Code view the class, or export it from there, again the code displays as above (and is saved in this form to the filesystem if exported).

However, if the file is modified and saved in Studio, or modified and saved or even simply compiled (import and compile) in VS Code then the source is serialised and returned to Studio/VS Code by Ensemble as:

From part of a previous conversation with @Brett Saviano  (Request textDocument/documentSymbol failed. Error: name must not be falsy · intersystems-community/vscode-objectscript · Discussion #1530) we understand this serialisation is the correct behaviour.

However, we are left with a few of questions:

  1. Serialisation does not appear to happen when the Ensemble -> System Explorer -> Classes page exports the class - is this correct? (ie present in Ensemble with the comment as '//Property', then the exported class has '//Property' rather than '// Property')
  2. Is this likely to be the same reason that the "InterSystems" tab in VS Code exports the code without the space (they are using the same underlying mechanism)?
  3. Can anyone come with any ways in which classes such as the one attached came to be in our Production Ensemble in the first place?
  4. We tried importing and re-exporting the class using $SYSTEM.OBJ - Load(file, "cuk") to import, and Export(class, file) to export. It did not modify/serialise the class - the '//Property' was left intact.
  5. And finally, is there any way of programmatically forcing this serialisation change and exporting the changed classes - our repo was created with the non-serialised versions of these classes, and now the classes with this issue sporadically show up as "phantom changes" - not relating to the development we are actually attempting to do!  
8 条新评论
讨论 (8)4
登录或注册以继续
问题
· 四月 30

Clearing Web Sessions from Terminal

I ran into a situation where VS Code consumed all available web sessions and was unable to get to the Management Console to clear them. I was able to establish a terminal session, though.

Is there a method or routine available through the IRIS terminal that allows one to clear web sessions? I've searched the administration and class documentation and haven't found a solution.

3 条新评论
讨论 (3)3
登录或注册以继续