#Interoperabilidade

0 Seguidores · 75 Postagens

Na verificação de integridade, interoperabilidade é a capacidade de diferentes sistemas de tecnologia da informação e aplicações de software se comunicarem, trocarem dados e usarem as informações que foram trocadas.

Artigo Heloisa Paiva · Out. 17, 2025 3m read

Para gerenciar o acúmulo de dados de produção, o InterSystems IRIS permite aos usuários controlar o tamanho do banco de dados eliminando (expurgando) periodicamente os dados. Esta purga pode ser aplicada a mensagens, registros, processos de negócio e alertas gerenciados.

Consulte a documentação para obter mais detalhes sobre a configuração da tarefa de expurgo:

https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=EGMG_purge#EGMG_purge_settings

0
0 16
Artigo Heloisa Paiva · Out. 16, 2025 13m read

.

Estou muito emocionado de continuar com a minha série de artigos "InterSystems para Dummies", e hoje queremos contar tudo sobre uma das funções mais potentes que temos para a interoperabilidade.

Mesmo que já as tenha utilizado, planejamos analisar a fundo como aproveitá-las ao máximo e melhorar ainda mais nossa produção.

O que é um Record Mapper?

Essencialmente, um Record Mapper é uma ferramenta que permite mapear dados de arquivos de texto a mensagens de produção e vice-versa. A inteface do Portal de Administração, por outro lado, permite criar uma representação visual de um arquivo de texto e um modelo de objeto válido desses dados para mapear-los a um único objeto de mensagem de produção persistente.

Portanto, se você deseja importar dados de um arquivo CSV à sua classe persistente, pode tentar com um par de classes de entrada (por FTP ou diretório de arquivos). Mas não se apresse! Vamos abordar cada um desses pontos no seu devido tempo.


TIP: Todos os exemplos e classes descritos nessse artigo podem ser baixados no seguinte link: https://github.com/KurroLopez/iris-recordmap-fordummies.git


Como começar?

Vou direto ao ponto e especifico nosso cenário.

Precisamos importar informações de nossos clientes, incluindo seu nome, data de nascimento, número de identificação nacional, endereço, cidade e país.

Abra seu portal IRIS e selecione Interoperabilidade – Criar – Record Maps. image

Crie um novo Record Maps com o nome do pacote e a classe. image

Em nosso exemplo, o nome do pacote é Demo.Data, enquanto o nome da classe é es PersonalInfo.

O primeiro passo é configurar o arquivo CSV. Isso significa determinar o caractere separador, se os campos de string têm aspas duplas, etc. image

Se você usa o sistema operacional Windows, o terminador de registro comum é CRLF (Char(10) Char(12)).

Como meu arquivo CSV é padrão, separado por ponto e vírgula (;), devo definir o caractere do separador de campos.

Agora, vou declarar os campos do perfil do cliente (nome, sobrenome, data de nascimento, número de identificação nacional, endereço, cidade e país). image

Esta é uma definição básica, mas você pode estabelecer mais condições em relação ao seu arquivo CSV, se desejar. image

Lembre-se de que, por padrão, um campo %String tem um comprimento máximo de 50 caracteres. Portanto, atualizarei este valor para permitir mais caracteres no campo de endereço (um máximo de 100).

Também definirei o formato de data usando o formato ISO (aaaa-mm-dd), que corresponde ao número 3.

Além disso, tornarei os campos Nome, Sobrenome e Data de Nascimento obrigatórios. image

Pronto! Vamos pressionar o botão "Gerar" para criar a classe persistente! image

Dê uma olhada na classe gerada: /// THIS IS GENERATED CODE. DO NOT EDIT.
/// RECORDMAP: Generated from RecordMap 'Demo.Data.PersonalInfo' /// on 2025-07-14 at 08:37:00.646 [2025-07-14 08:37:00.646 UTC] /// by user SuperUser Class Demo.Data.PersonalInfo.Record Extends (%Persistent, %XML.Adaptor, Ens.Request, EnsLib.RecordMap.Base) [ Inheritance = right, ProcedureBlock ] {

Parameter INCLUDETOPFIELDS = 1;

Property Name As %String [ Required ];

Property Surname As %String [ Required ];

Property DateOfBirth As %Date(FORMAT = 3) [ Required ];

Property NationalId As %String;

Property Address As %String(MAXLEN = 100);

Property City As %String;

Property Country As %String;

Parameter RECORDMAPGENERATED = 1;

Storage Default
{
<Data name="RecordDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>Name</Value>
</Value>
<Value name="3">
<Value>%Source</Value>
</Value>
<Value name="4">
<Value>DateOfBirth</Value>
</Value>
<Value name="5">
<Value>NationalId</Value>
</Value>
<Value name="6">
<Value>Address</Value>
</Value>
<Value name="7">
<Value>City</Value>
</Value>
<Value name="8">
<Value>Country</Value>
</Value>
<Value name="9">
<Value>Surname</Value>
</Value>
</Data>
<DataLocation>^Demo.Data.PersonalInfo.RecordD</DataLocation>
<DefaultData>RecordDefaultData</DefaultData>
<ExtentSize>2000000</ExtentSize>
<IdLocation>^Demo.Data.PersonalInfo.RecordD</IdLocation>
<IndexLocation>^Demo.Data.PersonalInfo.RecordI</IndexLocation>
<StreamLocation>^Demo.Data.PersonalInfo.RecordS</StreamLocation>
<Type>%Storage.Persistent</Type>
}

}

Como você pode ver, cada propriedade tem o nome dos campos em nosso arquivo CSV.

Neste ponto, criaremos um arquivo CSV com a seguinte estrutura para testar nosso Record Mapper:

Name;Surname;DateOfBirth;NationalId;Address;City;Country Matthew O.;Wellington;1964-31-07;208-36-1552;1485 Stiles Street;Pittsburgh;USA Deena C.;Nixon;1997-03-03;495-26-8850;1868 Mandan Road;Columbia;USA Florence L.;Guyton;2005-04-10;21 069 835 790;Invalidenstrasse 82;Contwig;Germany Maximilian;Hahn;1945-10-17;92 871 402 258;Boxhagener Str. 97;Hamburg;Germany Amelio;Toledo Zavala;1976-06-07;93789292F;Plaza Mayor, 71;Carbajosa de la Sagrada;Spain

Você pode usá-lo como teste agora.

Clique em "Selecionar arquivo de amostra" selecione a amostra em /irisrun/repo/Samples e escolha PersonalInfo-Test.csv. image

Neste momento, você poderá observar como seus dados são importados: image

Os Problemas Aumentam

Justo quando você pensa que está tudo pronto, você recebe uma nova especificação do seu chefe:

"Precisamos dos dados para poder carregar o número de telefone do cliente e armazenar mais de um (fixo, celular, etc.)".

Ops... Preciso atualizar meu Record Map e adicionar um número de telefone. No entanto, deve haver mais de um... Como posso fazer isso?


Nota: Você pode fazer isso diretamente na mesma classe. No entanto, criarei uma nova para fins explicativos e a salvarei nos exemplos. Desta forma, você pode revisar e executar o código seguindo todos os passos deste artigo.


Bem, é hora de reabrir o Record Map que acabamos de criar.

Adicione o novo campo "Phone", mas lembre-se de indicar que este campo é "Repetido". image

Como atribuímos este campo como "Repetido", devemos definir o caractere separador para os dados replicados. Este indicador está no mesmo lugar onde normalmente especificamos o separador de campo. image

Perfeito! Vamos carregar o arquivo CSV de exemplo com os números de telefone separados por #. image

Se dermos uma olhada na classe persistente que produzimos, podemos ver que o campo "Phone" é do tipo %String:

Property Phone As list Of %String(MAXLEN = 20);

Ok, Kurro, mas como podemos enviar este arquivo?

Essa é uma pergunta muito boa, caro leitor.

O InterSystems IRIS nos fornece duas classes de entrada (inbound): EnsLib.RecordMap.Service.FileServiceEnsLib.RecordMap.Service.FTPService

Não vou me aprofundar nessas classes porque seria muito longo. No entanto, podemos revisar suas funções principais.

Em resumo, o serviço monitora os processos em uma pasta definida, captura os arquivos armazenados nesse diretório, os carrega, os lê linha por linha e envia esse registro para o Processo de Negócio designado.

Isso ocorre tanto no servidor quanto nos diretórios FTP.

Vamos ao que interessa...


Nota: Apresentarei meus exemplos usando a classeEnsLib.RecordMap.Service.FileService. No entanto, a classe EnsLib.RecordMap.Service.FTPService realiza as mesmas operações.


Se você baixou o código de exemplo, verá que uma produção foi criada com dois componentes:

Uma classe de Serviço (EnsLib.RecordMap.Service.FileService), que carregará os arquivos, e uma classe de Negócio (Demo.BP.ProcessData),que processará cada um dos registros lidos do arquivo. Neste caso, usaremos este último apenas para ver os rastros de comunicação.

É importante configurar alguns parâmetros na classe do Business Service. image

File Path: É um registro que a classe usa para monitorar se há arquivos pendentes de processamento. Ao colocar um arquivo neste diretório, o processo de carregamento é ativado automaticamente e envia cada registro para a classe definida como Processo de Negócios.

File Spec: É um padrão de arquivo para buscar (por padrão, é *, mas podemos definir alguns arquivos que desejamos diferenciar de outros processos). Por exemplo, podemos ter duas classes de escuta de entrada no mesmo diretório, cada uma com uma classe RecordMap diferente. Podemos atribuir a extensão .pi1 aos arquivos que a classe PersonalInfo processará, enquanto .pi2 marcará os arquivos que serão processados pela classe PersonalInfoPhone.

Archive Path: É um diretório onde os arquivos são movidos após serem processados.

Work Path: É um caminho onde o adaptador deve colocar o arquivo de entrada enquanto processa os dados. Esta configuração é útil quando se usa o mesmo nome de arquivo para envios repetidos. Se o WorkPath não for especificado, o adaptador não moverá o arquivo durante o processamento.

Call Interval: É a frequência (calculada em segundos) das verificações do adaptador para os arquivos de entrada nos locais especificados.

RecordMap: É o nome da classe Record Map, que contém a definição dos dados no arquivo.

Target Config Name: É o nome do Processo de Negócio que manipula os dados armazenados no arquivo.

image

Subdirectory Levels: É um espaço onde o processo busca um novo arquivo. Por exemplo, se um processo adiciona um arquivo a cada dia (segunda, terça, quarta, quinta e sexta), ele buscará em todos os subdiretórios, começando pelo diretório raiz, sempre que especificarmos o nível 1. Por padrão, o nível 0 significa que ele buscará apenas no diretório raiz.

Delete From Server: Esta função indica que, se o diretório dos arquivos processados não for especificado, o arquivo será excluído do diretório raiz.

File Access Timeout: É um tempo definido (calculado em segundos) para acessar o arquivo. Se o arquivo for somente leitura ou houver algum problema que impeça o acesso ao diretório, será exibido um erro.

Header Count:: É uma característica importante que indica o número de cabeçalhos que devem ser ignorados. Por exemplo, se o arquivo tiver um cabeçalho que especifica os campos que contém, você deve indicar quantas linhas de cabeçalho ele contém para que possam ser ignoradas e apenas as linhas de dados possam ser lidas.

Carregar um Arquivo

Como mencionei anteriormente, o processo de carregamento é ativado quando um arquivo é colocado no diretório do processo

Nota: As seguintes instruções são baseadas no código de exemplo. Na pasta "samples", você encontrará o arquivo PersonalInfoPhone-Test.csv. Você deve copiar este arquivo para a pasta do processo para que ele seja processado automaticamente.


