Annonce Irène Mykhailova · Nov 1, 2025
Bonjour et bienvenue à la récapitulation de la Communauté des Développeurs d’octobre 2025.
Statistiques générales
✓ Nouvelles publications 24 publiées en octobre :
 15 nouveaux articles
 9 nouvelles annonces

✓ Nouveaux membres 1 ayant rejoint en octobre
✓ Publications 1,295 publiées depuis le début
✓ Membres 188 ayant rejoint depuis le début

0
0 8
InterSystems Developer Community is a community of 25058 amazing developers
Ici, les programmeurs d'InterSystems IRIS apprennent et partagent, se tiennent informés, évoluent ensemble et s'amusent !
Annonce Irène Mykhailova · Oct 1, 2025
Bonjour et bienvenue à la récapitulation de la Communauté des développeurs, septembre 2025.
Statistiques générales
✓ Nouvelles publications 26 publiées le septembre :
 16 nouveaux articles
 10 nouvelles annonces

✓ Nouveaux membres 1 ayant rejoint le septembre
✓ Publications 1,270 publiées depuis le début
✓ Membres 187 ayant rejoint depuis le début

0
0 21
Annonce Irène Mykhailova · Sept 1, 2025
Bonjour et bienvenue à la récapitulation de la Communauté des développeurs d'août 2025.
Statistiques générales
✓ Nouvelles publications 22 publiées l'Août :
 12 nouveaux articles
 10 nouvelles annonces

✓ Nouveaux membres 4 ayant rejoint l'Août
✓ Publications 1,244 publiées depuis le début
✓ Membres 185 ayant rejoint depuis le début

0
0 25
Annonce Irène Mykhailova · Août 1, 2025
Bonjour et bienvenue à la récapitulation de la communauté des développeurs Juillet 2025.
Statistiques générales
✓ Nouvelles publications 23 publiées le Juillet :
 12 nouveaux articles
 10 nouvelles annonces
 1 nouvelle question

✓ Nouveaux membres 3 ayant rejoint le Juillet
✓ Publications 1,222 publiées depuis le début
✓ Membres 181 ayant rejoint depuis le début

0
0 18
Article Irène Mykhailova · Juil 15, 2025 3m read

Notre objectif au sein de la Communauté des développeurs est de promouvoir un contenu technique original, fiable et de haute qualité, créé par et pour les développeurs. Si des outils d'IA comme ChatGPT peuvent être utiles à la rédaction, nous attendons de tout contenu publié qu'il reflète une expertise authentique et une compréhension personnelle. Si vous avez des questions ou souhaitez discuter de votre problème de codage avec une IA, pensez à utiliser le chat IA de la Communauté des développeurs.

Utilisation acceptable de l'IA

1
0 57
Annonce Irène Mykhailova · Juil 1, 2025
Bonjour et bienvenue à la récapitulation de la communauté des développeurs de juin 2025.
Statistiques générales
✓ Nouvelles publications 24 publiées le juin :
 10 nouveaux articles
 13 nouvelles annonces
 1 nouvelle question

✓ Nouveaux membres 8 ayant rejoint le Juin
✓ Publications 1,199 publiées depuis le début
✓ Membres 178 ayant rejoint depuis le début

0
0 21
Annonce Irène Mykhailova · Avr 1, 2025
Bonjour et bienvenue à la récapitulation de la Communauté des Développeurs mars 2025.
Statistiques générales
✓ Nouvelles publications 20 publiées le mars :
 9 nouveaux articles
 9 nouvelles annonces
 2 nouvelles questions

✓ Nouveaux membres 4 ayant rejoint le mars
✓ Publications 1,133 publiées depuis le début
✓ Membres 166 ayant rejoint depuis le début

0
0 25
Annonce Irène Mykhailova · Fév 1, 2025
Bonjour et bienvenue à la récapitulation de la communauté des développeurs janvier 2025.
Statistiques générales
✓ Nouvelles publications 22 publiées le janvier :
 13 nouveaux articles
 7 nouvelles annonces
 2 nouvelles questions

✓ Nouveaux membres 3 ayant rejoint le janvier
✓ Publications 1,095 publiées depuis le début
✓ Membres 158 ayant rejoint depuis le début

0
0 27
Annonce Irène Mykhailova · Jan 1, 2025
Bonjour et bienvenue à la récapitulation de la communauté des développeurs, décembre 2024.
Statistiques générales
✓ Nouvelles publications 16 publiées le Décembre :
 6 nouveaux articles
 10 nouvelles annonces

✓ Nouveaux membres 1 ayant rejoint le Décembre
✓ Publications 1,073 publiées depuis le début
✓ Membres 155 ayant rejoint depuis le début

0
0 31
Annonce Irène Mykhailova · Déc 1, 2024
Bonjour et bienvenue à la récapitulation de la communauté des développeurs du novembre 2024.
Statistiques générales
✓ Nouvelles publications 15 publiées le novembre :
 10 nouveaux articles
 5 nouvelles annonces

✓ Nouveaux membres 1 ayant rejoint le Novembre
✓ Publications 1,057 publiées depuis le début
✓ Membres 154 ayant rejoint depuis le début

0
0 43
Annonce Irène Mykhailova · Juil 1, 2024
Bonjour et bienvenue à la récapitulation de la communauté des développeurs Juin 2024.
Statistiques générales
✓ Nouvelles publications 42 publiées le Juin :
 27 nouveaux articles
 11 nouvelles annonces
 4 nouvelles questions

✓ Nouveaux membres 2 ayant rejoint le Juin
✓ Publications 961 publiées depuis le début
✓ Membres 138 ayant rejoint depuis le début

0
0 29
Annonce Irène Mykhailova · Juin 1, 2024
Bonjour et bienvenue à la récapitulation de la communauté des développeurs Mai 2024.
Statistiques générales
29 publications publiées le mai :
 10 nouveaux articles
 18 nouvelles annonces
 1 nouvelle question

1 nouvel membre
919 publications publiées depuis le début
135 membres ont rejoint depuis le début

0
0 61
Annonce Irène Mykhailova · Jan 1, 2024
Bonjour et bienvenue à la récapitulation de la communauté des développeurs Décembre 2023.
Statistiques générales
✓ Nouvelles publications 43 publiées le Décembre :
 16 nouveaux articles
 12 nouvelles annonces
 15 nouvelles questions

✓ Nouveaux membres 2 ayant rejoint le Décembre
✓ Publications 700 publiées depuis le début
✓ Membres 117 ayant rejoint depuis le début

0
0 27
Annonce Irène Mykhailova · Nov 7, 2025

Bonjour les développeurs,

Suite au succès retentissant du concours de l'année dernière, nous avons décidé de le renouveler. Bienvenue au

🏆 Concours « Réalisation des idées » 🏆

Soumettez une application mettant en œuvre une idée issue du portail InterSystems Ideas, ayant le statut Community Opportunity ou Future Consideration, créée avant la publication de cette annonce et nécessitant une programmation 😉

Durée : 17 novembre - 7 décembre 2025

Prix : $12,000$


0
0 7
Article Guillaume Rongier · Oct 30, 2025 7m read

Introduction

Dans mon dernier article, j'ai présenté FHIR Data Explorer, une application de validation de concept qui connecte InterSystems IRIS, Python, et Ollama afin de permettre la recherche sémantique et la visualisation des données de soins de santé au format FHIR. Ce projet participe actuellement au concours InterSystems External Language Contest.

Dans cette suite, nous verrons comment j'ai intégré Ollama pour générer les résumés des dossiers médicaux directement à partir des données FHIR structurées stockées dans IRIS, à l'aide de modèles linguistiques locaux légers (LLM) tels que Llama 3.2:1B ou Gemma 2:2B.

L'objectif était de créer un pipeline d'IA entièrement local capable d'extraire, de formater et de présenter les dossiers médicaux des patients tout en garantissant la confidentialité et le contrôle total des données.

Toutes les données des patients utilisées dans cette démonstration proviennent de paquets FHIR, qui ont été analysés et chargés dans IRIS via le module IRIStool. Cette approche facilite la requête, la conversion et la vectorisation des données de soins de santé à l'aide d'opérations pandas familières en Python. Si vous désirez en savoir plus sur la manière dont j'ai construit cette intégration, consultez mon article précédent Création d'un référentiel vectoriel FHIR avec InterSystems IRIS et Python via le module IRIStool.

Les deux outils, IRIStool et FHIR Data Explorer sont disponibles sur InterSystems Open Exchange — et font partie de mes contributions au concours. Si vous les trouvez utiles, n'hésitez pas à voter pour eux!

1. Configuration avec Docker Compose

Pour simplifier la configuration et la rendre reproductible, tout fonctionne localement au moyen de Docker Compose.
Une configuration minimale se présente comme suit:

services:
  iris:
    container_name: iris-patient-search
    build:
      context: .
      dockerfile: Dockerfile
    image: iris-patient-search:latest  
    init: true
    restart: unless-stopped
    volumes:
      - ./storage:/durable
    ports:
      - "9092:52773"# Management Portal / REST APIs
      - "9091:1972"# SuperServer port
    environment:
      - ISC_DATA_DIRECTORY=/durable/iris
    entrypoint: ["/opt/irisapp/entrypoint.sh"]

  ollama:
    image: ollama/ollama:latest
    container_name: ollama
    pull_policy: always
    tty: true
    restart: unless-stopped
    ports:
      - 11424:11434
    volumes:
      - ./ollama_entrypoint.sh:/entrypoint.sh
