#Code Snippet

0 Seguidores · 58 Publicaciones

Fragmento de código es un término de programación que se refiere a una pequeña región del código fuente, reutilizable código, máquina o texto. 

Artículo Jose-Tomas Salvador · mayo 7, 2025 4m read

Una extensión "extiende" o mejora un recurso FHIR o un elemento de datos de forma personalizada. La extensión puede añadirse a la raíz de un recurso, como “Patient.ethnicity” en el perfil US Core, y también pueden añadirse a elementos individuales como HumanName, Address o Identifier.

¿Sabíais que también podéis añadir una extensión a un tipo de dato primitivo?

Los elementos primitivos normalmente almacenan un solo elemento y son el componente más básico en FHIR. Por ejemplo: "Keren", false, 1234, 12/08/2024, etc.

Por ejemplo, los recursos del paciente podrían verse así:

0
0 35
Artículo Alberto Fuentes · ene 31, 2024 13m read

Tenemos un conjunto de datos bastante apetecible con recetas escritas por múltiples usuarios de Reddit, sin embargo, la mayor parte de la información está en texto libre en forma de título y descripción de un mensaje. Vamos a averiguar cómo podemos, de forma muy sencilla, cargar los datos, extraer algunas características y analizarlos empleando funcionalidades de LLM (Large Language Model) de OpenAI desde Python Embebido y el framework Langchain.

Cargar los datos

Lo primero es lo primero: ¿necesitamos cargar los datos o podemos sencillamente conectarnos a ellos?

Hay diferentes formas para plantearlo: por ejemplo, con el Mapeo de registros CSV que puedes utilizar en una producción de interoperabilidad o incluso instalar directamente una aplicación de OpenExchange como csvgen para que nos ayude.

Utilizaremos en este caso las Foreign Tables. Una funcionalidad muy útil para proyectar datos físicamente almacenados en otra parte y tenerlos accesibles desde el SQL de IRIS. Podemos utilizarlo directamente para echar un primer vistazo a los ficheros del conjunto de datos.

Creamos un Foreign Server:

CREATE FOREIGN SERVER dataset FOREIGN DATA WRAPPER CSV HOST '/app/data/'

Y a continuación, creamos una Foreign Table que se conecta al fichero CSV:

CREATE FOREIGN TABLE dataset.Recipes (
  CREATEDDATE DATE,
  NUMCOMMENTS INTEGER,
  TITLE VARCHAR,
  USERNAME VARCHAR,
  COMMENT VARCHAR,
  NUMCHAR INTEGER
) SERVER dataset FILE 'Recipes.csv' USING
{
  "from": {
    "file": {
       "skip": 1
    }
  }
}

¡Y ya está!, inmediatamente podemos lanzar consultas SQL sobre dataset.Recipes: image

## ¿Qué datos necesitamos? Los datos son muy interesantes y tenemos hambre. Sin embargo, si queremos decidir qué receta vamos a cocinar necesitamos algo más de información que podamos utilizar para analizar las recetas.

Vamos a trabajar con dos clases persistentes (tablas):

  • yummy.data.Recipe: una clase que contiene el título y la descripción de la receta así como algunas otras propiedades que queremos extraer y analizar (por ejemplo: Score, Difficulty, Ingredients, CuisineType, PreparationTime).
  • yummy.data.RecipeHistory: una clase sencilla para anotar un registro de qué estamos haciendo con la receta.

Podemos ahora cargar en nuestras tablas yummy.data* el contenido del conjunto de datos de recetas:

do ##class(yummy.Utils).LoadDataset()

Hasta aquí tiene buena pinta, pero aún debemos averiguar cómo vamos a generar los datos para los campos como: Score, Difficulty, Ingredients, PreparationTime and CuisineType.

## Analizar las recetas Queremos procesar el título y la descripción de cada receta y:

  • Extraer información como Difficulty, Ingredients, CuisineType, etc.
  • Construir nuestra propia puntuación de la receta basada en nuestro criterio, de forma que podamos decidir qué vamos a cocinar.

Vamos a utilizar lo siguiente:

  • yummy.analysis.Analysis - una estructura genérica de análisis que vamos a re-utilizar en caso de que queramos implementar diferentes tipos de análisis.
  • yummy.analysis.SimpleOpenAI - un análisis que usa Python Embebido + framework Langchain + OpenAI LLM.

Los LLM (Large Language Models) son una herramienta realmente increíble para procesar lenguaje natural.

LangChain está preparado para trabajar con Python, así que podemos utilizarlo directamente en InterSystems IRIS a través de Embedded Python.

La clase SimpleOpenAI tiene esta pinta:

/// Simple OpenAI analysis for recipes
Class yummy.analysis.SimpleOpenAI Extends Analysis
{

Property CuisineType As %String;

Property PreparationTime As %Integer;

Property Difficulty As %String;

Property Ingredients As %String;

/// Run
/// You can try this from a terminal:
/// set a = ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(8))
/// do a.Run()
/// zwrite a
Method Run()
{
    try {
        do ..RunPythonAnalysis()

        set reasons = ""

        // mis estilos de cocina favoritos
        if "spanish,french,portuguese,italian,korean,japanese"[..CuisineType {
            set ..Score = ..Score + 2
            set reasons = reasons_$lb("It seems to be a "_..CuisineType_" recipe!")
        }

        // no quiero estar el día entero cocinando :)
        if (+..PreparationTime < 120) {
            set ..Score = ..Score + 1
            set reasons = reasons_$lb("You don't need too much time to prepare it") 
        }
        
        // bonus para mis ingredientes favoritos!
        set favIngredients = $listbuild("kimchi", "truffle", "squid")
        for i=1:1:$listlength(favIngredients) {
            set favIngred = $listget(favIngredients, i)
            if ..Ingredients[favIngred {
                set ..Score = ..Score + 1
                set reasons = reasons_$lb("Favourite ingredient found: "_favIngred)
            }
        }

        set ..Reason = $listtostring(reasons, ". ")

    } catch ex {
        throw ex
    }
}

/// Update recipe with analysis results
Method UpdateRecipe()
{
    try {
        // call parent class implementation first
        do ##super()

        // add specific OpenAI analysis results
        set ..Recipe.Ingredients = ..Ingredients
        set ..Recipe.PreparationTime = ..PreparationTime
        set ..Recipe.Difficulty = ..Difficulty
        set ..Recipe.CuisineType = ..CuisineType

    } catch ex {
        throw ex
    }
}

/// Run analysis using embedded Python + Langchain
/// do ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(8)).RunPythonAnalysis(1)
Method RunPythonAnalysis(debug As %Boolean = 0) [ Language = python ]
{
    # load OpenAI APIKEY from env
    import os
    from dotenv import load_dotenv, find_dotenv
    _ = load_dotenv('/app/.env')

    # account for deprecation of LLM model
    import datetime
    current_date = datetime.datetime.now().date()
    # date after which the model should be set to "gpt-3.5-turbo"
    target_date = datetime.date(2024, 6, 12)
    # set the model depending on the current date
    if current_date > target_date:
        llm_model = "gpt-3.5-turbo"
    else:
        llm_model = "gpt-3.5-turbo-0301"

    from langchain.chat_models import ChatOpenAI
    from langchain.prompts import ChatPromptTemplate
    from langchain.chains import LLMChain

    from langchain.output_parsers import ResponseSchema
    from langchain.output_parsers import StructuredOutputParser

    # init llm model
    llm = ChatOpenAI(temperature=0.0, model=llm_model)

    # prepare the responses we need
    cuisine_type_schema = ResponseSchema(
        name="cuisine_type",
        description="What is the cuisine type for the recipe? \
                     Answer in 1 word max in lowercase"
    )
    preparation_time_schema = ResponseSchema(
        name="preparation_time",
        description="How much time in minutes do I need to prepare the recipe?\
                     Anwer with an integer number, or null if unknown",
        type="integer",
    )
    difficulty_schema = ResponseSchema(
        name="difficulty",
        description="How difficult is this recipe?\
                     Answer with one of these values: easy, normal, hard, very-hard"
    )
    ingredients_schema = ResponseSchema(
        name="ingredients",
        description="Give me a comma separated list of ingredients in lowercase or empty if unknown"
    )
    response_schemas = [cuisine_type_schema, preparation_time_schema, difficulty_schema, ingredients_schema]

    # get format instructions from responses
    output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
    format_instructions = output_parser.get_format_instructions()
    
    analysis_template = """\
    Interprete and evaluate a recipe which title is: {title}
    and the description is: {description}
    
    {format_instructions}
    """
    prompt = ChatPromptTemplate.from_template(template=analysis_template)

    messages = prompt.format_messages(title=self.Recipe.Title, description=self.Recipe.Description, format_instructions=format_instructions)
    response = llm(messages)

    if debug:
        print("======ACTUAL PROMPT")
        print(messages[0].content)
        print("======RESPONSE")
        print(response.content)

    # populate analysis with results
    output_dict = output_parser.parse(response.content)
    self.CuisineType = output_dict['cuisine_type']
    self.Difficulty = output_dict['difficulty']
    self.Ingredients = output_dict['ingredients']
    if type(output_dict['preparation_time']) == int:
        self.PreparationTime = output_dict['preparation_time']

    return 1
}

}

El método RunPythonAnalysis es donde sucede todo lo relativo a OpenAI :). Puedes probarlo directamente desde tu terminal utilizando una receta en particular:

do ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(12)).RunPythonAnalysis(1)

Obtendremos un resultado como el siguiente:

USER>do ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(12)).RunPythonAnalysis(1)
======ACTUAL PROMPT
                    Interprete and evaluate a recipe which title is: Folded Sushi - Alaska Roll
                    and the description is: Craving for some sushi but don't have a sushi roller? Try this easy version instead. It's super easy yet equally delicious!