NOTA: Se estiver trabalhando com Docker, use o seguinte comando: docker cp .\PersonalInfoPhone-Test.csv containerId:/opt/irisbuild/process/containerId é o ID do seu contêiner, ex: : docker cp .\PersonalInfoPhone-Test.csv 66f96b825d43398ba6a1edcb2f02942dc799d09f1b906627e0563b1392a58da1:/opt/irisbuild/process/` image


Para cada registro, ele dispara uma chamada para o Processo de Negócio com todos os dados. image

Excelente trabalho! Em apenas alguns passos, você conseguiu criar um processo que pode ler arquivos de um diretório e gerenciar esses dados de forma rápida e fácil. O que mais você poderia pedir aos seus processos de interoperabilidade?

Complex Record Map (Mapa de Registro Complexo)

Ninguém quer ter uma vida complexa, mas eu prometo que você vai se apaixonar pelos Complex Record Maps.

Os Complex Record Maps são exatamente o que o nome indica. Trata-se de uma combinação de vários Record Maps que nos fornece informações mais completas e estruturadas.

Imaginemos que nosso chefe nos contacta e nos apresenta os seguintes requisitos:

“Precisamos de informações do cliente com mais números de telefone, incluindo códigos de país e prefixos. Também precisamos de mais endereços de contato, incluindo códigos postais, países e nomes de estados.

Um cliente pode ter um número de telefone, dois ou nenhum”.

Se precisarmos de mais informações sobre números de telefone e endereços, como vimos anteriormente, incluir essas informações em uma única linha seria muito complicado. Vamos separar as diferentes partes de que precisamos:

  • Informações do cliente necessárias.
  • Números de telefone (de 0 a 5).
  • Endereço postal (de 0 a 2).

Para cada seção, criaremos um apelido para diferenciar o tipo de informação que inclui.

Vamos construir cada seção:

Passo 1 Projete um novo Record Maps para as informações do cliente (Nome, Sobrenome, Data de Nascimento e Documento Nacional de Identidade) e inclua um identificador para indicar que se trata da seção USER. image

O nome da seção deve ser único para os tipos de dados "USER", pois são responsáveis por configurar as colunas e posições de cada dado. O conteúdo deve ser semelhante ao seguinte: USER|Matthew O.;Wellington;1964-07-31;208-36-1552 Em NEGRITO, o nome da seção, em ITALICO, o conteúdo.

Passo 2 Crie as seções PHONE e ADDRESS para os números de telefone e os endereços postais.

Lembre-se de especificar o nome da seção e ativar a opção Complex Record Map. imageimage

Agora devemos ter três classes:

  • Demo.Data.ComplexUser
  • Demo.Data.ComplexPhone
  • Demo.Data.ComplexAddress

Passo 3 Complete o Complex Record Map.

Abra a opção "Complex Record Maps": image

A primeira coisa que vemos aqui é uma estrutura com um cabeçalho e um rodapé. O cabeçalho pode ser outro mapa de registros para armazenar informações do pacote de dados (por exemplo, informações do departamento do usuário, etc.).

Como estas seções são opcionais, vamos ignorá-las em nosso exemplo. image

Defina o nome deste registro (por exemplo, PersonalInfo) e adicione novos registros para cada seção. image

Se quisermos que uma das seções tenha repetições, devemos indicar os valores mínimos e máximos de repetição. image

De acordo com as especificações anteriores, o arquivo com as informações se parecerá com isto:

USER|Matthew O.;Wellington;1964-07-31;208-36-1552
PHONE|1;305;2089160
PHONE|1;805;9473136
ADDR|1485 Stiles Street;Pittsburgh;15286;PA;USA

Se quisermos carregar um arquivo, precisamos de um serviço que possa ler este tipo de arquivos, e o InterSystems IRIS nos fornece duas classes de entrada para isso:

EnsLib.RecordMap.Service.ComplexBatchFileServiceEnsLib.RecordMap.Service.ComplexBatchFTPService Como mencionei anteriormente, usaremos a classe EnsLib.RecordMap.Service.ComplexBatchFileService como exemplo. No entanto, o processo para FTP é idêntico.

Utilize a mesma configuração que o Record Map, exceto pelo número de linha do cabeçalho, porque este tipo de arquivo não precisa de um: image

Como mencionei anteriormente, o processo de carregamento é ativado quando um arquivo é colocado no diretório do processo.

Nota: As seguintes instruções são baseadas no código de exemplo.

Na pasta "samples", você encontrará o arquivo PersonalInfoComplex.txt. Você deve copiar este arquivo para a pasta do processo para que ele seja processado automaticamente.


NOTA: Se estiver trabalhando com o exemplo do Docker, utilize o seguinte comando:

docker cp .\ PersonalInfoComplex.txt containerId:/opt/irisbuild/process/p
containerId é o ID do seu contêiner, ex: docker cp .\ PersonalInfoComplex.txt 66f96b825d43398ba6a1edcb2f02942dc799d09f1b906627e0563b1392a58da1:/opt/irisbuild/process/

Aqui podemos ver cada linha chamando o Business Service: imageimageimage

Como você já deve ter notado, os Record Maps são uma ferramenta potente para importar dados de forma complexa e estruturada. Permitem salvar informações em tabelas relacionadas ou processar cada dado de forma independente.

Graças a esta ferramenta, você pode criar rapidamente processos de carregamento de dados em lote e armazená-los sem a necessidade de realizar leituras complexas de dados, separação de campos, validação de tipos de dados, etc.

Espero que este artigo seja útil.

Nos vemos no próximo "InterSystems para Dummies".

0
0 17
Artigo Heloisa Paiva · Set. 30, 2025 3m read

Oi pessoal!  Esse artigo é para quem está começando com InterSystems IRIS. Espero que ajude!

O InterSystems IRIS é uma plataforma de dados unificada: uma base de dados de alta performance com ferramentas de interoperabilidade e análise integradas em um só produto. Você tem SQL e NoSQL na mesma máquina, além de jeitos nativos de rodar Python com seus dados. Em resumo: menos peças móveis, mais capacidade de processamento.

Por que engenheiros escolhem IRIS

0
0 28
Artigo Henry Pereira · Ago. 3, 2025 5m read

artisan cover

Se você já viu um artesão em ação — seja um ceramista moldando o barro até virar arte ou um luthier transformando madeira bruta em um violão extraordinário — sabe que a mágica não está nos materiais, e sim no cuidado, na técnica e no processo. Tenho uma guitarra feita por um luthier que me inspira todos os dias. Mas, vou confessar, criar algo assim é um talento que não tenho.

Por outro lado, no digital, vejo muita gente esperando essa mesma “mágica” de uma IA generativa... prompts genéricos como “crie um app” e esperando resultados espetaculares. Espera-se que a IA faça milagres com zero contexto.

Foi dessa frustração que nasceu o dc-artisan — uma ferramenta para quem quer ser um verdadeiro artesão na criação de prompts. A proposta? Ajudar qualquer pessoa a transformar aquela ideia bruta e mal formata em um prompt funcional, eficiente e cheio de contexto.

Assim como o artesão não faz belas peças por sorte, gerar bons resultados com IA exige intenção, preparo e um bom processo. O problema quase nunca é a IA em si — é como nós a utilizamos. Igual ao luthier que escolhe a madeira certa e molda cada detalhe, engenhar prompts bons exige clareza, estrutura e propósito.

Acreditamos que o mundo merece muito mais do que “prompts mágicos” que só levem à decepção. A IA generativa mostra todo seu potencial guiada por humanos com clareza, objetivos reais e estrutura bem pensada. Nenhum artesão cria beleza por acidente — gerar boas respostas com IA requer preparo e cuidado.

Engenharia de Prompt é uma Arte — e o dc-artisan é Sua Oficina

O dc-artisan trata a criação de prompts como um ofício — algo sistematizado, que pode ser aprendido, testado e aprimorado. Ele oferece uma caixa de ferramentas completa, indo além da tentativa-e-erro.

De início, o dc-artisan busca entender seu prompt, ele interage diretamente com você:

  • Perguntas inteligentes: O dc-artisan analisa seu prompt e faz perguntas pontuais para entender o que você realmente quer, quem é o público, qual o formato esperado e que informações faltam. Por exemplo:
    • “Você espera qual tipo de saída? Um resumo textual, código ou dados estruturados?”
    • “Quem é o público-alvo?”
    • “Qual será o tipo de conteúdo usado com esse prompt?”

prompt enhance

Essas interações te ajudam a entender não apenas o “o que” você quer dizer, mas o “por quê”.

Com o objetivo claro, o dc-artisan analisa a estrutura do seu prompt e oferece sugestões personalizadas — para melhorar a clareza, ajustar o tom e preencher lacunas críticas de contexto.

E o melhor: tudo isso dentro do editor favorito — o VS Code! Você pode incluir variáveis diretamente no seu prompt (como {task} ou {audience}), o que oferece flexibilidade e reaproveitamento. É possível visualizar instantaneamente como os prompts ficam com diferentes substituições — e ver exatamente como vão funcionar.

Mas não para por aí.

Teste, Ajuste e Melhore Seus Prompts

O dc-artisan também ajuda você a fazer tuning de prompts para extrair o melhor desempenho. Basta fazer o upload de um CSV com casos de teste para que a ferramenta avalie de forma automatizada a consistência, a qualidade da saída e o impacto da estrutura do prompt em diferentes contextos.

O dc-artisan analisa cada resposta e gera um relatório completo com métricas de similaridade — assim você pode otimizar seus prompts com dados reais, não com achismo.

testing

Criar Prompts Sem Contexto Não É Técnica — É Caos

Criar prompts sem estrutura é como tentar esculpir madeira vendado. Algo pode sair... mas dificilmente vai soar como um bom instrumento.

Muitos caem em dois extremos: prompts vagos e curtos demais, ou blocos enormes de conteúdo jogados sem organização. Nenhum dos dois funciona. Ou a IA não entende o que você quer, ou se perde em um mar de informações inúteis.

E quando o contexto é longo ou confuso demais, até LLMs avançadas perdem o rumo. Em vez de pensar, elas repetem o conteúdo anterior ou se prendem a padrões do início da conversa. Ironicamente, modelos com contexto grandes (como 32k tokens) são ainda mais propensos a este erro.

A solução para esse problema? RAG (Retrieval-Augmented Generation): não dar qualquer informação à IA, mas sim as relevantes, no momento certo.

Como o dc-artisan e o Modo RAG Pipeline Entram em Cena

O dc-artisan une criação de prompts com gestão de contexto. Ele não só te ajuda a escrever prompts melhores — ele garante que a IA receba informação relevante, e não um tsunami aleatório.

Com o Modo RAG Pipeline, você pode:

  • 🗂️ Enviar e dividir documentos: PDF, DOCX, Markdown, TXT — tudo isso pode ser dividido em blocos e enviado para sua base vector.
  • 🧬 Inspecionar blocos de texto: Veja cada bloco de embedding vector com clareza.
  • 🧹 Gerenciar conteúdo: Remova diretamente blocos desatualizados ou irrelevantes, mantendo a “memória” da IA enxuta e refinada.

rag

Essa ideia foi inspirada pelo Portal de Ideias da InterSystems (DPI-I-557)

Arquitetura Robusta e Flexível

Um dos maiores diferenciais do dc-artisan está por trás dos panos: seu backend. A extensão roda sobre o InterSystems IRIS Interoperability, com um adapter para o liteLLM que desenvolvemos.

Essa estrutura garante flexibilidade e integração com múltiplas LLMs. Você não fica preso a uma única IA. Pode alternar e conectar, no mesmo ambiente, com plataformas como OpenAI, Gemini, Claude, Azure OpenAI entre outras.

Cada vez mais desenvolvedores estão percebendo que prompting não é sobre “mágica” — é sobre propósito, clareza e contexto. Não se trata de chutar palavras certas, mas projetar prompts como engenheiros e não como feiticeiros.

Assim como luthiers criam instrumentos com alma a partir de madeira, você também pode esculpir prompts com contexto, previsíveis e eficazes com uma ferramenta feita sob medida para isso.

dc-artisan não é só uma ferramenta — é uma mudança de mentalidade: do improviso para a precisão, da sorte para a técnica, da intuição para a arte.

🎸 Pronto pra criar prompts com as próprias mãos?
⚙️ Abra o VS Code, instale o dc-artisan e comece a esculpir seu prompt como um artesão — não como um mágico.

dc-artisan

2
0 26
Artigo Heloisa Paiva · Jul. 31, 2025 2m read

Se você trabalha com Produções, destacar as conexões entre Business Hosts é um recurso muito conveniente, permitindo aos desenvolvedores obter uma representação visual do fluxo de dados.

Esse recurso funciona por padrão com todos os Business Hosts do sistema. Se um usuário escreve seus próprios Business Services, Processes ou Operations, ele deve implementar o método OnGetConnections para que essa funcionalidade funcione com seus Business Hosts personalizados (ou usar as propriedades  Ens.DataType.ConfigNamepara as conexões).
Dito isso, o SMP mostra apenas a primeira camada de conexões do Business Host selecionado. Às vezes, precisamos obter conexões de conexões recursivamente para construir um grafo completo de fluxo de dados. Ou podemos precisar dessas informações de conexão para verificar quais sistemas downstream podem ser afetados por uma mudança upstream.

0
0 16
Artigo Larissa Prussak · Jul. 3, 2025 2m read

Fazendo Profiling de Documentos CCD com o CCD Data Profiler da LEAD North

Já abriu um CCD e foi recebido por uma parede de XML confuso? Você não está sozinho. Apesar de ser um formato essencial para a troca de dados clínicos, os CCDs são notoriamente densos, verbosos e pouco amigáveis à leitura humana. Para desenvolvedores e analistas que tentam validar sua estrutura ou extrair informações significativas, navegar por esses documentos pode parecer mais arqueologia do que engenharia.

Apresentando o CCD Data Profiler

0
0 22
Artigo Henry Pereira · Maio 30, 2025 6m read

imagem

Sabe aquela sensação de receber o resultado do seu exame de sangue e parecer que está em grego? É exatamente esse problema que o FHIRInsight veio resolver. Surgiu da ideia de que dados médicos não deveriam ser assustadores ou confusos – deveriam ser algo que todos podemos utilizar. Exames de sangue são extremamente comuns para verificar nossa saúde, mas, sejamos sinceros, interpretá-los é difícil para a maioria das pessoas e, às vezes, até para profissionais da área que não trabalham em um laboratório. O FHIRInsight quer tornar todo esse processo mais simples e acessível.

Logo FHIRInsight

🤖 Por que criamos o FHIRInsight

Tudo começou com uma simples pergunta:

“Por que interpretar um exame de sangue ainda é tão difícil — até para médicos, às vezes?”

Se você já olhou para um resultado de laboratório, provavelmente viu um monte de números, abreviações enigmáticas e uma “faixa de referência” que pode ou não se aplicar à sua idade, sexo ou condição. É uma ferramenta de diagnóstico, com certeza — mas sem contexto, vira um jogo de adivinhação. Até profissionais de saúde experientes às vezes precisam recorrer a diretrizes, artigos científicos ou opiniões de especialistas para entender tudo direito.

É aí que o FHIRInsight entra em cena.

Não foi feito apenas para pacientes — fizemos para quem está na linha de frente do atendimento. Para médicos em plantões intermináveis, para enfermeiros que captam sutis padrões nos sinais vitais, para qualquer profissional de saúde que precise tomar decisões certas com tempo limitado e muita responsabilidade. Nosso objetivo é facilitar um pouco o trabalho deles — transformando dados clínicos FHIR em algo claro, útil e fundamentado em ciência médica real. Algo que fale a língua humana.

O FHIRInsight faz mais do que só explicar valores de exames. Ele também:

  • Fornece aconselhamento contextual sobre se um resultado é leve, moderado ou grave
  • Sugere causas potenciais e diagnósticos diferenciais com base em sinais clínicos
  • Recomenda próximos passos — sejam exames adicionais, encaminhamentos ou atendimento de urgência
  • Utiliza RAG (Retrieval-Augmented Generation) para incorporar artigos científicos relevantes que fundamentam a análise

Imagine um jovem médico revisando um hemograma de um paciente com anemia. Em vez de buscar cada valor no Google ou vasculhar revistas médicas, ele recebe um relatório que não só resume o problema, mas cita estudos recentes ou diretrizes da OMS que embasam o raciocínio. Esse é o poder de combinar IA e busca vetorial sobre pesquisas selecionadas.

E o paciente?

Ele não fica mais diante de um monte de números, sem saber o que significa “bilirrubina 2,3 mg/dL” ou se deve se preocupar. Em vez disso, recebe uma explicação simples e cuidadosa. Algo que se assemelha mais a uma conversa do que a um laudo clínico. Algo que ele realmente entende — e que pode levar ao consultório do médico mais preparado e menos ansioso.

Porque é disso que o FHIRInsight trata de verdade: transformar complexidade médica em clareza e ajudar profissionais e pacientes a tomarem decisões melhores e mais confiantes — juntos.

🔍 Por dentro da tecnologia

Claro que toda essa simplicidade na superfície é viabilizada por uma tecnologia poderosa atuando discretamente nos bastidores.

Veja do que o FHIRInsight é feito:

  • FHIR (Fast Healthcare Interoperability Resources) — padrão global de dados de saúde. É assim que recebemos informações estruturadas como resultados de exames, histórico do paciente, dados demográficos e atendimentos. O FHIR é a linguagem que os sistemas médicos falam — e nós traduzimos essa linguagem em algo que as pessoas possam usar.
  • Busca Vetorial RAG (Retrieval-Augmented Generation): o FHIRInsight aprimora seu raciocínio diagnóstico indexando artigos científicos em PDF e URLs confiáveis em um banco de dados usando a busca vetorial nativa do InterSystems IRIS. Quando um resultado de exame fica ambíguo ou complexo, o sistema recupera conteúdo relevante para embasar suas recomendações — não da memória do modelo, mas de pesquisas reais e atualizadas.
  • Engenharia de Prompts para Raciocínio Médico: refinamos nossos prompts para guiar o LLM na identificação de um amplo espectro de condições relacionadas ao sangue. Desde anemia por deficiência de ferro até coagulopatias, desequilíbrios hormonais ou gatilhos autoimunes — o prompt conduz o LLM pelas variações de sintomas, padrões laboratoriais e causas possíveis.
  • Integração com LiteLLM: um adaptador customizado direciona requisições para múltiplos provedores de LLM (OpenAI, Anthropic, Ollama etc.) através de uma interface unificada, permitindo fallback, streaming e troca de modelo com facilidade.

Tudo isso acontece em segundos — transformando valores brutos de laboratório em insights médicos explicáveis e acionáveis, seja para um médico revisando 30 prontuários ou um paciente tentando entender seus números.

🧩 o Adapter LiteLLM: Uma Interface para Todos os Modelos

Nos bastidores, os relatórios do FHIRInsight movidos a IA são impulsionados pelo LiteLLM — uma library python que nos permite chamar mais de 100 LLMs (OpenAI, Claude, Gemini, Ollama etc.) por meio de uma única interface estilo OpenAI.

Porém, integrar o LiteLLM ao InterSystems IRIS exigiu algo mais permanente e reutilizável do que scripts Python escondidos numa Business Operation. Então, criamos nosso próprio Adapter LiteLLM.

Conheça o LiteLLMAdapter

Esse adaptador lida com tudo o que você esperaria de uma integração robusta de LLM:

  • Recebe parâmetros como prompt, model e temperature
  • Carrega dinamicamente variáveis de ambiente (por exemplo, chaves de API)

Para encaixar isso em nossa produção de interoperabilidade em um simples Business Operation:

  • Gerencia configuração em produção por meio da configuração padrão LLMModel
  • Integra-se ao componente FHIRAnalyzer para geração de relatórios em tempo real
  • Atua como uma “ponte de IA” central para quaisquer componentes futuros que precisem de acesso a LLM

Eis o fluxo de forma simplificada:

set response = ##class(dc.LLM.LiteLLMAdapter).CallLLM("Me fale sobre hemoglobina.", "openai/gpt-4o", 0.7)
write response

🧪 Conclusão

Quando começamos a desenvolver o FHIRInsight, nossa missão era simples: tornar exames de sangue mais fáceis de entender — para todo mundo. Não apenas pacientes, mas médicos, enfermeiros, cuidadores... qualquer pessoa que já tenha encarado um resultado de laboratório e pensado: “Ok, mas o que isso realmente significa?”

Todos nós já passamos por isso.

Ao combinar a estrutura do FHIR, a velocidade do InterSystems IRIS, a inteligência dos LLMs e a profundidade de pesquisas médicas reais via busca vetorial, criamos uma ferramenta que transforma números confusos em histórias significativas. Histórias que ajudam pessoas a tomarem decisões mais inteligentes sobre sua saúde — e, quem sabe, detectar algo cedo que passaria despercebido.

Mas o FHIRInsight não é só sobre dados. É sobre como nos sentimos ao olhar para esses dados. Queremos que seja claro, acolhedor e empoderador. Que a experiência seja... como um verdadeiro “vibecoding” na saúde — aquele ponto perfeito onde código inteligente, bom design e empatia humana se encontram.

Esperamos que você experimente, teste, questione — e nos ajude a melhorar.

Diga o que gostaria de ver no próximo release. Mais condições? Mais explicações? Mais personalização?

Isso é só o começo — e adoraríamos que você ajudasse a moldar o que vem a seguir.

1
0 33
Artigo Larissa Prussak · Jun. 5, 2025 3m read

O IRIS oferece suporte nativo para transformações CCDA e FHIR, mas o acesso e a visualização desses recursos exigem tempo considerável de configuração e conhecimento do produto. O aplicativo IRIS Interop DevTools foi desenvolvido para preencher essa lacuna, permitindo que implementadores comecem a utilizar e visualizar imediatamente as capacidades de transformação embutidas no produto.

Além do ambiente de transformação IRIS XML, XPath e CCDA, o pacote Interop DevTools agora inclui:

0
0 33
Artigo Larissa Prussak · Maio 23, 2025 1m read

Depois que implementamos um novo container baseado em containers.intersystems.com/intersystems/irishealth:2023.1 esta semana, percebemos que nosso FHIR Repository começou a responder com um Erro 500. Isso aconteceu devido a violações de PROTECT no novo namespace e banco de dados HSSYSLOCALTEMP utilizado por essa versão dos componentes FHIR do IRIS for Health.

A solução para isso é adicionar a permissão "%DB_HSSYSLOCALTEMP" nas Aplicações Web que processam as requisições FHIR. Você pode automatizar essa configuração executando o seguinte método de classe nos namespace(s) onde essas Aplicações Web estão definidas:

do ##class(HS.HealthConnect.FHIRServer.Upgrade.MethodsV6).AddLOCALTEMPRoleToCSP()

No nosso caso, isso não foi suficiente. Parte do nosso código customizado precisa acessar o token JWT Bearer enviado pelo cliente, o qual antes era obtido através do elemento AdditionalInfo "USER:OAuthToken", que não está mais presente na build 2023.6.1.809, conforme descrito em: https://docs.intersystems.com/upgrade/results?product=ifh&versionFrom=20...

Contornamos esse problema adicionando a seguinte lógica para buscar o token diretamente do cache de tokens:

$$$ThrowOnError(##class(HS.HC.Util.InfoCache).GetTokenInfo(pInteropRequest.Request.AdditionalInfo.GetAt("USER:TokenId"), .pTokenInfo)) set OAuthToken = pTokenInfo("token_string")

0
0 19
Artigo Larissa Prussak · Maio 23, 2025 1m read

 

Apresento o FHIRCraft, uma ferramenta leve para gerar recursos FHIR sintéticos.

Talvez vocês estejam pensando:
“Mas... já não existe o Synthea, que gera um monte de recursos FHIR?”
Exatamente — e foi justamente por isso que criei este aplicativo.

O FHIRCraft foi desenvolvido para gerar recursos FHIR mais simples, pequenos e focados. Diferente do Synthea, ele não pretende simular prontuários clínicos completos nem fluxos assistenciais. É pensado para quem está começando com FHIR, quer fazer testes de forma progressiva ou explorar como funciona um recurso específico de forma isolada.

0
0 28
Artigo Heloisa Paiva · Abr. 30, 2025 17m read

Olá Comunidade,

Gostaria de apresentar meu último pacoteOpenAPI-Suite.Este é um conjunto de ferramentas para gerar código ObjectScript a partir deuma 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.
0
0 43
Artigo Heloisa Paiva · Abr. 18, 2025 18m read

Image generated by OpenAI DALL·E

Sou um grande fã de ficção científica, mas embora eu esteja totalmente a bordo da nave Star Wars (desculpas aos meus colegas Trekkies!), sempre apreciei os episódios clássicos de Star Trek da minha infância. A tripulação diversificada da USS Enterprise, cada um dominando suas funções únicas, é uma metáfora perfeita para entender os agentes de IA e seu poder em projetos como o Facilis. Então, vamos embarcar em uma missão intergaláctica, utilizando a IA como a tripulação da nossa nave e  audaciosamente ir audaciosamente ir homem jamais esteve!  Esse conceito de trabalho em equipe é uma analogia maravilhosa para ilustrar como os agentes de IA funcionam e como os usamos em nosso projeto DC-Facilis. Então, vamos mergulhar e assumir o papel de um capitão de nave estelar, liderando uma tripulação de IA em territórios inexplorados!

Bem-vindo ao CrewAI!

Para gerenciar nossa tripulação de IA, usamos uma estrutura fantástica chamada CrewAI.É enxuta, extremamente rápida e opera como uma plataforma Python multiagente. Uma das razões pelas quais a amamos, além do fato de ter sido criada por outro brasileiro, é sua incrível flexibilidade e design baseado em funções.

from crewai import Agent, Task, Crew

the taken quote

Conheça os Planejadores

No Facilis, nossos agentes de IA são divididos em dois grupos. Vamos começar com o primeiro, que gosto de chamar de "Os Planejadores".

O Agente de Extração

O papel principal do Facilis é receber uma descrição em linguagem natural de um serviço REST e criar automaticamente toda a interoperabilidade necessária. Portanto, nosso primeiro membro da tripulação é o Agente de Extração. Este agente tem a tarefa de "extrair" as especificações da API a partir da descrição fornecida pelo usuário.

Aqui está o que o Agente de Extração procura:

  • Host (obrigatório)
  • Endpoint (obrigatório)
  • Params (opcional)
  • Port (se disponível)
  • modelo JSON (para POST/PUT/PATCH/DELETE)
  • Autenticação (se aplicável)
    def create_extraction_agent(self) -> Agent:
        return Agent(
            role='Extrator de Especificações de API',
            goal='Extrair especificações de API de descrições em linguagem natural',
            backstory=dedent("""
                        Você é especializado em interpretar descrições em linguagem natural
                        e extrair especificações de API estruturadas.
            """),
            allow_delegation=True,
            llm=self.llm
        )

    def extract_api_specs(self, descriptions: List[str]) -> Task:
        return Task(
            description=dedent(f"""
               Extraia as especificações de API das seguintes descrições:
                {json.dumps(descriptions, indent=2)}
                
               Para cada descrição, extraia:
                - Host (obrigatório)
                - Endpoint (obrigatório)
                - Params (opcional)
                - Port (se disponível)
                - modelo JSON (para POST/PUT/PATCH/DELETE)
                - Autenticação (se aplicável)
                
                Marque quaisquer campos obrigatórios ausentes como 'missing'.
                Retorne os resultados em formato JSON como um array de especificações.
            """),
            expected_output="""Um array JSON contendo as especificações de API extraídas com todos os campos obrigatórios e opcionais""",
            agent=self.extraction_agent
        )

O Agente de Validação

Próximo na fila, o Agente de Validação! Sua missão é garantir que as especificações de API coletadas pelo Agente de Extração estejam corretas e consistentes. Ele verifica:

  1. Formato de host válido
  2. Endpoint começando com '/'
  3. Métodos HTTP válidos (GET, POST, PUT, DELETE, PATCH)
  4. Número de porta válido (se fornecido)
  5. Presença de modelo JSON para métodos aplicáveis.

    def create_validation_agent(self) -> Agent:
        return Agent(
            role='API Validator',
            goal='Validar especificações de API quanto à correção e consistência.',
            backstory=dedent("""
                 Você é um especialista em validação de API, garantindo que todas as especificações
                 atendam aos padrões e formatos necessários.
            """),
            allow_delegation=False,
            llm=self.llm
        )

 def validate_api_spec(self, extracted_data: Dict) -> Task:
        return Task(
            description=dedent(f"""
                Valide a seguinte especificação de API:
                {json.dumps(extracted_data, indent=2)}
                
                Verifique:
                1. Formato de host válido
                2. Endpoint começando com '/'
                3. Métodos HTTP válidos  (GET, POST, PUT, DELETE, PATCH)
                4. Número de porta válido (se fornecido)
                5. Presença de modelo JSON para métodos aplicáveis.
                
               Retorne os resultados da validação em formato JSON.
            """),
            expected_output="""Um objeto JSON contendo os resultados da validação com quaisquer erros ou confirmação de validade""",
            agent=self.validation_agent
        )

O Agente de Interação

Avançando, conhecemos o Agente de Interação, nosso Especialista em Interação com o Usuário. Seu papel é obter quaisquer campos de especificação de API ausentes que foram marcados pelo Agente de Extração e validá-los com base nas descobertas do Agente de Validação. Eles interagem diretamente com os usuários para preencher quaisquer lacunas.

O Agente de Produção

Precisamos de duas informações cruciais para criar a interoperabilidade necessária: namespace e nome da produção. O Agente de Produção interage com os usuários para coletar essas informações, de forma muito semelhante ao Agente de Interação.

O Agente de Transformação de Documentação

Assim que as especificações estiverem prontas, é hora de convertê-las em documentação OpenAPI. O Agente de Transformação de Documentação, um especialista em OpenAPI, cuida disso.

    def create_transformation_agent(self) -> Agent:
        return Agent(
            role='Especialista em Transformação OpenAPI',
            goal='Converter especificações de API em documentação OpenAPI',
            backstory=dedent("""
                Você é um especialista em especificações e documentação OpenAPI.
                Seu papel é transformar detalhes de API validados em documentação
                OpenAPI 3.0 precisa e abrangente.
            """),
            allow_delegation=False,
            llm=self.llm
        )

    def transform_to_openapi(self, validated_endpoints: List[Dict], production_info: Dict) -> Task:
        return Task(
            description=dedent(f"""
                Transforme as seguintes especificações de API validadas em documentação OpenAPI 3.0:
                
                Informações de Produção:
                {json.dumps(production_info, indent=2)}
                
               Endpoints Validados:
                {json.dumps(validated_endpoints, indent=2)}
                
               Requisitos:
               1. Gerar especificação OpenAPI 3.0 completa
               2. Incluir schemas de requisição/resposta apropriados
               3. Documentar todos os parâmetros e corpos de requisição
               4. Incluir autenticação se especificado
               5. Garantir formatação de caminho apropriada
                
                Retorne a especificação OpenAPI nos formatos JSON e YAML.
            """),
            expected_output="""Um objeto JSON contendo a especificação OpenAPI 3.0 completa com todos os endpoints e schemas.""",
            agent=self.transformation_agent
        )

The Review Agent

Após a transformação, a documentação OpenAPI passa por uma revisão meticulosa para garantir conformidade e qualidade. O Agente de Revisão segue esta lista de verificação:

1.Conformidade OpenAPI 3.0

  • Especificação de versão correta
  • Elementos raiz obrigatórios
  • Validação da estrutura do schema
  1. Completude
  • Todos os endpoints documentados
  • Parâmetros totalmente especificados
  • Schemas de requisição/resposta definidos
  • Esquemas de segurança configurados corretamente
  1. Verificações de Qualidade
  • Convenções de nomenclatura consistentes
  • Descrições claras
  • Uso adequado de tipos de dados
  • Códigos de resposta significativos
  1. Melhores Práticas
  • Uso adequado de tags
  • Nomenclatura de parâmetros consistente
  • Definições de segurança apropriadas

Finalmente, se tudo parecer bom, o Agente de Revisão reporta um objeto JSON saudável com a seguinte estrutura:

{
 "is_valid": boolean,
 "approved_spec": object (a especificação OpenAPI revisada e possivelmente corrigida),
 "issues": [array de strings descrevendo quaisquer problemas encontrados],
 "recommendations": [array de sugestões de melhoria]
}

    def create_reviewer_agent(self) -> Agent:
        return Agent(
            role='Revisor de Documentação OpenAPI',
            goal='Garantir a conformidade e qualidade da documentação OpenAPI',
            backstory=dedent("""
                Você é a autoridade final em qualidade e conformidade de documentação OpenAPI.
               Com vasta experiência em especificações OpenAPI 3.0, você revisa meticulosamente
               a documentação para precisão, completude e adesão aos padrões.
            """),
            allow_delegation=True,
            llm=self.llm
        )


    def review_openapi_spec(self, openapi_spec: Dict) -> Task:
        return Task(
            description=dedent(f"""
                Revise a seguinte especificação OpenAPI para conformidade e qualidade:
                
                {json.dumps(openapi_spec, indent=2)}
                
                Lista de Verificação da Revisão::
                1. Conformidade OpenAPI 3.0
                - Verificar a especificação de versão correta
                - Verificar os elementos raiz obrigatórios
                - Validar a estrutura do schema
                
                2. Completude
                - Todos os endpoints devidamente documentados
                - Parâmetros totalmente especificados
                - Schemas de requisição/resposta definidos
                - Esquemas de segurança configurados corretamente
                
                3. Quality Checks
                - Consistent naming conventions
                - Clear descriptions
                - Proper use of data types
                - Meaningful response codes
                
                4. Best Practices
                - Proper tag usage
                - Consistent parameter naming
                - Appropriate security definitions
                
                Você deve retornar um objeto JSON com a seguinte estrutura:
                {{
                    "is_valid": boolean,
                    "approved_spec": object (a especificação OpenAPI revisada e possivelmente corrigida),
                    "issues": [array de strings descrevendo quaisquer problemas encontrados],
                    "recommendations": [array de sugestões de melhoria]
                }}
            """),
            expected_output=""" Um objeto JSON contendo: is_valid (boolean), approved_spec (object), issues (array), e recommendations (array)""",
            agent=self.reviewer_agent
        )

O Agente Iris

O último agente no grupo do planejador é o Agente Iris, que envia a documentação OpenAPI finalizada para o Iris.


    def create_iris_i14y_agent(self) -> Agent:
        return Agent(
            role='Especialista em Integração Iris I14y',
            goal='Integrar especificações de API com o serviço Iris I14y',
            backstory=dedent("""
                  Você é responsável por garantir uma integração suave entre o sistema de
                 documentação da API e o serviço Iris I14y. Você lida com a
                 comunicação com o Iris, valida as respostas e garante a integração
                 bem-sucedida das especificações da API.
            """),
            allow_delegation=False,
            llm=self.llm
        )

    def send_to_iris(self, openapi_spec: Dict, production_info: Dict, review_result: Dict) -> Task:
        return Task(
            description=dedent(f"""
               Enviar a especificação OpenAPI aprovada para o serviço Iris I14y:

                Informações de Produção:
                - Nome: {production_info['production_name']}
                - Namespace: {production_info['namespace']}
                - É Novo: {production_info.get('create_new', False)}

               Status da Revisão:
                - Aprovado: {review_result['is_valid']}
                
                Retornar o resultado da integração em formato JSON.
            """),
            expected_output="""Um objeto JSON contendo o resultado da integração com o serviço Iris I14y, incluindo o status de sucesso e os detalhes da resposta.""",
            agent=self.iris_i14y_agent
        )

class IrisI14yService:
    def __init__(self):
        self.logger = logging.getLogger('facilis.IrisI14yService')
        self.base_url = os.getenv("FACILIS_URL", "http://dc-facilis-iris-1:52773") 
        self.headers = {
            "Content-Type": "application/json"
        }
        self.timeout = int(os.getenv("IRIS_TIMEOUT", "504"))  # in milliseconds
        self.max_retries = int(os.getenv("IRIS_MAX_RETRIES", "3"))
        self.logger.info("IrisI14yService initialized")

    async def send_to_iris_async(self, payload: Dict) -> Dict:
        """
        Enviar carga útil para o endpoint de geração do Iris de forma assíncrona.
        """
        self.logger.info("Enviando carga útil para o endpoint de geração do Iris.")
        if isinstance(payload, str):
            try:
                json.loads(payload)  
            except json.JSONDecodeError:
                raise ValueError("Invalid JSON string provided")
        
        retry_count = 0
        last_error = None

        # Cria timeout para o aiohttp
        timeout = aiohttp.ClientTimeout(total=self.timeout / 1000)  # Converte ms para seconds

        while retry_count < self.max_retries:
            try:
                self.logger.info(f"Attempt {retry_count + 1}/{self.max_retries}: Enviando requisição para {self.base_url}/facilis/api/generate")
                
                async with aiohttp.ClientSession(timeout=timeout) as session:
                    async with session.post(
                        f"{self.base_url}/facilis/api/generate",
                        json=payload,
                        headers=self.headers
                    ) as response:
                        if response.status == 200:
                            return await response.json()
                        response.raise_for_status()

            except asyncio.TimeoutError as e:
                retry_count += 1
                last_error = e
                error_msg = f"Timeout occurred (attempt {retry_count}/{self.max_retries})"
                self.logger.warning(error_msg)
                
                if retry_count < self.max_retries:
                    wait_time = 2 ** (retry_count - 1)
                    self.logger.info(f"Waiting {wait_time} seconds before retry...")
                    await asyncio.sleep(wait_time)
                continue

            except aiohttp.ClientError as e:
                error_msg = f"Failed to send to Iris: {str(e)}"
                self.logger.error(error_msg)
                raise IrisIntegrationError(error_msg)

        error_msg = f"Failed to send to Iris after {self.max_retries} attempts due to timeout"
        self.logger.error(error_msg)
        raise IrisIntegrationError(error_msg, last_error)

Conheça os Geradores

Nosso segundo conjunto de agentes são os Geradores. Eles estão aqui para transformar as especificações OpenAPI em interoperabilidade InterSystems IRIS. Há oito deles neste grupo.

O primeiro deles é o Agente Analisador. Ele é como o planejador, traçando a rota. Seu trabalho é mergulhar nas especificações OpenAPI e descobrir quais componentes de Interoperabilidade IRIS são necessários.


    def create_analyzer_agent():
        return Agent(
            role="Analisador de Especificações OpenAPI",
            goal="Analisar minuciosamente as especificações OpenAPI e planejar os componentes de Interoperabilidade IRIS",
            backstory="""Você é um especialista em especificações OpenAPI e em Interoperabilidade InterSystems IRIS.
                     Seu trabalho é analisar documentos OpenAPI e criar um plano detalhado de como eles devem ser implementados como componentes de Interoperabilidade IRIS.""",
            verbose=False,
            allow_delegation=False,
            tools=[analyze_openapi_tool],
            llm=get_facilis_llm()
        )

   analysis_task = Task(
        description="""Analisar a especificação OpenAPI e planejar os componentes de Interoperabilidade IRIS necessários.
Incluir uma lista de todos os componentes que devem estar na classe de Produção."",
        agent=analyzer,
        expected_output="Uma análise detalhada da especificação OpenAPI e um plano para os componentes IRIS, incluindo a lista de componentes de Produção",
        input={
            "openapi_spec": openApiSpec,
            "production_name": "${production_name}" 
        }
    )

Em seguida, os Agentes de Business Services (BS) e Business Operations (BO) assumem o controle. Eles geram os Business Services e as Business Operations com base nos endpoints OpenAPI. Eles usam uma ferramenta útil chamada MessageClassTool para gerar as classes de mensagens perfeitas, garantindo a comunicação.


    def create_bs_generator_agent():
        return Agent(
            role="Gerador de Produção e Business Service IRIS",
            goal="Gerar classes de Produção e Business Service IRIS formatadas corretamente a partir de especificações OpenAPI",
            backstory="""Você é um desenvolvedor InterSystems IRIS experiente, especializado em Produções de Interoperabilidade.
                 Sua expertise reside na criação de Business Services e Produções capazes de receber e processar requisições de entrada com base emespecificações de API."",
            verbose=False,
            allow_delegation=True,
            tools=[generate_production_class_tool, generate_business_service_tool],
            llm=get_facilis_llm()
        )

    def create_bo_generator_agent():
        return Agent(
            role="Gerador de Business Operation IRIS",
            goal="Gerar classes de Business Operation IRIS formatadas corretamente a partir de especificações OpenAPI",
            backstory="""Você é um desenvolvedor InterSystems IRIS experiente, especializado em Produções de Interoperabilidade.
                 Sua expertise reside na criação de Business Operations capazes de enviar requisições para sistemas externos com base em especificações de API.""",
            verbose=False,
            allow_delegation=True,
            tools=[generate_business_operation_tool, generate_message_class_tool],
            llm=get_facilis_llm()
        )

    bs_generation_task = Task(
        description="Gerar classes de Business Service com base nos endpoints OpenAPI",
        agent=bs_generator,
        expected_output="Definições de classes de Business Service do IRIS",
        context=[analysis_task]
    )

    bo_generation_task = Task(
        description="Gerar classes de Business Operation com base nos endpoints OpenAPI",
        agent=bo_generator,
        expected_output="Definições de classes de Business Operation do IRIS",
        context=[analysis_task]
    )

    class GenerateMessageClassTool(BaseTool):
        name: str = "generate_message_class"
        description: str = "Gerar uma classe de Mensagem IRIS"
        input_schema: Type[BaseModel] = GenerateMessageClassToolInput

        def _run(self, message_name: str, schema_info: Union[str, Dict[str, Any]]) -> str:
            writer = IRISClassWriter()
            try:
                if isinstance(schema_info, str):
                    try:
                        schema_dict = json.loads(schema_info)
                    except json.JSONDecodeError:
                        return "Error: Invalid JSON format for schema info"
                else:
                    schema_dict = schema_info

                class_content = writer.write_message_class(message_name, schema_dict)
                # Armazenar a classe gerada.
                writer.generated_classes[f"MSG.{message_name}"] = class_content
                return class_content
            except Exception as e:
                return f"Error generating message class: {str(e)}"

Depois que BS e BO fazem o que têm que fazer, é a hora do Agente de Produção brilhar! Este agente junta tudo para criar um ambiente de produção coeso.

Depois que tudo estiver configurado, o próximo na linha é o Agente de Validação. Este entra em cena para uma verificação final, garantindo que cada classe Iris esteja ok.

Em seguida, temos o Agente de Exportação e o Agente de Coleção. O Agente de Exportação gera os arquivos .cls, enquanto o Agente de Coleção reúne todos os nomes de arquivos. Tudo é passado para o importador, que compila tudo no InterSystems Iris.


    def create_exporter_agent():
        return Agent(
            role="Exportador de Classes IRIS",
            goal="Exportar e validar definições de classes IRIS para arquivos .cls adequados",
            backstory="""Você é um especialista em implantação InterSystems IRIS. Seu trabalho é garantir que as definições de classes IRIS geradas sejam devidamente exportadas como arquivos .cls válidos que
                possam ser importados diretamente para um ambiente IRIS.""",
            verbose=False,
            allow_delegation=False,
            tools=[export_iris_classes_tool, validate_iris_classes_tool],
            llm=get_facilis_llm()
        )
        
    def create_collector_agent():
        return Agent(
            role="Coletor de Classes IRIS",
            goal="Coletar todos os arquivos de classes IRIS gerados em uma coleção JSON",
            backstory="""Você é um especialista em sistema de arquivos responsável por reunir e
                organizar os arquivos de classes IRIS gerados em uma coleção estruturada.""",
            verbose=False,
            allow_delegation=False,
            tools=[CollectGeneratedFilesTool()],
            llm=get_facilis_llm()
        )

    export_task = Task(
        description="Exportar todas as classes IRIS geradas como arquivos .cls válidos",
        agent=exporter,
        expected_output="Arquivos .cls IRIS válidos salvos no diretório de saída",
        context=[bs_generation_task, bo_generation_task],
        input={
            "output_dir": "/home/irisowner/dev/output/iris_classes"  # Optional
        }
    )

    collection_task = Task(
        description="Coletar todos os arquivos de classes IRIS gerados em uma coleção JSON",
        agent=collector,
        expected_output="Coleção JSON de todos os arquivos .cls gerados",
        context=[export_task, validate_task],
        input={
            "directory": "./output/iris_classes"
        }
    )

Limitações e Desafios

Nosso projeto começou como um experimento empolgante, onde meus colegas mosqueteiros e eu almejávamos criar uma ferramenta totalmente automatizada usando agentes. Foi uma jornada selvagem! Nosso foco principal estava em integrações de API REST. É sempre uma alegria receber uma tarefa com uma especificação OpenAPI para integrar; no entanto, sistemas legados podem ser uma história completamente diferente. Pensamos que automatizar essas tarefas poderia ser incrivelmente útil. Mas toda aventura tem suas reviravoltas: Um dos maiores desafios foi instruir a IA a converter OpenAPI para Interoperabilidade Iris. Começamos com o modelo openAI GPT3.5-turbo, que em testes iniciais se mostrou difícil com depuração e prevenção de interrupções. A mudança para o Anthropic Claude 3.7 Sonnet mostrou melhores resultados para o grupo Gerador, mas não tanto para os Planejadores... Isso nos levou a dividir nossas configurações de ambiente, usando diferentes provedores de LLM para flexibilidade. Usamos GPT3.5-turbo para planejamento e Claude sonnet para geração, uma ótima combinação! Essa combinação funcionou bem, mas encontramos problemas com alucinações. A mudança para o GT4o melhorou os resultados, mas ainda enfrentamos alucinações na criação de classes Iris e, às vezes, especificações OpenAPI desnecessárias, como o renomado exemplo Pet Store OpenAPI. Nos divertimos muito aprendendo ao longo do caminho, e estou super animado com o futuro incrível nesta área, com inúmeras possibilidades!

1
0 42
InterSystems Oficial Danusa Calixto · Mar. 26, 2025 5m read

A interface de usuário de interoperabilidade agora inclui experiências de usuário modernizadas para os aplicativos Editor DTL e Configuração da Produção que estão disponíveis para aceitação em todos os produtos de interoperabilidade. Você pode alternar entre as visualizações modernizada e padrão. Todas as outras telas de interoperabilidade permanecem na interface de usuário padrão. Observe que as alterações são limitadas a esses dois aplicativos e identificamos abaixo a funcionalidade que está disponível atualmente.

0
0 38
InterSystems Oficial Danusa Calixto · jan 28, 2025

Já faz um tempo que não posto sobre o Embedded Git na Comunidade de Desenvolvedores, e gostaria de fornecer uma atualização sobre a enorme quantidade de trabalho que fizemos este ano e para onde estamos indo a seguir.

Contexto

Se você estiver construindo soluções no IRIS e quiser usar o Git, isso é ótimo! Basta usar o VSCode com um repositório git local e enviar suas alterações para o servidor - é muito fácil.

Mas e se:

0
0 32
Artigo Heloisa Paiva · Dez. 9, 2024 1m read

A capacidade de reenviar mensagens sempre foi um dos destaques das nossas funcionalidades de interoperabilidade.

Com a versão 2024.3, que será lançada em breve (já disponível como prévia para desenvolvedores), tornamos esse processo ainda mais simples!

0
0 49
Artigo Heloisa Paiva · Out. 24, 2024 7m read

fastapi_logo

Descrição

Este é um modelo para um aplicativo FastAPI que pode ser implantado no IRIS como um aplicativo Web nativo.

Instalação

  1. Clone o repositório
  2. Crie um ambiente virtual
  3. Instale os requisitos
  4. Execute o arquivo docker-compose
git clone
cd iris-fastapi-template
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
docker-compose up

Uso

A URL base é http://localhost:53795/fastapi/.

Endpoints

  • /iris - Retorna um objeto JSON com as 10 principais classes presentes no namespace IRISAPP.
  • /interop - Um endpoint de ping para testar a estrutura de interoperabilidade do IRIS.
  • /posts - Um endpoint CRUD simples para um objeto Post.
  • /comments - Um endpoint CRUD simples para um objeto Comentário.

Como desenvolver a partir deste template

Veja o artigo de introdução ao WSGI: wsgi-introduction.

TL;DR: Você pode ativar ou desativar o sinalizador DEBUG no portal de segurança para que as alterações sejam refletidas no aplicativo à medida que você desenvolve.

Apresentação do código

app.py

Este é o arquivo principal do aplicativo FastAPI. Ele contém o aplicativo FastAPI e as rotas.

from fastapi import FastAPI, Request

import iris

from grongier.pex import Director

# import models
from models import Post, Comment, init_db
from sqlmodel import Session,select

app = FastAPI()

# create a database engine
url = "iris+emb://IRISAPP"
engine = init_db(url)
  • from fastapi import FastAPI, Request - Importe a classe FastAPI e a classe Request
  • import iris - Importe o módulo IRIS.
  • from grongier.pex import Director: Importe a classe Director para vincular o aplicativo Flask ao framework de interoperabilidade do IRIS.
  • from models import Post, Comment, init_db - Importe os modelos e a função init_db.
  • from sqlmodel import Session,select - Importe a classe Session e a função select do módulo sqlmodel.
  • app = FastAPI() - Crie um aplicativo FastAPI.
  • url = "iris+emb://IRISAPP" -Defina o URL do namespace IRIS.
  • engine = init_db(url) - Crie um mecanismo de banco de dados para o ORM sqlmodel..

models.py

Este arquivo contém os modelos para o aplicativo.

from sqlmodel import Field, SQLModel, Relationship, create_engine

class Comment(SQLModel, table=True):
    id: int = Field(default=None, primary_key=True)
    post_id: int = Field(foreign_key="post.id")
    content: str
    post: "Post" = Relationship(back_populates="comments")

class Post(SQLModel, table=True):
    id: int = Field(default=None, primary_key=True)
    title: str
    content: str
    comments: list["Comment"] = Relationship(back_populates="post")

Não há muito a dizer aqui, apenas a definição dos modelos com chaves estrangeiras e relacionamentos.

A função init_db é usada para criar o mecanismo de banco de dados.

def init_db(url):

    engine = create_engine(url)

    # create the tables
    SQLModel.metadata.drop_all(engine)
    SQLModel.metadata.create_all(engine)

    # initialize database with fake data
    from sqlmodel import Session

    with Session(engine) as session:
        # Create fake data
        post1 = Post(title='Post The First', content='Content for the first post')
        ...
        session.add(post1)
        ...
        session.commit()

    return engine
  • engine = create_engine(url) - Crie um mecanismo de banco de dados.
  • SQLModel.metadata.drop_all(engine) - Exclua todas as tabelas.
  • SQLModel.metadata.create_all(engine) - Crie todas as tabelas.
  • with Session(engine) as session: - Crie uma sessão para interagir com o banco de dados.
  • post1 = Post(title='Post The First', content='Content for the first post')- Crie um objeto Post..
  • session.add(post1) -Adicione o objeto Post à sessão.
  • session.commit() -Confirme as alterações no banco de dados.
  • return engine - Retorne o mecanismo de banco de dados.

/iris endpoint

######################
# IRIS Query exemplo #
######################

@app.get("/iris")
def iris_query():
    query = "SELECT top 10 * FROM %Dictionary.ClassDefinition"
    rs = iris.sql.exec(query)
    # Convert the result to a list of dictionaries
    result = []
    for row in rs:
        result.append(row)
    return result
  • @app.get("/iris") - Defina uma rota GET para o endpoint /iris .
  • query = "SELECT top 10 * FROM %Dictionary.ClassDefinition" -Defina a consulta para obter as 10 principais classes no namespace IRIS.
  • rs = iris.sql.exec(query) - Execute a consulta..
  • result = [] - Crie uma lista vazia para armazenar os resultados.
  • for row in rs: - Itere sobre o conjunto de resultados.
  • result.append(row) - Adicione a linha à lista de resultados.
  • return result - Retorne a lista de resultados.

/interop endpoint

########################
# IRIS interop exemplo #
########################
bs = Director.create_python_business_service('BS')

@app.get("/interop")
@app.post("/interop")
@app.put("/interop")
@app.delete("/interop")
def interop(request: Request):
    
    rsp = bs.on_process_input(request)

    return rsp

  • bs = Director.create_python_business_service('BS') - Crie um business service Python. -Deve ser criado fora da definição da rota para evitar múltiplas instâncias do serviço de negócios.
  • @app.get("/interop") - Define uma rota GET para o endpoint /interop.
  • @app.post("/interop") - Define uma rota POST para o endpoin /interop .
  • ...
  • def interop(request: Request): - Define o manipulador da rota.
  • rsp = bs.on_process_input(request) - Chame o método on_process_input do business service.
  • return rsp - Retorne a resposta.

/posts endpoint

############################
# operações CRUD de posts    #
############################

@app.get("/posts")
def get_posts():
    with Session(engine) as session:
        posts = session.exec(select(Post)).all()
        return posts
    
@app.get("/posts/{post_id}")
def get_post(post_id: int):
    with Session(engine) as session:
        post = session.get(Post, post_id)
        return post
    
@app.post("/posts")
def create_post(post: Post):
    with Session(engine) as session:
        session.add(post)
        session.commit()
        return post

Este endpoint é usado para realizar operações CRUD no objeto Post.

Não há muito a dizer aqui, apenas a definição das rotas para obter todos os posts, obter um post por ID e criar um post.

Tudo é feito usando o ORM sqlmodel.

/comments endpoint

############################
# operações  CRUD de comments #
############################


@app.get("/comments")
def get_comments():
    with Session(engine) as session:
        comments = session.exec(select(Comment)).all()
        return comments
    
@app.get("/comments/{comment_id}")
def get_comment(comment_id: int):
    with Session(engine) as session:
        comment = session.get(Comment, comment_id)
        return comment
    
@app.post("/comments")
def create_comment(comment: Comment):
    with Session(engine) as session:
        session.add(comment)
        session.commit()
        return comment

Este endpoint é usado para realizar operações CRUD no objeto Comment.

Não há muito a dizer aqui, apenas a definição das rotas para obter todos os comentários, obter um comentário por ID e criar um comentário.

Tudo é feito usando o ORM sqlmodel.

Solução de Problemas

Como executar o aplicativo FastAPI em modo autônomo

Você sempre pode executar um aplicativo Flask autônomo com o seguinte comando:

python3 /irisdev/app/community/app.py

Observação: você deve estar dentro do contêiner para executar este comando.

docker exec -it iris-fastapi-template-iris-1 bash

Reinicie o aplicativo no IRIS

Fique no modo DEBUG, faça várias chamadas para o aplicativo e as alterações serão refletidas no aplicativo.

Como acessar o Portal de Gerenciamento do IRIS

Você pode acessar o Portal de Gerenciamento do IRIS acessando http://localhost:53795/csp/sys/UtilHome.csp.

Execute este template localmente

Para isso, você precisa ter o IRIS instalado em sua máquina.

Em seguida, você precisa criar um namespace chamado IRISAPP.

Instale os requisitos.

Instale IoP :

#init iop
iop --init

# load production
iop -m /irisdev/app/community/interop/settings.py

# start production
iop --start Python.Production

Configure o aplicativo no portal de segurança.

0
0 43
Artigo Heloisa Paiva · Out. 20, 2024 6m read

Flask_logo

Descrição

Este é um modelo para um aplicativo Flask que pode ser implantado no IRIS como um aplicativo Web nativo.

Instalação

  1. Clone o repositório
  2. Crie um ambiente virtual
  3. Instale os requisitos
  4. Rode o arquivo docker-compose
git clone
cd iris-flask-template
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
docker-compose up

Uso

A URL de base http://localhost:53795/flask/.

Endpoints

  • /iris - Retorna um objeto JSON com as 10 principais classes presentes no namespace IRISAPP.
  • /interop - Um endpoint de ping para testar o framework de interoperabilidade do IRIS
  • /posts - Um simples enpoint CRUD para um objeto de Post
  • /comments - Um enpoint simples de CRUD para o objeto de comentário

Como desenvolver deste template

Veja o artigo de introdução ao WSGI wsgi-introduction.

TL;DR: Você pode ativar ou desativar o sinalizador DEBUG no portal de segurança para que as alterações sejam refletidas no aplicativo à medida que você desenvolve.

Apresentação do código

app.py

Este é o arquivo principal do aplicativo. Ele contém o aplicativo Flask e os endpoints.

from flask import Flask, jsonify, request
from models import Comment, Post, init_db

from grongier.pex import Director

import iris

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'iris+emb://IRISAPP'

db = init_db(app)
  • from flask import Flask, jsonify, request: Importa a livraria Flask
  • from models import Comment, Post, init_db: Importa os modelos e a função de inicialização de base de dados
  • from grongier.pex import Director: Importa a classe Director para vincular o app flask à framework de interoperabilidade do IRIS
  • import iris: Importa a livraria IRIS
  • app = Flask(__name__): Cria uma aplicação Flask
  • app.config['SQLALCHEMY_DATABASE_URI'] = 'iris+emb://IRISAPP': Define o URI da base de dados ao namespace IRISAPP
    • O esquema de URI iris+emb é usado para conectar ao IRIS como uma conexão embutida (sem necessidade de uma instância IRIS separada
  • db = init_db(app): Inicialize a base de dados com aplicação Flask.

models.py

O arquivo contem os modelos SQLAlchemy para a aplicação.

from dataclasses import dataclass
from typing import List
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

@dataclass
class Comment(db.Model):
    id:int = db.Column(db.Integer, primary_key=True)
    content:str = db.Column(db.Text)
    post_id:int = db.Column(db.Integer, db.ForeignKey('post.id'))

@dataclass
class Post(db.Model):
    __allow_unmapped__ = True
    id:int = db.Column(db.Integer, primary_key=True)
    title:str = db.Column(db.String(100))
    content:str = db.Column(db.Text)
    comments:List[Comment] = db.relationship('Comment', backref='post')

Não há muito o que dizer aqui, os modelos são definidos como classes de dados e são subclasses da classe db.Model

O uso do atributo __allow_unmapped_ é necessário para permitir a criação do objeto Post sem o atributo comments

dataclasses são usadas para ajudar com a serialização de objetos ao JSON

A função init_db inicializa a base de dados com a aplicação Flask.

def init_db(app):
    db.init_app(app)

    with app.app_context():
        db.drop_all()
        db.create_all()
        # Create fake data
        post1 = Post(title='Post The First', content='Content for the first post')
        ...
        db.session.add(post1)
        ...
        db.session.commit()
    return db
  • db.init_app(app): Inicializa a base de dados com a aplicação Flask
  • with app.app_context(): Cria um contexto para a aplicação
  • db.drop_all(): Descarta todas as tabelas na base de dados
  • db.create_all(): Cria todas as tabelas na base de dados
  • Cria dados falsos para a aplicação
  • retorna o objeto de base de dados

/iris endpoint

######################
# IRIS Query exemplo#
######################

@app.route('/iris', methods=['GET'])
def iris_query():
    query = "SELECT top 10 * FROM %Dictionary.ClassDefinition"
    rs = iris.sql.exec(query)
    # Converte o resultado em uma lista de dicionários
    result = []
    for row in rs:
        result.append(row)
    return jsonify(result)

Esse endpoint executa uma query na base de dados IRIS e retorna as top 10 classes presentes no namespace IRISAPP

/interop endpoint

########################
# IRIS interop exemplo #
########################
bs = Director.create_python_business_service('BS')

@app.route('/interop', methods=['GET', 'POST', 'PUT', 'DELETE'])
def interop():
    
    rsp = bs.on_process_input(request)

    return jsonify(rsp)

Este endpoint é usado para testar a estrutura de interoperabilidade do IRIS. Ele cria um objeto de Serviço de Negócio e o vincula ao aplicativo Flask.

Observação: O objeto bs deve estar fora do escopo da solicitação para mantê-lo ativo.

  • bs = Director.create_python_business_service('BS'): Cria um objeto Business Service chamado 'BS'
  • rsp = bs.on_process_input(request): Chama o método on_process_input do objeto Business Service com o objeto de requisição como um argumento

/posts endpoint

############################
# operações CRUD para posts    #
############################

@app.route('/posts', methods=['GET'])
def get_posts():
    posts = Post.query.all()
    return jsonify(posts)

@app.route('/posts', methods=['POST'])
def create_post():
    data = request.get_json()
    post = Post(title=data['title'], content=data['content'])
    db.session.add(post)
    db.session.commit()
    return jsonify(post)

@app.route('/posts/<int:id>', methods=['GET'])
def get_post(id):
    ...

Este endpoint é usado para realizar operações CRUD no objeto Post

Graças ao módulo dataclasses, o objeto Post pode ser facilmente serializado para JSON.

Aqui, usamos o método query do sqlalchemy para obter todos os posts e os métodos add e commit para criar um novo post

/comments endpoint

############################
# operações CRUD para comentários  #
############################

@app.route('/comments', methods=['GET'])
def get_comments():
    comments = Comment.query.all()
    return jsonify(comments)

@app.route('/comments', methods=['POST'])
def create_comment():
    data = request.get_json()
    comment = Comment(content=data['content'], post_id=data['post_id'])
    db.session.add(comment)
    db.session.commit()
    return jsonify(comment)

@app.route('/comments/<int:id>', methods=['GET'])
def get_comment(id):
    ...

Este endpoint é usado para realizar operações CRUD no objeto Comment.

O objeto Comment está vinculado ao objeto Post por uma chave estrangeira.

Solução de Problemas

Como executar o aplicativo Flask em modo autônomo

Você sempre pode executar um aplicativo Flask autônomo com o seguinte comando:

python3 /irisdev/app/community/app.py

Nota: você deve estar dentro do container para rodar este comando

docker exec -it iris-flask-template-iris-1 bash

Reinicie a aplicação no IRIS

Esteja no modo DEBUG, faça várias chamadas para o aplicativo e as alterações serão refletidas no aplicativo.

Como acessar o Portal de Gerenciamento do IRIS

Você pode acessar o Portal de Gerenciamento do IRIS acessandohttp://localhost:53795/csp/sys/UtilHome.csp.

Rode este template localmente

Para isso, você precisa ter o IRIS instalado em sua máquina.

Em seguida, você precisa criar um namespace chamado IRISAPP.

Instale os requisitos.

Instale IoP :

#init iop
iop --init

# carregue a produção 
iop -m /irisdev/app/community/interop/settings.py

# iniicie a produção
iop --start Python.Production

Configure a aplicação no portal de Segurança

0
0 48
Artigo Heloisa Paiva · Ago. 27, 2024 7m read

Tenho orgulho de anunciar o novo lançamento de iris-pex-embedded-python (v2.3.1) com uma nova interface de linha de comando.

Essa linha de comando é chamada iop, de Interoperability On Python.

Primeiro, eu gostaria de apresentar em algumas palavras as maiores mudanças no projeto desde a primeira versão.

Um breve histórico do projeto

A versão 1.0 foi uma prova de conceito para mostrar como a framework de interoperabilidade do IRIS pode ser usada com uma abordagem primeiro python enquanto permanece compatível com qualquer código ObjectScript existente.

O que isso significa? Significa que qualquer desenvolvedor python pode usar a framework de interoperabilidade do IRIS sem ter nenhum conhecimento de ObjectScript.

Exemplo:

from grongier.pex import BusinessOperation

class MyBusinessOperation(BusinessOperation):

    def on_message(self, request):
        self.log.info("Received request")

Incrível, não é?

Com a versão 1.1, eu adicionei a possibilidade de registrar as classes python para o IRIS com uma função ajudante.

from grongier.pex import Utils

Utils.register_file("/src/MyBusinessOperation.py")

A versão 2.0 foi um lançamento maior porque agora você pode instalar o projeto com pip.

pip install iris-pex-embedded-python

O que há de novo na versão 2.3.1

A versão 2.3.1 é um lançamento grande porque introduz uma nova interface de linha de comando

Essa interface de linha de comando pode ser usada com esse projeto python baseada neste módulo ou talvez projetos que não usem este módulo.

Deixe-me introduzi-lo e explicar porque pode ser usado em projetos sem python.

a interface de linha de comando

A linha de comando é parte deste projeto, para instalá-la você deve instalar o projeto com pip.

pip install iris-pex-embedded-python

Então você pode usar a linha de comando iop para iniciar a framework de interoperabilidade.

iop

saída:

usage: iop [-h] [-d DEFAULT] [-l] [-s START] [-k] [-S] [-r] [-M MIGRATE] [-e EXPORT] [-x] [-v] [-L]
optional arguments:
  -h, --help            display help and default production name
  -d DEFAULT, --default DEFAULT
                        set the default production
  -l, --lists           list productions
  -s START, --start START
                        start a production
  -k, --kill            kill a production (force stop)
  -S, --stop            stop a production
  -r, --restart         restart a production
  -M MIGRATE, --migrate MIGRATE
                        migrate production and classes with settings file
  -e EXPORT, --export EXPORT
                        export a production
  -x, --status          status a production
  -v, --version         display version
  -L, --log             display log

default production: UnitTest.Production

Vamos ver alguns exemplos.

help

O comando help exibe a ajuda e o nome padrão da produção.

iop -h

Saída:

usage: python3 -m grongier.pex [-h] [-d DEFAULT] [-l] [-s START] [-k] [-S] [-r] [-M MIGRATE] [-e EXPORT] [-x] [-v] [-L]
...
default production: PEX.Production

default

O comando default define a produção padrão.

Sem argumento, ele exibe a produção default.

iop -d

output :

default production: PEX.Production

Com um argumento, ele define a produção padrão.

iop -d PEX.Production

lists

O comando lists lista as produções.

iop -l

Saída:

{
    "PEX.Production": {
        "Status": "Stopped",
        "LastStartTime": "2023-05-31 11:13:51.000",
        "LastStopTime": "2023-05-31 11:13:54.153",
        "AutoStart": 0
    }
}

start

O comando start inicia uma produção

Para sair do comando, você deve apertar CTRL+C.

iop -s PEX.Production

Se nenhum argumento for dado, o comando start inicia a produção padrão.

iop -s

Saída:

2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting production
2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting item Python.FileOperation
2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting item Python.EmailOperation
...

kill

O comando kill mata uma produção (força parada).

O comando kill é o mesmo que o de parada (stop), mas com um "forçar" (force) na frente.

O comando kill não leva argumento porque só pode ter uma produção rodando.

iop -k 

stop

O comando stop para uma produção.

O comando stop não tem argumentos porque só pode ter uma produção rodando

iop -S 

restart

O comando restart reinicia uma produção.

O comando restart não tem argumentos porque só pode ter uma produção rodando.

iop -r 

migrate

O comando migrate migra uma produção e classes com arquivo de definições settings.

O comando migrate deve ter como argumento o caminho completo do arquivo de definições settings.

O arquivo de definições settings deve estar na mesma pasta do código python.

iop -M /tmp/settings.py

export

O comando export exporta uma produção.

Se não for dado nenhum argumento, o comando export exporta a produção padrão.

iop -e

Se um argumento for dado, o comando export exporta a produção no dado argumento.

iop -e PEX.Production

output :

{
    "Production": {
        "@Name": "PEX.Production",
        "@TestingEnabled": "true",
        "@LogGeneralTraceEvents": "false",
        "Description": "",
        "ActorPoolSize": "2",
        "Item": [
            {
                "@Name": "Python.FileOperation",
                "@Category": "",
                "@ClassName": "Python.FileOperation",
                "@PoolSize": "1",
                "@Enabled": "true",
                "@Foreground": "false",
                "@Comment": "",
                "@LogTraceEvents": "true",
                "@Schedule": "",
                "Setting": [
                    {
                        "@Target": "Adapter",
                        "@Name": "Charset",
                        "#text": "utf-8"
                    },
                    {
                        "@Target": "Adapter",
                        "@Name": "FilePath",
                        "#text": "/irisdev/app/output/"
                    },
                    {
                        "@Target": "Host",
                        "@Name": "%settings",
                        "#text": "path=/irisdev/app/output/"
                    }
                ]
            }
        ]
    }
}

status

O comando status dá o status de uma produção.

O comando status não leva argumentos pois apenas uma produção pode estar rodando.

iop -x 

output :

{
    "Production": "PEX.Production",
    "Status": "stopped"
}

Os status podem ser:

  • stopped (parada)
  • running (rodando)
  • suspended (suspensa)
  • troubled (com problema)

version

O comando version exibe a versão.

iop -v

output :

2.3.0

log

O comando log exibe o log.

Para sair do comando você deve apertar CTRL+C

iop -L

output :

2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting production
2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting item Python.FileOperation
2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting item Python.EmailOperation
...

Ele pode ser usado fora do projeto iris-pex-embedded-python?

Isso será uma escolha sua.

Mas, antes de você sair daqui, deixe eu te dizer porque eu acho que pode ser usado fora de um projeto iris-pex-embedded-python.

Primeiro, porque ele pode interagir com a produção sem a necessidade de usar um terminal iris. Isso significa que é mais fácil de usar em um script.

Segundo, porque o settings.py pode ser usado para importar uma produção e classes com variáveis de ambiente.

Aqui está um exemplo de settings.py:

import os

PRODUCTIONS = [
        {
            'UnitTest.Production': {
                "Item": [
                    {
                        "@Name": "Python.FileOperation",
                        "@ClassName": "Python.FileOperation",
                        "Setting": {
                            "@Target": "Host",
                            "@Name": "%settings",
                            "#text": os.environ['SETTINGS']
                        }
                    }
                ]
            }
        } 
    ]

Note o valor #text. É um variável de ambiente. Bacana, né?

Você se vê usando essa ferramenta de linha de comando, o suficiente para valer a pena continuar desenvolvendo?

Obrigado por ler e seus feedbacks são bem vindos.

0
0 52
Artigo Heloisa Paiva · Jun. 21, 2024 7m read

Introdução

Não muito tempo atrás, eu me deparei com a ideia de Usar a Sintaxe de Definição de Classe Python para criar classes IRIS no Portal de Ideias do InterSystems. Ela atraiu minha atenção porque integrar o máximo de sintaxes possíveis dá visibilidade aos produtos InterSystems para programadores de muitas linguagens.

O autor dessa ideia apontou a possibilidade de criar classes usando a sintaxe Python, em adição às já disponíveis no IRIS. Esse conceito me inspirou a escrever esse artigo, explorando as possibilidades de acessar o poder total da InterSystems utilizando apenas Python.

0
0 78
Artigo Danusa Calixto · Abr. 16, 2024 26m read

1. interoperability-embedded-python

Esta prova de conceito busca mostrar como o framework de interoperabilidade do iris pode ser usado com o embedded python.

1.1. Índice

1.2. Exemplo

from grongier.pex import BusinessOperation,Message

class MyBusinessOperation(BusinessOperation):
    
    def on_init(self):
        #Esse método é chamado quando o componente está se tornando ativo na produção

        self.log_info("[Python] ...MyBusinessOperation:on_init() is called")

        return

    def on_teardown(self):
        #Esse método é chamado quando o componente está se tornando inativo na produção

        self.log_info("[Python] ...MyBusinessOperation:on_teardown() is called")

        return

    def on_message(self, message_input:MyRequest):
        #É chamado do serviço/processo/operação, a mensagem é do tipo MyRequest com a propriedade request_string

        self.log_info("[Python] ...MyBusinessOperation:on_message() is called with message:"+message_input.request_string)

        response = MyResponse("...MyBusinessOperation:on_message() echos")

        return response

@dataclass
class MyRequest(Message):

    request_string:str = None

@dataclass
class MyResponse(Message):

    my_string:str = None

1.3. Registrar um componente

Graças ao método grongier.pex.Utils.register_component():

Inicie um shell do embedded python:

/usr/irissys/bin/irispython

Em seguida, use esse método de classe para adicionar uma classe do python à lista de componentes para interoperabilidade.

from grongier.pex import Utils

Utils.register_component(<ModuleName>,<ClassName>,<PathToPyFile>,<OverWrite>,<NameOfTheComponent>)

Por exemplo:

from grongier.pex import Utils

Utils.register_component("MyCombinedBusinessOperation","MyCombinedBusinessOperation","/irisdev/app/src/python/demo/",1,"PEX.MyCombinedBusinessOperation")

Isso é um truque, e não serve para produção.

2. Demonstração

A demonstração pode ser encontrada em src/python/demo/reddit/ e é composta de:

  • Um arquivo adapter.py com um RedditInboundAdapter que, dado um serviço, buscará postagens recentes do Reddit.

  • Um arquivo bs.py com três services que faz a mesma coisa: ele chamará nosso Process e enviará a postagem do Reddit. Um funciona sozinho, um usa o RedditInBoundAdapter que mencionamos antes e o último usa um inbound adapter do Reddit codificado em ObjectScript.

  • Um arquivo bp.py com um processo FilterPostRoutingRule que analisará nossas postagens do Reddit e as enviará para nossas operations se tiverem determinadas palavras.

  • Um arquivo bo.py com:

    • Duas operações de e-mail que enviarão um e-mail para uma empresa específica dependendo das palavras analisadas antes. Uma funciona sozinha e a outra funciona com um OutBoundAdapter.
    • Duas operações de arquivo que escreverão em um arquivo de texto dependendo das palavras analisadas antes. Uma funciona sozinha e outra funciona com um OutBoundAdapter.

Novo trace json para mensagens nativas do python: json-message-trace

3. Pré-requisitos

Verifique se você tem o git e o Docker desktop instalados.

4. Instalação

4.1. Com Docker

Faça o git pull/clone do repositório em qualquer diretório local

git clone https://github.com/grongierisc/interpeorability-embedded-python

Abra o terminal nesse diretório e execute:

docker-compose build

Execute o contêiner IRIS com seu projeto:

docker-compose up -d

4.2. Sem Docker

Instale o grongier_pex-1.2.4-py3-none-any.whl na sua instância iris local:

/usr/irissys/bin/irispython -m pip install grongier_pex-1.2.4-py3-none-any.whl

Em seguida, carregue as classes ObjectScript:

do $System.OBJ.LoadDir("/opt/irisapp/src","cubk","*.cls",1)

4.3. Com ZPM

zpm "install pex-embbeded-python" 

4.4. Com PyPI

pip3 install iris_pex_embedded_python

Importe as classes ObjectScript, abra um shell do embedded python e execute:

from grongier.pex import Utils
Utils.setup()

4.4.1. Problemas conhecidos

Se o módulo não estiver atualizado, remova a versão antiga:

pip3 uninstall iris_pex_embedded_python

ou remova manualmente a pasta grongier em <iris_installation>/lib/python/

ou force a instalação com o pip:

pip3 install --upgrade iris_pex_embedded_python --target <iris_installation>/lib/python/

5. Como executar a amostra

5.1. Contêineres Docker

Para ter acesso às imagens da InterSystems, é necessário acessar o seguinte URL: http://container.intersystems.com. Depois de se conectar com as credenciais da InterSystems, obtenha a senha para se conectar ao registro. No complemento VSCode do docker, na guia de imagens, ao pressionar "connect registry" e inserir o mesmo url de antes (http://container.intersystems.com) como um registro genérico, será solicitado as credenciais. O login é o habitual, mas a senha é a obtida do site.

Em seguida, será possível criar e compor os contêineres (com os arquivos docker-compose.yml e Dockerfile fornecidos).

5.2. Portal de Gerenciamento e VSCode

Esse repositório está pronto para o VS Code.

Abra a pasta interoperability-embedeed-python clonada localmente no VS Code.

Se solicitado (canto inferior direito), instale as extensões recomendadas.

IMPORTANTE: quando solicitado, reabra a pasta dentro do contêiner, para que você possa usar os componentes do python dentro dele. Na primeira vez que você fizer isso, o contêiner pode levar vários minutos para ficar pronto.

Ao abrir a pasta remota, você permite que o VS Code e quaisquer terminais abertos dentro dele usem os componentes do python dentro do contêiner. Configure-os para usar /usr/irissys/bin/irispython

PythonInterpreter

5.3. Abra a produção

Para abrir a produção, você pode acessar a [produção] (http://localhost:52773/csp/irisapp/EnsPortal.ProductionConfig.zen?PRODUCTION=PEX.Production).
Você também pode clicar na parte inferior no botão 127.0.0.1:52773[IRISAPP], selecionar Open Management Portal(Abrir Portal de Gerenciamento), clicar nos menus [Interoperability] e [Configure] e, depois, em [productions] e [Go].

A produção já tem uma amostra de código.

Aqui podemos ver a produção e nossos serviços e operações de python puro: interop-screenshot


Novo trace json para mensagens nativas do python: json-message-trace

6. O que está dentro do repositório

6.1. Dockerfile

Um dockerfile que instala algumas dependências do python (pip, venv) e sudo no contêiner para conveniência. Em seguida, ele cria o diretório dev e copia nele esse repositório git.

Ele inicia o IRIS e ativa %Service_CallIn para o Shell do Python. Use o docker-compose.yml relacionado para configurar facilmente parâmetros adicionais, como número da porta e onde você mapeia chaves e hospeda pastas.

Esse dockerfile termina com a instalação dos requisitos para os módulos do python.

Use o arquivo .env/ para ajustar o dockerfile usado em docker-compose.

6.2. .vscode/settings.json

Arquivo de configurações para você programar imediatamente no VSCode com o [plugin ObjectScript do VSCode] (https://marketplace.visualstudio.com/items?itemName=daimor.vscode-objectscript)

6.3. .vscode/launch.json

Arquivo de configurações se você quiser depurar com o ObjectScript do VSCode

Leia sobre todos os arquivos neste artigo

6.4. .vscode/extensions.json

Arquivo de recomendação para adicionar extensões se você quiser executar com o VSCode no contêiner.

Mais informações aqui

Archiecture

Isso é muito útil para trabalhar com o embedded python.

6.5. src folder

src
├── Grongier
│   └── PEX // Classes ObjectScript que envolvem código em python
│       ├── BusinessOperation.cls
│       ├── BusinessProcess.cls
│       ├── BusinessService.cls
│       ├── Common.cls
│       ├── Director.cls
│       ├── InboundAdapter.cls
│       ├── Message.cls
│       ├── OutboundAdapter.cls
│       ├── Python.cls
│       ├── Test.cls
│       └── _utils.cls
├── PEX // Alguns exemplos de classes envolvidas
│   └── Production.cls
└── python
    ├── demo // código em python real para executar essa demonstração
    |   `-- reddit
    |       |-- adapter.py
    |       |-- bo.py
    |       |-- bp.py
    |       |-- bs.py
    |       |-- message.py
    |       `-- obj.py
    ├── dist // Wheel usado para implementar componentes de interoperabilidade do python
    │   └── grongier_pex-1.2.4-py3-none-any.whl
    ├── grongier
    │   └── pex // Classes helper para implementar componentes de interoperabilidade
    │       ├── _business_host.py
    │       ├── _business_operation.py
    │       ├── _business_process.py
    │       ├── _business_service.py
    │       ├── _common.py
    │       ├── _director.py
    │       ├── _inbound_adapter.py
    │       ├── _message.py
    │       ├── _outbound_adapter.py
    │       ├── __init__.py
    │       └── _utils.py
    └── setup.py // configuração para criar o wheel

7. Como funciona

7.1. Arquivo __init__.py

Esse arquivo nos permite criar as classes para importar no código.
Ele obtém as classes de vários arquivos vistos antes e as transforma em classes callable. Assim, quando você quiser criar uma operação de negócios, por exemplo, pode só executar:

from grongier.pex import BusinessOperation

7.2. Classe common

A classe common não deve ser chamada pelo usuário. Ela define quase todas as outras classes.
Essa classe define:

on_init: esse método é chamado quando o componente é inicializado.
Use o método on_init() para inicializar qualquer estrutura necessária para o componente.

on_tear_down: é chamado antes de encerrar o componente.
Use para liberar qualquer estrutura.

on_connected: esse método é chamado quando o componente é conectado ou reconectado após ser desconectado.
Use o método on_connected() para inicializar qualquer estrutura necessária para o componente.

log_info: grava uma entrada de log do tipo "info". As entradas de log podem ser visualizadas no portal de gerenciamento.

log_alert: grava uma entrada de log do tipo "alert". As entradas de log podem ser visualizadas no portal de gerenciamento.

log_warning: grava uma entrada de log do tipo "warning". As entradas de log podem ser visualizadas no portal de gerenciamento.

log_error: grava uma entrada de log do tipo "error". As entradas de log podem ser visualizadas no portal de gerenciamento.

7.3. Classe business_host

A classe de host de negócios não deve ser chamada pelo usuário. É a classe base para todas as classes de negócios.
Essa classe define:

send_request_sync: envia a mensagem específica à operação ou ao processo de negócios alvo de maneira síncrona.
Parâmetros:

  • target: uma string que especifica o nome da operação ou do processo de negócios a receber a solicitação.
    O alvo é o nome do componente conforme especificado na propriedade Item Name na definição da produção, e não o nome da classe do componente.
  • request: especifica a mensagem a enviar ao alvo. A solicitação é uma instância de uma classe que é IRISObject ou subclasse da classe Message.
    Se o alvo é um componente ObjectScript integrado, você deve usar a classe IRISObject. A classe IRISObject permite que o framework PEX converta a mensagem para uma classe compatível com o alvo.
  • timeout: um inteiro opcional que especifica o número de segundos de espera antes de tratar a solicitação de envio como uma falha. O valor padrão é -1, que significa "esperar para sempre".
    description: um parâmetro de string opcional que define uma propriedade da descrição no cabeçalho da mensagem. O padrão é None.

Retorna: o objeto de resposta do alvo.

Gera: TypeError: se a solicitação não é do tipo Message ou IRISObject.


send_request_async: envia a mensagem específica ao processo ou operação de negócios alvo de maneira assíncrona. Parâmetros:

  • target: uma string que especifica o nome da operação ou do processo de negócios a receber a solicitação.
    O alvo é o nome do componente conforme especificado na propriedade Item Name na definição da produção, e não o nome da classe do componente.
  • request: especifica a mensagem a enviar ao alvo. A solicitação é uma instância de IRISObject ou de uma subclasse de Message.
    Se o alvo é um componente ObjectScript integrado, você deve usar a classe IRISObject. A classe IRISObject permite que o framework PEX converta a mensagem para uma classe compatível com o alvo.
  • description: um parâmetro de string opcional que define uma propriedade da descrição no cabeçalho da mensagem. O padrão é None.

Gera: TypeError: se a solicitação não é do tipo Message ou IRISObject.


get_adapter_type: nome do adaptador registrado.

7.4. Classe inbound_adapter

Inbound Adapter no Python é uma subclasse de grongier.pex.InboundAdapter no Python, que herda de todas as funções da classe common.
Essa classe é responsável por receber os dados do sistema externo, validar os dados e enviá-los ao serviço de negócios ao chamar o método process_input de BusinessHost. Essa classe define:

on_task: é chamado pelo framework de produção a intervalos determinados pela propriedade CallInterval do serviço de negócios.
A mensagem pode ter qualquer estrutura que esteja de acordo com o inbound adapter e serviço de negócios.

Exemplo de um inbound adapter (localizado no arquivo src/python/demo/reddit/adapter.py):

from grongier.pex import InboundAdapter
import requests
import iris
import json

class RedditInboundAdapter(InboundAdapter):
    """
    Esse adaptador usa solicitações para buscar postagens self.limit como dados da API Reddit
    antes de chamar o process_input para cada postagem.
    """
    def on_init(self):
        
        if not hasattr(self,'feed'):
            self.feed = "/new/"
        
        if self.limit is None:
            raise TypeError('no Limit field')
        
        self.last_post_name = ""
        
        return 1

    def on_task(self):
        self.log_info(f"LIMIT:{self.limit}")
        if self.feed == "" :
            return 1
        
        tSC = 1
        # Solicitação HTTP
        try:
            server = "https://www.reddit.com"
            request_string = self.feed+".json?before="+self.last_post_name+"&limit="+self.limit
            self.log_info(server+request_string)
            response = requests.get(server+request_string)
            response.raise_for_status()

            data = response.json()
            updateLast = 0

            for key, value in enumerate(data['data']['children']):
                if value['data']['selftext']=="":
                    continue
                post = iris.cls('dc.Reddit.Post')._New()
                post._JSONImport(json.dumps(value['data']))
                post.OriginalJSON = json.dumps(value)
                if not updateLast:
                    self.LastPostName = value['data']['name']
                    updateLast = 1
                response = self.BusinessHost.ProcessInput(post)
        except requests.exceptions.HTTPError as err:
            if err.response.status_code == 429:
                self.log_warning(err.__str__())
            else:
                raise err
        except Exception as err: 
            self.log_error(err.__str__())
            raise err

        return tSC

7.5. Classe outbound_adapter

Outbound Adapter no Python é uma subclasse de grongier.pex.OutboundAdapter no Python, que herda de todas as funções da classe common.
Essa classe é responsável por enviar os dados para o sistema externo.

O Outbound Adapter possibilita à operação ter uma noção dos batimentos cardíacos. Para ativar essa opção, o parâmetro CallInterval do adaptador precisa ser estritamente maior que 0.

image

Exemplo de um outbound adapter (localizado no arquivo src/python/demo/reddit/adapter.py):

class TestHeartBeat(OutboundAdapter):

    def on_keepalive(self):
        self.log_info('beep')

    def on_task(self):
        self.log_info('on_task')

7.6. Classe business_service

Essa classe é responsável por receber os dados do sistema externo e enviá-los aos processos ou operações de negócios na produção.
O serviço de negócios pode usar um adaptador para acessar o sistema externo, que é especificado sobrescrevendo o método get_adaptar_type.
Há três maneiras de implementar um serviço de negócios:

  • Serviço de negócios de sondagem com um adaptador - O framework de produção chama a intervalos regulares o método OnTask() do adaptador, que envia os dados recebidos ao método ProcessInput() do serviço de negócios, que, por sua vez, chama o método OnProcessInput com seu código.

  • Serviço de negócios de sondagem que usa o adaptador padrão - Nesse caso, o framework chama o método OnTask do adaptador padrão sem dados. Em seguida, o método OnProcessInput() realiza o papel do adaptador e é responsável por acessar o sistema externo e receber os dados.

  • Serviço de negócios sem sondagem - O framework de produção não inicia o serviço de negócios. Em vez disso, o código personalizado em um processo longo ou um que é inicializado a intervalos regulares inicia o serviço de negócios ao chamar o método Director.CreateBusinessService().

O serviço de negócios no Python é uma subclasse de grongier.pex.BusinessService no Python, que herda de todas as funções do host de negócios.
Essa classe define:

on_process_input: recebe a mensagem do inbound adapter pelo método ProcessInput e é responsável por encaminhá-la aos processos ou operações de negócios alvo.
Se o serviço de negócios não especifica um adaptador, o adaptador padrão chama esse método sem mensagem e o serviço de negócios é responsável por receber os dados do sistema externo e validá-los. Parâmetros:

  • message_input: uma instância de IRISObject ou subclasse de Message com os dados que o inbound adapter transmite.
    A mensagem pode ter qualquer estrutura que esteja de acordo com o inbound adapter e serviço de negócios.


Exemplo de um serviço de negócios (localizado no arquivo src/python/demo/reddit/bs.py):

from grongier.pex import BusinessService

import iris

from message import PostMessage
from obj import PostClass

class RedditServiceWithPexAdapter(BusinessService):
    """
    This service use our python Python.RedditInboundAdapter to receive post
    from reddit and call the FilterPostRoutingRule process.
    """
    def get_adapter_type():
        """
        Name of the registred Adapter
        """
        return "Python.RedditInboundAdapter"

    def on_process_input(self, message_input):
        msg = iris.cls("dc.Demo.PostMessage")._New()
        msg.Post = message_input
        return self.send_request_sync(self.target,msg)

    def on_init(self):
        
        if not hasattr(self,'target'):
            self.target = "Python.FilterPostRoutingRule"
        
        return

7.7. Classe business_process

Geralmente, contém a maior parte da lógica em uma produção.
Um processo de negócios pode receber mensagens de um serviço, processo ou operação de negócios.
Ele pode modificar a mensagem, convertê-la para um formato diferente ou roteá-la com base no conteúdo dela.
O processo de negócios pode rotear uma mensagem para uma operação ou outro processo de negócios.
Os processos de negócios no Python são uma subclasse de grongier.pex.BusinessProcess no Python, que herda de todas as funções do host de negócios.
Essa classe define:

on_request: processa as solicitações enviadas ao processo de negócios. Uma produção chama esse método quando uma solicitação inicial de um processo de negócios específico chega na fila apropriada e é atribuída a um trabalho em que deve ser executada.
Parâmetros:

  • request: uma instância de IRISObject ou subclasse de Message que contém a mensagem de solicitação enviada ao processo de negócios.

Retorna: Uma instância de IRISObject ou subclasse de Message que contém a mensagem de resposta que esse processo de negócios pode retornar ao componente de produção que enviou a mensagem inicial.


on_response: processa as respostas enviadas ao processo de negócios em resposta às mensagens ele que enviou ao alvo.
Uma produção chama esse método sempre que uma resposta a um processo de negócios específico chega na fila apropriada e é atribuída a um trabalho em que deve ser executada.
Geralmente, isso é uma resposta a uma solicitação assíncrona feita pelo processo de negócios onde o parâmetro responseRequired tem um valor "true".
Parâmetros:

  • request: uma instância de IRISObject ou subclasse de Message que contém a mensagem de solicitação inicial enviada ao processo de negócios.
  • response: uma instância de IRISObject ou subclasse de Message que contém a mensagem de resposta que esse processo de negócios pode retornar ao componente de produção que enviou a mensagem inicial.
  • callRequest: uma instância de IRISObject ou subclasse de Message que contém a solicitação que o processo de negócios enviou ao seu alvo.
  • callResponse: uma instância de IRISObject ou subclasse de Message que contém a resposta de entrada.
  • completionKey: uma string que contém a completionKey especificada no parâmetro completionKey do método SendAsync() de saída.

Retorna: Uma instância de IRISObject ou subclasse de Message que contém a mensagem de resposta que esse processo de negócios pode retornar ao componente de produção que enviou a mensagem inicial.


on_complete: é chamado depois que o processo de negócios recebeu e processou todas as respostas às solicitações que enviou aos alvos.
Parâmetros:

  • request: uma instância de IRISObject ou subclasse de Message que contém a mensagem de solicitação inicial enviada ao processo de negócios.
  • response: uma instância de IRISObject ou subclasse de Message que contém a mensagem de resposta que esse processo de negócios pode retornar ao componente de produção que enviou a mensagem inicial.

Retorna: Uma instância de IRISObject ou subclasse de Message que contém a mensagem de resposta que esse processo de negócios pode retornar ao componente de produção que enviou a mensagem inicial.


Exemplo de um processo de negócios (localizado no arquivo src/python/demo/reddit/bp.py):

from grongier.pex import BusinessProcess

from message import PostMessage
from obj import PostClass

class FilterPostRoutingRule(BusinessProcess):
    """
    This process receive a PostMessage containing a reddit post.
    It then understand if the post is about a dog or a cat or nothing and
    fill the right infomation inside the PostMessage before sending it to
    the FileOperation operation.
    """
    def on_init(self):
        
        if not hasattr(self,'target'):
            self.target = "Python.FileOperation"
        
        return

    def on_request(self, request):

        if 'dog'.upper() in request.post.selftext.upper():
            request.to_email_address = 'dog@company.com'
            request.found = 'Dog'
        if 'cat'.upper() in request.post.selftext.upper():
            request.to_email_address = 'cat@company.com'
            request.found = 'Cat'

        if request.found is not None:
            return self.send_request_sync(self.target,request)
        else:
            return

7.8. Classe business_operation

Essa classe é responsável por enviar os dados a um sistema externo ou local como um banco de dados do iris.
A operação de negócios pode usar opcionalmente um adaptador para processar a mensagem de saída que é especificada sobrescrevendo o método get_adapter_type.
Se a operação de negócios tiver um adaptador, ela usa o adaptador para enviar a mensagem ao sistema externo.
O adaptador pode ser um adaptador PEX, ObjectScript ou python.
A operação de negócios no Python é uma subclasse de grongier.pex.BusinessOperation no Python, que herda de todas as funções do host de negócios.

7.8.1. Sistema de despacho

Em uma operação de negócios, é possível criar qualquer número de funções semelhante ao método on_message que aceitam como argumento uma solicitação tipada como esse my_special_message_method(self,request: MySpecialMessage).

O sistema de despacho analisará automaticamente qualquer solicitação que chegar à operação e despachará as solicitações dependendo do tipo delas. Se o tipo de solicitação não for reconhecido ou não estiver especificado em qualquer função semelhante a on_message, o sistema de despacho a enviará para a função on_message.

7.8.2. Métodos

Essa classe define:

on_message: é chamado quando a operação de negócios recebe uma mensagem de outro componente da produção que não pode ser despachada a outra função.
Geralmente, a operação envia a mensagem ao sistema externo ou a encaminha para um processo ou outra operação de negócios. Se a operação tiver um adaptador, ela usa o método Adapter.invoke() para chamar o método no adaptador que envia a mensagem ao sistema externo. Se a operação estiver encaminhando a mensagem a outro componente da produção, ela usa o método SendRequestAsync() ou SendRequestSync().
Parâmetros:

  • request: uma instância de IRISObject ou subclasse de Message que contém a mensagem de entrada para a operação de negócios.

Retorna: O objeto de resposta

Exemplo de uma operação de negócios (localizada no arquivo src/python/demo/reddit/bo.py):

from grongier.pex import BusinessOperation

from message import MyRequest,MyMessage

import iris

import os
import datetime
import smtplib
from email.mime.text import MIMEText

class EmailOperation(BusinessOperation):
    """
    This operation receive a PostMessage and send an email with all the
    important information to the concerned company ( dog or cat company )
    """

    def my_message(self,request:MyMessage):
        sender = 'admin@example.com'
        receivers = 'toto@example.com'
        port = 1025
        msg = MIMEText(request.toto)

        msg['Subject'] = 'MyMessage'
        msg['From'] = sender
        msg['To'] = receivers

        with smtplib.SMTP('localhost', port) as server:
            server.sendmail(sender, receivers, msg.as_string())
            print("Successfully sent email")

    def on_message(self, request):

        sender = 'admin@example.com'
        receivers = [ request.to_email_address ]


        port = 1025
        msg = MIMEText('This is test mail')

        msg['Subject'] = request.found+" found"
        msg['From'] = 'admin@example.com'
        msg['To'] = request.to_email_address

        with smtplib.SMTP('localhost', port) as server:
            
            # server.login('username', 'password')
            server.sendmail(sender, receivers, msg.as_string())
            print("Successfully sent email")

Se essa operação for chamada usando uma mensagem MyRequest, a função my_message será chamada graças ao dispatcher. Caso contrário, a função on_message será chamada.

7.9. Classe director

A classe Director é usada para serviços de negócios sem sondagem, ou seja, serviços de negócios que não são chamados automaticamente pelo framework da produção (pelo inbound adapter) no intervalo da chamada.
Em vez disso, esses serviços de negócios são criados por um aplicativo personalizado ao chamar o método Director.create_business_service().
Essa classe define:

create_business_service: esse método inicia o serviço de negócios especificado.
Parâmetros:

  • connection: um objeto IRISConnection que especifica a conexão a uma instância IRIS para Java.
  • target: uma string que especifica o nome do serviço de negócios na definição da produção.

Retorna: um objeto que contém uma instância de IRISBusinessService

start_production: esse método inicia a produção.
Parâmetros:

  • production_name: uma string que especifica o nome da produção a iniciar.

stop_production: esse método interrompe a produção.
Parâmetros:

  • production_name: uma string que especifica o nome da produção a interromper.

restart_production: esse método reinicia a produção.
Parâmetros:

  • production_name: uma string que especifica o nome da produção a reiniciar.

list_productions: esse método retorna um dicionário de nomes das produções que estão sendo executadas no momento.

7.10. Os objects

Vamos usar dataclass para armazenar informações em nossas mensagens em um arquivo obj.py.

Exemplo de um objeto (localizado no arquivo src/python/demo/reddit/obj.py):

from dataclasses import dataclass

@dataclass
class PostClass:
    title: str
    selftext : str
    author: str
    url: str
    created_utc: float = None
    original_json: str = None

7.11. As messages

As mensagens conterão um ou mais objetos, localizados no arquivo obj.py.
Mensagens, solicitações e respostas herdam da classe grongier.pex.Message.

Essas mensagens nos permitem transferir informações entre qualquer serviço/processo/operação de negócios.

Exemplo de uma mensagem (localizada no arquivo src/python/demo/reddit/message.py):

from grongier.pex import Message

from dataclasses import dataclass

from obj import PostClass

@dataclass
class PostMessage(Message):
    post:PostClass = None
    to_email_address:str = None
    found:str = None

WIP Vale destacar que é necessário usar tipos ao definir um objeto ou uma mensagem.

7.12. Como registrar um componente

Você pode registrar um componente no iris de várias formas:

  • Somente um componente com register_component
  • Todos os componentes em um arquivo com register_file
  • Todos os componentes em um arquivo com register_folder

7.12.1. register_component

Inicie um shell do embedded python:

/usr/irissys/bin/irispython

Em seguida, use esse método de classe para adicionar um novo arquivo py à lista de componentes para interoperabilidade.

from grongier.pex import Utils
Utils.register_component(<ModuleName>,<ClassName>,<PathToPyFile>,<OverWrite>,<NameOfTheComponent>)

Por exemplo:

from grongier.pex import Utils
Utils.register_component("MyCombinedBusinessOperation","MyCombinedBusinessOperation","/irisdev/app/src/python/demo/",1,"PEX.MyCombinedBusinessOperation")

7.12.2. register_file

Inicie um shell do embedded python:

/usr/irissys/bin/irispython

Em seguida, use esse método de classe para adicionar um novo arquivo py à lista de componentes para interoperabilidade.

from grongier.pex import Utils
Utils.register_file(<File>,<OverWrite>,<PackageName>)

Por exemplo:

from grongier.pex import Utils
Utils.register_file("/irisdev/app/src/python/demo/bo.py",1,"PEX")

7.12.3. register_folder

Inicie um shell do embedded python:

/usr/irissys/bin/irispython

Em seguida, use esse método de classe para adicionar um novo arquivo py à lista de componentes para interoperabilidade.

from grongier.pex import Utils
Utils.register_folder(<Path>,<OverWrite>,<PackageName>)

Por exemplo:

from grongier.pex import Utils
Utils.register_folder("/irisdev/app/src/python/demo/",1,"PEX")

7.12.4. migrate

Inicie um shell do embedded python:

/usr/irissys/bin/irispython

Em seguida, use esse método estático para migrar o arquivo de configurações para o framework iris.

from grongier.pex import Utils
Utils.migrate()

7.12.4.1. Arquivo setting.py

Esse arquivo é usado para armazenar as configurações dos componentes de interoperabilidade.

Ele tem duas seções:

  • CLASSES: essa seção é usada para armazenar as classes dos componentes de interoperabilidade.
  • PRODUCTIONS: essa seção é usada para armazenar as classes dos componentes de interoperabilidade.

Por exemplo:

import bp
from bo import *
from bs import *

CLASSES = {
    'Python.RedditService': RedditService,
    'Python.FilterPostRoutingRule': bp.FilterPostRoutingRule,
    'Python.FileOperation': FileOperation,
    'Python.FileOperationWithIrisAdapter': FileOperationWithIrisAdapter,
}

PRODUCTIONS = [
    {
        'dc.Python.Production': {
        "@Name": "dc.Demo.Production",
        "@TestingEnabled": "true",
        "@LogGeneralTraceEvents": "false",
        "Description": "",
        "ActorPoolSize": "2",
        "Item": [
            {
                "@Name": "Python.FileOperation",
                "@Category": "",
                "@ClassName": "Python.FileOperation",
                "@PoolSize": "1",
                "@Enabled": "true",
                "@Foreground": "false",
                "@Comment": "",
                "@LogTraceEvents": "true",
                "@Schedule": "",
                "Setting": {
                    "@Target": "Host",
                    "@Name": "%settings",
                    "#text": "path=/tmp"
                }
            },
            {
                "@Name": "Python.RedditService",
                "@Category": "",
                "@ClassName": "Python.RedditService",
                "@PoolSize": "1",
                "@Enabled": "true",
                "@Foreground": "false",
                "@Comment": "",
                "@LogTraceEvents": "false",
                "@Schedule": "",
                "Setting": [
                    {
                        "@Target": "Host",
                        "@Name": "%settings",
                        "#text": "limit=10\nother<10"
                    }
                ]
            },
            {
                "@Name": "Python.FilterPostRoutingRule",
                "@Category": "",
                "@ClassName": "Python.FilterPostRoutingRule",
                "@PoolSize": "1",
                "@Enabled": "true",
                "@Foreground": "false",
                "@Comment": "",
                "@LogTraceEvents": "false",
                "@Schedule": ""
            }
        ]
    }
    }
]
7.12.4.1.1. Seção CLASSES

Essa seção é usada para armazenar as produções dos componentes de interoperabilidade.

Ela visa ajudar a registrar os componentes.

Esse dicionário tem a seguinte estrutura:

  • Chave: nome do componente
  • Valor:
    • Classe do componente (você precisa importar antes)
    • Módulo do componente (você precisa importar antes)
    • Outro dicionário com a seguinte estrutura:
      • module : nome do módulo do componente (opcional)
      • class : nome da classe do componente (opcional)
      • path : caminho do componente (obrigatório)

Por exemplo:

Quando o valor é uma classe ou módulo:

import bo
import bp
from bs import RedditService

CLASSES = {
    'Python.RedditService': RedditService,
    'Python.FilterPostRoutingRule': bp.FilterPostRoutingRule,
    'Python.FileOperation': bo,
}

Quando o valor é um dicionário:

CLASSES = {
    'Python.RedditService': {
        'module': 'bs',
        'class': 'RedditService',
        'path': '/irisdev/app/src/python/demo/'
    },
    'Python.Module': {
        'module': 'bp',
        'path': '/irisdev/app/src/python/demo/'
    },
    'Python.Package': {
        'path': '/irisdev/app/src/python/demo/'
    },
}
7.12.4.1.2. Seção de produções

Essa seção é usada para armazenar as produções dos componentes de interoperabilidade.

Ela visa ajudar a registrar uma produção.

Essa lista tem a seguinte estrutura:

  • Uma lista do dicionário com a seguinte estrutura:
    • dc.Python.Production: nome da produção
      • @Name: nome da produção
      • @TestingEnabled: enabled de teste da produção
      • @LogGeneralTraceEvents: eventos de rastreamento gerais do log da produção
      • Description: descrição da produção
      • ActorPoolSize: tamanho do pool de atores da produção
      • Item: lista dos itens da produção
        • @Name: nome do item
        • @Category: categoria do item
        • @ClassName: nome da classe do item
        • @PoolSize: tamanho do pool do item
        • @Enabled: enabled do item
        • @Foreground: foreground do item
        • @Comment: comentário do item
        • @LogTraceEvents: eventos de rastreamento do log do item
        • @Schedule: programação do item
        • Setting: lista de configurações do item
          • @Target: alvo da configuração
          • @Name: nome da configuração
          • #text: valor da configuração

A estrutura mínima de uma produção é:

PRODUCTIONS = [
        {
            'UnitTest.Production': {
                "Item": [
                    {
                        "@Name": "Python.FileOperation",
                        "@ClassName": "Python.FileOperation",
                    },
                    {
                        "@Name": "Python.EmailOperation",
                        "@ClassName": "UnitTest.Package.EmailOperation"
                    }
                ]
            }
        } 
    ]

Você também pode definir em @ClassName um item da seção CLASSES.

Por exemplo:

from bo import FileOperation
PRODUCTIONS = [
        {
            'UnitTest.Production': {
                "Item": [
                    {
                        "@Name": "Python.FileOperation",
                        "@ClassName": FileOperation,
                    }
                ]
            }
        } 
    ]

Como a produção é um dicionário, você pode adicionar no valor do dicionário de produção uma variável de ambiente.

Por exemplo:

import os

PRODUCTIONS = [
        {
            'UnitTest.Production': {
                "Item": [
                    {
                        "@Name": "Python.FileOperation",
                        "@ClassName": "Python.FileOperation",
                        "Setting": {
                            "@Target": "Host",
                            "@Name": "%settings",
                            "#text": os.environ['SETTINGS']
                        }
                    }
                ]
            }
        } 
    ]

7.13. Uso direto de Grongier.PEX

Se você não quiser usar o utilitário register_component, é possível adicionar um componente Grongier.PEX.BusinessService diretamente no portal de gerenciamento e configurar as propriedades:

  • %module:
    • Nome do módulo do seu código em python
  • %classname:
    • Nome da classe do seu componente
  • %classpaths:
    • Caminho onde está seu componente.
      • Pode ser um ou mais caminhos de classe (separados pelo caractere '|') necessários além de PYTHON_PATH

Por exemplo: component-config

8. Linha de comando

Desde a versão 2.3.1, você pode usar a linha de comando para registrar seus componentes e produções.

Para usá-la, você precisa utilizar o seguinte comando:

iop 

saída:

usage: python3 -m grongier.pex [-h] [-d DEFAULT] [-l] [-s START] [-k] [-S] [-r] [-M MIGRATE] [-e EXPORT] [-x] [-v] [-L]
optional arguments:
  -h, --help            display help and default production name
  -d DEFAULT, --default DEFAULT
                        set the default production
  -l, --lists           list productions
  -s START, --start START
                        start a production
  -k, --kill            kill a production (force stop)
  -S, --stop            stop a production
  -r, --restart         restart a production
  -M MIGRATE, --migrate MIGRATE
                        migrate production and classes with settings file
  -e EXPORT, --export EXPORT
                        export a production
  -x, --status          status a production
  -v, --version         display version
  -L, --log             display log

default production: PEX.Production

8.1. help

O comando help exibe a ajuda e o nome da produção padrão.

iop -h

saída:

usage: python3 -m grongier.pex [-h] [-d DEFAULT] [-l] [-s START] [-k] [-S] [-r] [-M MIGRATE] [-e EXPORT] [-x] [-v] [-L]
...
default production: PEX.Production

8.2. default

O comando default define a produção padrão.

Sem argumento, ele exibe a produção padrão.

iop -d

saída:

default production: PEX.Production

Com argumento, ele define a produção padrão.

iop -d PEX.Production

8.3. lists

O comando lists lista produções.

iop -l

saída:

{
    "PEX.Production": {
        "Status": "Stopped",
        "LastStartTime": "2023-05-31 11:13:51.000",
        "LastStopTime": "2023-05-31 11:13:54.153",
        "AutoStart": 0
    }
}

8.4. start

O comando start inicia uma produção.

Para sair do comando, pressione CTRL+C.

iop -s PEX.Production

saída:

2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting production
2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting item Python.FileOperation
2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting item Python.EmailOperation
...

8.5. kill

O comando kill encerra uma produção à força.

O comando kill é igual ao comando stop, mas força o encerramento.

O comando kill não aceita argumentos, porque só pode ser executada uma produção.

iop -k 

8.6. stop

O comando stop interrompe uma produção.

O comando stop não aceita argumentos, porque só pode ser executada uma produção.

iop -S 

8.7. restart

O comando restart reinicia uma produção.

O comando restart não aceita argumentos, porque só pode ser executada uma produção.

iop -r 

8.8. migrate

O comando migrate migra uma produção e classes com o arquivo de configurações.

O comando migate precisa receber o caminho absoluto do arquivo de configurações.

O arquivo de configurações precisa estar na mesma pasta que o código em python.

iop -M /tmp/settings.py

8.9. export

O comando export exporta uma produção.

Se nenhum argumento for fornecido, o comando export exporta a produção padrão.

iop -e

Se um argumento for fornecido, o comando export exporta a produção indicada no argumento.

iop -e PEX.Production

saída:

{
    "Production": {
        "@Name": "PEX.Production",
        "@TestingEnabled": "true",
        "@LogGeneralTraceEvents": "false",
        "Description": "",
        "ActorPoolSize": "2",
        "Item": [
            {
                "@Name": "Python.FileOperation",
                "@Category": "",
                "@ClassName": "Python.FileOperation",
                "@PoolSize": "1",
                "@Enabled": "true",
                "@Foreground": "false",
                "@Comment": "",
                "@LogTraceEvents": "true",
                "@Schedule": "",
                "Setting": [
                    {
                        "@Target": "Adapter",
                        "@Name": "Charset",
                        "#text": "utf-8"
                    },
                    {
                        "@Target": "Adapter",
                        "@Name": "FilePath",
                        "#text": "/irisdev/app/output/"
                    },
                    {
                        "@Target": "Host",
                        "@Name": "%settings",
                        "#text": "path=/irisdev/app/output/"
                    }
                ]
            }
        ]
    }
}

8.10. status

O comando status dá o status de uma produção.

O comando status não aceita argumentos, porque só pode ser executada uma produção.

iop -x 

saída:

{
    "Production": "PEX.Production",
    "Status": "stopped"
}

O status pode ser:

  • stopped (interrompido)
  • running (em execução)
  • suspended (suspenso)
  • troubled (com problema)

8.11. version

O comando version exibe a versão.

iop -v

saída:

2.3.0

8.12. log

O comando log exibe o log.

Para sair do comando, pressione CTRL+C.

iop -L

saída:

2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting production
2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting item Python.FileOperation
2021-08-30 15:13:51.000 [PEX.Production] INFO: Starting item Python.EmailOperation
...

9. Créditos

A maior parte do código foi retirada do PEX para Python, de Mo Cheng e Summer Gerry.

Funciona somente a partir do IRIS 2021.2

0
1 72
Artigo Larissa Prussak · Mar. 15, 2024 1m read

Rubrica de perguntas frequentes da InterSystems


Os mapas de registros são usados ​​para mapear com eficiência arquivos contendo registros delimitados ou registros de largura fixa para classes de mensagens usadas pela função de interoperabilidade e para mapear arquivos de classes de mensagens da função de interoperabilidade para arquivos de texto.

As definições de mapeamento do mapa de registros podem ser criadas usando o Portal de Gerenciamento, e também fornecemos um assistente de registro CSV que permite definir durante a leitura de um arquivo CSV.

0
0 69
Artigo Danusa Calixto · jan 11, 2024 6m read

Fui desafiado a criar um aplicativo de bot do Azure que possa recuperar e publicar dados no IRIS for Health.

 

Os dados de um paciente já foram registrados no repositório FHIR do IRIS for Health.

O MRN do paciente é 1001. O nome dele é Taro Yamad. (em japonês: 山田 太郎)

Esse bot pode publicar novas leituras de oxímetro como um recurso de observação associado ao paciente.

Visão geral de como o aplicativo de bot funciona abaixo:

 

(1) Em um aplicativo como o Teams, um usuário fala "Hello" (Olá).

O Teams envia a mensagem ao "Serviço de Canal Bot Framework" , hospedado pela Microsoft.

(2) O Serviço de Canal Bot Framework pergunta ao Bot.

O serviço pergunta ao Bot "Where is the endpoint?"(Onde está o endpoint?)

(3) O bot retorna as informações sobre o endpoint ao serviço.

O Bot conhece o endpoint.

(4) O serviço faz a solicitação do usuário ao endpoint.

O endpoint é um web application que é publicado no Azure web app.

(Minha amostra está escrita em Python.)  

​​(5) O endpoint cria a resposta e a envia ao serviço.

(6) O serviço recebe a resposta do endpoint e a passa ao usuário.

O IRIS não apareceu no fluxo acima.

Adicionei uma chamada do web application em python para a interoperabilidade do IRIS for Health. E preparei o repositório FHIR no IRIS assim:

 

A amostra de interoperabilidade do IRIS está aqui 👉 https://github.com/iijimam/iris-teams-interop

Você pode compilar e iniciar o contêiner com "docker-compose up -d". (Ele inclui os dados do paciente de amostra (MRN=1001) e as configurações necessárias.) 

Observação: o web application em python precisa chamar aplicativos via https. Mas removi os arquivos de certificado do meu repositório para evitar violar a política do git.

O web application em Python chama a classe dispatch REST do IRIS usando este URL "https://webserveraddress/production/request".

Confira as instruções abaixo para criar um aplicativo de Bot do Azure.

(1) Crie um grupo de recursos no Azure.

(2) Crie um Bot do Azure no grupo de recursos.

Você precisará confirmar seu "MicrosoftAppId" e "MicrosoftAppPassword". Vou explicar isso depois.

Se você quiser usar o bot do Teams, você precisa adicionar o canal do Teams ao bot. (O gif acima usa o canal do Teams.)

(3) Crie um web application no mesmo grupo de recursos que o bot no Azure.

Você obterá o endereço do servidor da web depois de criá-lo.

(4) Defina o endpoint na configuração do bot.

(5) Implante seu código para o web application.

Você pode escolher o repositório Git.

(6) Teste!

Vamos ver os detalhes!

(1) Crie um grupo de recursos no Azure.

Você pode criar um novo grupo de recursos a partir desta página (Grupos de recurso - Microsoft Azure)

Criei um novo recurso "202312ISJBotRG".

(2) Crie um Bot do Azure no grupo de recursos.

Depois de mover o grupo de recursos, você pode selecionar o menu "Create" (Criar).

   

Depois de criar o bot, você precisa definir "New client secret" (Novo segredo do cliente) na página [Configuration] da seguinte maneira:

Anote o caractere secreto e o valor do "Microsoft App ID".

Essas informações são necessárias para definir seu web application.

Se você quiser usar o bot do Teams, adicione o canal do Teams ao seu bot.

Você pode adicionar o canal do Teams usando o menu [Channels] no seu bot. (basta clicar no ícone do Teams.)

 

(3) Crie um web application no mesmo grupo de recursos que o bot no Azure.

Crie um web application para seu grupo de recursos. 

Volte para a página do grupo de recursos. E crie um web application assim:

 

Você pode selecionar sua "Runtime stack" (pilha de camada) favorita (o exemplo é Python 3.8), "Region" (região) e "Pricing plan" (plano de preços).

Depois de criar o aplicativo, você pode obter o endereço do servidor web na página do web application.

Precisamos definir o endereço para o código de aplicativo python. Então anote essa string. (O exemplo é "202312isjbotwebapp.azurewebsites.net")

Em seguida, precisamos definir o id do bot, o segredo e os detalhes de implantação.

Acesse a página [Configuration] na página do web application.

Você precisa adicionar "MicrosoftAppId" e "MicrosoftAppPassword" de "New application setting" (Configuração do novo aplicativo) a "Application settings" (Configurações do aplicativo).

(MicrosoftAppPassword é o segredo do bot que você copia na etapa (2).)

 

Em seguida, precisamos definir as "General settings" (Configurações gerais) para implantação.

 

Usei este código de exemplo 👉https://github.com/microsoft/BotBuilder-Samples/tree/main/samples/python/44.prompt-for-user-input

O exemplo é simples, e é fácil de entender o que devo escrever sobre a comunicação do web application com o bot.

Atualizei alguns códigos nos diretórios bots e data_models.

Se você for executar o código no ambiente local, não precisa atualizá-lo.

Mas, se você quiser executá-lo no Azure web application, precisa atualizar o código de app.py assim:

definit_func(argv):
    APP = web.Application(middlewares=[aiohttp_error_middleware])
    APP.router.add_post("/api/messages", messages)
    return APP

ifname == "main": try: #web.run_app(APP, host="localhost", port=CONFIG.PORT) web.run_app(APP, host="0.0.0.0", port=CONFIG.PORT) except Exception as error: raise error

Adicionei esta chamada "python3.8 -m aiohttp.web -H 0.0.0.0 -P 8000 app:init_func" para iniciar um web application em Python no Azure.

Se você quiser saber os detalhes, consulte este URL👉https://stackoverflow.com/questions/60507967/running-an-python-app-as-an-azure-web-app

Meu código está aqui 👉 https://github.com/iijimam/teams-bot

Para executá-lo, você precisa atualizar partes do código.

  • Linha  15 e 16 de config.py: você precisa atualizar as informações do seu bot.
  • Linha 10 de GoIRIS.py : você precisa atualizar o endpoint que é o servidor REST do IRIS.

(4) Defina o endpoint na configuração do bot.

Acesse a página Configuration do bot.

Defina o endereço do servidor web + "/api/messages" como "Messaging endpoint".

 

(5) Implante seu código para o web application.

Eu uso o repositório git. É fácil implantar para o web application no Azure!!

Você pode definir as informações na página [Deployment Center] (Centro de implantação) do web application.

Após a implantação, você pode começar a testar!

(6) Teste!

Você pode testar seu aplicativo usando o "Test in Web Chat" (Testar no web chat) na página do bot.

 

Se você adicionar o canal do Teams, pode testar no Teams.

 

Depois de clicar em [Open in Teams] (Abrir no Teams), você pode abrir seu chat do teams e testar.😀

Bônus:

Você pode testar sem implantar seu aplicativo como um web application do Azure.

Quando o aplicativo em python e a interoperabilidade do IRIS estiverem prontos, você pode publicar esse aplicativo usando ngrok assim:

ngrok http 8000 --host-header=172.18.28.1:8000

ngrok pode oferecer um serviço de tunelamento.  Posso acessar minha porta local diretamente da área pública usando o serviço de tunelamento ngrok. 

E precisamos mudar o endpoint na configuração do bot assim:

Depois de configurado, posso testar usando o web chat.

0
0 120
Artigo Danusa Calixto · jan 4, 2024 8m read

Introdução

Este artigo busca explorar o funcionamento e desenvolvimento do sistema FHIR-PEX, aproveitando os recursos do InterSystems IRIS.

Otimizando a identificação e o processamento de exames médicos nos centros de diagnóstico clínico, nosso sistema visa aumentar a eficiência e precisão dos fluxos de trabalho de saúde. Ao integrar os padrões FHIR ao banco de dados Java-PEX do InterSystems IRIS, o sistema ajuda os profissionais de saúde com recursos de validação e roteamento, melhorando, em última análise, a tomada de decisões e o cuidado dos pacientes.

como funciona

  • Interoperabilidade IRIS: Recebe mensagens no padrão FHIR, garantindo a integração e a compatibilidade com os dados da saúde.

  • Processamento de informações com "PEX Java": Processa mensagens no formato FHIR e as direciona a tópicos do Kafka com base nas regras configuradas globalmente no banco de dados, possibilitando um processamento e roteamento de dados eficiente, especialmente para exames direcionados à quarentena.

  • Tratamento de retornos do Kafka via back-end Java externo: Interpreta apenas os exames direcionados à quarentena, permitindo que o sistema trate os retornos do Kafka por um back-end Java externo. Facilita a geração de insights prognósticos para profissionais da saúde através de IA Generativa, contando com consultas de resultados de exames anteriores dos respectivos pacientes.

Desenvolvimento

Através do PEX (Production EXtension) da InterSystems, ferramenta de extensibilidade que permite aprimoramento e personalização do comportamento do sistema, elaboramos uma Operação de Negócios. Este componente tem a tarefa de processar mensagens recebidas no formato FHIR dentro do sistema. Veja este exemplo:

import com.intersystems.enslib.pex.*;
import com.intersystems.jdbc.IRISObject;
import com.intersystems.jdbc.IRIS;
import com.intersystems.jdbc.IRISList;
import com.intersystems.gateway.GatewayContext;

import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.*;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

public class KafkaOperation extends BusinessOperation {
// Conexão ao InterSystems IRIS
private IRIS iris;

// Conexão ao Kafka
private Producer<Long, String> producer;

// Endereço do servidor do Kafka (se forem vários, separados por vírgula)
public String SERVERS;

// Nome do nosso Produtor
public String CLIENTID;

/// Caminho para o arquivo Config
public String CONFIG;

public void OnInit() throws Exception {
[...]
}

public void OnTearDown() throws Exception {
[...]
}

public Object OnMessage(Object request) throws Exception {
    IRISObject req = (IRISObject) request;
    LOGINFO("Received object: " + req.invokeString("%ClassName", 1));

    // Crie o registro
    String value = req.getString("Text");
    String topic = getTopicPush(req);
    final ProducerRecord<Long, String> record = new ProducerRecord<>(topic, value);

    // Envie o novo registro
    RecordMetadata metadata = producer.send(record).get();

    // Retorne as informações do regitro
    IRISObject response = (IRISObject)(iris.classMethodObject("Ens.StringContainer","%New",topic+"|"+metadata.offset()));
    return response;
}

private Producer<Long, String> createProducer() throws IOException {
[...]
}

private String getTopicPush(IRISObject req) {
[...]
}

[...]
}

`