entrypoint: ["/entrypoint.sh"]

Toutes les configurations sont disponibles sur la page du projet GitHub.

2. Intégration d'Ollama dans le flux de travail

Ollama fournit une API REST locale simple pour exécuter efficacement des modèles sur le processeur, ce qui le rend idéal pour les applications de santé où la confidentialité et les performances sont importantes.

Pour connecter IRIS et Streamlit à Ollama, j'ai implémenté une classe Python légère pour transmettre les réponses de l'API Ollama:

import requests, json

classollama_request:def__init__(self, api_url: str):
        self.api_url = api_url

    defget_response(self, content, model):
        payload = {
            "model": model,
            "messages": [
                {"role": "user", "content": content}
            ]
        }
        response = requests.post(self.api_url, json=payload, stream=True)

        if response.status_code == 200:
            for line in response.iter_lines(decode_unicode=True):
                if line:
                    try:
                        json_data = json.loads(line)
                        if"message"in json_data and"content"in json_data["message"]:
                            yield json_data["message"]["content"]
                    except json.JSONDecodeError:
                        yieldf"Error decoding JSON line: {line}"else:
            yieldf"Error: {response.status_code} - {response.text}"

Cela permet de transmettre les résultats du modèle en temps réel, ce qui donne aux utilisateurs l'impression de “surveiller“ le travail de l'IA en direct dans l'interface utilisateur Streamlit.

3. Préparation des données du patient pour LLM

Avant d'envoyer quoi que ce soit à Ollama, les données doivent être compactes, structurées et pertinentes sur le plan clinique.
Pour cela, j'ai écrit une classe qui extrait et formate les données les plus pertinentes du patient (données démographiques, état de santé, observations, procédures, etc.) au format YAML, qui est à la fois compréhensible et compatible avec LLM.

Le processus simplifié est le suivant:

  1. Sélectionnez la chaîne du patient dans IRIS via pandas
  2. Extrayez les données démographiques et convertissez-les au format YAML
  3. Traitez chaque table médicale (conditions, observations, etc.)
  4. Supprimez les champs inutiles ou superflus
  5. Générez un document YAML concis utilisé comme contexte d'invite LLM.

Cette chaîne est ensuite transmise directement à l'invite LLM, formant le contexte structuré à partir duquel le modèle génère le compte rendu sommaire du patient.


4. Pourquoi limiter le nombre d'enregistrements?

Lors du développement de cette fonctionnalité, j'ai remarqué que la transmission de tous les dossiers médicaux provoquait souvent une confusion ou un biais dans les petits LLM envers les saisies plus anciennes, ce qui faisait perdre de vue les événements récents.

Pour résoudre ce problème, j'ai décidé:

  • De n'inclure qu'un nombre limité d'enregistrements par catégorie dans l' ordre chronologique inverse (plus récents en premier)
  • D'utiliser un format concis YAML au lieu du format brut JSON
  • De normaliser les types de données (horodatages, valeurs nulles, etc.) pour plus de cohérence

Cette conception aide les petits LLM à se concentrer sur les données les plus pertinentes sur le plan clinique data en évitant la “surcharge d'invites”.


💬 4. Génération du dossier médical du patient

Une fois les données au format YAML prêtes, l'application Streamlit les envoie à Ollama au moyen d'une simple invite telle que:

“Vous êtes assistant clinique. À partir des données suivantes du patient, rédigez un dossier médical concis en soulignant les pathologies pertinentes et les tendances récentes.”

Le résultat est renvoyé ligne par ligne à l'interface utilisateur, ce qui permet à l'utilisateur de surveiller la rédaction du dossier en temps réel.
Chaque modèle produit un résultat légèrement différent, même au moyen de la même invite, révélant des différences fascinantes dans le raisonnement et le style.


🧠 5. Comparaison des LLM locaux

Pour évaluer l'efficacité de cette approche, j'ai testé trois modèles ouverts et légers, disponibles via Ollama:

ModèleParamètresStyle de dossierRemarques
Llama 3.2:1B1BStructuré, factuelRésultat hautement littéral et schématique
Gemma 2:2B2BNarratif, à caractère humainLe plus cohérent et le plus sensible au contexte
Gemma 3:1B1BConcis, synthétiqueOmettant parfois certains détails, mais très compréhensible

Vous trouverez des exemples de résultats dans le  dossier GitHub suivant. Chaque dossier du patient met en évidence la manière dont la taille du modèle et le style d'entraînement influencent la structure, la cohérence et le niveau de détail du texte.

Voici une interprétation comparative de leur comportement:

  • Llama 3.2:1B reproduit généralement la structure des données mot pour mot, presque comme s'il effectuait une exportation de base de données. Ses résumés sont techniquement précis, mais ne sont pas fluides, ressemblant davantage à un rapport clinique structuré qu'à un texte naturel.
  • Gemma 3:1B offre une meilleure fluidité linguistique, mais compresse ou omet des détails mineurs. 
  • Gemma 2:2B offre le meilleur équilibre. Il organise les informations en sections significatives (conditions, facteurs de risque, recommandations de soins) tout en conservant un ton fluide.

En bref:

  • Llama 3.2:1B = précision factuelle
  • Gemma 3:1B = dossiers médicaux concis
  • Gemma 2:2B = narration cliniquement pertinente

Même sans ajustement, une curation des données et une conception des invites judicieuses permettent aux petits LLM locaux de produire des comptes rendus cliniques cohérents et pertinents dans leur contexte.


🔒 6. Pourquoi les modèles locaux sont-ils importants?

En utilisant Ollama localement, vous obtenez:

  • Un contrôle total des données — les données du patient ne disparaissent jamais de l'environnement
  • Des performances déterministes — une latence stable sur le processeur
  • Un déploiement léger — fonctionne même sans processeur graphique
  • Une conception modulaire — facile de passer d'un modèle à l'autre ou d'ajuster les invites

Cette configuration est donc idéale pour les hôpitaux, les centres de recherche ou les environnements universitaires qui souhaitent expérimenter en toute sécurité la documentation et la synthèse basées sur l'IA.


🧭 Conclusion

Cette intégration démontre que même les petits modèles locaux, lorsqu'ils sont correctement guidés par des données structurées et des invites claires, peuvent produire des dossiers médicaux utiles et proches de ceux rédigés par des humains.

Au moyen d'IRIS pour la gestion des données, Python pour convertir les données et Ollama pour la génération de texte, nous obtenons un pipeline IA entièrement local et axé sur la confidentialité pour la génération de renseignements cliniques.

1
0 18
Article Sylvain Guilbaud · Nov 6, 2025 5m read

Introduction

Dans mon article précédent, j'ai présenté le module IRIStool, qui intègre de manière transparente la bibliothèque pandas pour Python à la base de données IRIS. Je vais maintenant vous expliquer comment utiliser IRIStool pour exploiter InterSystems IRIS comme base pour une recherche sémantique intelligente dans les données de soins de santé au format FHIR.

Cet article décrit ce que j'ai fait pour créer une base de données pour mon autre projet, FHIR Data Explorer. Les deux projets sont candidats au concours InterSystems actuel, alors n'hésitez pas à voter pour eux si vous les trouvez utiles.

Ils sont disponibles sur Open Exchange:

Dans cet article, nous aborderons les sujets suivants:

  • Connexion à la base de données InterSystems IRIS via Python
  • Création d'un schéma de base de données compatible FHIR
  • Importation de données FHIR au moyen d'intégrations vectorielles pour la recherche sémantique

Conditions préalables

Installez IRIStool à partir de la page Github IRIStool et Data Manager.

1. Configuration de la connexion IRIS

Commencez par configurer votre connexion à l'aide des variables d'environnement dans un fichier .env:

IRIS_HOST=localhost
IRIS_PORT=9092
IRIS_NAMESPACE=USER
IRIS_USER=_SYSTEM
IRIS_PASSWORD=SYS

Connectez-vous à IRIS à l'aide du gestionnaire de contexte du module IRIStool:

from utils.iristool import IRIStool
import os
from dotenv import load_dotenv

load_dotenv()

with IRIStool( host=os.getenv('IRIS_HOST'), port=os.getenv('IRIS_PORT'), namespace=os.getenv('IRIS_NAMESPACE'), username=os.getenv('IRIS_USER'), password=os.getenv('IRIS_PASSWORD') ) as iris: # IRIStool manages the connection automatically pass

2. Création du schéma FHIR

Commencez par créer une table pour stocker les données FHIR, puis, tout en extrayant les données des paquets FHIR, créez des tables avec des capacités de recherche vectorielle pour chacune des ressources FHIR extraites (comme Patient, Osservability, etc.). 

Le module IRIStool simplifie la création de tables et d'index!

Table de référentiel FHIR

