#Code Snippet

0 Abonnés · 15 Publications

Un extrait de code est un terme de programmation désignant une petite région de code source réutilisable, de code machine ou de texte.

Article Iryna Mykhailova · Mars 7, 2025 3m read

Je me suis lancé un défi : trouver un moyen de faire en sorte qu'une variable se surveille elle-même pour une certaine valeur et fasse quelque chose lorsqu'elle atteint cette valeur sans avoir à la vérifier à chaque fois que quelque chose la touche. En gros, un moyen de dire "à un moment donné pendant l'exécution de ce code, si x = 0 (ou quelle que soit la condition) faire ceci". La classe avec laquelle j'ai fini par surveiller un %Status :

0
0 42
Article Lorenzo Scalese · Oct 30, 2024 10m read

L'utilisation traditionnelle d'une production IRIS consiste, pour un adaptateur entrant, à recevoir des données d'une source externe, à envoyer ces données à un service IRIS, puis à faire en sorte que ce service envoie ces données par l'intermédiaire de la production.

Cependant, grâce à un adaptateur entrant personnalisé, nous pouvons faire en sorte qu'une production IRIS soit plus performante. Nous pouvons utiliser une production IRIS pour traiter les données de notre propre base de données sans aucun déclencheur externe.

BEn utilisant une production IRIS de cette manière, vos tâches de traitement des données peuvent désormais tirer parti de toutes les fonctionnalités intégrées d'une production IRIS, y compris:

  • Suivi et contrôle avancés
  • Traitement multithread pour l'évolutivité
  • Logique métier basée sur la configuration
  • Opérations IRIS intégrées pour se connecter rapidement à des systèmes externes
  • Récupération rapide des défaillances du système

La documentation pour créer un adaptateur entrant personnalisé peut être consultée à: https://docs.intersystems.com/hs20231/csp/docbook/DocBook.UI.Page.cls?KEY=EGDV_adv#EGDV_adv_adapterdev_inbound

Regardons 3 exemples d'une production simple configurée pour traiter des objets "Fish" à partir d'une base de données.

Dans le premier exemple, nous allons créer une production pilotée par les données qui traitera continuellement les données.

Dans le deuxième exemple, nous modifierons cette production pour traiter les données uniquement à des moments précis.

Dans le troisième exemple, nous modifierons cette production pour traiter les données uniquement lorsqu'elles sont déclenchées via une tâche système.

Exemple 1: Traitement continu des données

Cet exemple est une simple production configurée pour traiter continuellement des objets "Fish" à partir d'une base de données. Tout ce que fait la production est de rechercher continuellement de nouveaux objets fish, de convertir ces objets fish en JSON, puis de recracher ce JSON dans un fichier.

Tout d'abord, nous créons l'objet Fish que nous avons l'intention de traiter:

Class Sample.Fish Extends (%Persistent, Ens.Util.MessageBodyMethods, %JSON.Adaptor, %XML.Adaptor)
{

Parameter ENSPURGE As%Boolean = 0;Property Type As%String;Property Size As%Numeric;Property FirstName As%String;Property Status As%String [ InitialExpression = "Initialized" ];
Index StatusIndex On Status;
}

L'état est important car c'est ainsi que nous distinguerons l'état des objets fish non traités de l'état des objets traités.

En fixant ENSPURGE à 0 empêchera cet objet d'être purgé avec les en-têtes du message à l'avenir.

Deuxièmement, nous créons un adaptateur personnalisé pour rechercher les nouveaux objets fish:

Class Sample.Adapter.FishMonitorAdapter Extends Ens.InboundAdapter
{

/// La valeur d'état d'objet Fish sera demandée par l'adaptateur. Tous les objets Fish correspondants auront leur état défini par SetFishStatus et seront ensuite envoyés au service.Property GetFishStatus As%String [ InitialExpression = "Initialized", Required ];/// L'état d'objet Fish correspond à la valeur que le service attribue à l'objet Fish avant qu'il ne soit envoyé au service.Property SetFishStatus As%String [ InitialExpression = "Processed", Required ];Parameter SETTINGS = "GetFishStatus:Basic,SetFishStatus:Basic";Parameter SERVICEINPUTCLASS = "Sample.Fish";
Method OnTask() As%Status
{
	//Curseur pour rechercher les objets Fish correspondantsset getFishStatus = ..GetFishStatus
	&sql(declare fishCursor cursorforselectIDinto :fishId
		from Sample.Fish
		whereStatus = :getFishStatus)
	
	//Exécution du curseur
	&sql(open fishCursor)
	for {
		&sql(fetch fishCursor)
		quit:SQLCODE'=0//Changez l'état de chaque objet Fish correspondant et envoyez-le au service (BusinessHost).set fishObj = ##class(Sample.Fish).%OpenId(fishId)
		set fishObj.Status = ..SetFishStatus$$$ThrowOnError(fishObj.%Save())
		$$$ThrowOnError(..BusinessHost.ProcessInput(fishObj))
	}
	&sql(close fishCursor)
	if SQLCODE < 0 {
		throw##class(%Exception.SQL).CreateFromSQLCODE(SQLCODE,%msg)
	}
	
	quit$$$OK
}

La méthode OnTask() recherche tous les objet Fish correspondant à la valeur GetFishStatus configurée. Pour chaque objet Fish trouvé, elle modifie son état en fonction de la valeur SetFishStatus configurée, puis le transmet à la méthode ProcessInput du service.

Troisièmement, nous créons un service personnalisé pour utiliser cet adaptateur:

Class Sample.Service.FishMonitorService Extends Ens.BusinessService
{

/// Élément de configuration auquel les messages doivent être envoyésProperty TargetConfigName As Ens.DataType.ConfigName;Parameter SETTINGS = "TargetConfigName:Basic";Parameter ADAPTER = "Sample.Adapter.FishMonitorAdapter";
Method OnProcessInput(pInput As Sample.Fish, pOutput As%RegisteredObject) As%Status
{
    quit:..TargetConfigName=""//Envoyer l'objet Fish vers la cible configuréequit..SendRequestAsync(..TargetConfigName, pInput)
}

}

Ce service prend les objet Fish en entrée et les transmet via une requête asynchrone à la cible configurée.

Quatrièmement, nous créons un processus métier personnalisé pour convertir l'objet Fish en JSON.

Class Sample.Process.FishToJSONProcess Extends Ens.BusinessProcess
{

/// Élément de configuration auquel les messages doivent être envoyésProperty TargetConfigName As Ens.DataType.ConfigName;Parameter SETTINGS = "TargetConfigName:Basic";
Method OnRequest(pRequest As Sample.Fish, Output pResponse As Ens.Response) As%Status
{
	//Convertissez l'objet Fish en un flux JSONdo pRequest.%JSONExportToStream(.jsonFishStream)
	//Créez un nouveau conteneur de flux avec un flux JSONset tRequest = ##class(Ens.StreamContainer).%New(jsonFishStream)
	//Envoyez le conteneur de flux à la cible configuréequit..SendRequestAsync(..TargetConfigName, tRequest, 0)
}

Method OnResponse(request As Ens.Request, ByRef response As Ens.Response, callrequest As Ens.Request, callresponse As Ens.Response, pCompletionKey As%String) As%Status
{
    quit$$$OK
}

}

La méthode OnRequest() est la seule méthode qui agisse. Il accepte l'objet Fish, génère un flux JSON à partir de l'objet Fish, conditionne ce flux dans un conteneur Ens.StreamContainer, puis transmet ce conteneur de flux via une requête asynchrone à la cible configurée.

Enfin, nous configurons la production:

Class Sample.DataProduction Extends Ens.Production
{

XData ProductionDefinition
{
<Production Name="Sample.DataProduction" LogGeneralTraceEvents="false">
  <Description></Description>
  <ActorPoolSize>2</ActorPoolSize>
  <Item Name="Sample.Service.FishMonitorService" Category="" ClassName="Sample.Service.FishMonitorService" PoolSize="1" Enabled="true" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
    <Setting Target="Host" Name="TargetConfigName">Sample.Process.FishToJSONProcess</Setting>
  </Item>
  <Item Name="Sample.Process.FishToJSONProcess" Category="" ClassName="Sample.Process.FishToJSONProcess" PoolSize="1" Enabled="true" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
    <Setting Target="Host" Name="TargetConfigName">EnsLib.File.PassthroughOperation</Setting>
  </Item>
  <Item Name="EnsLib.File.PassthroughOperation" Category="" ClassName="EnsLib.File.PassthroughOperation" PoolSize="1" Enabled="true" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
    <Setting Target="Adapter" Name="FilePath">C:\temp\fish\</Setting>
  </Item>
</Production>
}

}

Il ne reste plus qu'à la tester. Pour cela, il suffit d'ouvrir une fenêtre de terminal et de créer un nouvel objet Fish.

En regardant les messages de production, nous pouvons voir que l'objet Fish a été trouvé et transformé:

Nous pouvons inspecter la trace des deux messages:

En regardant le dossier de sortie (C:\temp\fish\), nous pouvons voir le fichier de sortie:

Exemple 2: Traitement des données basé sur les horaires

Pour les cas d'utilisation où nous ne voulons traiter les données qu'à des moments précis, comme la nuit, nous pouvons configurer le service pour qu'il s'exécute selon des horaires précis.

Pour modifier l'exemple 1 pour qu'il fonctionne selon des horaires, nous créons d'abord une spécification de l'horaire. La documentation sur la manière de procéder est disponible ici: https://docs.intersystems.com/iris20231/csp/docbook/DocBook.UI.PortalHelpPage.cls?KEY=Ensemble%2C%20Schedule%20Editor

Ensuite, nous modifions la configuration du service pour utiliser cet horaire:

Class Sample.DataProduction Extends Ens.Production
{

XData ProductionDefinition
{
<Production Name="Sample.DataProduction" LogGeneralTraceEvents="false">
  <Description></Description>
  <ActorPoolSize>2</ActorPoolSize>
  <Item Name="Sample.Service.FishMonitorService" Category="" ClassName="Sample.Service.FishMonitorService" PoolSize="1" Enabled="true" Foreground="false" Comment="" LogTraceEvents="false" Schedule="@Midnight Processing">
    <Setting Target="Host" Name="TargetConfigName">Sample.Process.FishToJSONProcess</Setting>
  </Item>
  <Item Name="Sample.Process.FishToJSONProcess" Category="" ClassName="Sample.Process.FishToJSONProcess" PoolSize="1" Enabled="true" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
    <Setting Target="Host" Name="TargetConfigName">EnsLib.File.PassthroughOperation</Setting>
  </Item>
  <Item Name="EnsLib.File.PassthroughOperation" Category="" ClassName="EnsLib.File.PassthroughOperation" PoolSize="1" Enabled="true" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
    <Setting Target="Adapter" Name="FilePath">C:\temp\fish\</Setting>
  </Item>
</Production>
}

}

Maintenant, lorsque nous regardons cet onglet "Tâches" du service, nous voyons qu'il n'y a aucun tâche en cours:

Désormais, ce service n'aura plus que des tâches à exécuter entre minuit et 1 heure du matin.

Exemple 3: Traitement de données sur la base des événements avec le Gestionnaire de tâches

Pour les cas d'utilisation où nous ne voulons traiter les données qu'une seule fois à un moment précis ou lorsqu'un événement particulier a lieu, nous pouvons configurer le service pour qu'il ne s'exécute que lors de l'exécution d'une tâche système.

Pour modifier l'exemple 1 afin qu'il ne s'exécute que lorsqu'il est déclenché par une tâche, nous créons d'abord une tâche personnalisée pour déclencher le service.

Class Sample.Task.TriggerServiceTask Extends%SYS.Task.Definition
{

/// Le nom du service métier que cette tâche doit exécuter.Property BuinessServiceName As%String [ Required ];
Method OnTask() As%Status
{
	#dim pBusinessService As Ens.BusinessService
	$$$ThrowOnError(##class(Ens.Director).CreateBusinessService(..BuinessServiceName, .pBusinessService))
	Quit pBusinessService.OnTask()
}

}

Deuxièmement, nous configurons une nouvelle tâche système. La documentation sur la configuration des tâches système est disponible ici: https://docs.intersystems.com/iris20233/csp/docbook/Doc.View.cls?KEY=GSA_manage_taskmgr

Ceci est la partie personnalisée du processus de configuration pour cet exemple:

En outre, je configure la tâche pour qu'elle soit exécutée à la demande, mais vous pouvez aussi établir un horaire.

Enfin, nous configurons la production:

Class Sample.DataProduction Extends Ens.Production
{

XData ProductionDefinition
{
<Production Name="Sample.DataProduction" LogGeneralTraceEvents="false">
  <Description></Description>
  <ActorPoolSize>2</ActorPoolSize>
  <Item Name="Sample.Service.FishMonitorService" Category="" ClassName="Sample.Service.FishMonitorService" PoolSize="0" Enabled="true" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
    <Setting Target="Host" Name="TargetConfigName">Sample.Process.FishToJSONProcess</Setting>
  </Item>
  <Item Name="Sample.Process.FishToJSONProcess" Category="" ClassName="Sample.Process.FishToJSONProcess" PoolSize="1" Enabled="true" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
    <Setting Target="Host" Name="TargetConfigName">EnsLib.File.PassthroughOperation</Setting>
  </Item>
  <Item Name="EnsLib.File.PassthroughOperation" Category="" ClassName="EnsLib.File.PassthroughOperation" PoolSize="1" Enabled="true" Foreground="false" Comment="" LogTraceEvents="false" Schedule="">
    <Setting Target="Adapter" Name="FilePath">C:\temp\fish\</Setting>
  </Item>
</Production>
}

}

Notez que nous avons fixé le PoolSize de Sample.Service.FishMonitorService à 0.

Il ne reste plus qu'à la tester. Pour cela, il suffit d'ouvrir une fenêtre de terminal et de créer un nouvel objet Fish.

En regardant les messages de production, nous pouvons voir que l'objet Fish n'a pas encore été transformé:

Ensuite, nous exécutons la tâche à la demande pour déclencher le service:

Maintenant, en regardant les messages de production, nous pouvons voir que le service a été déclenché, ce qui a permis de trouver et de traiter l'objet Fish:

Nous pouvons inspecter la trace des deux messages:

En regardant le dossier de sortie (C:\temp\fish\), nous pouvons voir le fichier de sortie:

Conclusion

Les exemples ci-dessus sont assez simples. Vous pouvez cependant configurer les productions pour en faire beaucoup plus. Y compris…

En fait, il est possible de réaliser ici tout ce qui peut être fait dans le cadre d'une production typique d'IRIS.

0
0 44
Article Pierre LaFay · Juin 26, 2024 1m read

Récemment, j'ai voulu obtenir une liste de toutes les requêtes mises en cache et de leurs textes. Voici comment procéder.

Créez d'abord une procédure SQL renvoyant le texte de la requête mise en cache à partir d'un nom de routine de requête mise en cache :

Class test.CQ
{

/// SELECT test.CQ_GetText()
ClassMethod GetText(routine As %String) As %String [ CodeMode = expression, SqlProc ]
{
##class(%SQLCatalog).GetCachedQueryInfo(routine)
}

}

Ensuite, vous pouvez exécuter cette requête :

SELECT Routine, test.CQ_GetText(Routine)
FROM %SQL_Manager.CachedQueryTree()
0
0 42
Article Sylvain Guilbaud · Jan 29, 2024 13m read

Nous avons un délicieux dataset avec des recettes écrites par plusieurs utilisateurs de Reddit, mais la plupart des informations sont du texte libre comme le titre ou la description d'un article. Voyons comment nous pouvons très facilement charger l'ensemble de données, extraire certaines fonctionnalités et l'analyser à l'aide des fonctionnalités du grand modèle de langage OpenAI contenu dans Embedded Python et le framework Langchain.

Chargement de l'ensemble de données

Tout d’abord, nous devons charger l’ensemble de données ou pouvons-nous simplement nous y connecter ?

Il existe différentes manières d'y parvenir : par exemple CSV Record Mapper vous pouvez utiliser dans une production d'interopérabilité ou même de belles applications OpenExchange comme csvgen.

Nous utiliserons Foreign Tables. Une fonctionnalité très utile pour projeter des données physiquement stockées ailleurs vers IRIS SQL. Nous pouvons l'utiliser pour avoir une toute première vue des fichiers de l'ensemble de données.

Nous créons un Foreign Server:

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

Et puis une table étrangère qui se connecte au fichier 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
    }
  }
}