Dentro da aplicação, o método getTopicPush assume a responsabilidade de identificar o tópico para o qual a mensagem será enviada.

A determinação do tópico a que a mensagem será enviada depende da existência de uma regra na global "quarantineRule", conforme lido no IRIS.

String code = FHIRcoding.path("code").asText();
String system = FHIRcoding.path("system").asText();

IRISList quarantineRule = iris.getIRISList("quarantineRule",code,system);

 String reference = quarantineRule.getString(1);
 String value = quarantineRule.getString(2);

 String observationValue = fhir.path("valueQuantity").path("value").asText()

Quando há a global ^quarantineRule, a validação do objeto FHIR pode ser validada.

private boolean quarantineValueQuantity(String reference, String value, String observationValue) {
    LOGINFO("quarantine rule reference/value: " + reference + "/" + value);
    double numericValue = Double.parseDouble(value);
    double numericObservationValue = Double.parseDouble(observationValue);

    if ("<".equals(reference)) {
        return numericObservationValue < numericValue;
    }
    else if (">".equals(reference)) {
        return numericObservationValue > numericValue;
    }
    else if ("<=".equals(reference)) {
        return numericObservationValue <= numericValue;
    }
    else if (">=".equals(reference)) {
        return numericObservationValue >= numericValue;
    }
    
    return false;
}