[Video Recipe](https://www.youtube.com/watch?v=1LJPS1lOHSM)
# Ingredients
Serving Size:  \~5 sandwiches      
* 1 cup of sushi rice
* 3/4 cups + 2 1/2 tbsp of water
* A small piece of konbu (kelp)
* 2 tbsp of rice vinegar
* 1 tbsp of sugar
* 1 tsp of salt
* 2 avocado
* 6 imitation crab sticks
* 2 tbsp of Japanese mayo
* 1/2 lb of salmon  
# Recipe     
* Place 1 cup of sushi rice into a mixing bowl and wash the rice at least 2 times or until the water becomes clear. Then transfer the rice into the rice cooker and add a small piece of kelp along with 3/4 cups plus 2 1/2 tbsp of water. Cook according to your rice cookers instruction.
* Combine 2 tbsp rice vinegar, 1 tbsp sugar, and 1 tsp salt in a medium bowl. Mix until everything is well combined.
* After the rice is cooked, remove the kelp and immediately scoop all the rice into the medium bowl with the vinegar and mix it well using the rice spatula. Make sure to use the cut motion to mix the rice to avoid mashing them. After thats done, cover it with a kitchen towel and let it cool down to room temperature.
* Cut the top of 1 avocado, then slice into the center of the avocado and rotate it along your knife. Then take each half of the avocado and twist. Afterward, take the side with the pit and carefully chop into the pit and twist to remove it. Then, using your hand, remove the peel. Repeat these steps with the other avocado. Dont forget to clean up your work station to give yourself more space. Then, place each half of the avocado facing down and thinly slice them. Once theyre sliced, slowly spread them out. Once thats done, set it aside.
* Remove the wrapper from each crab stick. Then, using your hand, peel the crab sticks vertically to get strings of crab sticks. Once all the crab sticks are peeled, rotate them sideways and chop them into small pieces, then place them in a bowl along with 2 tbsp of Japanese mayo and mix until everything is well mixed.
* Place a sharp knife at an angle and thinly slice against the grain. The thickness of the cut depends on your preference. Just make sure that all the pieces are similar in thickness.
* Grab a piece of seaweed wrap. Using a kitchen scissor, start cutting at the halfway point of seaweed wrap and cut until youre a little bit past the center of the piece. Rotate the piece vertically and start building. Dip your hand in some water to help with the sushi rice. Take a handful of sushi rice and spread it around the upper left hand quadrant of the seaweed wrap. Then carefully place a couple slices of salmon on the top right quadrant. Then place a couple slices of avocado on the bottom right quadrant. And finish it off with a couple of tsp of crab salad on the bottom left quadrant. Then, fold the top right quadrant into the bottom right quadrant, then continue by folding it into the bottom left quadrant. Well finish off the folding by folding the top left quadrant onto the rest of the sandwich. Afterward, place a piece of plastic wrap on top, cut it half, add a couple pieces of ginger and wasabi, and there you have it.
                    
                    The output should be a markdown code snippet formatted in the following schema, including the leading and trailing "```json" and "```":
json
{
        "cuisine_type": string  // What is the cuisine type for the recipe?                                  Answer in 1 word max in lowercase
        "preparation_time": integer  // How much time in minutes do I need to prepare the recipe?                                    Anwer with an integer number, or null if unknown
        "difficulty": string  // How difficult is this recipe?                               Answer with one of these values: easy, normal, hard, very-hard
        "ingredients": string  // Give me a comma separated list of ingredients in lowercase or empty if unknown
}

                    
======RESPONSE
json
{
        "cuisine_type": "japanese",
        "preparation_time": 30,
        "difficulty": "easy",
        "ingredients": "sushi rice, water, konbu, rice vinegar, sugar, salt, avocado, imitation crab sticks, japanese mayo, salmon"
}

Tiene muy buena pinta. Parece que nuestro prompt o pregunta a OpenAI es capaz de devolvernos información que realmente podemos utilizar. Vamos a ejecutar el análisis completo desde el terminal:

set a = ##class(yummy.analysis.SimpleOpenAI).%New(##class(yummy.data.Recipe).%OpenId(12))
do a.Run()
zwrite a
USER>zwrite a
a=37@yummy.analysis.SimpleOpenAI  ; <OREF>
+----------------- general information ---------------
|      oref value: 37
|      class name: yummy.analysis.SimpleOpenAI
| reference count: 2
+----------------- attribute values ------------------
|        CuisineType = "japanese"
|         Difficulty = "easy"
|        Ingredients = "sushi rice, water, konbu, rice vinegar, sugar, salt, avocado, imitation crab sticks, japanese mayo, salmon"
|    PreparationTime = 30
|             Reason = "It seems to be a japanese recipe!. You don't need too much time to prepare it"
|              Score = 3
+----------------- swizzled references ---------------
|           i%Recipe = ""
|           r%Recipe = "30@yummy.data.Recipe"
+-----------------------------------------------------

## Analizar todas las recetas Naturalmente, querremos ejecutar el análisis para todas las recetas que hemos cargado.

Puedes analizar un rango de recetas (utilizando sus identificadores), de esta forma:

USER>do ##class(yummy.Utils).AnalyzeRange(1,10)
> Recipe 1 (1.755185s)
> Recipe 2 (2.559526s)
> Recipe 3 (1.556895s)
> Recipe 4 (1.720246s)
> Recipe 5 (1.689123s)
> Recipe 6 (2.404745s)
> Recipe 7 (1.538208s)
> Recipe 8 (1.33001s)
> Recipe 9 (1.49972s)
> Recipe 10 (1.425612s)

Después de eso, vamos a echar un vistazo de nuevo a la tabla de recetas y comprobemos los resultados:

select * from yummy_data.Recipe

image

Creo que podría intentar la Pizza con calabaza o el Tofu con Kimchi y cerdo al estilo coreano :). De todas formas, debo asegurarme y preguntar en casa antes de empezar a cocinar :)

Conclusión

Puedes encontrar el ejemplo completo en https://github.com/isc-afuentes/recipe-inspector

Con este ejemplo sencillo hemos aprendido cómo utilizar técnicas LLM para extraer características y analizar ciertas partes de nuestros datos en InterSystems IRIS.