Et voilà, nous pouvons immédiatement exécuter des requêtes SQL sur dataset.Recipes: image

## De quelles données avons-nous besoin ? L’ensemble de données est intéressant et nous avons faim. Cependant, si nous voulons décider d'une recette à cuisiner, nous aurons besoin de plus d'informations que nous pourrons utiliser pour analyser.

Nous allons travailler avec deux classes persistantes (tables):

  • yummy.data.Recipe: une classe contenant le titre et la description de la recette et quelques autres propriétés que nous souhaitons extraire et analyser (par exemple Score, Difficulty, Ingredients, CuisineType, PreparationTime)
  • yummy.data.RecipeHistory: une classe simple pour enregistrer que faisons-nous avec la recette

Nous pouvons maintenant charger nos tables yummy.data* avec le contenu de l'ensemble de données:

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

Cela a l'air bien, mais nous devons encore découvrir comment générer des données pour les champs Score, Difficulty, Ingredients, PreparationTime et CuisineType. ## Analyser les recettes Nous souhaitons traiter le titre et la description de chaque recette et :

  • Extraire des informations telles que Difficulté, Ingrédients, Type de Cuisine, etc.
  • Construire notre propre score en fonction de nos critères afin que nous puissions décider de ce que nous voulons cuisiner.

Nous allons utiliser ce qui suit :

  • yummy.analysis.Analysis - une structure d'analyse générique que nous pouvons réutiliser au cas où nous souhaiterions construire plus d'analyse.
  • yummy.analysis.SimpleOpenAI - une analyse qui utilise le modèle Embedded Python + Langchain Framework + OpenAI LLM.

LLM (large language models) sont vraiment un excellent outil pour traiter le langage naturel.

LangChainest prêt à fonctionner en Python, nous pouvons donc l'utiliser directement dans InterSystems IRIS en utilisant Embedded Python.

La classe complète SimpleOpenAI ressemble à ceci:

/// Analyse OpenAI simple pour les recettes
Class yummy.analysis.SimpleOpenAI Extends Analysis
{

Property CuisineType As %String;

Property PreparationTime As %Integer;

Property Difficulty As %String;

Property Ingredients As %String;

/// Run
/// Vous pouvez essayer ceci depuis un 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 = ""

        // mes types de cuisine préférés
        if "spanish,french,portuguese,italian,korean,japanese"[..CuisineType {
            set ..Score = ..Score + 2
            set reasons = reasons_$lb("It seems to be a "_..CuisineType_" recipe!")
        }

        // je ne veux pas passer toute la journée à cuisiner :)
        if (+..PreparationTime < 120) {
            set ..Score = ..Score + 1
            set reasons = reasons_$lb("You don't need too much time to prepare it") 
        }
        
        // bonus pour les ingrédients préférés !
        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
    }
}

/// Mettre à jour la recette avec les résultats de l'analyse
Method UpdateRecipe()
{
    try {
        // appeler d'abord l'implémentation de la classe parent
        do ##super()

        // ajouter des résultats d'analyse spécifiques à OpenAI
        set ..Recipe.Ingredients = ..Ingredients
        set ..Recipe.PreparationTime = ..PreparationTime
        set ..Recipe.Difficulty = ..Difficulty
        set ..Recipe.CuisineType = ..CuisineType

    } catch ex {
        throw ex
    }
}

