#HealthShare

0 Abonnés · 90 Publications

InterSystems HealthShare est une plateforme informatique de santé destinée aux hôpitaux, aux réseaux de distribution intégrés (IDN) et aux échanges d'informations sur la santé (HIE) régionaux et nationaux. HealthShare comprend des technologies d'échange d'informations sur la santé, d'agrégation de données, de workflow, d'analyse de texte et d'analytique.

En savoir plus

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
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 Lorenzo Scalese · Sept 23, 2025 8m read

Mes clients me contactent régulièrement à propos du dimensionnement de la mémoire lorsqu'ils reçoivent des alertes indiquant que la mémoire libre est inférieure à un seuil ou lorsqu'ils constatent que la mémoire libre a soudainement diminué. Existe-t-il un problème? Leur application va-t-elle cesser de fonctionner parce qu'elle manque de mémoire pour exécuter les processus système et applicatifs? La réponse est presque toujours non, il est inutile de s'inquiéter. Mais cette réponse simple n'est généralement pas suffisante. Que se passe-t-il?

Considérez le graphique ci-dessous. Il montre le résultat de la métrique free dans vmstat. Il existe d'autres moyens d'afficher la mémoire libre d'un système, par exemple la commande free -m. Parfois, la mémoire libre disparaît progressivement au fil du temps. Le graphique ci-dessous est un exemple exagéré, mais il illustre bien ce qui se passe.

image

Comme vous pouvez le constater, vers 2 heures du matin, une partie de la mémoire est récupérée, puis chute soudainement à près de zéro. Ce système exécute l'application IntelliCare EHR sur la base de données InterSystems IRIS. Les informations vmstat proviennent d'un fichier HTML ^SystemPerformance qui collecte les métriques vmstat, iostat et plusieurs autres métriques système. Que se passe-t-il d'autre sur ce système ? Comme nous sommes en pleine nuit, je ne m'attends pas à ce qu'il se passe grand-chose à l'hôpital. Examinons iostat pour les volumes de la base de données.

image

On constate une augmentation soudaine des lectures au moment où la mémoire libre diminue. La baisse de la mémoire libre signalée correspond à un pic des lectures en gros blocs (taille de requête de 2048 Ko) indiqué dans iostat pour le disque de la base de données. Il s'agit très probablement d'un processus de sauvegarde ou d'une copie de fichiers. Bien sûr, corrélation n'est pas synonyme de causalité, mais cela vaut la peine d'être examiné et, en fin de compte, cela explique ce qui se passe.

Examinons d'autres résultats de ^SystemPerformance. La commande free -m est exécutée à la même fréquence que vmstat (par exemple, toutes les 5 secondes) et est accompagnée de la date et de l'heure, ce qui nous permet également de représenter graphiquement les compteurs dans free -m.