# Créer une table de référentiel principal pour les paquets FHIR brutsifnot iris.table_exists("FHIRrepository", "SQLUser"):
    iris.create_table(
        table_name="FHIRrepository",
        columns={
            "patient_id": "VARCHAR(200)",
            "fhir_bundle": "CLOB"
        }
    )
    iris.quick_create_index(
        table_name="FHIRrepository",
        column_name="patient_id"
    )

Table de patients avec support vectoriel

# Création d'une table de patients au moyen de la colonne vectorielle pour la recherche sémantiqueifnot iris.table_exists("Patient", "SQLUser"):
    iris.create_table(
        table_name="Patient",
        columns={
            "patient_row_id": "INT AUTO_INCREMENT PRIMARY KEY",
            "patient_id": "VARCHAR(200)",
            "description": "CLOB",
            "description_vector": "VECTOR(FLOAT, 384)",
            "full_name": "VARCHAR(200)",
            "gender": "VARCHAR(30)",
            "age": "INTEGER",
            "birthdate": "TIMESTAMP"
        }
    )
<span class="hljs-comment"># Création d'index standards</span>
iris.quick_create_index(table_name=<span class="hljs-string">"Patient"</span>, column_name=<span class="hljs-string">"patient_id"</span>)
iris.quick_create_index(table_name=<span class="hljs-string">"Patient"</span>, column_name=<span class="hljs-string">"age"</span>)

<span class="hljs-comment"># Création d'un index vectoriel HNSW pour la recherche par similarité</span>
iris.create_hnsw_index(
    index_name=<span class="hljs-string">"patient_vector_idx"</span>,
    table_name=<span class="hljs-string">"Patient"</span>,
    column_name=<span class="hljs-string">"description_vector"</span>,
    distance=<span class="hljs-string">"Cosine"</span>
)</code></pre>

3. Importation de données FHIR à l'aide de vecteurs

Générez facilement des intégrations vectorielles à partir des descriptions des patients FHIR et insérez-les dans IRIS:

from sentence_transformers import SentenceTransformer

# Initialisation du modèle de convertisseur model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')

# Exemple : Traitement des données du patient patient_description = "45-year-old male with hypertension and type 2 diabetes" patient_id = "patient-123"# Création d'un encodage vectoriel vector = model.encode(patient_description, normalize_embeddings=True).tolist()

# Insertion des données du patient à l'aide du vecteur iris.insert( table_name="Patient", patient_id=patient_id, description=patient_description, description_vector=str(vector), full_name="John Doe", gender="male", age=45, birthdate="1979-03-15" )

4. Recherche sémantique

Lorsque vos données sont téléchargées, vous pouvez effectuer des recherches par similarité:

# Requête de recherche
search_text = "patients with diabetes"
query_vector = model.encode(search_text, normalize_embeddings=True).tolist()

# définition de requête SQL query = f""" SELECT TOP 5 patient_id, full_name, description, VECTOR_COSINE(description_vector, TO_VECTOR(?)) as similarity FROM Patient ORDER BY similarity DESC """# définition des paramètres de requête parameters = [str(query_vector)]

# Recherche de patients similaires à l'aide de la recherche vectorielle results = iris.query(query, parameters)

# Impression des données du DataFrameifnot results.empty: print(f"{results['full_name']}: {results['similarity']:.3f}")

Conclusion

  • Le module IRIStool simplifie l'intégration d'IRIS avec des méthodes Python intuitives pour la création de tables et d'index
  • IRIS prend en charge le stockage hybride SQL + vecteur de manière native, ce qui permet de réaliser les deux requêtes traditionnelles et la recherche sémantique
  • Les intégrations vectorielles permettent une recherche intelligente dans les données de soins de santé FHIR à l'aide du langage naturel
  • Les index HNSW fournissent une recherche par similarité efficace à grande échelle

Cette approche prouve qu'InterSystems IRIS peut servir de base solide pour créer des applications de santé intelligentes avec des capacités de recherche sémantique sur les données FHIR.

0
0 8
Article Iryna Mykhailova · Nov 5, 2025 6m read

Salut!

C'est encore moi 😁. Dans l'article précédent Comment écrire un service API REST pour exporter le paquet FHIR généré au format JSON, nous avons généré une ressource DocumentReference, dont le contenu était encodé en Base64

Question!! Est-il possible d'écrire un service REST pour le décoder? Je voudrais vraiment savoir ce que contiennent les données du message🤔🤔🤔

Bien, allons-y!

1. Créez une nouvelle classe utilitaire datagen.utli.decodefhirjson.cls pour décoder les données contenues dans DocumentReference
 

Class datagen.utli.decodefhirjson Extends%RegisteredObject
{
}

2. Écrivez une fonction Python decodebase64docref pour 
a. parcourir le paquet FHIR
b. découvrir la ressource DocumentReference
  - récupérer le premier élément dans le contenu
   - récupérer la pièce jointe à partir de  contenu
     - récupérer les données à partir de la pièce jointe
c. décoder les données par Base64
 (pour cette partie, j'ai demandé l'aide de Chat-GPT😂🤫) 

Class datagen.utli.decodefhirjson Extends%RegisteredObject
{

ClassMethod decodebase64docref(fhirbundle = "") As%String [ Language = python ] { # w##class(datagen.utli.decodefhirjson).decodebase64docref() import base64 import json

def decode_discharge_summary(bundle_json):
    <span class="hljs-string">"""
    Extracts and decodes the Base64-encoded discharge summary note
    from a FHIR Bundle containing a DocumentReference.
    """</span>
    <span class="hljs-keyword">for</span> entry in bundle_json.get(<span class="hljs-string">"entry"</span>, []):
        resource = entry.get(<span class="hljs-string">"resource"</span>, {})
        <span class="hljs-keyword">if</span> resource.get(<span class="hljs-string">"resourceType"</span>) == <span class="hljs-string">"DocumentReference"</span>:
            # Traverse to the attachment
            content_list = resource.get(<span class="hljs-string">"content"</span>, [])
            <span class="hljs-keyword">if</span> not content_list:
                <span class="hljs-keyword">continue</span>
            attachment = content_list[<span class="hljs-number">0</span>].get(<span class="hljs-string">"attachment"</span>, {})
            base64_data = attachment.get(<span class="hljs-string">"data"</span>)
            <span class="hljs-keyword">if</span> base64_data:
                decoded_text = base64.b64decode(base64_data).decode(<span class="hljs-string">"utf-8"</span>)
                <span class="hljs-keyword">return</span> decoded_text
    <span class="hljs-keyword">return</span> None


# Example usage
# Load your FHIR Bundle JSON from a file or object
#with <span class="hljs-keyword">open</span>(<span class="hljs-string">"fhir_bundle.json"</span>, <span class="hljs-string">"r"</span>) <span class="hljs-keyword">as</span> f:
#    fhir_bundle = json.loads(f)
<span class="hljs-keyword">if</span> fhirbundle==<span class="hljs-string">""</span>:
    rtstr=f<span class="hljs-string">"&#x26a0;&#xfe0f; No input found - JSON string is required."</span>
    jsstr={<span class="hljs-string">"operation_outcome"</span> : rtstr}
    <span class="hljs-keyword">return</span> json.dumps(jsstr, indent=<span class="hljs-number">2</span>)

fhir_bundle = json.loads(fhirbundle)
decoded_note = decode_discharge_summary(fhir_bundle)

<span class="hljs-keyword">if</span> decoded_note:
    #<span class="hljs-keyword">print</span>(<span class="hljs-string">"&#x1f4dd; Decoded Discharge Summary:\n"</span>)
    #<span class="hljs-keyword">print</span>(decoded_note)
    rtstr=f<span class="hljs-string">"&#x1f4dd; Decoded Discharge Summary:\n {decoded_note}"</span>
<span class="hljs-keyword">else</span>:
    #<span class="hljs-keyword">print</span>(<span class="hljs-string">"&#x26a0;&#xfe0f; No DocumentReference with Base64 note found."</span>)
    rtstr=f<span class="hljs-string">"&#x26a0;&#xfe0f; No DocumentReference with Base64 note found."</span>
jsstr={<span class="hljs-string">"data"</span> : rtstr}
<span class="hljs-keyword">return</span> json.dumps(jsstr, indent=<span class="hljs-number">2</span>)

}

}

Pour tester cette fonction, j' essaie une astuce qui consiste à utiliser la fonction genfhirbundle pour générer un paquet FHIR dans une chaîne JSON,  comme expliqué dans l'article précédent Comment écrire un service API REST pour exporter le paquet FHIR généré au format JSON 

Générons un paquet FHIR et stockons-le dans une variable jsonstr

set jsonstr=##class(datagen.utli.genfhirjson).genfhirbundle(1)

Testons la fonction de décodage decodebase64docref  avec jsonstr

w##class(datagen.utli.decodefhirjson).decodebase64docref(jsonstr)

Bien 😉 Ça semble correct. Je peux désormais lire le message décodé.


Maintenant,  revenez à l'article précédent Comment écrire un service API REST pour exporter les données patient générées au format .csv

Nous aimerions ajouter une nouvelle fonction et mettre à jour le chemin d'accès pour la classe datagen.restservice .

1. Ajoutez une nouvelle fonction DecodeDocRef, qui est censée traiter le paquet FHIR  joint dans le corps au format JSON.