/// Exécuter une analyse à l'aide de 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
}

}

La méthode RunPythonAnalysis c'est ici qu'entre en jeu OpenAI :). Vous pouvez le lancer directement depuis votre terminal pour une recette donnée :

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

Nous obtiendrons un résultat comme celui-ci :

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  
# Recette     
* 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.

                    
Le résultat doit être un extrait de code de démarque formaté selon le schéma suivant, incluant les caractères "```json" and "```" :
json
{
        "cuisine_type": string  // Quel est le type de cuisine de la recette ? Réponse en 1 mot maximum en minuscule
        "preparation_time": integer  // De combien de temps en minutes ai-je besoin pour préparer la recette ? Répondez avec un nombre entier, ou nul si inconnu
        "difficulty": string  // À quel point cette recette est-elle difficile ? Répondez avec l'une de ces valeurs : facile, normal, difficile, très difficile
        "ingredients": string  // Donnez-moi une liste d'ingrédients séparés par des virgules en minuscules ou vide si inconnu
}

                    
======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"
}

Ça à l'air bon. Il semble que notre invite OpenAI soit capable de renvoyer des informations utiles. Exécutons toute la classe d'analyse depuis le 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"
+-----------------------------------------------------

## Analyser toutes les recettes ! Naturellement, vous souhaitez exécuter l’analyse sur toutes les recettes que nous avons chargées.

Vous pouvez analyser une gamme d’identifiants de recettes de cette façon :

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)

Après cela, regardez à nouveau votre tableau de recettes et vérifiez les résultats.

select * from yummy_data.Recipe

image

Je pense que je pourrais essayer la pizza à la courge poivrée ou le kimchi coréen au tofu et au porc :). De toute façon, je devrai vérifier à la maison :)

Notes finales

Vous pouvez trouver l'exemple complet sur https://github.com/isc-afuentes/recipe-inspector

Avec cet exemple simple, nous avons appris à utiliser les techniques LLM pour ajouter des fonctionnalités ou analyser certaines parties de vos données dans InterSystems IRIS.

Avec ce point de départ, vous pourriez penser à :

  • Utiliser InterSystems BI pour explorer et parcourir vos données à l'aide de cubes et de tableaux de bord.
  • Créer une application Web et fournir une interface utilisateur (par exemple Angular) pour cela, vous pouvez exploiter des packages tels que RESTForms2 pour générer automatiquement des API REST pour vos classes persistantes.
  • Et pourquoi garder en base l'information indiquant les recettes que vous aimez et celles que vous n'aimez pas, puis d'essayer de déterminer si une nouvelle recette vous plaira ? Vous pourriez essayer une approche IntegratedML, ou même une approche LLM fournissant des exemples de données et construisant un cas d'utilisation RAG (Retrieval Augmented Generation).

Quelles autres choses pourriez-vous essayer ? Laissez-moi savoir ce que vous pensez!

0
0 208
Article Pierre LaFay · Déc 21, 2023 4m read

Je reprends ici une expérience vécue qui m'a fait changer ma manière d'aborder le développement d'une fonctionnalité nouvelle pour moi.

Dans le cadre d'un projet, je devais générer un fichier excel assez complexe (rapport avec des variables calculées sur différents intervalles de temps et sur des ensembles dépendants d'un contexte).

3
0 80
Article Pierre LaFay · Déc 17, 2023 1m read

Si Iris propose bien de créer des clés suivant une séquence, comment faire pour obtenir un n° séquentiel dans un autre contexte ?

Dans mon cas, je créé automatiquement des centres de soins, et je souhaite leur fixer une numéro du type :

APP-DD-999

  • APP = Nom de l'application utilisée par le centre
  • DD = n° du département du centre
  • 999 : n° séquentiel dans le département

Il est bien sur possible que des centres soient créés de manière concurrente, il faut donc gérer cette concurrence éventuelle.

4
0 76
Article Lorenzo Scalese · Nov 9, 2023 14m read

Salut les devs,

Actuellement, je travaille sur un projet qui requiert une gestion hautement dynamique des événements. Dans le contexte du langage de programmation Java, mon premier choix aurait été d'opter instinctivement pour l'utilisation du "Observer Pattern". Le "Observer Pattern" représente une approche pour gérer les interactions entre les objets en établissant un mécanisme de notification. Il permet à plusieurs observateurs de réagir de manière autonome aux changements d'état d'un sujet, favorisant ainsi la flexibilité et la modularité du code. Si vous n'êtes pas familier avec ce modèle de conception, vous pouvez trouver de plus amples informations à ce sujet sur ceWikipedia

Bien que ce soit naturel et couramment utilisé dans certains langages de programmations comme le Java ou le C++, en ObjectScript pas du tout. 

2
1 147
Article Lorenzo Scalese · Sept 15, 2023 6m read

Salut les développeurs,

Dans cet article, je vais vous montrer comment exécuter du code au moment de la compilation avec les macros ObjectScript.

Voici un cas d'utilisation qui m'a récemment amené à utiliser cette fonctionnalité :

Dans le cadre d'une application médicale développée depuis plus de 20 ans, nous avons un grand nombre de paramètres. Bien que nous disposions de procédures pour documenter ces paramètres, il peut être utile d'avoir une vue rapide sur les paramètres réellement utilisés par le code de l'application.

0
0 99
Article Sylvain Guilbaud · Sept 6, 2023 3m read

pour démonter/monter une base de données, utilisez les méthodes Dismount() et Mount() dans la classe SYS.Database disponible dans l'espace de noms %SYS.
NB : l'ID de la base de données est le nom du chemin absolu de son répertoire.

Vous trouverez quelques exemples de démontage/montage et de méthodes contrôlant si une base de données est montée (Mounted=1) ou non (Mounted=0), ou affichant rapidement tous les attributs d'une base de données (via zwrite)

0
0 120
Article Sylvain Guilbaud · Fév 15, 2023 8m read

Introduction

Dans un article précédent, j'ai abordé les modèles d'exécution des tests unitaires via le gestionnaire de paquets ObjectScript. Cet article va un peu plus loin, en utilisant les actions GitHub pour piloter l'exécution des tests et la création de rapports. Le cas d'utilisation qui nous motive est l'exécution du CI pour l'un de mes projets Open Exchange, AppS.REST (voir l'article d'introduction à ce projet ici). Vous pouvez voir l'implémentation complète dont les extraits de cet article ont été tirés sur GitHub ; elle pourrait facilement servir de modèle pour l'exécution de l'IC pour d'autres projets utilisant le gestionnaire de paquets ObjectScript.