Exemplo prático:

Ao definir uma global, como:

Set ^quarantineRule("59462-2","http://loinc.org") = $LB(">","500") 

Isso estabelece uma regra para o código "59462-2" e o sistema ""http://loinc.org"" na global ^quarantineRule, especificando uma condição em que o valor é definido como quarentena quando ultrapassa 500. No aplicativo, o método getTopicPush pode então usar essa regra para determinar o tópico apropriado para enviar a mensagem com base no resultado da validação.

Dada a atribuição, o JSON abaixo seria enviado para quarentena, porque corresponde à condição especificada por ter:

 {
          "system": "http://loinc.org",
          "code": "59462-2",
          "display": "Testosterone"
}

"valueQuantity": { "value": 550, "unit": "ng/dL", "system": "http://unitsofmeasure.org", "code": "ng/dL" }

Observação FHIR:

{
    "resourceType": "Observation",
    "id": "3a8c7d54-1a2b-4c8f-b54a-3d2a7efc98c9",
    "status": "final",
    "category": [
      {
        "coding": [
          {
            "system": "http://terminology.hl7.org/CodeSystem/observation-category",
            "code": "laboratory",
            "display": "laboratory"
          }
        ]
      }
    ],
    "code": {
      "coding": [
        {
          "system": "http://loinc.org",
          "code": "59462-2",
          "display": "Testosterone"
        }
      ],
      "text": "Testosterone"
    },
    "subject": {
      "reference": "urn:uuid:274f5452-2a39-44c4-a7cb-f36de467762e"
    },
    "encounter": {
      "reference": "urn:uuid:100b4a8f-5c14-4192-a78f-7276abdc4bc3"
    },
    "effectiveDateTime": "2022-05-15T08:45:00+00:00",
    "issued": "2022-05-15T08:45:00.123+00:00",
    "valueQuantity": {
      "value": 550,
      "unit": "ng/dL",
      "system": "http://unitsofmeasure.org",
      "code": "ng/dL"
    }
}