Par exemple, dans ce cas, nous prévoyons un POST.

Le contenu du corps est packagé par défaut sous la forme %CSP.BinaryStream et stocké dans la variable %request.Content, nous pouvons donc utiliser la fonction .Read()  à partir de la classe %CSP.BinaryStream pour lire le BinaryStream

ClassMethod DecodeDocRef() As%Status
{
    // récupération du corps - chaîne json#dim bistream As%CSP.BinaryStream =""set bistream=%request.Contentset jsstr=bistream.Read()
<span class="hljs-comment">//décodage des données de référence du document</span>
<span class="hljs-keyword">w</span> <span class="hljs-keyword">##class</span>(datagen.utli.decodefhirjson).decodebase64docref(jsstr)
<span class="hljs-keyword">return</span> <span class="hljs-built_in">$$$OK</span>

}

2. Ensuite, nous ajoutons un chemin d'accès pour le service REST et compilons😀

<Route Url="/decode/docref" Method="POST" Call="DecodeDocRef" />

La classe  datagen.restservice mise à jour se présentera comme suit.


Génial!! C'est tout ce qu'il y a à faire!😁

Nous allons vérifier cela dans Postman!!

COLLEZ le chemin d'accès  suivant

localhost/irishealth/csp/mpapp/decode/docref