Les fonctionnalités dont la mise en œuvre a été démontrée comprennent :

  • Compilation et test d'un paquet ObjectScript
  • Rapport sur la mesure de la couverture des tests (en utilisant le paquet TestCoverage) via codecov.io
  • Téléchargement d'un rapport sur les résultats des tests en tant qu'artefact de comppilation.

L'environnement de compilation

Il existe une documentation complète sur les actions GitHub ici. Dans le cadre de cet article, nous nous contenterons d'explorer les aspects présentés dans cet exemple.

Un flux de travail dans les actions GitHub est déclenché par un ensemble configurable d'événements et consiste en un certain nombre de tâches qui peuvent être exécutées séquentiellement ou en parallèle. Chaque tâche comporte un ensemble d'étapes - nous allons entrer dans le détail des étapes de notre exemple d'action plus tard. Ces étapes consistent en références à des actions disponibles sur GitHub, ou peuvent simplement être des commandes shell. Un extrait du modèle initial de notre exemple ressemble à ceci :

# Flux de travail d'intégration continue
name: CI

# Contrôle le moment où l'action sera exécutée. Déclenche le flux de travail sur les événements push ou pull request
# événements dans toutes les branches
on: [push, pull_request]

# Un flux de travail est composé d'une ou plusieurs tâches qui peuvent être exécutées séquentiellement ou en parallèle.
jobs:
  # Ce flux de travail contient une seule tâche appelé "build".
  build :
    # Le type d'exécuteur sur lequel le travail sera exécuté
    runs-on : ubuntu-latest

    env:
    # Variables d'environnement utilisables tout au long de la tâche de "compilation", par exemple dans les commandes au niveau du système d'exploitation.
package: apps.rest
    container_image : intersystemsdc/iris-community:2019.4.0.383.0-zpm
    # D'autres éléments seront abordés plus tard...

    # Les étapes représentent une séquence de tâches qui seront exécutées dans le cadre du travail.
     steps:
          # Ceux-ci seront montrés plus tard...

Dans cet exemple, un certain nombre de variables d'environnement sont utilisées. Pour appliquer cet exemple à d'autres paquets utilisant le gestionnaire de paquets ObjectScript, la plupart d'entre elles n'auront pas besoin d'être modifiées, alors que certaines le seront.

    env:
      # ** POUR UN USAGE GÉNÉRAL, IL FAUDRA PROBABLEMENT CHANGER : **
      package: apps.rest
      container_image: intersystemsdc/iris-community:2019.4.0.383.0-zpm

      # ** POUR UN USAGE GÉNÉRAL, IL FAUDRA PEUT-ÊTRE CHANGER : **
      build_flags: -dev -verbose # Télécharger en mode -dev pour obtenir le code de test unitaire préchargé
      test_package: UnitTest

      # ** POUR UN USAGE GÉNÉRAL, IL NE FAUDRA PAS CHANGER : **
      instance: iris
      # Remarque : la valeur test_reports est dupliquée dans la variable d'environnement test_flags.
      test_reports: test-reports
      test_flags: >-
       -verbose -DUnitTest.ManagerClass=TestCoverage.Manager -DUnitTest.JUnitOutput=/test-reports/junit.xml
       -DUnitTest.FailuresAreFatal=1 -DUnitTest.Manager=TestCoverage.Manager
       -DUnitTest.UserParam.CoverageReportClass=TestCoverage.Report.Cobertura.ReportGenerator
       -DUnitTest.UserParam.CoverageReportFile=/source/coverage.xml