O aplicativo Quarkus Java

Após o envio para o tópico desejado, um aplicativo Quarkus Java foi criado para receber exames em quarentena.

@ApplicationScoped
 public class QuarentineObservationEventListener {

@Inject
PatientService patientService;

@Inject
EventBus eventBus;

@Transactional
@Incoming("observation_quarantine")
public CompletionStage<Void> onIncomingMessage(Message<QuarentineObservation> quarentineObservationMessage) {
	var quarentineObservation = quarentineObservationMessage.getPayload();
	var patientId = quarentineObservation.getSubject()
			.getReference();
	var patient = patientService.addObservation(patientId, quarentineObservation);
	publishSockJsEvent(patient.getId(), quarentineObservation.getCode()
			.getText());
	return quarentineObservationMessage.ack();
}

private void publishSockJsEvent(Long patientId, String text) {
	eventBus.publish("monitor", MonitorEventDto.builder()
			.id(patientId)
			.message(" is on quarentine list by observation ." + text)
			.build());
}
 }

Esse segmento do sistema tem a tarefa de persistir as informações recebidas de Kafka, armazená-las nas observações do paciente no banco de dados e notificar a ocorrência ao monitor.

O monitor

Por fim, o monitor do sistema é responsável por fornecer uma visualização simples do front-end. Isso permite que os profissionais de saúde revisem os dados do paciente/exame e realizem as ações necessárias.