Con esto como punto de partida, podrías plantearte cosas como:

  • Utilizar InterSystems BI para explorar y navegar tus datos utilizando cubos y cuadros de mando.
  • Crear una aplicación web y añadir una interfaz gráfica (e.g. con Angular) para esta aplicación, podrías utilizar paquetes como RESTForms2 para generar automáticamente APIs REST para tus clases persistentes.
  • ¿Qué tal si almacenas si una receta te gusta o no, y después intentas determinar si una nueva receta te gustará? Podrías plantearlo con IntegratedML, o incluso con LLM pasándole algunos datos de ejemplo e implementando un caso de uso tipo RAG (Retrieval Augmented Generation).

¿Qué otras cosas se os ocurren?

0
0 190
Artículo Daniel Aguilar · nov 24, 2023 1m read

Necesitaba averiguar en tiempo de ejecución si el último test había fallado o no.
 

Después de investigar un poco, aquí está el código:

ClassMethod isLastTestOk() As %Boolean{  set in = ##class(%UnitTest.Result.TestInstance).%OpenId(^UnitTest.Result)  for i=1:1:in.TestSuites.Count() {    #dim suite As %UnitTest.Result.TestSuite    set suite = in.TestSuites.GetAt(i)    return:suite.Status=0 $$$NO  }  quit $$$YES}
0
0 58
Artículo Alberto Fuentes · ago 21, 2023 1m read

Una característica muy potente en Studio es utilizar snippets o fragmentos de código.

Podemos hacer lo mismo también en VS Code de la siguiente manera:

Aquí tienes las instrucciones genéricas.

1. Vete a Code > Settings ... > Configure User Snippets y selecciona objectscript.

2. Añade tu fragmento de código, aquí tienes un ejemplo:

"SQL Statement": {
    "prefix": ["sql"],
    "body": ["#dim rs As %SQL.ISelectResult",
            "set rs = ##class(%SQL.Statement).%ExecDirect(,\"SELECT * FROM\")",
            "while rs.%Next() {",
            "\twrite rs.ID, !",
            "}"]
}

Donde:

1
1 173
Pregunta Yone Moreno · abr 17, 2023

Buenas tardes,

Antes que nada, muchísimas gracias por leer esta duda, y sobre todo por dedicar tiempo en entenderla y en responderla. Gracias.

Por favor, necesitaríamos su ayuda. Actualmente estamos desarrollando una Integración REST, y se nos da un caso que nos gustaría comentar con ustedes, a fin de hallar pistas, documentación, ejemplos, o mecanismos para gestionarlo y depurarlo.

En la Operación REST recibimos:

2
0 114
Pregunta Yone Moreno · abr 18, 2023

Buenos días;

Primeramente, expresar gracias reales sinceras y profundas por el tiempo que dedican ustedes a leer, entender y responder esta duda. Sobre todo muchas gracias por responderla de la mejor manera posible. Gracias.

Tenemos una duda:

El Sistema Origen nos informa en el body que envía hacia el método del Servicio REST "getCursosAdmitidosInscripcionAbierta" de la siguiente manera:

{
    "agrupacionPuesto": ["37","38"]
}

 

Siendo el Mensaje Request:

1
0 289
Pregunta Yone Moreno · abr 17, 2023

Buenos días;

Primero agradecer infinito el apoyo de ustedes y sobre todo y más importante el tiempo que dedican a leer, comprender y sobre todo a responder esta duda. Muchísimas gracias.

Hemos estado trasteando en un Servicio REST para adaptarlo a tal y como lo necesita Sistema Origen. Observamos que si nos enviaran una Header con mayúsculas "idUserLogueado" al tratar de obtener valor en Servicio en el LOGINFO sale vacío:

set idUserLogueado = pInput.GetAttribute("idUserLogueado")

$$$LOGINFO("idUserLogueado: "_idUserLogueado)
2
0 268
Pregunta Yone Moreno · mar 13, 2023

Buenos días

Realizando una integración en la que se transforma un XML en un JSON que representa un recurso FHIR; ¿por favor podrían ayudarnos con lo siguiente?

Conocemos que se puede crear un JSON "a mano" que represente una estructura FHIR, e incluso añadir llamadas a funciones tales como:

"display"(##class(Util.TablasMaestras).getValorMaestra("CENTROS_CONCERTADOS.CODIGO_TO_NOMBRE_HOSPITAL",busquedaOrdenesRequest.codigoHospitalDestino))
 

Y concatenaciones como:

"reference"("#"_(mensaje.DNI))
 

Sin embargo la cuestión es: ¿se podrían añadir condicionales del tipo "if"?

1
0 293
Pregunta Yone Moreno · mar 13, 2023

Buenos días;

Antes que nada agradecer el tiempo, conocimiento y dedicación de ustedes, al leer y responder esta cuestión.

Actualmente disponemos de un circuito que recibe SOAP y envía SOAP, relacionado con la Administración Electrónica; el cual hemos generado al importar el WSDL del Sistema Destino.

Lo interesante y el desafío es lo siguiente:

Si por SoapUI cargamos el WSDL de Sistema Destino y enviamos 1 mensaje SOAP directos, nos responden OK

Sin embargo, curiosamente, al enviar el mismo mismo mismo mensaje, por el ESB, nos da una Excepción el Sistema Destino:

3
0 364
Pregunta Yone Moreno · feb 9, 2023

Buenas tardes,
Por favor agradeceríamos que nos lean y respondan.

Necesitamos dada una solicitud de Autenticación emitida hacia nuestro servidor de autorización OAuth 2.0; que en caso de error, el mensaje respondido sea personalizado.

Actualmente si apuntamos desde POSTMAN a:
https://[IP]:[Puerto]/oauth2/token?grant_type=client_credentials&scope=my/scope

Username: Erróneo
Password: Errónea

Obtenemos:
{
    "error": "server_error",
    "error_description": "ERROR #5002: Error de cache: <INVALID OREF>zAuthorize+28^OAuth2.Server.Token.1"
}

1
0 147
Pregunta Yone Moreno · dic 1, 2022

Hola,

Primero agradecer su tiempo y auxilio.

Les explico: estamos enviando una petición REST POST desde el SoapUI, y dentro le incluimos un "Authorization Bearer".

POST http://[IP]:[Port]/aplicaciones/scs/informescctest/Servicios.SOAP.InformesCConcertadosv01r00.cls HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/xml
Authorization: Bearer eyJ0e[...]
SOAPAction: http://SCS.Servicios/InformesCConcertadosv01r00/ResultadosER7
Content-Length: 1871
Host: AAA
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

1
0 635
Artículo Luis Angel Pérez Ramos · oct 17, 2022 2m read

Acabo de redactar un ejemplo rápido para ayudar a un colega a cargar datos en IRIS desde R usando RJDBC y pensé que sería útil compartirlo aquí para futuras consultas.

Fue bastante sencillo, aparte de que a IRIS no le gusta el uso de puntos "." en los nombres de las columnas; la solución alternativa es simplemente renombrar las columnas. Alguien con más conocimientos que yo en R seguramente pueda ofrecer un enfoque más amplio smiley

# Es necesario un valor válido para el JAVA_HOME antes de cargar la librería (RJDBC)
Sys.setenv(JAVA_HOME="C:\\Java\\jdk-8.0.322.6-hotspot\\jre")
library(RJDBC)
library(dplyr)
# Conexión a IRIS – se requiere la ruta a la librería JAR de InterSystems JDBC JAR de tu instalación
drv <- JDBC("com.intersystems.jdbc.IRISDriver", "C:\\InterSystems\\IRIS\\dev\\java\\lib\\1.8\\intersystems-jdbc-3.3.0.jar","\"")
conn <- dbConnect(drv, "jdbc:IRIS://localhost:1972/USER", "IRIS Username", "IRIS Password")
dbListTables(conn)
# Para mayor confusión, cargar el dataset de IRIS:)
data(iris)
# A IRIS no le gustan los puntos "." en el nombre de las columnas, así que los renombramos. (Probablemente se pueda codificar de una forma más genérica, pero no soy muy bueno con R.)
iris <- iris %>% rename(sepal_length = Sepal.Length, sepal_width = Sepal.Width, petal_length = Petal.Length, petal_width = Petal.Width)

# dbWriteTable/dbGetQuery/dbReadTable funcionan
dbWriteTable(conn, "iris", iris, overwrite = TRUE)
dbGetQuery(conn, "select count(*) from iris")
d <- dbReadTable(conn, "iris")
0
0 127
Pregunta Yone Moreno · sep 21, 2022

Buenos días,

💭🧱🧑‍💻 Hemos estado indagando y construyendo gracias al enorme apoyo, soporte, y asistencia ofrecida por el siguiente ejemplo:

https://es.community.intersystems.com/post/ejemplo-de-integraci%C3%B3n-…

Y del código de Github de los circuitos de ejemplo para el FIND y el MOVE:

https://github.com/intersystems-ib/iris-dicom-sample

Sería de agradecer si ustedes nos leen y responden a las siguiente cuestiones:

Desarrollando el MOVE, nos hemos encontrado con que en los primeros envíos sí se realiza y obtenemos respuesta:

MOVE

STORE

3
0 208
Artículo Alberto Fuentes · ago 25, 2022 4m read

Hace varios años, estaba enseñando los conocimientos básicos de nuestro framework %UnitTest durante la clase de Fundamentos de Caché (ahora llamada Developing Using InterSystems Objects and SQL). Un alumno preguntó si era posible recoger estadísticas de rendimiento mientras se ejecutan pruebas unitarias. Unas semanas más tarde, añadí un código adicional a los ejemplos de %UnitTest para responder a esa pregunta. Ahora lo comparto con la Comunidad.

1
0 107
Pregunta Yone Moreno · ago 1, 2022

Buenos días,

Nos gustaría compartir lo que estamos investigando y en lo que necesitamos su apoyo, para continuar con ello:

1) Hemos desarrollado un Circuito REST: Servicio REST + Proceso + Operacion REST.

Los cuales reciben un fichero de estudios de imágenes médicas .dcm y lo envían tal cual. Para ello enviamos mediante el simulador dcm4che y en concreto mediante la línea:

./stowrs --disableTM --allowAnyHost  --url https://[IP del ESB]:[Puerto del ESB]/aplicaciones/scs/cconcertadostest/almacenarImagen /opt/contenedor/PACS/dcm4che-5.27.0-bin/dcm4che-5.27.0/bin/shared/dicom/d1I00012.dcm  

2
0 266
Artículo Alberto Fuentes · jul 5, 2022 1m read

ObjectScript no incluye ningún método por defecto para añadir un array JSON a otro. Este es un fragmento de código que utilizo y que es equivalente al método concat() de JavaScript.

Puedes llamarlo con cualquier número de argumentos para concatenarlos en un nuevo array. Si un argumento es un array dinámico, sus elementos serán añadidos. Si no, el argumento en sí será añadido.

ClassMethod ConcatArrays(pArgs...) As %DynamicArray
{
	set outArray = []
	for i=1:1:pArgs {
		set arg = pArgs(i)
		if ($IsObject(arg) && arg.%IsA("%DynamicArray")) {
			set iter = arg.%GetIterator()
			while iter.%GetNext(.key, .value) {
				do outArray.%Push(value)
			}
		} else {
			do outArray.%Push(arg)
		}
	}
	return outArray
}

Contadme si hay una forma mejor de hacer esto!

0
0 320
Pregunta Yone Moreno · jun 13, 2022

Hola buenos días,

Agradeceríamos si ustedes nos leen y responden:

Disponemos de 2 entornos: INTegracion en HealthConnect 2020 y PREproduccion en Ensemble 2018

¿Que un tipo de dato sea %GlobalCharacterStream influye? en concreto: ¿se generan ficheros .stream en el servidor si subimos clases que hagan uso de %GlobalCharacterStream?

Ojeando la documentación, parece que explica lo siguiente:

Parece que NO sería persistente , parece que "almacena caracteres en nodos de datos globales"

2
0 154
Pregunta Yone Moreno · jun 3, 2022

Buenos días,

Agradeceríamos que nos leyeran y si tuvieran oportunidad, nos respondieran, por favor.

La situación actual es la siguiente:

Disponemos de 2 entornos PREproduccion e INTegracion :

Versión de Ensemble en PREproduccion:

Cache for UNIX (Red Hat Enterprise Linux for x86-64) 2018.1.6 (Build 717U) Thu Feb 24 2022 13:27:54 EST

Versión de la herramienta para convertir XML a ER7 y viceversa, el ITB, en PRE (la obtenemos en el fichero ITB.info.cls):

Parameter VERSION = 2.1;

Versión de Ensemble en INTegracion:

3
0 260
Artículo Ricardo Paiva · ago 11, 2021 3m read

Me encontré con un interesante caso de uso de ObjectScript con una solución general que quería compartir.

Caso de uso:

Tengo una matriz JSON (específicamente, en mi caso, una matriz de problemas de Jira) que quiero agregar en algunos campos, por ejemplo: categoría, prioridad y tipo de problema. Después quiero combinar los agregados en una lista simple con el total de cada uno de los grupos. Por supuesto, para la agregación, tiene sentido utilizar una matriz local en el formulario:

agg(category, priority, type) = total

De tal manera que para cada registro en la matriz de entrada simplemente puedo hacer lo siguiente:

Do $increment(agg(category, priority, type))

Pero, cuando haya hecho la agregación, quiero conseguir un formulario más fácil sobre el que iterar, como una matriz con subíndices enteros:

summary = n
summary(1) = $listbuild(total1, category1, priority1, type1)
...
summary(n) = $listbuild(totalN, categoryN, priorityN, typeN)

Solución básica:

El enfoque sencillo es simplemente tener tres bucles "For" anidados con $Order, por ejemplo:

Set category = ""
For {
    Set category = $Order(agg(category))
    Quit:category=""
    
    Set priority = ""
    For {
        Set priority = $Order(agg(category,priority))
        Quit:priority=""
        
        Set type = ""
        For {
            Set type = $Order(agg(category,priority,type),1,total)
            Quit:type=""
            
            Set summary($i(summary)) = $listbuild(total,category,priority,type)
        }
    }

}

Esto es lo que empecé a hacer, pero es mucho código, y si tuviera más dimensiones que agregar se volvería difícil de manejar rápidamente. Esto me hizo preguntarme: ¿hay una solución general para conseguir lo mismo? ¡Resulta que sí la hay!

Mejor solución con $Query:

Decidí que usar $query podría ayudar. Ten en cuenta que esta solución asume una capacidad uniforme de los subíndices/valores en toda la matriz local, sucederían cosas extrañas si se vulnerara esta suposición.

ClassMethod Flatten(ByRef deep, Output flat) [ PublicList = deep ]
{
    Set reference = "deep"
    For {
        Set reference = $query(@reference)
        Quit:reference=""
        Set value = $listbuild(@reference)
        For i=1:1:$qlength(reference) {
            Set value = value_$listbuild($qsubscript(reference,i))
        }
        Set flat($i(flat)) = value
    }
}

Así que el fragmento de código anterior se sustituye por:

Do ..Flatten(.agg,.summary)

Hay que tener en cuenta algunas cosas sobre esta solución:

  • deep necesita estar en la PublicList de $query para ser capaz de operar en ella
  • en cada iteración, reference se cambia para hacer referencia al siguiente conjunto de subíndices que tenga un valor en deep, por ejemplo el valor podría ser: deep("foo","bar")
  • $qlength devuelve el número de subíndices en reference
  • $qsubscript devuelve el valor del enésimo subíndice de reference
  • Cuando las listas $listbuild están concatenadas, el resultado es una lista $listbuild válida con las listas combinadas (¡esto es mucho mejor que usar cualquier otro separador!)

Resumen

$query, $qlength y $qsubscript son útiles para lidiar con matrices globales/locales de capacidad arbitraria.

Lecturas adicionales

$Query: https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_FQUERY
$QSubscript: https://docs.intersystems.com/irisforhealthlatest/csp/docbook/Doc.View.cls?KEY=RCOS_fqsubscript
$QLength: https://docs.intersystems.com/irisforhealthlatest/csp/docbook/Doc.View.cls?KEY=RCOS_fqlength

1
0 201
Pregunta Yone Moreno · abr 22, 2022

Hola, buenos días

Primero agradecer su tiempo al leer y responder nuestra cuestión.

Hemos estado leyendo, indagando, investigando respecto a cómo realizar múltiples envíos desde 1 proceso del tipo "DICOM.BP.QueryProcess" a varias operaciones destino

Actualmente estamos usando el ejemplo básico, el que incluye Ensemble, y este dispone de una propiedad mediante la cual desde la producción escribimos a qué operación envía nuestro proceso:

Viéndose gráficamente el circuito:

Siendo en el código la propiedad una String sencilla:

Es decir, el código es:

1
0 246
Pregunta Yone Moreno · abr 1, 2022

Buenos días,

Agradecer a ustedes el tiempo dedicado en leer y responder a esta duda

Necesitamos indagar la forma de mostrar un EnsLib.DICOM.Document mediante LOGINFO, en las trazas

Hemos tratado de emplear:

set writer=##class(%XML.Writer).%New()

set status=writer.OutputToString()

set status=writer.RootObject(..DocumentFromService)

set xml= writer.GetXMLString()

$$$LOGINFO("..DocumentFromService en xml: "_xml)

 

En la traza nos informa de que el método "RootObject" para los documentos DICOM, los EnsLib.DICOM.Document, no existe:

1
0 213
Artículo Jose-Tomas Salvador · abr 20, 2022 1m read

Encontré este pequeño artículo de @Brendan Bannon de hace unos años... pero creo que es muy útil para cuando tengamos estructuras de almacenamiento basadas puramente en globals y queramos tener la posibilidad de acceder a ellas desde el punto de vista de Objetos y/o Relacional.

El fichero ZIP adjunto contiene un paquete de ejemplos de mapeos SQL Storage (válido para IRIS y Caché) que he hecho y recopilado a lo largo de estos años.

0
0 148
Pregunta Yone Moreno · mar 30, 2022

Buenos días,

Primero, ante todo, muchas gracias de antemano por leernos y responder

Además, agradecer cualquier apoyo, porque es un alivio, apoyo, aporte, auxilio contar con personas con más entendimiento, conocimiento y práctica.

Existe la siguiente necesidad:

Se dispone de 2 circuitos:

1º Circuito DICOM de "Studio" ( Servicio clásico )

Servicio: clase: DICOM.BS.QueryService

Proceso: clase: DICOM.BP.QueryProcess

Operacion: clase:  EnsLib.DICOM.Operation.TCP

Probamos desde la "Salida" del "Studio" mediante:

do ##class(DICOM.BS.QueryService).TestFind("102030")

Donde "102030" es el PatientID del usuario

3
0 257
Pregunta Yone Moreno · mar 28, 2022

Buenos días,

Agradeceríamos el apoyo de ustedes:

Desarrollando una integración para realizar un circuito "Query / Retrieve" con estudios de imágenes médicas DICOM, necesitaríamos lo siguiente:

Opciones de invocación del servicio DICOM TCP que se ha publicado para esta integración, alternativas a la opción por linea de comando

En concreto hemos leído: Recibir documento DICOM con un PDF embebido y metadatos

Adaptando el ejemplo, empleamos la línea:

./storescu -b VNAPRE -c ESBPRE@10.136.4.XYZ:19ABC ./embeddedpdf.dcm

Mediante la cual simulamos el envío de un documento DICOM con un PDF

1
0 286
Pregunta Yone Moreno · mar 10, 2022

Hola ¿cómo están?

Estamos indagando cómo recuperar datos mediante una Operación: EnsLib.DICOM.Operation.TCP

Hemos configurado en: Ensemble > Configuración de DICOM > Configuración de DICOM 

Las configuraciones de contexto como:

ESBPRE > VNAPRE

VNAPRE > ESBPRE

Siendo para ambas los Contextos de Presentación, los por defecto:

Lo cual hemos generado mediante el código encontrado en el ejemplo del "Namespace: ENSDEMO", el cual hemos incluido en el OnStart() de la Producción:

5
0 346
Artículo Ricardo Paiva · dic 3, 2021 2m read

Si defines una tabla/clase persistente, el compilador de clases genera una definición de almacenamiento adecuada. Otra opción es definir un mapeo SQL para un almacenamiento global que ya existe.  Esto ya se explicó estupendamente en otra serie de artículos:  El arte del mapeo de globales para Clases 1 de 3

Después de definir el diagrama de almacenamiento, el compilador de la clase lo podrá ampliar, pero los parámetros fundamentales de almacenamiento no cambiarán.
Esto no significa que no puedas cambiarlo manualmente.

Mi artículo El mapa de bits adoptado incluye un caso como este. Y en combinación con algunas Condiciones WHERE estáticas, como se describió anteriormente, permite que esté disponible también para SQL.

La definición habitual de tus globals de almacenamiento se parecerá a este ejemplo:

<DataLocation>^User.PersonD</DataLocation>
<IdLocation>^User.PersonD</IdLocation>
<IndexLocation>^User.PersonI</IndexLocation>
<StreamLocation>^User.PersonI</StreamLocation
>

Ahora cambiaremos esta definición por una dinámica

<DataLocation>@%MyData</DataLocation>
<IdLocation>@%MyId</IdLocation>
<IndexLocation>@%MyIndex</IndexLocation>
<StreamLocation>@%MyStream</StreamLocation>

y añadimos algo de comodidad

Parameter MANAGEDEXTENT As INTEGER = 0;
ClassMethod SetStorage(ref As %String) As %Integer [ SqlName = SetStorage, SqlProc ]
{
    set %MyData=ref_"D"
      , %MyId=%MyData
      , %MyIndex=ref_"I"
      , %MyStream=ref_"S"
    quit $$$OK
}

Para acceder a los objetos, dirigimos nuestro almacenamiento y lo llenamos

write ##class(Person).SetStorage("^mtemp.Person")
write ##class(Person).Populate(5)

y en SQL:

SELECT  from Person where SetStorage('^mtemp.Person')=1

Funciona con PPG  (^||Person)
mediante el namespace (^|"USER"|Person)
también corresponde al subíndice que se utilizó en El mapa de bits adoptado  con otra variante de ClassMethod SetStorage() 

Para acceso a objetos (sin SQL), funciona incluso para variables locales.
No es el uso deseado, pero demuestra la flexibilidad de esta función. 

Pero ten cuidado. NO funcionará con sharding. Pero eso no es una sorpresa.

0
0 222
Pregunta Yone Moreno · oct 27, 2021

En primer lugar gracias por su ayuda y tiempo

Necesitaríamos encontrar dentro del PID:3 cual cumple la siguiente condición:

PID 3.4.1 = "CAC" y PID 3.5 = "JHN"

Hemos estado investigando cómo podríamos hacerlo

Hemos conseguido obtener campos individuales en una petición de llamada de la siguiente manera:

request.GetValueAt("ORCgrp(1).ORC:OrderingProvider(1).IDNumber")

##class(Ens.Util.Time).ConvertDateTime(request.GetValueAt("PID:DateTimeofBirth"),"%Y%m%d","%Y-%m-%d")

 

Sin embargo, ¿cómo nos recomendarían buscar un campo específico en el PID que cumpla con un criterio específico?

1
0 120
Artículo Alberto Fuentes · oct 29, 2021 2m read

ObjectScript tiene diferentes formas para gestionar errores (códigos %Status, %Exception, SQLCODE, etc). La mayor parte del código de sistema utiliza códigos %Status pero las %Exception son en ocasiones más fáciles de gestionar.

Si tienes que trabajar o mantener aplicaciones con cierto recorrido seguramente acabes lidiando con distintas técnicas de gestión de errores. A continuación os dejo una chuleta u hoja de trucos que puede servir como referencia. ¡Espero que os sea útil!

/// Status from SQLCODE:
set st = $$$ERROR($$$SQLError, SQLCODE, $g(%msg))  //embedded SQL
set st = $$$ERROR($$$SQLError, rs.%SQLCODE, $g(rs.%Message)) //dynamic SQL

/// Exception from SQLCODE:
throw ##class(%Exception.SQL).CreateFromSQLCODE(SQLCODE,%msg) //embedded SQL
throw ##class(%Exception.SQL).CreateFromSQLCODE(rs.%SQLCODE,rs.%Message) //dynamic SQL
throw:(SQLCODE'=0)&&(SQLCODE'=100) ##class(%Exception.SQL).CreateFromSQLCODE(SQLCODE,%msg) //don't throw if query succeeds or finds no data 

/// Exception from status:
$$$ThrowOnError(st)

/// Status from exception:
set st = err.AsStatus()

/// Creating a custom error status:
set st = $$$ERROR($$$GeneralError,"Custom error message")

/// Throwing a custom exception:
$$$ThrowStatus($$$ERROR($$$GeneralError,"Custom error message"))

/// Handling a SOAP error with a status:
try {
  // SOAP request code
} Catch err {
  if $ZERROR["<ZSOAP>" {
    Set st = %objlasterror
  } Else {
    Set st = err.AsStatus()
  }
}
return st

/// Defining a custom exception class
Class App.Exceptions.SomeException Extends %Exception.AbstractException
{
Method OnAsStatus() As %Status
{
  return $$$ERROR($$$GeneralError,"Custom error message")
}
}

/// Throwing and catching a custom exception
try {
  throw ##class(App.Exceptions.SomeException).%New()
} catch err {
  if err.%ClassName(1) = ##class(App.Exceptions.SomeException).%ClassName(1) {
    //some handling unique to this type of exception
  }
}
0
0 121
Artículo Mathew Lambert · feb 28, 2020 2m read

¡Hola Comunidad!

ObjectScript tiene al menos tres formas de manejar errores (códigos de estado, excepciones, SQLCODE, etc...). La mayor parte del código del sistema usa estados, pero las excepciones son más fáciles de manejar por varias razones. Al trabajar con código heredado, se invierte un tiempo en traducir las distintas técnicas. Yo uso mucho estos fragmentos de código como referencia. Espero que también os sean útiles.

4
1 408