avec le corps suivant (j'ai utilisé un paquet FHIR simplifié, vous pouvez tester le paquet complet😀)

{
    "resourceType": "Bundle",
    "type": "transaction",
    "id": "98bfce83-7eb1-4afe-bf2b-42916512244e",
    "meta": {
        "lastUpdated": "2025-10-13T05:49:07Z"
    },
    "entry": [
        {
            "fullUrl": "urn:uuid:5be1037d-a481-45ca-aea9-2034e27ebdcd",
            "resource": {
                "resourceType": "DocumentReference",
                "id": "5be1037d-a481-45ca-aea9-2034e27ebdcd",
                "status": "current",
                "type": {
                    "coding": [
                        {
                            "system": "http://loinc.org",
                            "code": "18842-5",
                            "display": "Discharge summary"
                        }
                    ]
                },
                "subject": {
                    "reference": "9e3a2636-4e87-4dee-b202-709d6f94ed18"
                },
                "author": [
                    {
                        "reference": "2aa54642-6743-4153-a171-7b8a8004ce5b"
                    }
                ],
                "context": {
                    "encounter": [
                        {
                            "reference": "98cd848b-251f-4d0b-bf36-e35c9fe68956"
                        }
                    ]
                },
                "content": [
                    {
                        "attachment": {
                            "contentType": "text/plain",
                            "language": "en",
                            "data": "RGlzY2hhcmdlIHN1bW1hcnkgZm9yIHBhdGllbnQgOWUzYTI2MzYtNGU4Ny00ZGVlLWIyMDItNzA5ZDZmOTRlZDE4LiBEaWFnbm9zaXM6IFN0YWJsZS4gRm9sbG93LXVwIGluIDIgd2Vla3Mu",
                            "title": "Discharge Summary Note"
                        }
                    }
                ]
            },
            "request": {
                "method": "POST",
                "url": "DocumentReference"
            }
        }
    ]
}

Ça marche parfaitement!!!😆😉

Merci infiniment à tous d'avoir pris le temps de lire cet article. 😘

0
0 10
InterSystems officiel Adeline Icard · Nov 4, 2025

InterSystems IRIS Adaptive Analytics version 2025.4.1 est désormais disponible sur la page de distribution des logiciels InterSystems. Cette version inclut AtScale 2025.4.1 et est compatible avec le fichier UDAF (User-Defined Aggregate Function) Adaptive Analytics existant (version 2024.1). Parmi les nouvelles fonctionnalités d'AtScale 2025 :

0
0 7
Article Iryna Mykhailova · Nov 3, 2025 13m read

Bonjour à tous,

Continuons à travailler sur la génération de données de test et l'exportation des résultats via une API REST. 😁

Ici, je souhaite réutiliser la classe `datagen.restservice` créée dans l'article précédent : « Écriture d'un service API REST pour exporter les données patient générées au format .csv ».

Cette fois-ci, nous prévoyons de générer un bundle FHIR incluant plusieurs ressources pour tester le référentiel FHIR.

Voici une référence si vous souhaitez en savoir plus sur FHIR : « The Concept of FHIR: A Healthcare Data Standard Designed for the Future ».

C'est parti ! 😆

0
0 10
Annonce Irène Mykhailova · Sept 8, 2025

Salut la Communauté,

Nous sommes ravis de vous annoncer le deuxième concours de rédaction d'articles techniques en français !

✍️ Concours d'articles techniques ✍️

C'est l'occasion idéale pour tous les passionnés de technologie InterSystems de partager leurs connaissances et d'exposer leurs talents de rédacteur. Peu importe votre niveau d'expérience, tout le monde est invité à participer. Les articles peuvent traiter de nombreux sujets : de la mise en œuvre technique jusqu’à vos impressions et retours d'expérience sur l’utilisation des produits ou services InterSystems. Alors laissez libre cours à votre créativité et à votre expertise !

📅 Période du concours : du 15 septembre au 2 novembre

👉 Bonne nouvelle ! Le concours est prolongé jusqu’au 30 novembre !

🎁 Cadeaux pour tous : un cadeau spécial pour chaque participant !

🏅 Prix pour les auteurs de meilleurs articles 

1
0 62
Article Sylvain Guilbaud · Oct 29, 2025 14m read

Aperçu

Cette interface Web est conçue pour faciliter la gestion des tables de recherche de données (Data Lookup Tables) via une page Web conviviale. Elle est particulièrement utile lorsque les valeurs de votre table de recherche sont volumineuses, dynamiques et changeantes. Les utilisateurs finaux peuvent gérer efficacement les données de la table de recherche en fonction de leurs besoins grâce à un accès contrôlé à cette interface Web (autorisations de lecture, d'écriture et de suppression limitées à cette page).

Les données gérées via cette interface peuvent être utilisées de manière transparente pour les règles HealthConnect ou les transformations de données, ce qui élimine le besoin d'une surveillance et d'une gestion manuelles constantes des tables de recherche et permet ainsi un gain de temps considérable.

Remarque:
Si la table de recherche de données classique ne répond pas à vos besoins en matière de mappage, vous pouvez créer une table personnalisée et adapter cette interface Web ainsi que sa classe de support avec un minimum de modifications. Un exemple de code de classe est disponible sur demande.

Conditions préalables

Avant d'utiliser l'interface Web, veillez à avoir créé une table de recherche de données vide dans HealthConnect:

  1. Accédez à Interoperability → Configure → Data Lookup Tables.
  2. Cliquez sur New pour ouvrir la fenêtre contextuelle "Create New Lookup Table" (Créer une nouvelle table de recherche).
  3. Saisissez le nom souhaité pour votre table de recherche et cliquez sur OK.
  4. Cliquez sur Save pour finaliser la création de la table.
  5. Vérifiez la création de la table de recherche en cliquant sur Open dans la même fenêtre. Votre nouvelle table devrait apparaître dans la liste.

Dans cette documentation, la table de recherche de données utilisée à titre d'exemple est intitulée:
"Exemple de table de recherche de données"

Mode d'emploi

Le processus de configuration est simple:

  1. Obtention du code:Téléchargez le code fourni à partir du référentiel GitHub ou copiez-le à partir du lien communautaire.
  2. Création d'une page CSP:Créez une nouvelle page CSP dans votre environnement HealthConnect et insérez tout le code du fichier DataLookupWebApp.
  3. Création d'une classe:Copiez et insérez le code de classe fourni dans la documentation ou dans le référentiel.

Configuration

Avant la compilation et l'exécution:

  1. Indiquez le nom de votre table de recherche de données sur la page Web en remplaçant l'espace réservé indiqués par le cadre rouge dans la capture d'écran ci-dessous par le nom réel de votre table de recherche.

  1. Vérifiez si le nom de la classe dans le code correspond au nom de votre classe personnalisée.

  1. Attribuez un nom à votre table de recherche dans la classe, comme indiqué par myDtName = 'votre table de recherche'. Consultez la capture d'écran ci-dessus.
  1. Compilez la classe et la page CSP pour finaliser la configuration.

Mise à l'essai de l'application

Ajout d'enregistrements

  • Saisissez la clé et la paire clé-valeur correspondante dans les champs du formulaire.
  • Cliquez sur Add Record pour insérer les données dans la table de recherche.

Capture d'écran 1: Ajout d'enregistrements (122 – Radiographie de la colonne vertébrale)

Mise à jour de l'enregistrement

  • Sélectionnez un enregistrement existant et modifiez la paire clé-valeur si nécessaire.

  • Cliquez sur Update pour enregistrer les modifications. Exemple : Modification de  122 "Radiographie de la colonne vertébrale" en "Radiographie abdominale".

Suppression de l'enregistrement

  • Sélectionnez un enregistrement à supprimer (144 – IRM)

  • Cliquez sur le bouton Delete pour supprimer l'enregistrement de la table de recherche. Exemple : Suppression de "144 - IRM".

Vérification de la table de recherche de données dans HealthConnect

Accédez à Interoperability → Configure → Data Lookup Tables à Open

Recherche de l'enregistrement

Code / Ressources

  • La version complète du code source est également disponible pour téléchargement et consultation sur GitHub.

https://github.com/pandeySR/Reusable-Web-Interface-for-Data-Lookup-Table

<!DOCTYPE html>
<!--
This web application is designed and developed to insert, update, and delete records (key–value pairs) in the Data Lookup Table through a web interface.
Author: Sanjib Pandey
Date: 16/10/2025
-->
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="author" content="Sanjib Pandey" />
  <title>YOUR TITLE : Data Lookup Table - Data Record </title>
  <!-- Bootstrap CSS CDN, jQuery, Datatable -->
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> 
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"> 
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
  <link rel="stylesheet" href="https://cdn.datatables.net/1.13.5/css/dataTables.bootstrap5.min.css" />
  <script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
  <script src="https://cdn.datatables.net/1.13.5/js/jquery.dataTables.min.js"></script>
  <script src="https://cdn.datatables.net/1.13.5/js/dataTables.bootstrap5.min.js"></script>
  <style>
    h2 {
      text-align: center;
      color: #fff;
      background-color: #00b300;
      padding: 15px 10px;
      border-radius: 5px;
      margin-bottom: 5px;
      margin-top: 0px;
    }
    html, body {
      height: 100%;
      margin: 0;
    }
    body {
      display: flex;
      min-height: 100vh;
      background-color: #f8f9fa;
      padding-top: 10px;
      flex-direction: column;
      align-items: center;
    }
    .container {
      max-width: 1100px;
      background: white;
      padding: 20px;
      border-radius: 8px;
      box-shadow: 0015px rgba(0,0,0,0.2);
      margin-top: 5px;
      width: 100%;
    }
 
    .table-scroll-vertical
    {
 	 max-height: 450px;
 	 overflow-y: auto;
  	 border: 1px solid #dee2e6;
  	 border-radius: 5px;
  	 margin-top: 0; 
	}
	
	.small-italic 
	{
  	 font-size: 0.85em; 
  	 font-style: italic; 
  	 color: #C0C0C0; 
	}
	
	.dataTables_filter label 
	{
  	 font-weight: bold;
  	 color: #333;
  	 color: red;
  	 float: left;
	}

	.dataTables_filter input
	{
  	 border: 2px solid #993399;
  	 border-radius: 5px;
  	 padding: 6px 10px;
  	 outline: none;
   	 width: 250px;
	}
	
	.page-footer
	{
  	 text-align: center;
  	 padding: 10px 5px;
  	 font-size: 0.95rem;
 	 color: #809fff;
 	 background-color: #f8f9fa;
  	 border-top: 1px solid #993399;
  	 margin-top: 5px;
	}
  </style>
  <style>
  @keyframes blink
  {
    50%
    {
      opacity: 0;
    }
  }
</style>
</head>
<body>
<div class="container">
<div style="overflow: hidden;">
  <div style="float: left; font-size: smaller; font-weight: bold;color:#b3ffff;animation: blink 1s infinite;">
    Environment : DEV
  </div>
  <h2 style="text-align: center; margin: 0;">
    Your Organization Name or Company Name<br>
    <span style="font-size: smaller; color: yellow;text-align: center;">Data Lookup Table</span>
  </h2>
</div> 
 <form id="recordForm"class="form-horizontal" action="" method="POST">
 	<div class="form-group">
        <!--for any other information if you want> -->
       </div>
      <div class="row mb-3">
        <label for="key"class="col-sm-3 col-form-label fw-bold">Key :</label>
        <div class="col-sm-9">
          <input type="text"class="form-control" id="key" name="key" placeholder="Enter the Key data !" required />
        </div>
      </div>
      <div class="row mb-3">
        <label for="kvalue"class="col-sm-3 col-form-label fw-bold">Key Pair Value</label>
        <div class="col-sm-9">
          <input type="text"class="form-control" id="kvalue" name="kvalue" placeholder="Enter the key paris value !" required />
        </div>
      </div>
	<div class="d-flex justify-content-between align-items-center mt-4 flex-wrap">
		<p class="mb-2 mb-md-0 text-primary">
    		<i>Click the edit icon to modify a record, then press "Update" to save. To delete, press the "Delete" button.</i> 
  		</p> 
  		
	<div class="d-flex flex-wrap justify-content-end">
		<button class="btn btn-success me-2" id="Add" type="submit" name="Add">
			<i class="fa fa-plus me-1" style="font-size:18px; color:white;"></i> Add Record
		</button>
	
		<button class="btn btn-primary me-2" type="submit" name="Update">
			<i class="fa fa-edit me-1" style="font-size:18px;"></i> Update
    	</button>

		<button class="btn btn-danger me-2" id="Delete" type="submit" name="Delete">
			<i class="fa fa-remove me-1" style="font-size:18px;"></i> Delete
		</button>
		
		<button class="btn btn-info" id="Cancel" type="submit" name="Cancel">
  			<i class="glyphicon glyphicon-refresh" style="font-size:18px;"></i> Cancel
		</button>
  	   </div> 
    </div>
    <div></div>
	</form>
<script language=SQL name="query">
  SELECT KeyName, DataValue FROM Ens_Util.LookupTable WHERE TableName='Data Lookup Table Example'
</script>

<script language=cache runat=server>
 S myKeyName=$Get(%request.Data("key",1))
 S myKeyValue=$Get(%request.Data("kvalue",1))
 S myDtName="Data Lookup Table Example"
 I ($Data(%request.Data("Add",1)))
 {
	I ((myKeyName '="") && (myKeyValue '="") && (myDtName '=""))
	{	
		I ##Class(SANJIB.DataLookup.Methods).ChkLookupKeyValue(myDtName,myKeyName)="F"
		{
			D##Class(SANJIB.DataLookup.Methods).InsertLookUpValues(myDtName,myKeyName,myKeyValue)
		}
		else
		{	W"The key has already been inserted.."&html<<divstyle=color:red>#(myKeyName)#</div>> }
		
	}
 }
 I ($Data(%request.Data("Update",1)))
 {
	I ((myKeyName '="") && (myKeyValue '="") && (myDtName '=""))
	{
		D##Class(SANJIB.DataLookup.Methods).UpdateLookUpValues(myDtName,myKeyName,myKeyValue)
	}
 }
 I ($Data(%request.Data("Delete",1)))
 {
	 I ((myKeyName '="") && (myKeyValue '="") && (myDtName '=""))
	 {
		D##Class(SANJIB.DataLookup.Methods).DeleteLookUpValues(myDtName,myKeyName)		
	 }
 }	
</script>
 <div class="table-responsive table-scroll-vertical"> 
    <table class="table table-bordered border-primary table table-hover mt-2" id="dataTable" style="min-width: 1000px;">
      <thead class="table-warning">
        <tr>
          <th>Key Data</th>
          <th>Key Pair Value</th>
          <th style="text-align:center;">Actions</th> 
        </tr>
      </thead>
      <tbody id="tableRecordset">
 		</tr>
 		<csp:while counter=myQueryRow condition=query.Next()>
 			<tr class='#($S(myQueryRow#2:"LightRow",1:"LightRow"))#'>
  				<csp:while counter=myQueryColumn condition="(myQueryColumn<query.GetColumnCount())">
      				<td style="background-color:#e6ffff">#(query.GetData(myQueryColumn))#</td>
  						</csp:while>
 							<td style="text-align:center;">
          						<button class="btn btn-sm btn-danger me-2 edit-delete-btn" data-id="#(query.GetData(0))#">
    								<i class="fa fa-edit"></i>
    								<i class="fa fa-trash"></i>
  								</button>
        					</td> 
 						</tr>
 				</csp:while>
      		</tbody>
       </table>
    </div>
  </div>
  
<script language=javascript>
  document.addEventListener('DOMContentLoaded', () => 
  {
    const editButtons = document.querySelectorAll('.edit-delete-btn');
    editButtons.forEach(button =>
    {
      button.addEventListener('click', () => 
      {
        const row = button.closest('tr');
        document.getElementById("Add").disabled=true
        const kID=row.cells[0].textContent.trim();
        const pairVal = row.cells[1].textContent.trim();
        document.getElementById('key').value = kID;
        document.getElementById('kvalue').value = pairVal;
        document.getElementById('key').focus();
      });
    });
  });
</script>
 <script language=javascript>
    document.getElementById('Cancel').addEventListener('click', () => 
    {
    	event.preventDefault();
    	document.getElementById('recordForm').reset();
    });
</script>

<script language=javascript>
  $(document).ready(function()
  {
    $('#dataTable').DataTable(
    {
      "order": [[0, "asc"]],
      "paging": true,
      "lengthChange": false,
      "searching": true,
      "info": true,
      "autoWidth": false
    });
  		$('#dataTable_filter input').css('font-size', '12px').attr('placeholder', 'Type to search..?');
  });
</script>

</body> 

<script language="javascript">
  document.addEventListener('DOMContentLoaded', () =>
  {
  	const updateBtn = document.querySelector('button[name="Update"]');
  	const deleteBtn = document.querySelector('button[name="Delete"]');
  	const addBtn = document.getElementById('Add');
  	const cancelBtn = document.getElementById('Cancel');
  	updateBtn.style.display = 'none';
  	deleteBtn.style.display = 'none';
  	addBtn.style.visibility = 'visible';  
  	addBtn.disabled = false;
  document.querySelectorAll('.edit-delete-btn').forEach(editBtn =>
  {
    editBtn.addEventListener('click', () =>
    {
      updateBtn.style.display = 'inline-block';
      deleteBtn.style.display = 'inline-block';
      addBtn.style.display = 'none';
      addBtn.style.visibility = 'visible'; 
      addBtn.disabled = true;
    });
  });
  updateBtn.addEventListener('click', (e) =>
  {
    updateBtn.style.display = 'none';
    deleteBtn.style.display = 'none';
    addBtn.style.display = 'inline-block';
    addBtn.style.visibility = 'visible';
    addBtn.disabled = false;
  });
  deleteBtn.addEventListener('click', (e) =>
  {
    updateBtn.style.display = 'none';
    deleteBtn.style.display = 'none';
    addBtn.style.display = 'inline-block';
    addBtn.style.visibility = 'visible';
    addBtn.disabled = false;
  });
  cancelBtn.addEventListener('click', (e) =>
  {
    e.preventDefault();  
    document.getElementById('recordForm').reset();
    updateBtn.style.display = 'none';
    deleteBtn.style.display = 'none';
    addBtn.style.display = 'inline-block';
    addBtn.style.visibility = 'visible';  
    addBtn.disabled = false;              
  });
});
</script>

<footer class="page-footer">
  <a href="https://sanjibpandey.wixsite.com/pandey/" target="_blank" rel="noopener noreferrer" style="color: #6c757d; text-decoration: none;"> https://sanjibpandey.wixsite.com/pandey/</a>
</footer>
</html>

 

/// Cette classe est conçue et créée pour insérer, mettre à jour et supprimer des enregistrements (clé & valeur)/// dans la table de recherche de données à partir d'une application Web. /// Ensuite, cela peut être utilisé dans des règles ou des transformations de données à l'aide de la fonction de recherche./// Auteur : Sanjib Pandey/// Date : 16/10/2025Class SANJIB.DataLookup.Methods Extends%Persistent
{

/// Avec cette méthode, on ajoute une valeur à la table de recherche. Les paramètres obligatoires sont:/// dtTableName : Le nom de votre table de recherche de données/// keyData     : La clé/// keyValue    : La valeur ( la valeur de paire de clés)	ClassMethod InsertLookUpValues(dtTable As%String = "", keyData As%String = "", keyValue As%String = "") As%Status
{
	S tSC = $$$OKTry
	{
		I (dtTable '="") && (keyData '="") && (keyValue '="")
		{
			S mySQLStatement = "INSERT into Ens_Util.LookupTable (KeyName,DataValue,TableName) values ('"_keyData_"','"_keyValue_"','"_dtTable_"')"S myRecordSet = ##class(%SQL.Statement).%ExecDirect(,mySQLStatement)
			S tSC={"InserRecorded":(myRecordSet)}
		}
	}
	Catch (Exception)
	{
		Throw Exception	
	}
	Q tSC
}

/// Cette méthode vérifie si la valeur existe déjà dans la table de recherche de données.ClassMethod ChkLookupKeyValue(dtTable As%String, keyData As%String) As%Status
{
	S tSC = $$$OKTry
	{
		I (dtTable '="") && (keyData '="")
		{
			S mySQLStatement="SELECT KeyName FROM Ens_Util.LookupTable WHERE TableName='"_dtTable_"' and KeyName='"_keyData_"'"S myRecordSet = ##class(%SQL.Statement).%ExecDirect(,mySQLStatement)
			D myRecordSet.%Next()
			I (myRecordSet.%SQLCODE=0)
			{ 
				S tSC= "T"
			}
			else
			{
				S tSC="F"
			}
		}
		else
		{
			S tSC= "Invalid Parameters - missing table name or key !"
		}
	}
	Catch (Exception)
	{
		Throw Exception	
	}
	Q tSC
}

/// Cette méthode met à jour uniquement la valeur clé dans la table de recherche de données.ClassMethod UpdateLookUpValues(dtTable As%String = "", keyData As%String = "", keyValue As%String = "") As%Status
{
	S tSC = $$$OKTry
	{
		I (dtTable '="") && (keyData '="") && (keyValue '="")
		{
			S mySQLStatement = "UPDATE Ens_Util.LookupTable SET DataValue='"_keyValue_"' WHERE TableName='"_dtTable_"' AND KeyName='"_keyData_"'"S myRecordSet = ##class(%SQL.Statement).%ExecDirect(,mySQLStatement)
			S tSC={"Updated Record":(myRecordSet)}
		}
	}
	Catch (Exception)
	{
		Throw Exception	
	}
	Q tSC
}

/// Cette méthode est utilisée pour supprimer un enregistrement de la table de recherche de données.ClassMethod DeleteLookUpValues(dtTable As%String, keyData As%String) As%Status
{
	S tSC = $$$OKTry
	{
		I (dtTable '="") && (keyData '="") 
		{
			S mySQLStatement = "DELETE FROM Ens_Util.LookupTable WHERE  TableName='"_dtTable_"' And KeyName='"_keyData_"'"S myRecordSet = ##class(%SQL.Statement).%ExecDirect(,mySQLStatement)
			S tSC={"Deleted Record":(myRecordSet)}
		}
	}
	Catch (Exception)
	{
		Throw Exception	
	}
	Q tSC
}

Storage Default
{
<Data name="MethodsDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
</Data>
<DataLocation>^SANJIB.DataLookup.MethodsD</DataLocation>
<DefaultData>MethodsDefaultData</DefaultData>
<IdLocation>^SANJIB.DataLookup.MethodsD</IdLocation>
<IndexLocation>^SANJIB.DataLookup.MethodsI</IndexLocation>
<StreamLocation>^SANJIB.DataLookup.MethodsS</StreamLocation>
<Type>%Storage.Persistent</Type>
}

}

Conclusion

Cette interface Web réutilisable et cette classe offrent un moyen simple mais efficace pour la gestion des tables de recherche de données dans HealthConnect, en améliorant l'efficacité et la flexibilité. Grâce à l'accès contrôlé dont bénéficient les utilisateurs finaux, cette interface réduit les frais administratifs traditionnellement associés à la maintenance des tables de recherche.

0
0 17
Article Lorenzo Scalese · Oct 28, 2025 10m read

Le déploiement de nouvelles instances IRIS peut être une tâche fastidieuse, en particulier lors de la mise en place de plusieurs environnements avec des configurations en miroir.

J'ai fait face à ce problème très souvent et je souhaite partager mon expérience et mes recommandations concernant l'utilisation d'Ansible pour rationaliser le processus d'installation d'IRIS. Mon approche inclut également la gestion des tâches supplémentaires généralement effectuées avant et après l'installation d'IRIS.

Ce manuel suppose que vous disposez d'une compréhension de base du fonctionnement d'Ansible, je ne détaillerai donc pas ses principes fondamentaux. Toutefois, si vous avez des questions sur les points abordés ici, n'hésitez pas à les poser dans les commentaires ci-dessous.

Les exemples fournis dans ce manuel ont été testés à l'aide d' Ansible 3.6 sur un serveur Red Hat 8, avec IRIS 2023.1.1 et Red Hat 8 comme environnement client. D'autres versions d'Ansible, de Red Hat (ou d'autres variantes d'UNIX) et d'IRIS peuvent également fonctionner, mais les résultats peuvent varier.

Installation d'Ansible

Le serveur Ansible nécessite une distribution Linux. Nous utilisons Red Hat 8 dans cet article, mais d'autres distributions et versions Linux devraient également fonctionner.

Pour installer les paquets Ansible, il faut d'abord installer EPEL:

[ansible@auto01 ansible]$ yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm

Ensuite, il faut installer Ansible:

[ansible@auto01 ansible]$ yum install ansible

En plus des paquets, Ansible nécessite un accès SSH aux serveurs distants. Je recommande de créer une paire de clés SSH, ce qui est plus sûr que l'utilisation de mots de passe traditionnels. De plus, l'utilisateur servant à se connecter aux serveurs distants doit disposer de privilèges administratifs (c'est-à-dire faire partie du groupe wheel).

Fichiers et dossiers

Pour préserver une structure organisée, je recommande les fichiers et dossiers suivants dans le répertoire ansible:

[ansible@auto01 ansible]$ ls -l
total 4
-rw-r--r--. 1 ansible ansible 247 Dec  500:57 ansible.cfg
drwxrwxr-x. 2 ansible ansible   6 Dec  500:56 files
drwxrwxr-x. 2 ansible ansible   6 Dec  500:56 inventory
drwxrwxr-x. 2 ansible ansible   6 Dec  500:56 library
drwxrwxr-x. 2 ansible ansible   6 Dec  500:56 playbooks
drwxrwxr-x. 2 ansible ansible   6 Dec  500:56 templates
drwxrwxr-x. 2 ansible ansible   6 Dec  500:56 vars
drwxrwxr-x. 2 ansible ansible   6 Dec  500:56 vault
Fichier/DossierDescription
ansible.cfgFichier de configuration d'Ansible. Contient des directives sur le comportement d'Ansible.
filesContient les fichiers supplémentaires nécessaires aux playbooks, tels que le fichier tar.gz d'installation d'IRIS.
inventoryContient les fichiers d'inventaire de l'hôte. Vous pouvez avoir un seul fichier d'inventaire volumineux ou plusieurs fichiers de moindre taille. Le fractionnement de l'inventaire nécessite davantage d'efforts lorsque vous exécutez des playbooks sur plusieurs hôtes.
libraryContient des fichiers de bibliothèque supplémentaires d'Ansible. Non requis pour ces exemples, mais utile pour de futures extensions.
playbooksContient tous les playbooks développés, y compris le playbook d'installation IRIS décrit ci-dessous.
templatesContient les fichiers modèles utilisés par les playbooks. Ceux-ci sont transférés vers les serveurs et instanciés avec les paramètres corrects.
varsContient les variables disponibles pour tous les playbooks.
vaultContient des variables sensibles accessibles uniquement via la commande ansible-vault. Utile pour gérer les mots de passe.

 

Après avoir configuré cette structure de dossiers, copiez le programme d'installation IRIS et la clé de licence IRIS dans le dossier files. Le résultat devrait apparaître comme suit:

[ansible@auto01 ansible]$ ls -l files/
total 759976
-rw-rw-r--. 1 ansible ansible 778207913 Dec  514:32 IRISHealth-2023.1.1.380.0.22870-lnxrh8x64.tar.gz
-rw-rw-r--. 1 ansible ansible      1160 Sep  519:13 iris.key

 

Inventaire

Pour exécuter des playbooks dans Ansible, il est nécessaire de définir l'inventaire des serveurs. Il existe plusieurs méthodes pour ce faire, et chacune présente ses propres avantages. Dans cet article, nous utiliserons un seul fichier pour définir tous les serveurs.

Le ficher servers.yml contiendra l'inventaire complet, répertoriant chaque serveur ainsi que les variables requises pour l'installation d'IRIS. Voici un exemple:

[ansible@auto01ansible]$catinventory/servers.yml 
---all:  hosts:test01.mydomain:      iris_user:irisusr      iris_group:irisgrp      mgr_user:irisown      mgr_group:irismgr      platform:lnxrh8x64      iris_cmd:iris      iris_instances:        - name:TEST01          superserver_port:51773          webserver_port:52773          binary_file:IRISHealth-2023.1.1.380.0.22870-lnxrh8x64          key_file:iris.key          install_dir:/test/iris          jrnpri_dir:/test/jrnpri          jrnsec_dir:/test/jrnsec          config_globals:16384          config_errlog:10000          config_routines:"0,128,0,128,0,1024"          config_gmheap:1048576          config_locksiz:128057344

 

Fichier coffre-fort

Pour sécuriser les mots de passe, créez un fichier coffre-fort contenant les mots de passe des comptes IRIS SuperUser et CSPSystem.

Pour modifier le fichier coffre-fort par défaut, utilisez la commande suivante:

[ansible@auto01ansible]$ansible-vaulteditvault/defaults.yml---# Default passwordsiris_user_passwd:"Ch4ngeTh!s"

 

Playbook

Pour effectuer une installation IRIS, il est nécessaire d'exécuter plusieurs tâches sur le serveur cible. Ces tâches sont regroupées et classées dans un fichier appelé playbook.
Un playbook consiste essentiellement en une liste de tâches qui sont exécutées de manière séquentielle sur les hôtes distants.

Vous trouverez ci-dessous le playbook que j'ai développé pour installer IRIS:

[ansible@auto01ansible]$catplaybooks/install_iris.yml## Playbook to install Iris#- hosts:all  become:yes  gather_facts:no  tasks:  - name:"Load default passwords"    include_vars:"../vault/defaults.yml"### PRE-INSTALL TASKS:  - name:"Install required packets"    yum:      name:"{{ item }}"      state:latest    loop:      -"httpd"      -"java-1.8.0-openjdk"      -"mod_auth_mellon"      -"mod_ssl"  - name:"Create iris group"    group:      name:"{{ iris_group }}"      gid:5005  - name:"Create iris mgr group"    group:      name:"{{ mgr_group }}"      gid:5006  - name:"Create iris owner user"    user:      name:"{{ mgr_user }}"      uid:5006      group:"{{ iris_group }}"      groups:"{{ mgr_group }}"  - name:"Create iris user"    user:      name:"{{ iris_user }}"      uid:5005      group:"{{ iris_group }}"  - name:"Create mgr folder"    file:      path:"{{ item.install_dir }}/mgr"      state:directory      owner:"{{ iris_user }}"      group:"{{ iris_group }}"      mode:0775    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Copy license key"    copy:      src:"../files/{{ item.key_file }}"      dest:"{{ item.install_dir }}/mgr/iris.key"      owner:"{{ iris_user }}"      group:"{{ iris_group }}"      backup:yes    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Create /install folder"    file:      path:"/install"      state:directory      mode:0777  - name:"Create Instances install folders"    file:      path:"/install/{{ item.name }}"      state:directory      mode:0777    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Copy IRIS installer"    copy:      src:"../files/{{ item.binary_file }}.tar.gz"      dest:"/install/{{ item.name }}/"    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Untar IRIS installer"    command:      cmd:"tar -xzf /install/{{ item.name }}/{{ item.binary_file }}.tar.gz"      chdir:"/install/{{ item.name }}/"    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"### IRIS INSTALL:  - name:"Install Iris"    command:      cmd:"./irisinstall_silent"      chdir:"/install/{{ item.name }}/{{ item.binary_file }}"    environment:      ISC_PACKAGE_INSTANCENAME:"{{ item.name }}"      ISC_PACKAGE_INSTALLDIR:"{{ item.install_dir }}"      ISC_PACKAGE_PLATFORM:"{{ platform }}"      ISC_PACKAGE_UNICODE:"Y"      ISC_PACKAGE_INITIAL_SECURITY:"Normal"      ISC_PACKAGE_MGRUSER:"{{ mgr_user }}"      ISC_PACKAGE_MGRGROUP:"{{ mgr_group }}"      ISC_PACKAGE_USER_PASSWORD:"{{ iris_user_passwd }}"      ISC_PACKAGE_CSPSYSTEM_PASSWORD:"{{ iris_user_passwd }}"      ISC_PACKAGE_IRISUSER:"{{ iris_user }}"      ISC_PACKAGE_IRISGROUP:"{{ iris_group }}"      ISC_PACKAGE_SUPERSERVER_PORT:"{{ item.superserver_port }}"      ISC_PACKAGE_WEBSERVER_PORT:"{{ item.webserver_port }}"      ISC_PACKAGE_CLIENT_COMPONENTS:"standard_install"      ISC_PACKAGE_STARTIRIS:"N"    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Remove installers"    file:      path:"/install/{{ item.name }}"      state:absent    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"### IRIS CUSTOMIZATIONS:  - name:"Change iris.cpf"    lineinfile:      path:"{{ item[0].install_dir }}/iris.cpf"      regexp:"{{ item[1].from }}"      line:"{{ item[1].to }}"      backup:yes    with_nested:      -"{{ iris_instances }}"      -[{from:"^TerminalPrompt=.*",to:"TerminalPrompt=8,3,2"},{from:"^FreezeOnError=0",to:"FreezeOnError=1"},{from:"^AutoParallel=.*",to:"AutoParallel=0"},{from:"^FastDistinct=.*",to:"FastDistinct=0"},{from:"^LockThreshold=.*",to:"LockThreshold=10000"},{from:"^EnsembleAutoStart=.*",to:"EnsembleAutoStart=1"},{from:"^MaxIRISTempSizeAtStart=.*",to:"MaxIRISTempSizeAtStart=300"}]    loop_control:      label:"{{ item[0].name }}: {{ item[1].to }}"  - name:"Change Journal Current Dir"    lineinfile:      path:"{{ item.install_dir }}/iris.cpf"      regexp:"^CurrentDirectory=.*"      line:"CurrentDirectory={{ item.jrnpri_dir }}"      backup:yes    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Change Journal Alternate Dir"    lineinfile:      path:"{{ item.install_dir }}/iris.cpf"      regexp:"^AlternateDirectory=.*"      line:"AlternateDirectory={{ item.jrnsec_dir }}"      backup:yes    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Change Journal Prefix name"    lineinfile:      path:"{{ item.install_dir }}/iris.cpf"      regexp:"^JournalFilePrefix=.*"      line:"JournalFilePrefix={{ item.name }}_"      backup:yes    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Change Globals memory"    lineinfile:      path:"{{ item.install_dir }}/iris.cpf"      regexp:"^globals=.*"      line:"globals=0,0,{{ item.config_globals }},0,0,0"      backup:yes    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Change errlog memory"    lineinfile:      path:"{{ item.install_dir }}/iris.cpf"      regexp:"^errlog=.*"      line:"errlog={{ item.config_errlog }}"      backup:yes    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Change routines memory"    lineinfile:      path:"{{ item.install_dir }}/iris.cpf"      regexp:"^routines=.*"      line:"routines={{ item.config_routines }}"      backup:yes    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Change gmheap memory"    lineinfile:      path:"{{ item.install_dir }}/iris.cpf"      regexp:"^gmheap=.*"      line:"gmheap={{ item.config_gmheap }}"      backup:yes    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"  - name:"Change locksiz memory"    lineinfile:      path:"{{ item.install_dir }}/iris.cpf"      regexp:"^locksiz=.*"      line:"locksiz={{ item.config_locksiz }}"      backup:yes    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"### START IRIS:  - name:"Start Iris"    command:"iris start {{ item.name }}"    loop:"{{ iris_instances }}"    loop_control:      label:"{{ item.name }}"...

Comme vous pouvez le constater, ce playbook comporte plusieurs tâches, dont la plupart sont explicites d'après leur nom. Les commentaires indiquent les tâches à effectuer avant l'installation, l'installation proprement dite et les personnalisations à effectuer après l'installation. Après avoir exécuté ce playbook, vous disposerez d'une nouvelle instance IRIS installée sur le système cible, dont la mémoire et d'autres paramètres auront été personnalisés.

Lancez l'installation!

Après avoir configuré l'inventaire, le ficheir coffre-fort et les playbooks, vous êtes prêt à exécuter l'installation IRIS à l'aide d'Ansible.
Pour ce faire, exécutez la commande suivante:

[ansible@auto01 ansible]$ ansible-playbook -K --ask-vault-pass -i inventory/servers.yml playbooks/install_iris.yml
BECOME password: 
Vault password: 

PLAY [all] ************************************************************************************************************************************************** . . .

Lorsque l'exécution du playbook est terminée, vous recevez un résumé de statuts des tâches qui vous permet de vérifier que tout a été exécuté avec succès.

Et voilà, vous venez d'installer IRIS à l'aide d'Ansible! 😁

0
0 14
InterSystems officiel Adeline Icard · Oct 27, 2025

Cette version met l'accent sur la fiabilité des mises à niveau, l'extension de la sécurité et l'amélioration du support pour plusieurs services cloud InterSystems. Avec cette version, toutes les offres majeures, notamment FHIR Server, InterSystems Data Fabric Studio (IDS), IDS avec Supply Chain et IRIS Managed Services, prennent désormais en charge la sécurité avancée, offrant ainsi une sécurité unifiée et renforcée.

Nouvelles fonctionnalités et améliorations

0
0 16
InterSystems officiel Adeline Icard · Oct 24, 2025

Les versions de maintenance 2025.1.2 et 2024.1.5 de la plateforme de données InterSystems IRIS, d'InterSystems IRIS for Health et d'HealthShare Health Connect sont désormais disponibles en disponibilité générale (GA). Ces versions incluent les correctifs pour plusieurs alertes et avis publiés récemment, notamment :

0
0 15
Article Iryna Mykhailova · Oct 23, 2025 6m read

Salut,

C'est moi encore 😁. Je travaille actuellement à la génération de fausses données patients à des fins de test avec Chat-GPT et Python. J'aimerais également partager mon apprentissage. 😑

Tout d'abord, créer un service d'API REST personnalisé est facile en utilisant %CSP.REST.

Commençons ! 😂

1. Créez une classe datagen.restservice qui étend %CSP.REST.

Class datagen.restservice Extends%CSP.REST
{
Parameter CONTENTTYPE = "application/json";
}

2. Ajoutez une fonction genpatientcsv() pour générer les données du patient et les regrouper dans une chaîne csv

0
0 18
Article Sylvain Guilbaud · Oct 22, 2025 4m read

Bonjour à tous les membres de la communauté!

Beaucoup d'entre vous se souviennent certainement des fonctionnalités NLP disponibles dans IRIS sous le nom iKnow, qui ont été supprimées depuis peu de temps. Mais... Tout a-t-il été supprimé ? NON! Un petit village résiste à la suppression: les index iFind!

Vous vous demandez peut-être à quoi servent ces magnifiques index. C'est très simple : ils indexent le texte dans les colonnes String et Stream et accélèrent considérablement les requêtes.

À quoi servent les index %iFind?

Il s'agit d'un type d'index basé sur des cartes binaires. Ces index mappent chaque valeur unique d'une colonne dans une chaîne de bits, indiquant pour chaque ligne la disponibilité d'une valeur donnée. Vous trouverez plus de détails sur ces types d'index dans la documentation officielle .

Nous disposons de deux types d'index %iFind : minimal et basique, qui est une extension du type minimal.

%iFind.Index.Minimal

Prend en charge des recherches SQL de mots et d'expressions avec caractères génériques, des recherches approximatives et des expressions régulières. Ne prend pas en charge les recherches par cooccurrence ou par expressions positionnelles, ni la mise en évidence des résultats.

%iFind.Index.Basic

Prend en charge toutes les fonctionnalités de l'index minimal (recherche SQL de mots et d'expressions avec des caractères génériques) et ajoute la prise en charge de la cooccurrence, la recherche d'expressions positionnelles et la mise en évidence des résultats. Les fonctionnalités de cet index sont suffisantes pour la plupart des recherches en texte intégral courantes.

Comment définir un index %iFind?

Rien de plus simple, je vais vous montrer à l'aide d'un petit exemple tiré d'un développement que j'ai mis en place pour lire les informations relatives aux appels d'offres publics en Espagne:

Class Inquisidor.Object.Licitacion Extends (%Persistent, %XML.Adaptor) [ DdlAllowed ]
{

Property IdLicitacion As%String(MAXLEN = 200);Property Titulo As%String(MAXLEN = 2000);
...

Index IndexIdLicitation On IdLicitacion [ PrimaryKey ];
Index IndexTitulo On (Titulo) As%iFind.Index.Basic(INDEXOPTION = 0, LANGUAGE = "es");

Comme vous pouvez le voir, nous avons défini la propriété Titre sur laquelle nous voulons effectuer des recherches afin de trouver certaines offres. Voici un exemple des titres que nous allons avoir:

Service de maintenance et d'assistance de l'application informatique CIVITAS pour la gestion de la carte sanitaire individuelle de la Direction régionale de la santé de Castille-et-León.

Pour définir l'index %iFind, nous utiliserons la configuration %iFind.Index.Basic(INDEXOPTION = 0, LANGUAGE = "es") . Dans ce cas, il s'agit d'un index de base. Comme vous pouvez le voir, nous disposons d'une série de propriétés qui nous permettent de définir le fonctionnement de notre index. Voyons les propriétés disponibles:

  • IGNOREPUNCTUATION: Prend en charge 2 valeurs, 0 et 1. Par défaut, la valeur est 1 et les signes de ponctuation dans le texte sont ignorés.
  • INDEXOPTION: cette option peut également prendre les valeurs 0 et 1. Elle permet de spécifier si l'index autorisera la lemmatisation ou la décomposition des textes. En raison de la taille importante que cela peut nécessiter, cette option ne doit être activée que si cela est nécessaire (valeur 1).
  • LANGUAGE: permet de définir le dictionnaire à utiliser pour les recherches, dans notre exemple, l'espagnol.
  • LOWER: cette option peut prendre les valeurs 0 ou 1. Elle permet d'indiquer si l'index sera sensible à la casse (majuscules/minuscules). Par défaut, elle est définie sur 0, ce qui signifie qu'elle est ignorée.
  • USERDICTIONARY: cette option permet à l'index d'utiliser un dictionnaire utilisateur avant l'indexation.

Comment utiliszer l'index %iFind dans une requête?

Pour utiliser ce type d'index, nous devons utiliser la notation suivante:

SELECT * FROM Inquisidor_Object.Licitacion WHERE %ID%FIND search_index(IndexTitulo, ?)

Voyons comment fonctionne l'index. Dans mon exemple, j'ai une table contenant 800 000 enregistrements d'appels d'offres publics. Voyons le plan avec le LIKE traditionnel sur notre table:

Voyons maintenant le plan utilisant l'index:

Comme vous pouvez le constater, le rapport entre le temps de recherche normal et celui obtenu avec l'index %iFind est astronomique : 1239110280 pour la requête sans index contre 8323422 pour la requête indexée, soit une rapidité 150 fois supérieure.

Si vous souhaitez voir plus en détail le type de recherches permises par les index %iFind, vous trouverez here ici la documentation nécessaire.

J'espère que cela vous sera utile!

0
0 19
Article Iryna Mykhailova · Oct 21, 2025 2m read

Bonjour,

Je voulais partager avec vous une méthode pratique qui m'a été utile lors de mes développements sur Health Connect Cloud avec VS Code et GitBash. Lors de ces développements, si des modifications sont effectuées directement sur le serveur, comme des règles de routage ou des déploiements de composants, elles ne sont pas automatiquement incluses dans le contrôle de code source. Vous devez donc exporter les modifications depuis le serveur vers vos fichiers locaux et les envoyer vers votre dépôt distant. Je suis sûr qu'il existe des méthodes plus simples pour gérer ce problème, que je suis en train de tester, mais pour une solution rapide, j'ai pensé qu'il serait utile d'utiliser une méthode de pré-validation qui déclenche un rappel dans GitBash – voir ci-dessous.

0
0 16