Implementação de langchainPT

Através do monitor, o sistema permite que os profissionais de saúde solicitem recomendações da IA ​​Generativa.

@Unremovable
@Slf4j
@ApplicationScoped
public class PatientRepository {
	@Tool("Get anamnesis information for a given patient id")
	public Patient getAnamenisis(Long patientId) {
		log.info("getAnamenisis called with id " + patientId);
		Patient patient = Patient.findById(patientId);
		return patient;
	}

	@Tool("Get the last clinical results for a given patient id")
	public List<Observation> getObservations(Long patientId) {
		log.info("getObservations called with id " + patientId);
		Patient patient = Patient.findById(patientId);
		return patient.getObservationList();
	}

}

segue implementação de langchain4j

@RegisterAiService(chatMemoryProviderSupplier = RegisterAiService.BeanChatMemoryProviderSupplier.class, tools = {PatientRepository.class})
public interface PatientAI {

	@SystemMessage("""
			You are a health care assistant AI. You have to recommend exams for patients based on history information.
			""")
	@UserMessage("""
			 Your task is to recommend clinical exams for the patient id {patientId}.

			 To complete this task, perform the following actions:
			 1 - Retrieve anamnesis information for patient id {patientId}.
			 2 - Retrieve the last clinical results for patient id {patientId}, using the property 'name' as the name of exam and 'value' as the value.
			 3 - Analyse results against well known conditions of health care.

			 Answer with a **single** JSON document containing:
			 - the patient id in the 'patientId' key
			 - the patient weight in the 'weight' key
			 - the exam recommendation list in the 'recommendations' key, with properties exam, reason and condition.
			 - the 'explanation' key containing an explanation of your answer, especially about well known diseases.

			Your response must be just the raw JSON document, without ```json, ``` or anything else.
			 """)
	String recommendExams(Long patientId);
}