Si vous voulez adapter cela à votre propre paquet, il suffit de déposer votre propre nom de paquet et votre image de conteneur préférée (doit inclure zpm - voir https://hub.docker.com/r/intersystemsdc/iris-community). Vous pourriez également vouloir changer le paquet de tests unitaires pour qu'il corresponde à la convention de votre propre paquet (si vous devez charger et compiler les tests unitaires avant de les exécuter pour gérer toutes les dépendances de chargement/compilation ; j'ai eu quelques problèmes bizarres spécifiques aux tests unitaires pour ce paquet, donc cela pourrait même ne pas être pertinent dans d'autres cas).

Le nom de l'instance et le répertoire test_reports ne devraient pas être modifiés pour d'autres utilisations, et les test_flags fournissent un bon ensemble de valeurs par défaut - ils permettent de faire en sorte que les échecs des tests unitaires signalent l'échec de la compilation, et gèrent également l'exportation des résultats des tests au format jUnit et un rapport de couverture de code.

Étapes de compilation

Vérification des référentiels GitHub

Dans notre exemple de motivation, deux dépôts doivent être vérifiés - celui qui est testé, et aussi mon fork de Forgery (parce que les tests unitaires en ont besoin).

    # Vérifie ce référentiel sous $GITHUB_WORKSPACE, afin que votre tâche puisse y accéder.
    - uses: actions/checkout@v2

    # Il faut aussi vérifier le timleavitt/forgery jusqu'à la version officielle installable via ZPM
    - uses: actions/checkout@v2
      with:
        repository: timleavitt/forgery
        path: forgery

$GITHUB_WORKSPACE est une variable d'environnement très importante, représentant le répertoire racine où tout cela fonctionne. Du point de vue des permissions, vous pouvez faire à peu près tout ce que vous voulez dans ce répertoire ; ailleurs, vous pouvez rencontrer des problèmes.

Exécution du conteneur IRIS d'InterSystems

Après avoir configuré un répertoire où nous finirons par placer nos rapports de résultats de tests, nous allons exécuter le conteneur InterSystems IRIS Community Edition (+ZPM) pour notre compilation.

    - name: Run Container
      run: |
        # Créer le répertoire test_reports pour partager les résultats des tests avant l'exécution du conteneur.
        mkdir $test_reports
        chmod 777 $test_reports
        # Lancer l'instance InterSystems IRIS
        docker pull $container_image
        docker run -d -h $instance --name $instance -v $GITHUB_WORKSPACE:/source -v $GITHUB_WORKSPACE/$test_reports:/$test_reports --init $container_image
        echo halt > wait
        # Attendez que l'instance soit prête
        until docker exec --interactive $instance iris session $instance &lt; wait; do sleep 1; done

Il y a deux volumes partagés avec le conteneur - l'espace de travail GitHub (pour que le code puisse être chargé ; nous y rapporterons également des informations sur la couverture des tests), et un répertoire séparé où nous placerons les résultats des tests jUnit.

Après la fin de "docker run", cela ne signifie pas que l'instance est complètement démarrée et prête à être commandée. Pour attendre que l'instance soit prête, nous continuerons à essayer d'exécuter une commande "halt" via la session iris ; cela échouera et continuera à essayer une fois par seconde jusqu'à ce que cela réussisse (éventuellement), indiquant que l'instance est prête.

Installation des bibliothèques liées aux tests

Pour notre cas d'utilisation motivant, nous utiliserons deux autres bibliothèques pour les tests - TestCoverage et Forgery. TestCoverage peut être installé directement via le Community Package Manager ; Forgery (actuellement) doit être chargé via zpm "load" ; mais les deux approches sont valables.

    - name: Install TestCoverage
      run: |
        echo "zpm \"install testcoverage\":1:1" > install-testcoverage
        docker exec --interactive $instance iris session $instance -B &lt; install-testcoverage
        # Solution aux problèmes de permissions dans TestCoverage (création d'un répertoire pour l'exportation des sources)
        chmod 777 $GITHUB_WORKSPACE

    - name: Install Forgery
      run: |
        echo "zpm \"load /source/forgery\":1:1" > load-forgery
        docker exec --interactive $instance iris session $instance -B &lt; load-forgery

L'approche générale consiste à écrire les commandes dans un fichier, puis à les exécuter en session IRIS. Le ":1:1" supplémentaire dans les commandes ZPM indique que la commande doit quitter le processus avec un code d'erreur si une erreur se produit, et s'arrêter à la fin si aucune erreur ne se produit ; cela signifie que si une erreur se produit, elle sera signalée comme une étape de compilation ayant échoué, et nous n'avons pas besoin d'ajouter une commande "halt" à la fin de chaque fichier.

Compilation et test du paquet

Enfin, nous pouvons effectivement compiler et exécuter des tests pour notre paquet. C'est assez simple - remarquez l'utilisation des variables d'environnement $build_flags/$test_flags que nous avons définies plus tôt.

    # Exécute un ensemble de commandes en utilisant l'exécuteur runners
    - name: Build and Test
      run: |
        # Exécution de compilation
        echo "zpm \"load /source $build_flags\":1:1" > build
        # Le paquet de test est compilé en premier comme solution de contournement pour certains problèmes de dépendance.
        echo "do \$System.OBJ.CompilePackage(\"$test_package\",\"ckd\") " > test
        # Exécution des tests
        echo "zpm \"$package test -only $test_flags\":1:1" >> test
        docker exec --interactive $instance iris session $instance -B < build && docker exec --interactive $instance iris session $instance -B < test && bash <(curl -s https://codecov.io/bash)

Cela suit le même schéma que nous avons vu, écrire des commandes dans un fichier puis utiliser ce fichier comme entrée de la session iris.

La dernière partie de la dernière ligne télécharge les résultats de la couverture du code sur codecov.io. Super facile !

Téléchargement des résultats des tests unitaires

Supposons qu'un test unitaire échoue. Il serait vraiment ennuyeux de devoir revenir en arrière dans le journal de compilation pour trouver ce qui n'a pas fonctionné, bien que cela puisse toujours fournir un contexte utile. Pour nous faciliter la vie, nous pouvons télécharger nos résultats formatés par jUnit et même exécuter un programme tiers pour les transformer en un joli rapport HTML.

    # Générer et télécharger le rapport HTML xUnit
    - name: XUnit Viewer
      id: xunit-viewer
      uses: AutoModality/action-xunit-viewer@v1
      if: always()
      with:
        # Avec -DUnitTest.FailuresAreFatal=1, un test unitaire qui échoue fera échouer la compilation avant ce point.
        # Cette action pourrait autrement mal interpréter notre sortie de style xUnit et faire échouer la compilation même si
        # tous les tests sont passés.
        fail: false
    - name: Atacher le rapport
      uses: actions/upload-artifact@v1
      if: always()
      with:
        name: ${{ steps.xunit-viewer.outputs.report-name }}
        path: ${{ steps.xunit-viewer.outputs.report-dir }}

Ces informations sont principalement tirées du fichier readme à l'adresse https://github.com/AutoModality/action-xunit-viewer.

Le résultat final

Si vous voulez voir les résultats de ce flux de travail, regardez :

Les journaux pour le job CI sur intersystems/apps-rest (y compris les artefacts de compilation) : https://github.com/intersystems/apps-rest/actions?query=workflow%3ACI
Rapports de couverture de test : https://codecov.io/gh/intersystems/apps-rest

N'hésitez pas à me faire savoir si vous avez des questions !

0
0 96
Article Lorenzo Scalese · Fév 8, 2023 1m read

Les snippets sont l'une des fonctions les plus utiles de Studio.

Voici comment ajouter des snippets à VSCode.

Voici une instruction générale.

  1. Allez dans Fichier - Préférences - Snippets utilisateur et choisissez objectscript.

  2. Ajoutez votre snippet, voici un exemple :

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

Ici :

  • prefix (préfixe) - ce que vous devez saisir pour que le snippet apparaisse
  • body (corps) - corps du snippet

Mieux encore, les snippets peuvent inclure des espaces réservés, par exemple :

"Method": {
    "prefix": ["method"],
    "body": ["set sc = ##class(${1:class}).${2:method}()"]
}

Après avoir inséré ce snippet, vous êtes automatiquement déplacé au début du premier espace réservé, utilisez <TAB> pour ne pas itérer dans les espaces réservés.

Bonne chance pour le codage !

2
0 193
Article Lorenzo Scalese · Fév 1, 2023 3m read

Ajout de VSCode dans votre conteneur IRIS

L'une des façons les plus simples de mettre en place des environnements de développement reproductibles est de créer des conteneurs pour ces environnements. Je trouve que lors d'itérations rapides, il est très pratique d'héberger une instance de vscode dans mon conteneur de développement. Ainsi, j'ai créé un rapide script de conteneur pour ajouter un vscode basé sur un navigateur dans un conteneur IRIS. Cela devrait fonctionner pour la plupart des conteneurs 2021.1+. Mon référentiel de code est disponible ici

Conteneur InterSystems IRIS avec vscode et pré-connecté

Réf.Valeur
Utilisateur_SYSTEM
Mot de passeSYS

image

Aperçu

Ce projet crée un conteneur IRIS avec une version hébergée (basée sur le web) de vscode disponible dans le même conteneur IRIS. Ceci fournit :

  • Modification du code du même conteneur
  • Pré-connexion à l'instance IRIS du conteneur
  • Liens à partir du Portail de gestion
  • Démarrage automatique de l'IDE avec le conteneur

Démarrage rapide

  1. Téléchargez ou git clone https://github.com/nickmitchko/Hosting-vscode-in-a-container.git
  2. A la racine du projet, lancez docker build . -t vscode-irishealth-ml:latest --no-cache
  3. Lancez docker-compose up
    • Vous n'utilisez pas docker compose ? Voir ici
  4. Naviguez vers Management Portal
  5. Connectez-vous avec l'utilisateur et le mot de passe figurant en haut de ce guide
  6. Cliquez sur VSCODE Link dans la fenêtre Favoris
  7. Lorsque vous y êtes invité, utilisez le même mot de passe dans le vscode pour vous connecter à l'instance IRIS.
# Nouveau dossier pour le projet
mkdir vscode-iris
cd vscode-iris

# Clonez le référentiel ici
git clone https://github.com/nickmitchko/Hosting-vscode-in-a-container.git .

# Créez une image
docker build . -t vscode-irishealth-ml:latest --no-cache

# Exécution (A) ou (B) uniquement
#
# (A) Exécution du fichier de composition
docker-compose up
# OU (B) si vous préférez un démon
docker-compose up -d

Ajoutez la persistance

Si vous cherchez une instance iris persistante, il faut commenter les lignes 16-20 du fichier docker-compose.yml. Cela permet d'ajouter un montage de stockage persistant au conteneur.

    volumes:
    - "./durable/:/durable/"
    environnement:
    - ISC_DATA_DIRECTORY=/durable/iconfig

Modification de l'image de base

Cette image est construite à partir des images zpm de la communauté des développeurs d'InterSystems (disponible ici). Ces images incluent la commande zpm qui nous permet d'installer facilement à partir du référentiel de paquets, mais la licence communautaire n'est que de 90 jours.

La balise d'image utilisée pour les constructions est la suivante :

FROM intersystemsdc/irishealth-ml-community:latest

Si vous voulez remplacer l'image, remplacez la première ligne du fichier docker par l'étiquette de l'image souhaitée (soit une instance IRIS personnalisée, soit une instance prise en charge). Par exemple :

FROM containers.intersystems.com/intersystems/irishealth-community:2021.2.0.651.0

Pas de Docker-Compose

Si vous n'utilisez pas docker compose, vous pouvez toujours exécuter le conteneur comme suit :

# Après avoir créé le conteneur
# --après la commande est nécessaire
docker run --name vscode -d \
    --publish 1972:1972 \
    --publish 52773:52773 \
    --publish 51773:51773 \
    --publish 53773:53773 \
    --publish 8080:8080 \
    --publish 8888:8888 \
    vscode-irishealth-ml:latest \
    --after "/bin/bash /install/boot.sh"
0
0 939
Article Lorenzo Scalese · Déc 21, 2022 4m read

Cet article décrit et contient un exemple de la manière d'intégrer un fichier PDF externe dans un segment HL7, plus précisément dans ADT_A01:2.3.1 OBX().  Cela peut être utile lorsqu'on tente de mettre des images ou d'autres données externes dans un message HL7.  Dans cet exemple, le nom du fichier PDF à intégrer est fourni dans le message HL7 entrant dans le champ OBX(1):ObservationValue.


Les stipulations de base de cet exemple sont les suivantes :

  1. Accepter un fichier PDF
  2. Accepter un message HL7
  3. En utilisant le nom du fichier PDF, intégrer les données PDF dans le message HL7
  4. Transmettre le message au fichier

  

En utilisant EnsLib.File.InboundAdapter, l'exemple a deux Services Métiers configurés.  L'un est destiné à recevoir des fichiers PDF à l'aide de l'EnsLib.File.PassthroughService intégré.  L'autre accepte les messages HL7 en utilisant le service intégré EnsLib.HL7.Service.FileService.


PDF Processing

L'ordre de ces composants est essentiel car le fichier PDF doit être retiré en premier.  Il est également important que l'Archive Path et le Work Path du PDF Business Service soient définis sur le même chemin de répertoire.  Cela permet d'enregistrer localement une copie du PDF qui sera utilisée ultérieurement pour intégrer les données dans le message HL7. 

Pour que le Business Service enregistre le fichier PDF dans ce répertoire Archive/Work Path, il faut appeler SendRequestAsyn().  Cette méthode est appelée par défaut lors d'un envoi entre les composants d'Ensemble/HealthShare.

Pour ce faire, le PDF Business Service est configuré pour envoyer le message PDF à une Business Operation personnalisée qui accepte le message sans effectuer d'autres actions.

Remarque : Le composant cible du PDF Business Service n'est pas pertinent pour cet exemple, mais il pourrait être construit pour effectuer toute action supplémentaire avec le message PDF.


HL7 Processing

Le message HL7 du service commercial est envoyé à un processus commercial personnalisé.  Ce processus appelle un BPL qui identifie et ouvre le fichier PDF approprié, puis appelle un DTL qui se charge de l'intégration des données dans le message HL7.

=== BPL ===

Le BPL accomplit les tâches suivantes :

  1. Extraction du nom du fichier PDF à partir du message HL7.
  2. Demande au tableau Ens.StreamContainer, en recherchant les entrées qui correspondent au nom de fichier extrait.
  3. Appel d'un DTL pour effectuer l'integration
    1. Voir la section "DTL" de cet article
</ol>
  1. Le CPL envoie ensuite le message en aval à une transaction métier intégrée dans EnsLib.HL7.Operaiton.FileOperation.

 

=== DTL ===

  1. Utilisation de target.{OBX(*)} pour compter le nombre de segments OBX
  2. Incrémentation du nombre d'OBX par unité
  3. Ajout des données PDF à l'aide de StoreFieldStreamBase64() dans un nouveau segment OBX()
    1. Pour ce code, voir "DTL Code Action"
</ol>

 

=== DTL Code Action ===

//Récupérer l'objet du flux PDF précédemment enregistré

   Set pdfStreamContainer = ##Class(Ens.StreamContainer).%OpenId(context.StreamContainerID)

   Try {

     Set pdfStreamObj = pdfStreamContainer.StreamGet()

   }

   Catch {

      $$$TRACE("Error opening stream object ID = "_context.StreamContainerID)

      Quit

   }

 //Set PDF stream object into new OBX:5 segment

   Set status = target.StoreFieldStreamBase64(pdfStreamObj,"OBX("_count_"):ObservationValue(1)")

   Set ^testglobal("status",$H) = status


Sample Production

Voici la liste des fichiers inclus dans le modèle ci-joint.

  1. Le message HL7 initial contenant uniquement le nom du fichier PDF intégré dans OBX(1)
  2. Le fichier PDF modèle
  3. Une exportation de classe qui comprend les éléments suivants :
    1. Classe de production
    2. Transaction modèle PDF personnalisée
    3. Règle de routage
    4. CPL
    5. DTL
</ol>

 

Cet exemple a été construit sur Ensemble 2016.2.0 mais il a été testé sur Ensemble 2015.1.2 sans aucun problème de compatibilité.  Le fichier .zip se trouve ici :

https://community.intersystems.com/sites/default/files/post-associated-docs/sample_hl7_embedpdf_production_export.zip

0
0 402
Article Irène Mykhailova · Déc 19, 2022 1m read

EnsLib.HL7.Message.cls fournit de nombreuses méthodes API pour manipuler un message HL7.  RemoveSegmentAt(), par exemple, peut être utilisé pour supprimer un segment par chemin ou par index, mais pas plus d'un segment à la fois. Il peut arriver que vous ayez besoin de supprimer tous les segments d'un groupe ou même de nombreux groupes de segments du message HL7.  Bien sûr, vous pouvez itérer à travers chaque segment dans chaque groupe et les supprimer un par un, mais il y a un moyen beaucoup plus facile. 

Avec une seule commande, comme ci-dessous, vous pouvez supprimer tous les segments OBX dans un message ORU_R01 (msg) :

Set tSC = msg.SetValueAt(,"PIDgrpgrp(1).ORCgrp(1).OBXgrp()","remove")

Ou encore, avec la seule commande ci-dessous, vous pouvez facilement supprimer tous les segments PR1 et ROL d'un groupe spécifique dans un message ADT_A01 (msg) :

Set tSC = msg.SetValueAt(,"PR1grp(1)","remove")

J'espère que cet article vous sera utile. Faites-moi savoir si vous avez des questions ou des soucis. Merci et à bientôt !

0
0 102
Article Irène Mykhailova · Juin 7, 2022 4m read

Pour chaque propriété, requête ou index défini, plusieurs méthodes correspondantes seraient automatiquement générées lors de la compilation d'une classe. Ces méthodes peuvent être très utiles. Dans cet article, je décrirai certaines d'entre elles.

Properties

Disons que vous avez défini une propriété nommée "Property". Les méthodes suivantes seraient automatiquement disponibles (la propriété indiquée en gras est une partie variable, égale au nom de la propriété) :

ClassMethod PropertyGetStored(id)

Pour les propriétés de type de données, cette méthode renvoie leur valeur logique, pour les propriétés d'objet, elle renvoie l'id. C'est une référence globale wrappée à la globale de données de la classe et le moyen le plus rapide de récupérer la valeur de la propriété singulière. Cette méthode n'est disponible que pour les propriétés stockées.

Method PropertyGet()

C'est un getter de propriété. Peut être redéfini.

Method PropertySet(val) As %Status

C'est un définisseur de propriété. Peut être redéfini.


Propriétés de l'objet

S'il s'agit d'une propriété objet, certaines méthodes supplémentaires, liées à l'accès aux ID et OID, deviennent disponibles :

Method PropertySetObjectId(id)

Cette méthode définit la valeur de la propriété par ID, il n'est donc pas nécessaire d'ouvrir un objet pour le définir comme valeur de la propriété.

Method PropertyGetObjectId()

Cette méthode renvoie l'ID de la valeur de la propriété.

Method PropertySetObject(oid)

Cette méthode définit la valeur de la propriété par OID.

Method PropertyGetObject()

Cette méthode renvoie l'OID de la valeur de la propriété.

Propriétés du type de données

Pour une propriété de type de données, plusieurs autres méthodes de conversion entre différents formats sont disponibles :

ClassMethod PropertyDisplayToLogical(val)
ClassMethod PropertyLogicalToDisplay(val)
ClassMethod PropertyOdbcToLogical(val)
ClassMethod PropertyLogicalToOdbc(val)
ClassMethod PropertyXSDToLogical(val)
ClassMethod PropertyLogicalToXSD(val)
ClassMethod PropertyIsValid(val) As %Status

Vérification de la validité de la valeur de la propriété

ClassMethod PropertyNormalize(val)

Renvoi de la valeur logique normalisée

Remarques

  • Les relations constituent des propriétés et peuvent être obtenues ou définies à l'aide des méthodes suivantes
  • L'entrée val est toujours une valeur logique, sauf pour les méthodes de conversion de format.

  • Index

    Pour un index nommé "Index", les méthodes suivantes seraient automatiquement disponibles

    ClassMethod IndexExists(val) As %Boolean

    Renvoi de 1 ou 0 selon l'existence d'un objet avec ce val, où val est une valeur logique de la propriété indexée.


    Index uniques

    Pour les index uniques, des méthodes supplémentaires sont disponibles :

    ClassMethod IndexExists(val, Output id) As %Boolean

    Renvoi de 1 ou 0 en fonction de l'existence d'un objet avec ce val, où val est une valeur logique de la propriété indexée. Renvoi également de l'identifiant de l'objet (s'il a été trouvé) comme second argument.

    ClassMethod IndexDelete(val, concurrency = -1) As %Status

    Suppression de l'entrée dont la valeur d'index est égale à val.

    ClassMethod IndexOpen(val, concurrency, sc As %Status)  

    Renvoi de l'objet existant dont l'indice est égal à val.

    Remarques:

    a) Étant donné qu'un index peut être basé sur plusieurs propriétés, la signature de la méthode serait modifiée pour avoir plusieurs valeurs en entrée, par exemple, considérez cet index :

    Index MyIndex On (Prop1, Prop2);

    La méthode IndexExists aurait alors la signature suivante :

    ClassMethod IndexExists(val1, val2) As %Boolean

    Où val1 correspond à la valeur de Prop1 et val2 correspond à la valeur de Prop2. Les autres méthodes suivent la même logique.

    b) Caché génère un index IDKEY qui indexe le champ ID (RowID). Il peut être redéfini par l'utilisateur et peut également contenir plusieurs propriétés. Par exemple, pour vérifier si une classe a une propriété définie, exécutez :

    Write ##class(%Dictionary.PropertyDefinition).IDKEYExists(class, property)

    c) Toutes les méthodes d'indexation vérifient la présence d'une valeur logique

    d) Documentation

    Requêtes

    En ce qui concerne une requête (qui peut être une simple requête SQL ou une requête de classe personnalisée, voici mon post à ce sujet) nommée "Query", la méthode Func est générée :

    ClassMethod QueryFunc(Arg1, Arg2) As %SQL.StatementResult

    qui renvoie un %SQL.StatementResult utilisé pour itérer sur la requête. Par exemple, la classe Sample.Person de l'espace de noms Samples possède une requête ByName acceptant un paramètre. Elle peut être appelée depuis le contexte objet au moyen de ce code :

    Set ResultSet=##class(Sample.Person).ByNameFunc("A")
    While ResultSet.%Next() { Write ResultSet.Name,! }

    En outre, une classe de démonstration sur GitHub illustre ces méthodes.

    0
    0 79