Les compteurs:

  • Memtotal – Total de RAM physique.
  • used – RAM activement utilisée (applications + système d'exploitation + cache).
  • free – RAM complètement inutilisée.
  • shared – Mémoire partagée entre les processus.
  • buf/cache – RAM utilisée pour les tampons et le cache, récupérable si nécessaire.
  • available – RAM disponible sans swap.
  • swaptotal – Espace de swap total sur le disque.
  • swapused – Espace de swap actuellement utilisé.
  • swapfree – Espace de swap inutilisé.

Pourquoi la mémoire libre diminue-t-elle à 2 heures du matin?

  • Les lectures séquentielles de grande taille remplissent le cache de page du système de fichiers, consommant temporairement de la mémoire qui apparaît comme "utilisée" dans free -m.
  • Linux utilise de manière agressive la mémoire inexploitée pour la mise en cache des E/S afin d'améliorer les performances.
  • Une fois la sauvegarde terminée (≈ 03h00), la mémoire est progressivement récupérée au fur et à mesure que les processus en ont besoin.
  • Vers 6 heures du matin, l'hôpital commence à s'activer et la mémoire est utilisée pour IRIS et d'autres processus.

Une mémoire libre insuffisante ne constitue pas une pénurie, mais plutôt une utilisation de la mémoire "libre" par le système à des fins de mise en cache. Il s'agit d'un comportement normal sous Linux! Le processus de sauvegarde lit de grandes quantités de données, que Linux met agressivement en cache dans la mémoire tampon/cache. Le noyau Linux convertit la mémoire "libre" en mémoire "cache" afin d'accélérer les opérations d'E/S.

Résumé

Le cache du système de fichiers est conçu pour être dynamique. Si la mémoire est requise par un processus, elle sera immédiatement récupérée. Il s'agit d'un élément normal de la gestion de la mémoire sous Linux.


Les Huge Pages ont-elles un impact?

Pour optimiser les performances et réserver de la mémoire pour la mémoire partagée IRIS, la meilleure pratique pour les déploiements IRIS en production sur des serveurs dotés d'une mémoire importante consiste à utiliser les Huge Pages de Linux. Pour IntelliCare, j'utilise généralement 8 Go de mémoire par noyau et environ 75 % de la mémoire pour la mémoire partagée d'IRIS (tampons Routine et Global, GMHEAP et autres structures de mémoire partagée). La répartition de la mémoire partagée dépend des exigences de l'application. Vos exigences peuvent être complètement différentes. Par exemple, en utilisant ce rapport CPU/mémoire, 25 % suffisent-ils pour les processus IRIS et les processus du système d'exploitation de votre application?

InterSystems IRIS utilise l'E/S directe pour les fichiers de base de données et les fichiers journaux, ce qui contourne le cache du système de fichiers. Ses segments de mémoire partagée (globales, routines, gmheap, etc.) sont alloués à partir de Huge Pages.

  • Ces pages immenses (huge pages) sont dédiées à la mémoire partagée IRIS et n'apparaissent pas comme "libres" ou "cache" dans free -m.
  • Once allocated, huge pages are not available for filesystem cache or user processes.

Cela explique pourquoi les métriques free -m semblent "insuffisantes" même si la base de données IRIS elle-même ne manque pas de mémoire.


Comment la mémoire libre pour un processus est-elle calculée?

À partir de ce qui précède, dans free -m, les lignes pertinentes sont les suivantes:

  • free – RAM totalement inutilisée.
  • available – RAM encore utilisable sans échange.

La disponibilité est un bon indicateur: elle inclut la mémoire cache et les tampons récupérables, indiquant ce qui est réellement disponible pour les nouveaux processus sans échange. Quels processus? Pour plus d'informations, consultez InterSystems Data Platforms and Performance Part 4 - Looking at Memory . Voici une liste simple: système d'exploitation, autres processus d'application non-IRIS et processus IRIS.

Examinons un graphique de la sortie free -m.

image

Bien que la valeur de la mémoire libre (free) chute à près de zéro pendant la sauvegarde, la valeur de la mémoire disponible (available) reste beaucoup plus élevée (plusieurs dizaines de Go). Cela signifie que le système pourrait fournir cette mémoire aux processus si nécessaire.

A quel endroit apparaissent les pages immenses dans la mémoire libre?

Par défaut, free -m n'affiche pas directement les pages immenses. Pour les voir, vous avez besoin des entrées /proc/meminfo telles que HugePages_Total, HugePages_Free et Hugepagesize.

Puisque le système d'exploitation réserve des pages immenses au démarrage, elles sont effectivement invisibles pour free -m. Elles sont verrouillées et isolées du pool de mémoire général.

Résumé

  • La "mémoire disponible" insuffisante constatée vers 02h00 est due au remplissage du cache de pages Linux par des lectures de sauvegarde. Il s'agit d'un comportement normal qui n'indique pas une pénurie de mémoire.
  • Les pages immenses réservées à IRIS ne sont pas affectées et continuent à servir efficacement la base de données.
  • La mémoire réellement disponible pour les applications est mieux mesurée par la colonne disponible, qui montre que le système dispose encore d'une marge suffisante.

Mais attendez, que se passe-t-il si je n'utilise pas les Huge Pages?

Généralement, on n'utilise pas les Huge Pages sur les systèmes non productifs ou à mémoire limitée. Les gains de performances des Huge Pages ne sont généralement pas significatifs en dessous de 64 Go, bien qu'il soit toujours recommandé d'utiliser les Huge Pages pour protéger la mémoire partagée IRIS.

A propos. J'ai vu des sites rencontrer des problèmes en allouant des pages immenses moins grandes que la mémoire partagée, ce qui oblige IRIS à essayer de démarrer avec des tampons globaux très petits ou à échouer au démarrage si memlock est utilisé (envisagez memlock=192 pour les systèmes de production).

Sans Huge Pages, les segments de mémoire partagée IRIS ( globales, routines, gmheap, etc.) sont alloués à partir de pages de mémoire normales du système d'exploitation. Cela apparaîtrait sous la mémoire "utilisée" dans free -m. Cela contribuerait également à réduire la mémoire "disponible", car cette mémoire ne peut pas être facilement récupérée.

  • utilisée – Beaucoup plus élevée, reflétant la mémoire partagée IRIS + le noyau + d'autres processus.
  • libre – Probablement moins suffisante, car plus de RAM est allouée en permanence à IRIS dans le pool régulier.
  • buf/cache – Augmenterait toujours pendant les sauvegardes, mais la marge apparente pour les processus semblerait plus restreinte, car la mémoire IRIS se trouve dans le même pool.
  • disponible – Plus proche de la véritable “mémoire libre + cache récupérable” moins la mémoire IRIS. Cela semblerait plus petit que dans votre configuration Huge Pages.

Alors, faut-il utiliser Huge Pages dans des systèmes de production?

OUI!

Pour la protection de la mémoire. La mémoire partagée IRIS est protégée contre:

  • Remplacement en cas de sollicitation de la mémoire.
  • Concurrence avec les opérations du système de fichiers telles que les sauvegardes et les copies de fichiers, comme nous l'avons vu dans cet exemple.

Autres remarques - trop profondément dans les détails...

Comment les données sont-elles collectées?

La commande utilisée dans ^SystemPerformance pour une collecte de 24 heures (17 280 secondes) avec des coches toutes les 5 secondes est la suivante:

free -m -s 5 -c 17280 | awk '{now=strftime(""%m/%d/%y %T""); print now "" "" $0; fflush()}' > ","/filepath/logs/20250315_000100_24hours_5sec_12.log

0
0 22
Article Guillaume Rongier · Juil 2, 2025 4m read

Bonjour chers ingénieurs interface et développeurs d'applications,     

Saviez-vous que Python peut être utilisé dans des productions ou des intégrations?

L'interface utilisateur de configuration de production est low-code, mais dans de nombreux projets, vous pouvez arriver au point où il est nécessaire d'écrire du code. Comme nous l'avons vu dans le cours Architecture d'intégration, les Business Process comportent de nombreux emplacements où insérer du code, notamment l'éditeur BPL (pour les Business Process BPL) et l'éditeur DTL (pour les transformations de données).

😯 À partir d'InterSystems IRIS 2025.1, Python et InterSystems ObjectScript sont tous deux pris en charge dans les éditeurs BPL et DTL. 😄 Vous pouvez choisir entre ces langues selon vos préférences personnelles, et pas selon des besoins techniques. 😍

Rédaction du code Python dans l'éditeur BPL

Python est pris en charge dans l'éditeur BPL depuis InterSystems IRIS 2024.1. Examinons un Business Process BPL:

Business Process BPL comprend les activités suivantes (dans l'ordre) : activité Début, activité Affectation, activité Code avec un logo Python, activité If avec un logo Python, activité Transformation du côté vrai de l'activité If, et activité Fin.

Dans l'activité <code>, GetWinningBid, remarquez l'icône Python. En ouvrant cette activité <code>, on peut voir que le champ intitulé Code contient des instructions Python! Ces instructions utilisent la bibliothèque intégrée iris pour ouvrir un objet d'un ID donné.

Une activité Code provenant d'un éditeur BPL. Le langage est défini sur Python. Le code indique # Utilisation du corps de la requête pour ouvrir l'objet Bid avec l'ID donné. New line. bidID = request.id. New line. context.winningBid = iris.cls("AuctionServer.Bid")._OpenId(bidID).

Vous pouvez définir le langage pour l'ensemble du BPL (Python ou ObjectScript) via le paramètre Language dans l'onglet General u BPL, puis utiliser le menu déroulant Language Override (Remplacement du language) dans un champ de code donné pour remplacer la valeur par défaut du BPL.

L'activité <code> n'est pas la seule activité qui analyse le code dans un Business Process BPL. L'activité <code> analyse les instructions de code complètes, mais autres champs des activités BPL acceptent également des expressions de code. Vous pouvez les reconnaître grâce au menu déroulant Language Override (Remplacement du langage), où vous pouvez remplacer la valeur par défaut de BPL. 

Par exemple, l'activité <if>, qui crée un flux logique conditionnel, a un champ appelé Condition qui accepte des expressions de code. Il a un champ correspondant appelé Condition Language Override (Remplacement du langage de condition). Dans cet exemple, ce champ est défini sur Python.

Lorsque Python n'était pas encore pris en charge dans ces domaines, les ingénieurs d'interface devaient savoir comment écrire des expressions logiques dans ObjectScript. Par exemple, vous deviez savoir que || signifie Or, et && signifie And. Vous pouvez désormais utiliser Python, dont la syntaxe pour les expressions logiques est plus proche du langage naturel.

Dans l'éditeur BPL, s'il vous faut importer des paquetages Python, vous pouvez le faire sous les instructions Python From/Import dans l'onglet General.

Rédaction du code Python dans l'éditeur DTL 

Python est également pris en charge dans les champs de code de l'éditeur DTL depuis InterSystems IRIS 2025.1. Les utilisations courantes du code personnalisé dans DTL sont le formatage de chaînes et les calculs.

Une transformation de données DTL convertissant un objet source, AuctionServer.Bid, into a target object, AuctionServer.Order

Si vous devez importer des paquetages Python dans l'éditeur DTL, recherchez l'onglet Paramètres Transformation et utilisez le champ d' instruction Python From / Import. Le langage par défaut pour les expressions de code peut également être défini dans l'onglet d'instructions Transformation .

Dans l'onglet Paramètres de transformation du DTL, le langage est défini sur Python et le champ des instructions Python From / Import contient la mention from numpy import random.

Pour trouver les champs d'analyse de code des actions DTL, il suffit de rechercher tout champ associé à une liste déroulante Language Override. Par exemple, dans l'action <code> du DTL, vous pouvez écrire des instructions de code complètes. Les actions telles que Set, If, Trace et autres prennent en charge les expressions de code, qui peuvent être écrites en Python ou en ObjectScript.

Une activité de code issue d'une transformation de données DTL, avec le langage défini sur Python. Le contenu du champ de code indique bidders = [bid.User for bid in source.Lot.Bids()]. New line. bidders = set(bidders). New line. target.NumOutbid = (len(bidders) - 1).

Flux de travail axés sur le code

Si vous maîtrisez le processus d'édition de productions via un code, vous pouvez modifier les fichiers BPL et DTL via leurs fichiers de configuration. Cette méthode n'est pas aussi simple que les interfaces de l'éditeur, mais elle est très intuitive pour les programmeurs expérimentés.

Vous pouvez également utiliser Python pour écrire des méthodes dans des fonctions utilitaires personnalisées et des composants personnalisés, tels que des opérations commerciales et des services commerciaux (à l'exception des méthodes de rappel callback methods, comme défini dans la documentation).

Récapitulatif 

  • L'éditeur DTL et l'éditeur BPL prennent désormais en charge le code et les expressions Python. 
  • Si nécessaire, indiquez les paquetages que vous souhaitez importer via les paramètres généraux de BPL ou DTL. 
  • Vous pouvez définir un langage par défaut pour chaque DTL/BPL et remplacer des champs spécifiques si nécessaire.

Rejoignez la conversation!

  • Avez-vous déjà utilisé Python pour créer des intégrations personnalisées? 
  • Quelles questions avez-vous au sujet de la prise en charge de Python dans les intégrations?
  • Ajoutez vos commentaires ci-dessous!
0
0 28
Annonce Irène Mykhailova · Juin 16, 2025

Bonjour à tous,

L'équipe Certification d'InterSystems Learning Services développe actuellement deux nouveaux examens de certification HealthShare Unified Care Record. Nous sollicitons les commentaires de notre communauté afin de nous aider à évaluer et à définir leur contenu. Veuillez noter que ces examens remplaceront l'examen de spécialiste technique HealthShare Unified Care Record, que nous prévoyons de supprimer en janvier 2026. Les certifications obtenues dans cette technologie avant la suppression de l'examen resteront valables cinq ans à compter de la date d'obtention.

0
0 22
Article Sylvain Guilbaud · Juin 13, 2025 9m read

J'ai dû récemment rafraîchir mes connaissances sur le module EMPI de HealthShare et, comme je travaillais depuis quelque temps sur les fonctionnalités de stockage et de recherche vectorielle d'IRIS, j'ai tout naturellement fait le rapprochement.

Pour ceux d'entre vous qui ne connaissent pas bien les fonctionnalités EMPI, voici une brève introduction:

Index patient principal "Enterprise Master Patient Index"

En général, tous les EMPI fonctionnent de manière très similaire : ils ingèrent les informations, les normalisent et les comparent avec les données déjà présentes dans leur système. Dans le cas de l'EMPI de HealthShare, ce processus est connu sous le nom de NICE:

  • Normalisation: tous les textes ingérés à partir de la production interopérable sont normalisés en supprimant les caractères spéciaux.
  • Indexation: des index sont générés à partir d'une sélection de données démographiques afin d'accélérer la recherche de correspondances.
  • Comparaison: les correspondances trouvées dans les index sont comparées entre les données démographiques, puis des pondérations sont attribuées en fonction de critères correspondant au niveau de coïncidence.
  • Évaluation: la possibilité de relier les patients est évaluée à partir de la somme des pondérations obtenues.

Si vous voulez en savoir plus sur HealthShare Patient Index, vous pouvez consulter une série d'articles que j'ai écrits il y a quelque temps ici.

Quel est le défi?

Bien que le processus de configuration nécessaire pour obtenir les liens possibles ne soit pas extrêmement compliqué, je me suis demandé... Serait-il possible d'obtenir des résultats similaires en utilisant les fonctionnalités de stockage et de recherche vectorielle et en supprimant les étapes d'ajustement de la pondération? Au travail!

De quoi ai-je besoin pour mettre en œuvre mon idée?

J'aurai besoin des ingrédients suivants:

  • IRIS for Health pour mettre en œuvre la fonctionnalité et utiliser le moteur d'interopérabilité pour l'ingestion de messages HL7.
  • Bibliothèque Python pour générer les vecteurs à partir des données démographiques, dans ce cas, il s'agira de sentence-transformers.
  • Modèle pour générer les embeddings, après une recherche rapide sur Hugging Faces, j'ai choisi all-MiniLM-L6-v2.

Production d'interopérabilité

La première étape consistera à configurer la production chargée d'ingérer les messages HL7, de les transformer en messages contenant les données démographiques les plus pertinentes du patient, de générer les intégrations à partir de ces données démographiques et enfin de générer le message de réponse avec les correspondances possibles.

Jetons un coup d'œil à notre production:

Comme vous pouvez le voir, rien de plus simple. J'ai un Service métier HL7FileService qui récupère les messages HL7 à partir d'un répertoire (/shared/in), puis envoie le message au Processus métier (BP) EMPI.BP.FromHL7ToPatientRequestBPL où je vais créer le message avec les données démographiques du patient, puis nous l'envoyons à un autre BP appelé EMPI.BP.VectorizationBP où les informations démographiques seront vectorisées et où la recherche vectorielle sera effectuée, ce qui renverra un message avec tous les patients en double possibles.

Comme vous pouvez le voir, le BP FromHL7ToPatientRequesBPL est très simple:

Nous convertissons le message HL7 en un message que nous avons créé afin de stocker les données démographiques que nous avons jugées les plus pertinentes.

Messages entre les composants

Nous avons créé deux types de messages spécifiques: 

EMPI.Message.PatientRequest

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

Property Patient As EMPI.Object.Patient; }

EMPI.Message.PatientResponse

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

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

Ce type de message contiendra une liste des patients susceptibles d'être en double.

Voyons la définition de la classe EMPI.Object.Patient :

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

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

Nom, Address, Contact et BirthDateAndSex sont les propriétés dans lesquelles nous allons enregistrer les données démographiques les plus pertinentes sur les patients.

Voyons maintenant la magie: la génération d'intégration et la recherche vectorielle dans la production.

EMPI.BP.VectorizationBP

Intégration et recherche vectorielle

Une fois la requête PatientRequest reçue, nous allons générer les intégrations à l'aide d'une méthode Python:

Method VectorizePatient(name As%String, address As%String, contact As%String, birthDateAndSex As%String) As%String [ Language = python ]
{
    import iris
    import os
    import sentence_transformers
<span class="hljs-keyword">try</span> :
    <span class="hljs-keyword">if</span> not os.path.isdir(<span class="hljs-string">"/iris-shared/model/"</span>):
        model = sentence_transformers.SentenceTransformer(<span class="hljs-string">"sentence-transformers/all-MiniLM-L6-v2"</span>)            
        model.save('/iris-shared/model/')
    model = sentence_transformers.SentenceTransformer(<span class="hljs-string">"/iris-shared/model/"</span>)
    embeddingName = model.encode(name, normalize_embeddings=True).tolist()
    embeddingAddress = model.encode(address, normalize_embeddings=True).tolist()
    embeddingContact = model.encode(contact, normalize_embeddings=True).tolist()
    embeddingBirthDateAndSex = model.encode(birthDateAndSex, normalize_embeddings=True).tolist()

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

}

Analysons le code:

  • Avec la bibliothèque sentence-transformer, nous obtenons le modèle all-MiniLM-L6-v2 et l'enregistrons sur la machine locale (pour éviter toute connexion supplémentaire à l'Internet).
  • Une fois le modèle importé, nous pouvons générer les intégrations pour les champs démographiques à l'aide de la méthode encode.
  • À l'aide de la bibliothèque IRIS, nous exécutons la requête d'insertion pour conserver les intégrations pour le patient.

Recherche de patients en double

Nous avons maintenant la liste des patients enregistrés avec les intégrations générées à partir des données démographiques, interrogeons-la!

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

Voici la requête. Pour notre exemple, nous avons inclus une restriction afin d'obtenir les patients présentant une similitude supérieure à 0,8 pour toutes les données démographiques, mais nous pourrions la configurer pour affiner la requête.

Voyons l'exemple présentant le fichier messagesa28.hl7 avec des messages HL7 tels que ceux-ci:

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

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

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

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

Dans ce fichier, tous les patients sont différents, donc le résultat de l'opération sera du type suivant:

La seule correspondance est le patient lui-même. Introisons les messages HL7 provenant du fichier messagesa28Duplicated.hl7 avec des patients en double:

Comme vous pouvez le voir, le code a détecté le patient en double avec des différences mineures dans le nom (Maruchi est un diminutif affectueux de María et Mª est l'abréviation), bien sûr, ce cas est simplifié à l'extrême, mais vous pouvez vous faire une idée des capacités de la recherche vectorielle pour trouver des données en double, non seulement pour les patients, mais aussi pour tout autre type d'informations.

Prochaines étapes...

Pour cet exemple, j'ai utilisé un modèle commun pour générer les intégrations, mais le comportement du code pourrait être amélioré à l'aide d'un réglage fin utilisant des diminutifs, des surnoms, etc.

Merci de votre attention!

0
0 27
Article Iryna Mykhailova · Mai 2, 2025 3m read

Qui n'a jamais développé un bel exemple avec une image IRIS Docker et vu la génération de l'image échouer dans le Dockerfile parce que la licence sous laquelle l'image a été créée ne comportait pas certains privilèges ?

Dans mon cas, je déployais dans Docker une petite application utilisant le type de données Vector. Avec la version Community, ce n'est pas un problème, car elle inclut déjà la recherche et le stockage vectoriels. Cependant, lorsque j'ai remplacé l'image IRIS par une image IRIS classique (latest-cd), j'ai constaté que la compilation de l'image, y compris des classes générées, renvoyait l'erreur suivante :

0
0 24
Article Lorenzo Scalese · Avr 16, 2025 7m read

Qu'est-ce que JWT ??

JWT (JSON Web Token) est un standard ouvert (RFC 7519) qui offre une méthode légère, compacte et autonome pour transmettre en toute sécurité des renseignements entre deux parties. Il est couramment utilisé dans les applications web pour l'authentification, l'autorisation et l'échange d'informations.

Un JWT est généralement composé de trois parties:

1. En-tête JOSE (JSON Object Signing and Encryption)
2. Payload
3. Signature

Ces parties sont encodées au format Base64Url et concaténées avec des points (.) qui les séparent.

Structure d'un JWT

En-tête

{ "alg": "HS256", "typ": "JWT"}

Payload

{"sub": "1234567890", "name": "John Doe", "iat": 1516239022}

Signature:
La signature permet de vérifier que l'expéditeur du JWT est bien celui qu'il prétend être et de s'assurer que le message n'a pas été falsifié.

Pour créer la signature:

1. base64 En-tête et payload encodés en base64.
2. Application de l'algorithme de signature (par exemple, HMAC SHA256 ou RSA) avec une clé secrète (pour les algorithmes symétriques tels que HMAC) ou une clé privée (pour les algorithmes asymétriques tels que RSA).
3. Codage Base64Url du résultat pour obtenir la signature.

Exemple de JWT. Consultez le contenu du JWT 

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Création de JWT dans IRIS

Remarque : Avant 2024, la classe %OAuth2.JWT était utilisée pour générer des JWT dans IRIS. La classe %Net.JSON.JWT est désormais la classe principale pour la création de JWT, et j'utiliserai cette classe dans l'exemple de code.

JWK overview

Les JWK représentent une clé cryptographique, en particulier pour la signature et la vérification des JWT. Les JWK permettent de représenter les clés publiques (pour la vérification) et les clés privées (pour la signature) dans un format normalisé qui peut être facilement échangé entre les systèmes. Les JWKS contiennent plusieurs JWKs

Flux de travail JWT

1. Construisez votre en-tête en tant que %DynamicObject et ajoutez des en-têtes personnalisés si nécessaire.

2. Construisez le corps/les revendications directement en tant que %DynamicObject

3. Appelez la méthode Create de la classe %Net.JSON.JWT.

Set sc = ##Class(%Net.JSON.JWT).Create(header, , claims, jwks, , .JWT)

Création de JWK

Set sc = ##Class(%Net.JSON.JWK).Create("HS256","1212ASD!@#!#@$@#@$$#SDFDGD#%+_)(*@$SFFS",.privateJWK,.publicJWK)

Cela renverra la clé privée

{"kty":"oct","k":"MTIxMkFTRCFAIyEjQCRAI0AkJCNTREZER0QjJStfKSgqQCRTRkZT","alg":"HS256"

Quelques propriétés importantes de JWK

"kty": "oct" - représente l'algorithme symétrique
"kty": "RSA" / "kty": "EC" - represente l'algorithme asymétrique

Une fois que le JWK est créé, il peut être ajouté aux JWKS.

Créons des JWKS dans IRIS

Set sc = ##class(%Net.JSON.JWKS).PutJWK(jwk,.JWKS)

Cette méthode renvoie le JWKS

Génération du JWT dans IRIS

Vous pouvez créer des JWT à clé symétrique ou asymétrique dans IRIS. La classe %Net.JSON.JWK est essentiellement utilisée pour générer le JWT. Avant d'appeler la méthode, assurez-vous de créer et d'envoyer les JWKS pour le chiffrement symétrique et asymétrique lors de la génération du JWT.

Encryptage symétrique

Les algorithmes symétriques utilisent une clé secrète partagée, où l'expéditeur et le destinataire utilisent la même clé pour signer et vérifier le JWT. Ces algorithmes, tels que HMAC (HS256, HS512, HS384), génèrent un hachage (signature) pour le payload du JWT. Cette approche n'est pas recommandée pour les systèmes de haute sécurité, car la signature et la vérification sont exposées, ce qui pose des risques potentiels pour la sécurité.

La méthode Create de la classe %Net.JSON.JWK est utilisée pour générer le JWK. Elle accepte deux paramètres d'entrée et renvoie deux paramètres de sortie:

1. algorithm - l'algorithme pour lequel le JWK doit être créé.
2. secert - la clé utilisée pour signer et vérifier le JWT
3. privateJWK - la clé Web JSON privée qui est créée.
4. publicJWK - la clé Web JSON publique qui est créée.

Pour les algorithmes à clé symétrique, vous obtiendrez privateJWK

Pour les algorithmes à clé asymétrique, vous obtiendrez privateJWK et publicJWK

 
SymmetricKeyJWT

Le résultat 

LEARNING>d ##class(Learning.JWT.NetJWT).SymmetricKeyJWT()
privateJWK={"kty":"oct","k":"MTIxMkFTRCFAIyEjQCRAI0AkJCNTREZER0QjJStfKSgqQCRTRkZT","alg":"HS256"}  ; <DYNAMIC OBJECT>
privateJWKS="{""keys"":[{""kty"":""oct"",""k"":""MTIxMkFTRCFAIyEjQCRAI0AkJCNTREZER0QjJStfKSgqQCRTRkZT"",""alg"":""HS256""}]}"
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsIngtYyI6InRlIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.PcCs_I8AVy5HsLu-s6kQYWaGvuwqwPAElIad11NpM_E

Encryptage asymétrique

L'encryptage asymétrique fait référence à l'utilisation d'une paire de clés : une clé pour signer le jeton (clé privée) et l'autre pour le vérifier (clé publique). Il diffère de l'encryptage symétrique

Clé privée : cette clé est utilisée pour signer le jeton JWT. Elle est gardée secrète et ne doit jamais être exposée.
Clé publique : Cette clé est utilisée pour vérifier l'authenticité du JWT. Elle peut être partagée et distribuée en toute sécurité car elle ne peut pas être utilisée pour signer de nouveaux jetons.

Vous pouvez générer l'encryptage asymétrique JWT avec une clé/un certificat privé via %SYS.X509Credentials. Vous devez donc stocker votre certificat dans cette classe persistante.

 
AsymmetricWithx509

JWT dans les applications Web.

À partir de la version de 2023 , IRIS inclut par défaut la création de JWT intégrée pour les applications Web. Assurez-vous que l'authentification JWT est activée lors de la configuration de votre application Web

J'ai ajouté une brève explication  sur la configuration

1. Activez l' Authentication JWTdans votre application Web
2. Créez une classe REST si vous ne l'avez pas déjà fait
3. La ressource endpoint par défaut « /login » est incluse. Effectuez un appel API REST en utilisant l'authentification de base avec le payload comme {"user": "_SYSTEM", "password": "SYS"}.
4. La réponse sera un JSON contenant le "access_token," "refresh_token," et d'autres détails pertinents.
5. Utilisez le token d'accès pour l'autorisation.

0
1 42
Article Guillaume Rongier · Avr 14, 2025 5m read

Les référentiels, applications et serveurs FHIR servent généralement des données cliniques en petites quantités, par exemple pour renvoyer des données sur un patient, ses médicaments, ses vaccins, ses allergies, ou d'autres renseignements. Cependant, il est courant qu'une grande quantité de données au format FHIR/JSON soit demandée pour être utilisée pour charger des Data Lakes, identifier des cohortes étudiées, la santé de la population ou transférer des données d'un DME (dossier médical électronique) à un autre. Pour répondre à ces scénarios d'activités commerciales qui nécessitent d'importantes extractions et charges de données, il est recommandé d'utiliser la fonctionnalité d'accès aux données en masse FHIR fournie par l'institution HL7.

InterSystems IRIS for Health met en œuvre l' ccès aux données de masse FHIR (FHIR Bulk Data Access) avec la fonctionnalité BFC - Bulk FHIR Coordinator (https://docs.intersystems.com/irisforhealthlatest/csp/docbook/DocBook.UI.Page.cls?KEY=HXFHIR_bulk). Selon la documentation d'InterSystems, le BFC « simplifie l'interaction des données de masse FHIR pour les clients et, pour ne pas surcharger un serveur FHIR avec des demandes de données de masse, le Bulk FHIR Coordinator (BFC) d'InterSystems sert d'intermédiaire dans l'interaction entre un client de données de masse et un point d'extrémité de serveur de ressources FHIR pour les demandes de données de masse. Le Bulk FHIR Coordinator peut faciliter l'exportation de données de masse FHIR pour les serveurs de ressources FHIR qui ne prennent pas en charge nativement l'interaction de données de masse". Le diagramme de la documentation illustre comment le coordinateur FHIR en bloc sert d'intermédiaire dans l'interaction entre un client et les endpoints FHIR:

Le Coordinateur Bulk FHIR sert d'intermédiaire entre un client et un endpoint FHIR

Ainsi, si vous souhaitez migrer d'un serveur FHIR vers IRIS for Health, vous pouvez utiliser BFC pour vous connecter à un serveur FHIR cible afin d'obtenir toutes les données dont vous avez besoin sous forme de fichiers json (ressources ndjson), et les importer dans votre serveur FHIR InterSystems.

Dans cet article, vous découvrirez étape par étape les différentes manières de configurer et d'utiliser BFC pour obtenir toutes les données patient d'un référentiel FHIR dans des fichiers JSON en vue d'une utilisation ultérieure.

Étape 1 - Obtenir/configurer une instance IRIS for Health pour utiliser BFC

Si vous n'avez pas d'instance IRIS for Health, cliquez sur le lien suivant pour en obtenir une: https://openexchange.intersystems.com/package/iris-fhir-template. Suivez les procédures d'installation suivantes:

1.1 Installation de Docker:

Clone/git extrait le référentiel dans n'importe quel répertoire local

git clone https://github.com/intersystems-community/iris-fhir-template.git

Ouvrez le terminal dans ce répertoire et lancez:

docker-compose up -d


1.2 Ou installation de l'IPM (nécessite un IRIS for Health en cours d'exécution):

Ouvrez l'installation d'IRIS for Health avec le client IPM installé. Appel dans n'importe quel espace de nom:

USER>zpm "install fhir-server"

Ainsi, le serveur FHIR sera installé dans l'espace de noms FHIRSERVER.

Ou encore, pour une installation programmée, on peut appeler ce qui suit:

set sc=$zpm("install fhir-server")

Étape 2 - Création d'un nouveau justificatif d'identité

1. Access the Management Portal for the namespace FHIRSERVER (http://localhost:32783/csp/sys/%25CSP.Portal.Home.zen?$NAMESPACE=FHIRSERVER).

2. Accédez à l'Interopérabilité > Configuration > Informations d'identification:

3. Paramétrez les valeurs BulkCreds et enregistrez:

  • Identifiant: BulkCreds
  • Nom d'utilisateur: _SYSTEM
  • Mot de passe: SYS

Étape 3 - Configuration du BFC (Bulk Data Coordinator)

1. Accédez à BFC UI (http://localhost:32783/csp/healthshare/fhirserver/bulkfhir/index.html):

2. Appuyez sur le bouton + Nouvelle configuration > Création d'une nouvelle:

3. Configuration des paramètres:

  • Nom: BFC_Patients
  • Endpoint BFC (tout chemin relatif): /bulkfhir/patients
  • Gardez tous les autres paramètres tels quels

4. Appuyez sur le bouton Next.

5. Selectionnez HS.BulkFHIR.Auth.BasicAuth.Adapter et appuyez sur le bouton Next

6. Selectionnez HS.BulkFHIR.Fetch.PureFHIR.Adapter pour le champ Fetch Adapter et définissez les valeurs suivantes:

  • URL de l'endpoint: http://fhir-template:52773/fhir/r4
  • Configuration SSL: BFC_SSL
  • Type d'autorisation: HTTP
  • Identifiant HTTP: BulkCreds
  • Acceptez les valeurs par défaut pour tous les autres champs

7. Appuyez sur le bouton Next.

8. Selectionnez la valeur de HS.BulkFHIR.Storage.File.Adapter pour le champ Storage Adapter et définissez les valeurs suivantes:

  • Storage Adapter: HS.BulkFHIR.Storage.File.Adapter
  • URL du fichier: /bulkfhir/file
  • Répertoire: /usr/irissys/mgr/Temp/BulkFHIR/FHIRSERVER/

9. Appuyez sur le bouton Next.

10. Vérifiez la configuration et cliquez sur le bouton de configuration "Configure" en bas de la page:

11. Vous verrez le message de réussite: 

Étape 4 (étape finale) - Récupération de vos données de masse

1. Accéder à Exportations (Exports):

2. Appuyez sur le bouton + Nouvelle demande d'exportation:

3. Sélectionnez la valeur du champ de configuration BFC_Patients et appuyez sur le bouton Next

4. Sélectionnez l'option Patient et appuyez sur le bouton Export Now (Exportation immédiate):

5. Vous verrez le message de réussite:

6. Appuyez sur le bouton d'actualisation "Refresh" en haut de la page:

7. Consultez la session de configuration BFC_Patients terminée:

8. Appuyez sur le dernier bouton de téléchargement "Download" ():

9. Consultez la liste des fichiers json exportés:

10. Téléchargez n'importe quel fichier et consultez son contenu:

11. Pour lancer une exportation FHIR en masse à partir d'un client REST, envoyez une requête GET à votre endpoint BFC en indiquant l'opération souhaitée, par exemple:

  •     Système — GET https://bfcEndpoint/$export
  •     Patient — GET https://bfcEndpoint/Patient/$export
  •     Groupe — GET https://bfcEndpoint/Group/groupID/$export

12. Plus de détails sur l'utilisation via l'API: https://docs.intersystems.com/healthconnectlatest/csp/docbook/DocBook.UI.Page.cls?KEY=HXFHIR_bulk#HXFHIR_bulk_export_rest_initiate

13. Plus de détails sur l'utilisation de BFC:

  • https://docs.intersystems.com/healthconnectlatest/csp/docbook/DocBook.UI.Page.cls?KEY=HXFHIR_bulk#HXFHIR_bulk_intro
  • https://www.youtube.com/watch?v=J-AVP9MFMWI

Profitez-en!!

0
0 35
Annonce Irène Mykhailova · Mars 25, 2025

Êtes-vous développeur, ingénieur de données, ingénieur d'intégration ou data scientist et travaillez-vous avec les produits InterSystems ? Nous menons des entretiens de 30 à 45 minutes afin de comprendre votre expérience : comment vous avez commencé avec les produits InterSystems, où vous trouvez des exemples de code utiles et comment nous pouvons améliorer nos produits pour mieux vous accompagner dans votre parcours.

Si vous êtes intéressé(e), répondez à un court sondage (en anglais) pour partager vos coordonnées et votre expérience.

0
0 21
InterSystems officiel Adeline Icard · Fév 20, 2025

19 février 2025 – Alerte : les requêtes SQL renvoient des résultats erronés

InterSystems a corrigé deux problèmes pouvant entraîner le renvoi de résultats erronés par un petit nombre de requêtes SQL. De plus, InterSystems a corrigé une incohérence dans la gestion des types de données date/heure qui peut entraîner des résultats différents, inattendus, mais corrects, pour les applications existantes qui s'appuient sur le comportement antérieur et incohérent.

DP-436825 : les requêtes SQL avec jointure latérale peuvent renvoyer des résultats erronés

0
0 31
Article Sylvain Guilbaud · Jan 31, 2025 4m read

Préférez-vous ne pas lire? Regardez la vidéo de démonstration que j'ai créée:

<iframe allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/-OwOAHC5b3s" width="640"></iframe>


En tant que développeur d'interfaces, je reçois souvent des questions qui nécessitent d'étudier de grandes quantités de messages. Par exemple, lors d'une réunion récente, notre chef de projet m'a demandé combien de sites utilisaient réellement notre nouvelle interface de commandement.

D'habitude, j'essaie de copier la sortie de la visionneuse de messages pour la coller dans Excel ou simplement d'exécuter un rapport de messages pour chaque site qui passe des commandes et d'utiliser le nombre de messages renvoyés…

Cette fois-ci, en utilisant l'extension de navigateur Iris Whiz browser extension j'avais des options.

Option 1 - Simple: Exportation de CSV

Une idée mise en œuvre à partir du portail InterSystems Ideas, il suffit de cliquer sur le bouton Export (Exporter) en tant que CSV dans la barre de boutons d'IRIS Whiz pour télécharger la recherche en cours en tant que fichier CSV pour une manipulation facile d'Excel/Sheets.

Option 2 - Chic: Analyse

Dans ce cas, je venais de compléter l'outil d'analyse dans mon extension de navigateur Iris Whiz.

En ajoutant la valeur PV1-3.2 à mes critères de recherche de messages dans la visionneuse de messages (Message Viewer), j'ai pu facilement exécuter le rapport, cliquer sur Analyse et avoir instantanément les renseignements sous la forme d'un simple diagramme en forme de beignet - aucune exportation n'a été nécessaire.

 

 

Ensuite, le chef de projet a voulu savoir quels types d'examens étaient commandés par ces sites. J'ai ajouté la valeur OBR-4.2 à mes critères de recherche et j'ai relancé le rapport. En cliquant sur le bouton d'analyse, j'ai pu voir les sites qui passaient commande et les examens commandés. (Chaque critère de recherche de message est présenté sous la forme d'un graphique en anneau, étiqueté à la fin de la partie graphique de la page d'analyse)

La troisième question se pose.

Quelles commandes sont passées par quels sites?

En cliquant sur le site voulu dans le graphique interactif en anneau, j'ai pu visualiser les données dans la visionneuse de données de la page d'analyse. Un autre clic sur le bouton de filtrage à l'intérieur de cette boîte applique cette sélection de données comme filtre à tous les graphiques - ce qui signifie que le graphique en anneau des examens ne montre plus que les examens commandés pour ce site.

Graphique de site et graphique d'examen filtré par site:

 

Et enfin, la question la plus difficile.

Quand tout cela se produit-il?

Passer en revue les messages dans la page de visualisation des messages pour voir quand les commandes sont passées n'est pas une bonne idée...

Heureusement, j'ai ajouté une chronologie à la page d'analyse.

J'ai supprimé le filtre et cliqué sur le bouton 'Show on Line Graph' (Affichage du graphique linéaire) (activé pour le graphique PV1-3 dans la capture d'écran ci-dessus) afin d'afficher les données du site sur le graphique chronologique en haut de la page.

Une rapide capture d'écran nous a permis d'envoyer ce rapport à nos sites afin qu'ils puissent confirmer le nombre de commandes pour chaque jour et s'assurer que tout fonctionnait comme prévu.
Ces rapports devaient être exécutés chaque semaine, mais heureusement pour moi, cette tâche avait été simplifiée, notamment grâce à la fonction de recherche sauvegardée dans la page de visualisation des messages, qui me permettait de ne jamais avoir à me soucier des critères de recherche à ajouter.

 

Conclusions

1. Données sensibles:

Les données de votre recherche dans la visionneuse de messages (Message Viewer) sont envoyées dans un nouvel onglet du navigateur et disparaissent dès que l'onglet est fermé - vous n'avez donc pas à vous soucier de l'enregistrement de données sensibles dans le navigateur. Si vous souhaitez enregistrer un rapport utilisez la fonctionnalité par défaut d'InterSystems pour les recherches enregistrées et exécutez simplement le rapport à nouveau à une date ultérieure. J'avais prévu un mécanisme d'enregistrement des recherches à partir de la page d'analyse, mais il n'a pas été retenu dans cette version.

2. Vitesse:

La page d'analyse est alimentée par la recherche de messages et je n'ai pas mis de limites strictes à la quantité de données pouvant être affichées. Plus vous ajoutez de messages et de critères de recherche, plus la page d'analyse ralentira. C'est pourquoi j'ai ajouté une fenêtre contextuelle si vous essayez de charger plus de 200 messages, ce qui vous permet de choisir de charger ou non le diagramme à barres en haut de la page. 

Le diagramme à barres présente chaque message sous la forme d'une case à sélectionner. En cliquant sur une case du diagramme, le message est ajouté à la liste des messages sélectionnés dans la fenêtre de visualisation des données (à gauche de la page). Vous pouvez alors cliquer sur le bouton 'View Selected Messages' (Voir les messages sélectionnés) pour ouvrir ces messages dans une nouvelle page et profiter des fonctionnalités de comparaison des messages de l'extension.

Lorsque vous cliquez sur ce bouton, essayez de ne pas sélectionner trop de messages. Un maximum de 10 devrait suffire. 

Si vous téléchargez le diagramme à barres avec de grands ensembles de données (10 000), cela ne sera certainement pas bon pour votre navigateur, mais je vous laisse le soin de choisir.

0
0 45