Dessa forma, o sistema pode auxiliar os profissionais de saúde a tomar decisões e realizar ações.

Vídeo de demonstração

VÍDEO

Autores

OBSERVAÇÃO:

O aplicativo https://openexchange.intersystems.com/package/fhir-pex está participando atualmente do InterSystems Java Contest 2023. Sinta-se à vontade para explorar a solução e não hesite em entrar em contato se tiver alguma dúvida ou precisar de informações adicionais. Recomendamos executar o aplicativo em seu ambiente local para uma experiência prática. Obrigado pela oportunidade 😀!

0
0 85
Artigo Danusa Calixto · Nov. 28, 2023 8m read

Visão geral

A documentação online contém o tópico Defining and Using Class Queries (Definir e usar consultas de classe) para referência-

A personalização direta de procedimentos armazenados com ObjectScript tem sido útil para acessar o armazenamento NoSQL e as mensagens externas pela integração, para apresentar a saída em um formato tabular.

Por exemplo: um aplicativo que já usa 90% da interação SQL de um front-end também pode estender esse acesso aos outros 10% da funcionalidade de plataforma necessária, pelo mesmo acesso SQL.

A finalidade deste artigo é explorar como alcançar o mesmo efeito usando os métodos do Embedded Python.

Figura 1: procedimento armazenado como um SQL gateway para outra funcionalidade de plataforma

Demonstração

Para esse exemplo, foi definido o seguinte armazenamento NoSQL:

^alwo.IndexBook("A",1)="abc"
^alwo.IndexBook("A",2)="def"
^alwo.IndexBook("B")=""
^alwo.IndexBook("C")=""
^alwo.IndexBook("D",1)="gef"
^alwo.IndexBook("E",1)="ijk"
^alwo.IndexBook("E",2)="lmn"

Para teste, o procedimento armazenado pode ser executado em um terminal:

GBI>Do $SYSTEM.SQL.Shell()
SQL Command Line Shell
----------------------------------------------------
 
The command prefix is currently set to: <<nothing>>.
Enter <command>, 'q' to quit, '?' for help.

[SQL]GBI>>call alwo.PyProcTest_GetNotes('A')

Dumping result #1
Tab     NoteId  NoteText
A       1       abc
A       2       def
B       0
C       0
D       0
E       0
 
6 Rows(s) Affected
statement prepare time(s)/globals/cmds/disk: 0.0003s/4/100/0ms
          execute time(s)/globals/cmds/disk: 0.0009s/12/1,096/0ms
                                query class: %sqlcq.GBI.cls27, %Library.ProcedureContext
---------------------------------------------------------------------------

Como foi fornecida a aba ( primeira chave ) "A", os dados dos sub-nós são expandidos e retornados como registros.

Os outros nós que não foram selecionados ou não contêm dados são retornados como registros "0".

Conceitos de código

Quando uma consulta ( GetNotes ) é implementada em uma classe, o conteúdo da consulta pode ser obtido apenas com SQL.

Após a compilação, são gerados três class methods:

  • GetNotesExecute
  • GetNotesFetch
  • GetNotesClose

Há vários cenários em que os dados não são SQL:

  • Globais NoSQL
  • Interação parametrizada com um sistema externo para recuperar e consolidar dados

Ao implementar esses três métodos diretamente, é possível controlar o acesso e dar respostas tabulares para uma ampla variedade de recursos de plataforma.

Estas são algumas das variações de interação:

Armazenar em cache todos os dados de resposta antecipadamente

1. O método GetNotesExecute acessaria os recursos para criar uma reposta em um global temporário.

Isso seria útil para uma visão consistente sobre os dados, o que pode envolver o bloqueio do acesso a atualizações por um breve período.

2. O método GetNotesFetch seria chamado repetidamente, retornando registros dos dados temporários

3. The GetNotesClose limparia e excluiria os dados temporários

Dados de respostas dinâmicas

1. O método GetNotesExecute é chamado. Isso não faz muito coisa além de iniciar um contexto qHandle disponível para o método Fetch

2. O método GetNotesFetch é chamado. Sempre que um novo registro é recuperado dinamicamente

3. O método GetNotesClose exige pouca ou nenhuma limpeza

Essa é a abordagem usada no código de exemplo.

Oportunidades de paginação etc.

Dependendo do cenário, o preenchimento dinâmico de lotes de registros de retorno pode ser usado para reduzir a necessidade de executar uma "consulta completa" quando só é necessária uma área da fatia de registros de retorno.

O código

O método execute tem uma expressão $C(0). Isso serve apenas para corresponder a uma string Null, que é diferente de uma string vazia.

Uma string nula pode ser passada quando um procedimento armazenado é invocado com um argumento de string vazio.

O método GetNotesFetch atua como um wrapper objectscript para GetNotesFetchPy, onde ocorre o trabalho de verdade. A lógica é a expectativa de o framework de chamada usar os argumentos ByRef e o wrapper unir isso.

O código é um exemplo de como navegar e recuperar dados NoSQL por código Python.

A implementação em Python usa um bloco try-except para interromper problemas de ambiente de execução do código Python e propagar esses detalhes das informações de erros da maneira normal de volta ao aplicativo do cliente. Isso pode ser ativado ao descomentar a linha que começa com "#x=10/0".

Por exemplo, o erro interrompido retornado ao cliente:

[SQLCODE: <-400>:<Fatal error occurred>]
[%msg: <Python general error 'alwo.PyProcTest::GetNotesFetchPy:Traceback (most recent call last):
                   File "PyProcTest", line 21, in GetNotesFetchPy
                                                                 ZeroDivisionError: division by zero
                    '>]

///Ref: Defining and Using Class Queries
///https://docs.intersystems.com/iris20232/csp/docbook/DocBook.UI.Page.cls?KEY=GOBJ_queries
Classalwo.PyProcTest [ Abstract ]
{
 
///<example>
///do $SYSTEM.SQL.Shell()
///call alwo.PyProcTest_GetNotes('D')
///</example>
QueryGetNotes(tabNameAs%String) As%Query(ROWSPEC = "Tab:%String,NoteId:%Integer,NoteText:%String") [ SqlName = PyProcTest_GetNotes, SqlProc ]
{
}
 
///ObjectScript due to ByRef signature
ClassMethodGetNotesExecute(ByRefqHandleAs%Binary, tabNameAs%String = "") As%Status
{
  setqHandle=##class(alwo.PyNote.GetNotes.qHandle).%New()
  // Note that an empty string passed from SQL statement may appear as the null character $C(0) instead of empty string ""
  set:tabName'=$C(0) qHandle.selectedTab=tabName// may be empty string
  Quit$$$OK
}
 
///ObjectScript due to ByRef signature
ClassMethodGetNotesFetch(ByRefqHandleAs%Binary, ByRefRowAs%List, ByRefAtEndAs%Integer = 0) As%Status [ PlaceAfter = GetNotesExecute ]
{
  setrefRow=##class(alwo.PyNote.GetNotes.Row).%New()
 
  setstatus=..GetNotesFetchPy(.qHandle,.refRow)
  ifqHandle.atEnd {
    setAtEnd=1
  } else {
    // repack output row to $List format
    setRow=$ListBuild(refRow.Tab,+refRow.NoteId,refRow.NoteText)
  }
  Quitstatus
}
 
///Access to tabular view of global 2 keys deep with data at level 2 nodes
///<example>
///zwrite ^alwo.IndexBook
///
///^alwo.IndexBook("A",1)="abc"
///^alwo.IndexBook("A",2)="def"
///^alwo.IndexBook("B")=""
///^alwo.IndexBook("C")=""
///^alwo.IndexBook("D",1)="gef"
///^alwo.IndexBook("E",1)="ijk"
///^alwo.IndexBook("E",2)="lmn"
///<example>
///
///Required output
///<example>
///| Tab | NoteId | NoteText
///--------------------------
///| A   | 1      | abc
///| A   | 2      | def
///| B   | 0      |
///| C   | 0      |
///| D   | 1      | gef
///| E   | 1      | ijk
///| E   | 2      | lmn
///--------------------------
///</example>
ClassMethodGetNotesFetchPy(qHandleAsalwo.PyNote.GetNotes.qHandle, pRowAsalwo.PyNote.GetNotes.Row) As%String [ Language = python ]
{
importiris
importtraceback
ret=iris.cls('%SYSTEM.Status').OK()
try:
  # basedontheexistanceofdefinednodestheniterate
  gname="^alwo.IndexBook"
  gIterator=iris.gref(gname)
  # IterateonKey1"Tab name"whenKey2"NoteId"waspreviouslysettoempty
  if (None==qHandle.currentPage) or (""==qHandle.currentPage):
    qHandle.currentTab=gIterator.order([qHandle.currentTab])
    # changeoftabcontext
    if (None==qHandle.currentTab) or (qHandle.currentTab==""):  # norecords
      qHandle.atEnd=True
      returnret
    # defaultopenfirsttabifhasvalues
    ifqHandle.selectedTab==NoneorqHandle.selectedTab=="":
      qHandle.selectedTab=qHandle.currentTab
  pRow.Tab=qHandle.currentTab
  #x=10/0 # uncommenttodemonstrateZeroDivisionErrorhandling
  # IterateonKey2"NoteId"
  if (qHandle.selectedTab==qHandle.currentTab):
    qHandle.currentPage=gIterator.order([qHandle.currentTab,qHandle.currentPage])
    if (qHandle.currentPage!=None) and (qHandle.currentPage!=""):
      pRow.NoteId=qHandle.currentPage
      pRow.NoteText=gIterator.get([qHandle.currentTab,qHandle.currentPage])
      # checksifcurrentrecordwasthelastone
      next=gIterator.order([qHandle.currentTab,qHandle.currentPage])
      if (None==next) or (""==next):
        qHandle.currentPage=None  # causesiterateonKey1onnextmethodinvocation
exceptException:
 pErrorMessage='alwo.PyProcTest::GetNotesFetchPy:'+(traceback.format_exc())
 returniris.cls('%SYSTEM.Status').Error(2603,pErrorMessage)
returnret
}
 
///ObjectScript due to ByRef signature
ClassMethodGetNotesClose(ByRefqHandleAs%Binary) As%Status
{
  setqHandle=""
  Quit$$$OK
}
 
}

Classe helper qHandle. É inicializada em Execute e atualizada durante a recuperação de registro.

Classalwo.PyNote.GetNotes.qHandleExtends%RegisteredObject
{
 
PropertycurrentTabAs%String;
 
PropertycurrentPageAs%String;
 
PropertyselectedTabAs%String;
 
PropertyatEndAs%Integer [ InitialExpression = 0 ];
 
}

Classe helper para preencher linhas do Python com nomes legíveis:

Classalwo.PyNote.GetNotes.RowExtends%RegisteredObject
{
 
PropertyTabAs%String;
 
PropertyNoteIdAs%String;
 
PropertyNoteTextAs%String;
 
}

Espero que esse exemplo seja útil para explorar algumas novas ideias e possibilidades com o Embedded.

0
0 150
Artigo Danusa Calixto · Set. 4, 2023 10m read

 

Olá, comunidade!

Vocês já precisaram conectar o IRIS a um sistema SAP?

Tive que enfrentar o desafio de conectar o InterSystems IRIS ao SAP e, novamente, verifiquei o trabalho de concessão feito pela InterSystems relacionado à possibilidade de executar código nativo em Python dentro do IRIS.

Esse recurso facilitou muito a integração graças à biblioteca pyrfc.

Com essa biblioteca, consegui fazer chamadas a uma RFC (Chamada de Função Remota) do SAP a partir de uma classe do IRIS, além de receber dados do banco de dados do SAP. 

Mas o que é uma RFC?

A RFC é um protocolo de comunicação usado pelo SAP. Ele permite a interação entre diferentes sistemas, SAP ou não. Ele também permite que aplicativos externos se comuniquem com o sistema SAP e acessem as funções e os dados disponíveis nesse sistema.

Quando uma RFC é realizada, um aplicativo externo envia uma solicitação pela rede ao sistema SAP. O sistema SAP recebe e processa a solicitação, retornando os resultados ou dados solicitados ao aplicativo externo.

A RFC é usada para várias tarefas, como integração de sistema, transferência de dados, execução de programa e recuperação de informações do SAP.

Resumindo, uma RFC no SAP é uma chamada remota a uma função ou um serviço disponível em um sistema SAP que permite a comunicação e a troca de dados entre esse sistema e aplicativos ou sistemas externos.

Objetivo do artigo:

Decidi escrever este artigo para descrever as etapas que segui e o resultado que obtive caso isso possa ajudar alguém a evitar o esforço.

Também decidi publicar um aplicativo no OpenExchange (link) com quase tudo o que é necessário para iniciar o Docker e conectar.

Por que eu disse quase tudo? Para conectar ao servidor SAP, precisamos baixar o SDK SAP NetWeaver RFC. Infelizmente, ele é protegido por direitos autorais, então não posso compartilhar os arquivos com você. Por isso, se você quiser baixar esse software da página do SAP, precisa ter um usuário do tipo "S".

O aplicativo do OpenExchange está pronto para baixar o SDK, adicionar alguns arquivos a ele, configurar os arquivos de conexão do SAP e executar.

Você tem interesse? Bom, vamos lá!

Etapas para baixar o SDK SAP NetWeaver RFC:

Depois de conseguir o usuário "S" (caso não tenha um usuário "S", peça para o Administrador do seu sistema SAP fornecer um a você), é preciso seguir este link.

Clique no link com as instruções para baixar:

Depois, clique no link para baixar os arquivos:

Por fim, clique em "SAP NW RFC SDK 7.50"

Selecione a versão Linux  X86_64 Bit (se você planeja usar meu aplicativo do OpenExchange no Docker) e clique no botão de download:

Adicionando os arquivos do SDK SAP NetWeaver RFC ao projeto:

Depois de baixar e descompactar o arquivo, é necessário copiar o conteúdo da pasta "nwrfcsdk" para a pasta "nwrfcsdk" do projeto:

Explicação dos arquivos auxiliares e Dockerfile:

O arquivo "nwrfcsdk.conf" precisa conter o caminho em que os arquivos do SDK serão copiados. Se você não pretende mudar o caminho no Dockerfile, não é preciso modificar esse arquivo.

O Dockerfile já inclui a instalação das bibliotecas necessárias para fazer a conexão:

O que ele faz é copiar o arquivo "nwrfcsdk.conf", o conteúdo da pasta "nwrfcsdk" e o arquivo sapnwrfc.cfg (vou explicar esse arquivo depois) dentro do Docker.

Agora, é hora de instalar as bibliotecas do Python:

  • pyrfc: que é usada para executar a RFC do SAP 
  • configparser: que é usada para ler o arquivo com os parâmetros de conexão do arquivo sapnwrfc.cfg
  • Configurando a conexão do servidor SAP:

    Para conectá-lo ao servidor SAP, você precisa adicionar as informações necessárias ao arquivo "sapnwrfc.cfg".

    Se você não tiver essas informações, mas conseguir entrar no servidor SAP, basta seguir as etapas mencionadas abaixo para descobrir. Se você não tem acesso a um servidor SAP, peça para o Administrador do servidor SAP compartilhar essas informações com você.

    Obtendo os dados de conexão do servidor SAP:

    As capturas de tela podem variar um pouco dependendo se você estiver usando a SAP GUI (usuários do Windows) ou a SAP GUI para JAVA (usuários do macOS e Linux). No meu caso, é a SAP GUI para JAVA

    1 - Insira a SAP GUI e selecione seu Servidor. Depois, clique no botão Connect:

    2 - Insira seu nome de usuário e senha do SAP e pressione Enter ou clique no botão para aceitar:

    3 - Depois de entrar, acesse a barra de menu e selecione "System". Em seguida, selecione a opção "Status" no menu suspenso:

    Nessa janela, você pode obter o seguinte:

  • O valor do campo "Client" para a propriedade "client" do arquivo de configuração. 
  • O valor do campo "User" para a propriedade "user" do arquivo de configuração. (Nesse caso, meu usuário para entrar no SAP e na RFC é o mesmo. No entanto, em ambientes de produção, é recomendável usar usuários diferentes.)
  • O valor do campo "Host" para a propriedade "ashost" do arquivo de configuração. (Pode ser um nome de host ou um endereço IP)
  • Caso seu usuário para a comunicação FRC seja o mesmo que o utilizado para entrar no sistema SAP, use a mesma senha para a propriedade "passed" do arquivo de configuração.
  • Se você tiver problemas com a conexão, verifique com o Administrador do sistema SAP se o usuário que você definiu no arquivo de configuração tem as permissões para executar a RFC.

    Depois de seguir essas etapas, estamos prontos para começar!

    Verificando a conexão com o SAP

    Para verificar a conexão do SAP, você pode executar o método TestConnection localizado na classe RFC.RFCUtil.cls 

    ClassMethod TestConnection() [ Language = python ]
    {
     try:
       # Import python libraries
       from pyrfc import Connection, ABAPApplicationError, ABAPRuntimeError, LogonError, CommunicationError
       from configparser import ConfigParser
    
    
       # Connect to the SAP Server
       config = ConfigParser()
       config.read('/opt/irisapp/sapnwrfc.cfg')
       params_connection = config._sections['connection']
       conn = Connection(**params_connection)
    
    
       # Launch RFC call to STFC_CONNECTION
       result = conn.call('STFC_CONNECTION', REQUTEXT=u'Hello SAP!')
    
    
       # Close the connection
       conn.close()
    
    
       # Print the result
       print(result)
    
    
     except Exception as e:
       print(e)
     }
    

    Se tudo der certo, você verá algo assim no console de depuração:

    (Se você receber um erro, verifique se a configuração que você escreveu no arquivo de configuração "sapnwrfc.cfg" está correta. )

    Como você pode ver, é fácil chamar uma RFC:

    1 - Conecte-se ao servidor SAP

    2 - Execute o método conn.call(‘name_RFC_to_execute’, RFC_parameters)

    3 - Encerre a conexão

    4 - Processe o resultado.

    Nesse exemplo, o único parâmetro admitido é uma String, e a enviamos como um parâmetro REQUTEXT.

    Consulte os dados em uma tabela SAP

    Agora vamos chamar "RFC_READ_TABLE", que permite a leitura de uma tabela SAP.

    Neste exemplo, vamos ler todos os dados de uma tabela:

    ClassMethod GetDataTableSFLIGHT() [ Language = python ]
    {
      try:
        # Import python libraries
        from pyrfc import Connection, ABAPApplicationError, ABAPRuntimeError, LogonError, CommunicationError
        from configparser import ConfigParser
    
    
        # Connect to the SAP Server
        config = ConfigParser()
        config.read('/opt/irisapp/sapnwrfc.cfg')
        params_connection = config._sections['connection']
        conn = Connection(**params_connection)
    
    
        # Define query parameters
        params = {
            'QUERY_TABLE': 'SFLIGHT',
            'DELIMITER': ',',
            'FIELDS': [
                {'FIELDNAME': 'CARRID'},
                {'FIELDNAME': 'CONNID'},
                {'FIELDNAME': 'FLDATE'},
                {'FIELDNAME': 'PRICE'}
            ],
            'OPTIONS': [],
        }
    
    
        # Call to función RFC 'RFC_READ_TABLE'
        result = conn.call('RFC_READ_TABLE', **params)
    
    
        # Process results
        if 'DATA' in result:
            data = result['DATA']
            fields = result['FIELDS']
           
            # Imprime los nombres de campo
            for field in fields:
                print(field['FIELDNAME'], end='\t')
            print()
           
            # Imprime los datos
            for entry in data:
                values = entry['WA'].split(',')
                for value in values:
                    print(value, end='\t')
                print()
        else:
            print('No data found.')
    
    
        # Close SAP connection
        conn.close()
    
    
      except CommunicationError:
        print("Could not connect to server.")
        raise
      except LogonError:
        print("Could not log in. Wrong credentials?")
        raise
      except (ABAPApplicationError, ABAPRuntimeError):
        print("An error occurred.")
        raise
      except Exception as e:
        print(e)
    }
    

     

    Para esse exemplo, estamos transmitindo um tipo de estrutura como parâmetro com os seguintes dados:

    QUERY_TABLE:é o nome da tabela que queremos consultar  (SFLIGHT, uma tabela padrão de voos)

    DELIMITER: permite selecionar o separador.

    FIELDSsão as colunas da tabela que queremos obter. Nesse caso, estou solicitando CARRID (ID da empresa), CONNID (ID do voo de conexão), FLDATE (data do voo) e PRICE (preço do voo).
     

    Depois de executado, podemos processar a resposta e a imprimir na tela ou fazer o que quisermos com as informações... xD.

    Por fim, encerramos a conexão.

    Se tudo der certo, você verá algo assim no console de depuração:

    Consultando dados de uma tabela ao filtrar por um campo:

    Um exemplo um pouco mais complexo seria consultar a tabela de clientes para obter os dados de um cliente.

    ClassMethod GetDataCustomer(clientSapID As%String) [ Language = python ]
    {
     try:
       from pyrfc import Connection, ABAPApplicationError, ABAPRuntimeError, LogonError, CommunicationError
       from configparser import ConfigParser
    
    
       config = ConfigParser()
       config.read('/opt/irisapp/sapnwrfc.cfg')
       params_connection = config._sections['connection']
       conn = Connection(**params_connection)
    
    
      # Define the parameters
       params = {
         'QUERY_TABLE': 'KNA1',
         'DELIMITER': ',',
         'FIELDS': [{'FIELDNAME': 'KUNNR'}, {'FIELDNAME': 'SORTL'}, {'FIELDNAME': 'TELF1'}],
         'OPTIONS': [{'TEXT': "KUNNR = '" + clientSapID + "'"}],     
     }
    
    
     # Call the RFC 'RFC_READ_TABLE' to obtain the data
       result = conn.call('RFC_READ_TABLE', **params)
    
    
     # Process the result
       if 'DATA' in result:
         data = result['DATA']
         fields = result['FIELDS']
        
         # Print the fields names
         for field in fields:
             print(field['FIELDNAME'], end='\t')
         print()
        
         # Print the data fields.
         for entry in data:
             values = entry['WA'].split(',')
             for value in values:
                 print(value, end='\t')
             print()
       else:
         print('No data found.')
    
    
     # Close the connection
       conn.close()
    
    
     except CommunicationError:
       print("Could not connect to server.")
       raise
     except LogonError:
       print("Could not log in. Wrong credentials?")
       raise
     except (ABAPApplicationError, ABAPRuntimeError):
       print("An error occurred.")
       raise
     except Exception as e:
       print(e)
    }
    

     

    Parâmetros:

    QUERY_TABLE: é o nome da tabela que queremos consultar (KNA1 é a tabela de clientes no SAP)

    DELIMITER: permite selecione o separador.

    FIELDS: são as colunas da tabela que queremos obter. Nesse caso, estou solicitando KUNNR (ID do cliente SAP), SORTL (nome curto do cliente) e TELF1 (número de telefone do cliente).

    OPTIONS: é um filtro que queremos aplicar à consulta. Nesse caso, estamos filtrando pelo ID do cliente que é recebido no método pelo parâmetro.

    Depois de executado, podemos processar a resposta e a imprimir na tela ou fazer o que quisermos com isso... xD

    Por fim, encerre a conexão.

    Se tudo der certo, você verá algo assim:

    Esse é só o começo, porque, com as chamadas RFC, podemos realizar as operações Create, Read, Update e Delete, além de executar qualquer código SAP (personalizado ou padrão) exposto por uma RFC, programas, funções de módulo etc. A RFC é usada para ler e escrever dados, além de iniciar programas ou funções.

    Espero que, se você precisar lidar algum dia com qualquer integração entre a InterSystems e o SAP, este artigo seja útil.

    Se você tiver quaisquer dúvidas, escreva um comentário e tentarei ajudar.

    Muito obrigada por ler!

    Link para o aplicativo no OpenExchange: https://openexchange.intersystems.com/package/IrisSapConnector

    Link para o repositório: https://github.com/daniel-aguilar-garcia/IrisSapConnector/blob/main/sapnwrfc.cfg

    Link para a biblioteca pyrfc: https://sap.github.io/PyRFC/

    0
    0 124
    Artigo Cristiano Silva · Jul. 31, 2023 2m read

    Ao criar Business Hosts personalizados, muitas vezes é necessário adicionar propriedades à classe para configurações adicionais que serão usadas na inicialização ou operação do host. O próprio nome da propriedade nem sempre é muito descritivo, por isso é uma vantagem ter uma exibição de legenda personalizada com o campo.

    No Ensemble, era bastante simples:

    TEST> Set^CacheMsg("EnsColumns","pt-br","<propriedade>") = "<legenda>"

    Mas envolve um pouco mais de esforço no IRIS...

    0
    0 53