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 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 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 Sylvain Guilbaud · Oct 7, 2025 5m read

Aperçu Je suis ravi d'annoncer la sortie de testcontainers-iris-node, une bibliothèque Node.js qui facilite le lancement de conteneurs InterSystems IRIS temporaires pour l'intégration et les tests E2E. Ce projet vient naturellement compléter la gamme existante d'adaptateurs Testcontainers pour IRIS, notamment testcontainers-iris-python et testcontainers-iris-java.

Pourquoi testcontainers-iris-node? En tant que développeur Node.js travaillant avec InterSystems IRIS, j'ai souvent été confronté à des difficultés lors de la configuration d'environnements de test imitant la production. testcontainers-iris-node résout ce problème en exploitant le framework testcontainers-node pour créer des environnements IRIS isolés à la demande.

Ceci est particulièrement important pour:

  • Les tests d'intégration avec les bases de données IRIS
  • Les tests de pipelines de données ou de microservices
  • L'automatisation des environnements de test dans les pipelines CI

Fonctionnalités

  • Lancement d'IRIS dans des conteneurs Docker à l'aide de Testcontainers
  • Prise en charge des images et de la configuration Docker personnalisées
  • Stratégies d'attente pour s'assurer qu'IRIS est prêt avant le début des tests
  • Désinstallation de nettoyage entre les exécutions de tests

Pour commencer

npm install testcontainers-iris --save-dev

Exemple d'utilisation

import { IRISContainer } from"testcontainers-iris";
import { createConnection } from"@intersystems/intersystems-iris-native";

const IMAGE = "containers.intersystems.com/intersystems/iris-community:latest-preview"; const container = awaitnew IRISContainer(IMAGE).start(); const connection = createConnection(container.getConnectionOptions()); const iris = connection.createIris(); const version = iris.classMethodString("%SYSTEM.Version", "GetNumber");

Fonctionnement En interne, la bibliothèque étend GenericContainer à partir de testcontainers, ajoute des stratégies d'attente spécifiques à IRIS et fournit des méthodes auxiliaires pour la génération de chaînes de connexion et le remplacement des configurations.

Scénarios pris en charge

  • Suites de tests basées sur Jest ou Mocha
  • Environments CI (GitHub Actions, GitLab CI, Jenkins, etc.)
  • Développement et débogage locaux

Exemple de tests basées sur Mocha Vous pouvez également utiliser cette bibliothèque pour des tests d'intégration robustes avec Mocha. Voici un exemple de configuration:

test-setup.ts

import"source-map-support/register"import"reflect-metadata"import { IRISContainer, StartedIRISContainer } from"testcontainers-iris"import { IRISNative } from"../../src"import chai from"chai"import sinonChai from"sinon-chai"import chaiAsPromised from"chai-as-promised"
declare global {
    var container: StartedIRISContainer | undefinedvar connectionOptions: {
        host: string
        port: number
        user: string
        pwd: string
        ns: string
    }
}

process.env.TZ = "UTC" chai.should() chai.use(sinonChai) chai.use(chaiAsPromised)

before(async () => { console.log("Setting up test environment...") const image = process.env["IRIS_IMAGE"] let connectionOptions = { host: "localhost", port: 1972, user: "_SYSTEM", pwd: "SYS", ns: "USER", } if (image) { const container: StartedIRISContainer = awaitnew IRISContainer(image) .withNamespace("TEST") .start() console.log(IRIS container started at <span class="hljs-subst">${container.getConnectionUri()}</span>) global.container = container connectionOptions = { host: container.getHost(), port: container.getMappedPort(1972), user: container.getUsername(), pwd: container.getPassword(), ns: container.getNamespace(), } } global.connectionOptions = connectionOptions IRISNative.createConnection({ ...connectionOptions, sharedmemory: false }) })

after(async () => { console.log("Cleaning up test environment...") if (global.container) { await global.container.stop() } delete global.container })

Cas de test:

import { IRISNative, IRISConnection } from"../src/IRISNative"
describe("IRISNative test", () => {
    let connection: IRISConnection
    before(() => {
        const connectionOptions = global.connectionOptions
        connection = IRISNative.createConnection({ ...connectionOptions })
    })
    after(() => {
        if (connection) {
            connection.close()
        }
    })
    it("should work", async () => {
        const res = await connection.query(
            "SELECT 1 AS test1, '2' AS test2",
            [],
        )
        res.rows.should.be.an("array")
        res.rows.should.have.lengthOf(1)
        res.rows[0].should.be.an("object")
        res.rows[0].should.have.property("test1")
        res.rows[0].should.have.property("test2")
        res.rows[0].should.have.property("test1", 1)
        res.rows[0].should.have.property("test2", "2")
    })
})

Utilisation dans typeorm-iris Cette bibliothèque est également utilisée dans mon projet typeorm-iris qui fournit une prise en charge expérimentale de TypeORM pour InterSystems IRIS. testcontainers-iris-node alimente la configuration des tests d'intégration pour ce projet, aidant à valider la fonctionnalité ORM par rapport à des instances IRIS réelles.

Problèmes liés à l'adoption de la bibliothèque En tant que développeur chargé de l'adoption de la bibliothèque, l'un de mes plus grands défis consiste à tester plusieurs versions d'InterSystems IRIS. Cet outil simplifie considérablement ce processus en permettant de basculer facilement et d'automatiser des environnements conteneurisés avec des versions IRIS différentes.

Comparaison avec d'autres liaisons linguistiques Alors que testcontainers-iris-python et testcontainers-iris-java sont matures et prennent en charge des fonctionnalités avancées telles que les montages en bind et les scripts de démarrage personnalisés, la variante Node.js est rationalisée pour les environnements JavaScript/TypeScript et vise la simplicité et l'ergonomie pour les développeurs.

Contributions et commentaires Je suis ouvert à tous commentaires, problèmes et demandes de modification via GitHub: testcontainers-iris-node.

Pour concluretestcontainers-iris-node facilite la réalisation de tests automatisés et robustes des applications basées sur IRIS dans l'écosystème Node.js. Que vous développiez des API, des tâches ETL ou des services d'intégration, cet outil vous aide à garantir la fiabilité de vos flux de travail IRIS.

0
1 17
Article Sylvain Guilbaud · Sept 25, 2025 7m read

artisan cover

Si vous avez jamais observé un véritable artisan, qu'il s'agisse d'un potier transformant de l'argile en chef-d'œuvre ou d'un luthier donnant vie à un morceau de bois brut pour en faire une magnifique guitare, vous savez que la magie ne réside pas dans les matériaux, mais dans le soin, le savoir-faire et le traitement. Je le sais d'expérience : ma guitare électrique faite à la main est une source d'inspiration quotidienne, mais je dois avouer que créer quelque chose de ce genre est un don qui me manque.

Pourtant, dans le monde numérique, je vois souvent des gens qui espèrent que l'IA générative leur apporte une "solution miracle" en leur proposant des instructions vagues et hors contexte telles que "créez une application". Les résultats sont généralement décevants, sans aucune finesse ni créativité. Trop de gens attendent de l'IA qu'elle fasse des miracles sans contexte ni structure. Voilà ce qui nous a poussés à créer dc-artisan, un outil destiné aux artisans du prompt numérique. Notre objectif est de permettre à quiconque de transformer des prompts approximatifs et vagues en chefs-d'œuvre efficaces, fonctionnels et riches en contexte..

Tout comme lorsqu'on observe un grand artisan transformer des matières premières en œuvres d'art, créer avec GenAI est une question d'intention, de préparation et de travail minutieux. Le problème ne réside pas dans l'IA en soi, mais dans la manière dont nous l'utilisons. Tout comme un luthier doit sélectionner et façonner chaque morceau de bois avec soin, une ingénierie efficace des prompts nécessite un contexte, une structure et une intention bien définis.

Nous pensons que le monde mérite mieux que des "suggestions magiques" qui mènent à la déception. Une IA générative puissante est le fruit d'un accompagnement humain minutieux : un contexte précis, des objectifs concrets et une structure élaborée. Aucun artisan ne crée de la beauté par hasard : pour être fiables, les résultats d'une IA doivent être préparés avec soin.

dc-artisan aborde l'ingénierie rapide comme un véritable artisanat : de manière systématique, didactique et vérifiable. Il offre une boîte à outils complète pour aller au-delà des essais, des erreurs et des conjectures.

La première chose que fait dc-artisan est de chercher à comprendre votre prompt comme le ferait un collaborateur attentif. Lorsque vous commencez à rédiger, l'outil interagit directement avec vos entrées:

  • Questions de clarification: dc-artisan analyse votre demande initiale et vous pose des questions pertinentes afin de déterminer votre objectif principal, votre audience cible, le format souhaité et tout élément manquant. Par exemple:
    • “Quel type de résultat attendez-vous : un résumé textuel, un code ou des données structurées?”
    • “Quelle est l'audience cible?”
    • “Avec quel type de données ou d'informations ce prompt sera-t-il utilisé?”

prompt enhance

Ces interactions vous aident à clarifier non seulement ce que vous voulez que votre prompt dise, mais aussi pourquoi il le fait.

Une fois votre intention claire, dc-artisan examine la structure et propose des suggestions personnalisées afin d'améliorer la clarté, le ton et de compléter les détails manquants essentiels à un résultat riche en contexte et prêt à l'emploi.

Le meilleur dans tout ça? Vous pouvez utiliser toutes ces fonctionnalités directement dans votre éditeur préféré, VS Code ! Vous pouvez insérer des variables directement dans votre prompt (comme {task} ou {audience}) pour plus de flexibilité et de réutilisabilité, et prévisualiser instantanément le résultat final avec différentes substitutions. Vous voyez ainsi exactement comment cela fonctionnera dans la pratique.

Mais ce n'est pas tout. dc-artisan prend en charge le réglage rapide des prompts pour des performances optimales. Téléchargez un fichier CSV contenant des cas de test afin d'évaluer automatiquement la cohérence, la qualité des résultats et l'impact de la structure de vos prompts sur des entrées variées. dc-artisan évalue chaque réponse et génère des rapports complets avec des scores de qualité et des mesures de similarité, afin que vous puissiez mesurer et optimiser l'efficacité de vos prompts en toute confiance.

testing

Promouvoir sans contexte n'est pas un art, c'est du chaos

L'ingénierie des prompts sans structure revient à sculpter du bois les yeux bandés. Vous obtiendrez peut-être quelque chose, mais cela ne produira probablement pas un air mélodieux.

De nombreuses personnes ont recours à des prompts vagues ou surchargés, c'est-à-dire des commandes courtes et ambiguës ou des pages de contenu brut sans structure. Soit le modèle n'a aucune idée de ce que vous voulez, soit il se perd dans un marécage de bruit.

Lorsque le contexte du prompt devient trop long ou trop confus, même les LLM avancés peuvent se perdre. Au lieu de raisonner ou de générer de nouvelles stratégies, ils se laissent souvent déconcentrer, répétant le contenu précédent ou s'en tenant à des schémas familiers issus du début de l'historique des prompts. Ironiquement, les modèles plus volumineux avec des fenêtres contextuelles plus grandes (comme 32 000 tokens) sont encore plus sensibles à ce phénomène. Le simple fait de fournir plus de contexte (plus de documents, des prompts plus longs, des bases de connaissances entières) se retourne souvent contre vous, entraînant une surcharge contextuelle, des objectifs manqués et des résultats confus.

C'est précisément cette lacune que le RAG ( Génération augmentée par la récupération) vise à remplir : non pas en donnant plus d'informations aux LLM, mais en leur fournissant les connaissances les plus pertinentes au bon moment.

Comment dc-artisan et le mode pipeline RAG peuvent vous aider

dc-artisan unifie la création rapide de prompts et la gestion du contexte. Il vous aide non seulement à rédiger de meilleurs prompts, mais garantit également que votre IA reçoit des informations pertinentes et sélectionnées, et non un flot incessant d'informations futiles.

Avec le mode pipeline RAG vous pouvez:

  • 📄 Téléchargement et fragmentation des documents: PDF, DOCX, Markdown, TXT — à intégrer facilement dans votre base de données vectorielle.
  • 🧬 Inspection des fragments: à visualiser avec précision chaque unité atomique du texte intégré.
  • 🗑️ Nettoyage intelligent: à supprimer directement les contenus indésirables ou obsolètes depuis l'extension, afin que la base de connaissances de votre IA reste organisée et pertinente.

rag

Ce flux de travail est inspiré par le portail InterSystems Ideas Portal (see DPI-I-557)

Voici comment vous pouvez intégrer en toute simplicité une nouvelle section sur l'architecture backend de dc-artisan juste avant la section "Conclusion", en mettant en avant l'intégration avec l'interopérabilité InterSystems IRIS et notre adaptateur liteLLM personnalisé.

Ce qui distingue vraiment dc-artisan, c'est son backend robuste, conçu pour offrir à la fois interopérabilité et flexibilité. Le moteur de l'extension fonctionne sur L'interopérabilité InterSystems IRIS, en utilisant un adaptateur liteLLM personnalisé que nous avons développé.

Cette architecture signifie que vous n'êtes pas lié à un seul fournisseur de modèles linguistiques de grande taille (LLM). Au contraire, vous pouvez vous connecter et passer de manière transparente entre une large gamme de plateformes LLM de premier plan, notamment OpenAI, Gemini, Claude, Azure OpenAI, et bien d'autres, toutes gérées à partir d'un backend unifié de niveau entreprise.

Conclusion

De plus en plus de développeurs découvrent que la suggestion ne consiste pas à deviner les "mots magiques". Il s'agit plutôt de définir des objectifs réfléchis, d'utiliser un langage clair et de fournir un contexte pertinent, en rédigeant des prompts comme des ingénieurs, et non comme des magiciens. Tout comme les luthiers façonnent le bois pour créer des instruments dotés d'une âme, vous pouvez façonner des prompts pour créer des flux de travail IA à la fois fiables et riches en contexte à l'aide d'outils conçus spécialement pour votre métier.

dc-artisan est plus qu'un outil, c'est un changement de mentalité qui passe du codage intuitif à la clarté, la précision et au véritable art numérique.

🎸 Prêt à créer vos propres prompts?
⚙️ Lancez VS Code, installez dc-artisan et commencez à façonner votre IA comme un artisan, pas comme un magicien.

🗳️ Et si vous aimez ce que nous avons créé, votez pour nous dans le cadre du concours InterSystems IRIS Dev Tools Contest. Votre soutien compte beaucoup pour nous!

dc-artisan

0
0 21
Annonce Sylvain Guilbaud · Sept 5, 2025

#InterSystems Demo Games


⏯️  Éliminer le gaspillage dans les chaînes d'approvisionnement des soins de santé
 

Entre les rappels de fournitures, les stocks périmés et les dépenses inconsidérées, on estime que 25 milliards de dollars par an sont gaspillés dans les chaînes d'approvisionnement du secteur de la santé. Nous savons à quel point la chaîne d'approvisionnement est cruciale dans le secteur de la santé ; nous savons donc que ce problème mérite d'être résolu ! À quoi pourrait ressembler une solution ? Regardez et découvrez-la !

Présentateurs:
🗣 @Georgia Gans, Sales Engineer, InterSystems
🗣 @Annie Tong, Sales Engineer, InterSystems
🗣 @Boris Mamkin, Senior Sales Engineer, InterSystems

0
0 32
Article Sylvain Guilbaud · Août 29, 2025 1m read

Rubrique FAQ InterSystems

Par défaut, l'ordre des colonnes d'une table est déterminé automatiquement par le système. Pour modifier cet ordre, définissez explicitement l'ordre de chaque propriété à l'aide du mot-clé SqlColumnNumber lors de la définition de la classe.

Exemple :

Property Name As %String [SqlColumnNumber = 2];

Veuillez consulter la documentation ci-dessous.

SqlColumnNumber

Si vous souhaitez modifier le nom de la table SQL, spécifiez SqlTableName. Si vous souhaitez modifier le nom de la colonne (nom du champ), spécifiez SqlFieldName.

0
0 16
Article Sylvain Guilbaud · Août 14, 2025 3m read

Les données sont au cœur de la transformation numérique qui bouleverse le secteur de la santé. Ce changement radical nécessite de nouvelles bases pour gérer les énormes besoins en données des soins de santé modernes.

Le délai de mise sur le marché est crucial pour développer les prochaines avancées thérapeutiques, les connaissances génomiques et les flux de travail cliniques intelligents. Vous devez les mettre en œuvre dès maintenant.

0
0 13
Article Sylvain Guilbaud · Août 5, 2025 9m read

Bonjour, chers membres de notre communauté de développeurs!

Dans l'article d'aujourd'hui, nous allons examiner l'une des dernières fonctionnalités de télésurveillance de nos instances IRIS qui a été ajoutée au produit. Il s'agit de la compatibilité avec OpenTelemetry.

Que qu'est-ce que OpenTelemetry?

OpenTelemetry est un framework open source qui fournit les outils nécessaires, tels que des SDK et des normes, pour mettre en œuvre l'observabilité dans les systèmes et les applications.

Cette observabilité s'étend à trois types de données:

  1. Traces : contrôle du flux d'informations circulant dans les solutions grâce à l'inclusion de traces permettant d'identifier où elles passent et sous quelles conditions.
  2. Métriques : état du système, performances, latences dans les réponses, utilisation des ressources, etc.
  3. Journaux : inclus dans les systèmes pour une meilleure compréhension des événements qui se produisent.

OpenTelemetry utilise la norme ouverte OTLP qui définit comment toute la télémétrie précédemment définie doit être sérialisée et transportée. L'envoi de cette télémétrie peut être effectué via HTTP ou gRPC.

Open Telemetry avec IRIS

InterSystems IRIS exploite les fonctionnalités disponibles via OpenTelemetry SDK pour permettre l'exportation de toutes les données de télémétrie générées par l'instance configurée. D'où proviennent ces données de télémétrie? 

  1. Métriques : elles proviennent des informations dont nous disposons via l'API REST /api/monitor ( vous pouvez consulter la documentation officielle de cette API ici ).
  2. Journaux : messages enregistrés dans le fichier messages.log et informations stockées dans la base de données d'audit (si elle est activée).
  3. Traces : traces définies par l'utilisateur dans les applications développées dans l'instance.

Voyons maintenant comment configurer OpenTelemetry dans une instance IRIS

Configuration d'IRIS avec OpenTelemetry

Pour notre exemple de configuration, j'ai utilisé un projet que j'ai téléchargé sur GitHub et qui fonctionne sous Docker, mais il ne serait pas très compliqué de le configurer sur une instance installée sur site. Ce projet est disponible sur OpenExchange, associé à cet article.

Avant de présenter les différentes configurations, nous allons expliquer quels éléments feront partie de notre "écosystème de surveillance":

InterSystems IRIS

L'instance IRIS se chargera de générer les données de télémétrie que nous devons surveiller.

OpenTelemetry Collector

Il s'agit d'un outil fourni par OpenTelemetry chargé de collecter des données télémétriques provenant de différentes sources. Dans notre exemple, il s'agira uniquement d'IRIS, mais nous pourrions en ajouter autant que nécessaire.

Prometheus

Outil open source utilisé pour la surveillance des systèmes et la génération d'alertes. Cet outil sera chargé de recevoir les métriques accumulées par OpenTelemetry Collector.

Jaeger

Plateforme open source pour la gestion et la surveillance des traces des systèmes basés sur des microservices.

Configuration en Docker

Comme je l'ai mentionné précédemment, j'ai utilisé un déploiement en Docker afin de simplifier au maximum l'exemple. Analysons le fichier docker-compose.yml par parties pour mieux le comprendre.

Image d'IRIS Docker

  iris:    init:true    container_name:iris    build:      context:.      dockerfile:iris/Dockerfile    ports:      -52774:52773      -51774:1972    volumes:    -./iris/shared:/iris-shared    environment:    -ISC_DATA_DIRECTORY=/iris-shared/durable    -OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318    command:--check-capsfalse--ISCAgentfalse

Comme vous pouvez le voir, je définis l'image à utiliser dans un fichier Dockerfile qui ne présente pas grand intérêt et je ne vais donc pas m'attarder dessus. Le point intéressant de cette configuration est la définition d'une variable d'environnement appelée  OTEL_EXPORTER_OTLP_ENDPOINT  , qui sera l'URL où notre OpenTelemetry Collector attendra qu'IRIS lui envoie toutes les métriques, traces et journaux dont il dispose.

Une fois que nous aurons déployé notre instance IRIS, nous devrons la configurer pour qu'elle émette régulièrement les métriques et les journaux. Pour ce faire, nous accéderons à la configuration du moniteur depuis le portail de gestion:

Comme vous pouvez le voir, nous pouvons activer l'envoi des métriques et des journaux. Dans le cas des traces, celles-ci ne sont pas envoyées à intervalles réguliers, mais dès que la méthode "End" de l'instance de la classe %Trace.Provider est invoquée. Je n'entrerai pas plus dans les détails, mais vous pouvez consulter  the official documentation ici la documentation officielle.

Image d'OpenTelemetry Collector

otel-collector:
    build:
      context: .
      dockerfile: open-telemetry/Dockerfile
    container_name: otel-collector
    command: ["--config=/otel-local-config.yml"]
    volumes:
      - ./open-telemetry/otel-collector-config.yml:/otel-local-config.yml
    ports:
      - 4317:4317      # OTLP gRPC receiver
      - 4318:4318    # OTLP HTTP receiver
      - 9464:9464      # Metrics
    depends_on:
      - iris

Voici l'image d'OpenTelemetry Collector. J'ai à nouveau défini un fichier Dockerfile pour indiquer où obtenir l'image (ce n'est pas obligatoire). Comme vous pouvez le voir, nous rendons trois ports accessibles:

  • 4317: port pour la réception des métriques, des traces et des journaux via gRPC.
  • 4318: port pour la réception des métriques, des traces et des journaux via HTTP.
  • 9464: port ouvert pour que des outils tiers puissent consulter les informations reçues par OpenTelemetry Collector.

Nous avons également déclaré un fichier de configuration,  otel-local-config.yml  (le nom est modifiable). Jetons un œil à son contenu:

receivers:  otlp:    protocols:      grpc:        endpoint:"0.0.0.0:4317"      http:        endpoint:"0.0.0.0:4318"exporters:  otlp:    endpoint:jaeger:4317    tls:      insecure:true  prometheus:    endpoint:"0.0.0.0:9464"  debug:{}service:  pipelines:    traces:      receivers:[otlp]      exporters:[otlp]    metrics:      receivers:[otlp]      exporters:[prometheus]    logs:      receivers:[otlp]      exporters:[debug]

Que voyons-nous ici? C'est très simple, nous avons les sections suivantes: 

  • Récepteurs de données  , que nous avons appelés otlp dans notre cas, dans lesquels nous configurons l'adresse IP et les ports sur lesquels notre récepteur va attendre les informations provenant de systèmes tiers.
  • Exporteurs de données : où nous allons envoyer ou qui va extraire les données reçues dans l'OpenTelemetry Collector. Pour notre exemple, nous avons utilisé un exporteur déjà inclus dans OpenTelemetry, à savoir, Prometheus, which  qui se chargera d'obtenir les métriques dans l'instance locale de l'OpenTelemetry Collector sur le port 9464. Dans le cas de Jaeger , nous utilisons directement la capacité d'OpenTelemetry à envoyer des données directement à une IP (jaeger est le nom de l'instance au sein du réseau monté par Docker) qui correspondra à notre instance jaeger.
  • Services , où nous indiquerons lesquels des composants que nous avons configurés comme récepteurs et exporteurs se chargeront de la réception et de l'émission des métriques, des traces et des journaux. Comme vous pouvez le voir dans notre cas OpenTelemetry Collector sera le récepteur que nous utiliserons pour tout, Prometheus sera le récepteur des métriques et Jaeger, via OpenTelemetry Collector, sera le récepteur des traces.

Image de Prometheus Docker

Let's take a look at its configuration in Docker:

prometheus:    build:      context:.      dockerfile:prometheus/Dockerfile    container_name:prometheus    volumes:      -./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml    ports:      -9090:9090

Comme vous pouvez le constater, c'est très simple: notre image est à nouveau définie dans un fichier Dockerfile qui fait uniquement référence au nom de l'image, à un port 9090 où sera déployée l'interface web à laquelle nous pourrons accéder, et enfin à un fichier de configuration appelé  prometheus.yml 

Voyons ce que nous dit ce fichier prometheus.yml:

global:  scrape_interval:15sscrape_configs:  - job_name:'otel-collector'    static_configs:      - targets:['otel-collector:9464']

Nous avons la configuration suivante:

  • Scrape interval : intervalle de temps entre deux requêtes à OpenTelemetry Collector.
  • Scrape configs : lieu où nous donnons un nom à la tâche qui effectuera la recherche, ainsi que l'adresse IP et le port auxquels elle se connectera pour cette recherche.

Image de Jaeger Docker

Pour l'image de Jaeger, j'ai directement repris un exemple disponible sur notre cher chatGPT:

jaeger:
    image: jaegertracing/all-in-one:latest
    container_name: jaeger
    environment:
      - COLLECTOR_OTLP_ENABLED=true
    ports:
      - 16686:16686    # Jaeger UI
      - 14250:14250    # OTLP gRPC receiver

Le point le plus important, outre le port 16689 que nous utiliserons pour accéder à l'interface web, est la variable d'environnement COLLECTOR_OTLP_ENABLED, qui activera les ports par défaut de Jaeger pour permettre les connexions HTTP depuis OpenTelemetry Collector.  Ici  vous pouvez voir la liste des ports utilisés, mais je vous informe que le port 4317 est utilisé pour la transmission gRCP et le port 4318 pour le HTTP. Comme vous l'avez vu dans la configuration d'OpenTelemetry Collector, nous allons utiliser la connexion via gRCP.

Maintenant que tout est prêt, voyons comment cela fonctionne en démarrant le projet.

Émission des métriques, réception dans Prometheus

Nos instances sont désormais configurées et démarrées. Accédons à l'interface web fournie par Prometheus.  Dans notre exemple, nous l'avons mappée à http://localhost:9090 . Par défaut, elle nous indique où nous pouvons lancer des requêtes sur les métriques disponibles.

Si nous avons correctement configuré la connexion entre IRIS - OpenTelemetry Collector - Prometheus, depuis l'écran de requêtes de Prometheus, nous aurons accès à toutes les métriques standard disponibles dans IRIS, comme vous pouvez le voir dans la capture d'écran suivante en recherchant "iris_"

Si nous sélectionnons l'une d'entre elles, nous pouvons voir un graphique au fil du temps

Envoi de traces à Jaeger

Pour vérifier le fonctionnement de l'envoi des traces, nous allons utiliser la ressource la plus simple mise à notre disposition par IRIS, à savoir la méthode TestTraces() de la classe SYS.Monitor.OTel que vous pouvez consulter ici . Si vous souhaitez obtenir plus de détails, n'hésitez pas à me le faire savoir dans les commentaires et je me ferai un plaisir de rédiger un article à ce sujet.

Nous se limitons à exécuter la commande depuis la console à partir de l'espace de noms SYS:

%SYS>do##class(SYS.Monitor.OTel).TestTraces()

Cela nous renverra une trace qui devrait avoir été reçue par Jaeger. Vérifions cela depuis son interface graphique affichée sur http://localhost:1668

Dans les filtres du menu de gauche, nous pouvons voir que nous avons un service appelé  irisotel , Ce service est utilisé par IRIS pour tester l'envoi de traces de test, d'où le nom de la trace reçue test_trace .

Et voilà! Notre instance est désormais prête à envoyer toutes les données métriques et traces que nous souhaitons. Je reste à votre disposition si vous souhaitez approfondir le sujet.

0
0 29
Article Sylvain Guilbaud · Juil 18, 2025 11m read

🛠️ Gestion des configurations d'InterSystems API Manager (IAM = Kong Gateway) en CI/CD

🔍 Contexte : configurations d'InterSystems IAM

Dans le cadre de l'intégration d'InterSystems IAM dans un environnement sécurisé et contrôlé, InterSystems IAM repose sur Kong Gateway pour gérer les API exposées. Kong agit comme une API Gateway moderne, capable de gérer l’authentification, la sécurité, la gestion du trafic, les plugins, et bien plus encore.

0
0 24
Article Sylvain Guilbaud · Juil 8, 2025 3m read

Si vous migrez d'Oracle vers InterSystems IRIS, comme beaucoup de mes clients, vous risquez de rencontrer des modèles SQL spécifiques à Oracle nécessitant une conversion.

Prenons l'exemple suivant:

SELECT (TO_DATE('2023-05-12','YYYY-MM-DD') - LEVEL + 1) AS gap_date
FROM dual
CONNECT BY LEVEL <= (TO_DATE('2023-05-12','YYYY-MM-DD') - TO_DATE('2023-05-02','YYYY-MM-DD') + 1);

Dans Oracle:

  • LEVEL est une pseudo-colonne utilisée dans les requêtes hiérarchiques (CONNECT BY). Elle commence à 1 et s'incrémente de 1.
  • CONNECT BY LEVEL <= (...) détermine le nombre de lignes à générer.
  • La différence entre les deux dates plus un donne 11, donc la requête génère 11 lignes, en comptant à rebours à partir du 12 mai 2023 jusqu'au 2 mai 2023.

Répartition du résultat:

LEVEL = 1  → 2023-05-12
LEVEL = 2  → 2023-05-11
...
LEVEL = 11 → 2023-05-02

La question est maintenant de savoir comment obtenir ce résultat dans InterSystems IRIS, qui ne prend pas en charge CONNECT BY?

Une solution consiste à implémenter une requête de type SQL à l'aide d'ObjectScript qui imite ce comportement. Vous trouverez ci-dessous un exemple de définition CREATE QUERY qui accepte une date de début STARTDATE et un nombre de jours DAYS, et renvoie la liste des dates par ordre descendant.


✅ InterSystems IRIS: mise en œuvre d'une requête de l'intervalle de date

CREATE QUERY GET_GAP_DATE(IN STARTDATE DATE, IN DAYS INT)
  RESULTS (GAP_DATE DATE)
  PROCEDURE
  LANGUAGE OBJECTSCRIPT

Execute(INOUT QHandle BINARY(255), IN STARTDATE DATE, IN DAYS INT) { SET QHandle("start") = STARTDATE SET QHandle("days") = DAYS SET QHandle("level") = 1 RETURN $$$OK }

Fetch(INOUT QHandle BINARY(255), INOUT Row %List, INOUT AtEnd INT) { IF (QHandle("level") > QHandle("days")) { SET Row = "" SET AtEnd = 1 } ELSE { SET Row = $ListBuild(QHandle("start") - QHandle("level") + 1) SET QHandle("level") = QHandle("level") + 1 } RETURN $$$OK }

Close(INOUT QHandle BINARY(255)) { KILL QHandle QUIT $$$OK }

Vous pouvez exécuter la commande CREATE QUERY Vous pouvez exécuter la commande CREATE QUERY ci-dessus dans le portail IRIS System Management, ou via un outil tel que DBeaver ou un éditeur Python/Jupyter Notebook utilisant JDBC/ODBC.


🧪 Exemple d'utilisation:

Pour générer le même résultat que la requête Oracle ci-dessus, utilisez:

SELECT * FROM GET_GAP_DATE(
  TO_DATE('2023-05-12', 'YYYY-MM-DD'),
  TO_DATE('2023-05-12', 'YYYY-MM-DD') - TO_DATE('2023-05-02', 'YYYY-MM-DD') + 1
);

Cela donnera le résultat suivant:

GAP_DATE
----------
2023-05-12
2023-05-11
...
2023-05-02
(11 rows)

🔁 Utilisation avancée: Jointure avec d'autres tables

Vous pouvez également utiliser cette requête comme sous-requête ou dans des jointures:

SELECT * 
FROM GET_GAP_DATE(TO_DATE('2023-05-12', 'YYYY-MM-DD'), 11) 
CROSS JOIN dual;

Cela vous permet d'intégrer des plages de dates dans des flux SQL plus importants.


J'espère que cela sera utile à tous ceux qui sont confrontés à des scénarios de migration d'Oracle vers IRIS ! Si vous avez mis au point des solutions alternatives ou si vous proposez des améliorations, n'hésitez pas à me faire part de vos commentaires.

0
0 31
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 Sylvain Guilbaud · Mai 30, 2025 3m read

Kong fournit en open source un outil de gestion de ses configurations (écrit en Go), appelé decK (pour declarative Kong)

  • Vérifiez que decK reconnaît votre installation Kong Gateway via deck gateway ping
deck gateway ping   
Successfully connected to Kong!
Kong version:  3.4.3.11
  • Exporter la configuration de Kong Gateway dans un fichier "kong.yaml" via deck gateway dump
deck gateway dump -o kong.yaml
  • Après avoir modifié les adresses IP dans le fichier kong.yaml, afficher les différences via deck gateway diff
0
0 25
Article Sylvain Guilbaud · Mai 27, 2025 7m read

Je sais que ceux qui découvrent VS Code, Git, Docker, FHIR et d'autres outils peuvent parfois avoir des difficultés à configurer l'environnement. J'ai donc décidé de rédiger un article qui explique étape par étape l'ensemble du processus de configuration afin de faciliter les premiers pas.

Je vous serais très reconnaissant de bien vouloir laisser un commentaire à la fin de cet article pour me faire savoir si les instructions étaient claires, s'il manquait quelque chose ou si vous avez d'autres suggestions qui pourraient être utiles.

La configuration comprend:

✅ VS Code – Éditeur de code
✅ Git – Système de contrôle de version
✅ Docker – Lancement d'une instance de la communauté IRIS for Health
✅ Extension VS Code REST Client – Pour l'exécution de requêtes API FHIR
✅ Python – Pour l'écriture de scripts basés sur FHIR
✅ Jupyter Notebooks – Pour les tâches liées à l'IA et au FHIR

Avant de commencer: Vérifiez que vous disposiez des privilèges d'administrateur sur votre système.

Outre la lecture du guide, vous pouvez également suivre les étapes décrites dans les vidéos:

Pour Windows

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

Pour macOS

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

Vous trouverez un sondage à la fin de l'article, merci de partager vos progrès. Vos commentaires sont très appréciés.

Alors, c'est parti!

1. Installation de Visual Studio Code (VS Code)

VS Code sera l'éditeur principal pour le développement.

Windows et amp; macOS

  1. Accédez à la page de téléchargement de VS Code: https://code.visualstudio.com/
  2. Téléchargez le programme d'installation pour votre système d'exploitation:
    • Windows: .exe file
    • macOS: .dmg file
  3. Exécutez le programme d'installation et suivez les instructions.
  4. (Windows uniquement) : pendant l'installation, cochez la case Add to PATH" (Ajouter au PATH).
  5. Vérifiez l'installation:
  • ouvrez un terminal (Command Prompt, , PowerShell ou Terminal macOS)
  • Exécutez:
code --version
  • Vous devriez voir le numéro de version.

2. Installation de Git

Git est nécessaire pour le contrôle de version, le clonage et la gestion des référentiels de code.

Windows

  1. Téléchargez la dernière version à partir de: https://git-scm.com/downloads
  2. Exécutez le programme d'installation:
    • Choisissez "Use Git from the Windows Command Prompt" (Utiliser Git à partir de l'invite de commande Windows).
    • Conservez les paramètres par défaut et terminez l'installation.
  3. Vérifiez l'installation:
git --version

macOS

  1. Ouvrez le terminal et exécutez:
git --version

Si Git n'est pas installé, macOS vous demandera d'installer les outils de ligne de commande Suivez les instructions.

3. Installation de Docker

Docker est indispensable pour exécuter InterSystems IRIS for Health Community.

Windows

1.    Téléchargez Docker Desktop à partir de: https://www.docker.com/products/docker-desktop
2.    Exécutez le programme d'installation et suivez les instructions.
3.    Redémarrez votre ordinateur après l'installation.
4.    Activez WSL 2 Backend (si cela vous est demandé).
5.    Verifiez l'installation

À remarquer: L'installation de Docker nécessite des privilèges d'administrateur sur votre machine et au moins un redémarrage.

macOS

1.    Téléchargez Docker Desktop pour Mac à partir de: https://www.docker.com/products/docker-desktop
2.    Installez-le en glissant Docker.app dans le dossier Applications.
3.    Ouvrez Docker à partir du menu Applications.

Pour vous assurer que le moteur Docker Desktop fonctionne sous Windows ou macOS, procédez comme suit:

Démarrez Docker Desktop

Windows: Ouvrez Docker Desktop à partir du menu Start. L'icône Docker en forme de baleine devrait apparaître dans votre barre d'état système.

Mac: Lancez Docker Desktop à partir du dossier Applications. Une fois l'application lancée, l'icône Docker en forme de baleine apparaîtra dans la barre de menu.

Attendez l'initialisation

Une fois que vous avez lancé Docker Desktop, le moteur peut prendre un certain temps à démarrer. Recherchez un message d'état indiquant que Docker est "running" (en cours d'exécution) ou "started" (démarré).

Vérifiez via le terminal/Command Prompt:

Ouvrez le terminal (ou Command Prompt/PowerShell pour Windows) et exécutez:

docker --version

ou

docker info

Dépannage

Si le moteur ne fonctionne pas, essayez de redémarrer Docker Desktop ou vérifiez s'il y a des messages d'erreur dans l'interface utilisateur de Docker Desktop. Assurez-vous également que votre système répond aux exigences de Docker Desktop. Vous pouvez voir des messages d'erreur confus faisant référence à des pipes si vous essayez de créer une image Docker sans que Docker Desktop soit en cours d'exécution.

4. Création de l'image IRIS for Health et son exécution à l'aide de Docker

Avant de pouvoir démarrer un conteneur Docker exécutant IRIS for Health Community (qui comprend notre serveur FHIR), nous devons le créer.

  1. Clonez le référentiel FHIR dans un répertoire approprié de votre système de fichiers. Ouvrez un terminal dans VS Code et clonez ce référentiel à l'aide de la commande suivante:
    git clone https://github.com/pjamiesointersystems/Dockerfhir.git
     
  2. Accédez à ce répertoire et ouvrez le dossier dans VS Code. Suivez les instructions du fichier readme pour créer et exécuter le conteneur. Une étape essentielle consiste à vous assurer que le référentiel de base est disponible dans votre magasin Docker. Vous pouvez le faire à l'aide de la commande dans le terminal VS Code:
    docker pull containers.intersystems.com/intersystems/irishealth-community:latest-em
    Vous devriez recevoir une confirmation après quelques minutes.
  3. Accédez au répertoire dans VS Code où se trouve le fichier docker-compose.yaml, puis exécutez la commande suivante:
    docker-compose build 
    Cela lancera le processus de compilation, qui peut prendre jusqu'à 10 minutes, au cours desquelles un référentiel FHIR complet sera créé et chargé avec un échantillon de patients. 
  4. Une fois le processus de compilation terminé, lancez le conteneur à l'aide de la commande suivante:
    docker-compose up -d
    suivi par
    docker ps
    Vous devriez voir un conteneur nommé **iris-fhir** en cours d'exécution. Si le conteneur ne démarre pas, vérifiez les journaux:
    docker logs iris-fhir
     

5. Installation de l'extension VS Code REST Client

Cette extension vous permet d'envoyer des requêtes API FHIR depuis VS Code.

  1. Ouvrez VS Code.
  2. Accédez aux extensions (Ctrl + Shift + X or Cmd + Shift + X on macOS).
  3. Recherchez "REST Client". Il existe plusieurs clients REST, veuillez installer celui-ci:
  4. Clickez le bouton "Install".

6. Installation de Python

Python est indispensable pour les tâches de programmation liées à FHIR.

Windows

1.    Télécharger Python à partir de: https://www.python.org/downloads/
2.    Exécutez le programme d'installation et cochez la case "Add to PATH" (Ajouter au chemin). Vous aurez besoin d'informations d'identification administratives pour apporter des modifications au chemin d'accès
3.    Terminez l'installation.
4.    Verifiez l'installation:

python --version

macOS

  1. Ouvrez le Terminal et installez Python via Homebrew:
    Brew install python
    Si vous n'avez pas d'Homebrew, installez-le d'abord:
    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
     
  2. Verifiez l'installation:
python3 --version

7. Installation des blocs-notes Jupyter

Les notebooks Jupyter sont utilisés pour l'IA et le FHIR, ainsi que pour les tâches  FHIR SQL.

Windows & macOS

  1. Ouvrez le terminal (Command Prompt, PowerShell, ou macOS Terminal).
  2. Installez Jupyter à l'aide de pip:
    pip install jupyter
    jupyter --version
     
  3. Exécutez le bloc-note Jupyter:
jupyter notebook

Jupyter s'ouvrira alors dans votre navigateur web.

8.  Validation

Exécutez votre conteneur en accédant à votre fichier Docker Compose dans le shell. Exécutez la commande 

docker compose up -d
docker ps

Accédez au Portail de gestion d'IRIS:

  • Ouvrez votre navigateur et accédez à: http://localhost:8080/csp/sys/UtilHome.csp
  • Informations d'identification par défaut:

Nom d'utilisateur: _SYSTEM
Mot de passe: ISCDEMO

Accédez à l'API FHIR

  • Ouvrez votre navigateur et accédez à: http://localhost:8080/csp/healthshare/demo/fhir/r4/metadata

Dernières vérifications

Exécutez les commandes suivantes pour vérifier toutes les installations:

code --version       # VS Code
git --version        # Git
docker --version     # Docker
python --version     # Python
jupyter --version    # Jupyter

Si tout fonctionne, vous avez installé correctement tous les logiciels ci-dessus.

Dépannage

ProblèmeSolution
"Command not found" (Commande introuvable) pour n'importe quel outilVérifiez qu'il a bien été ajouté au PATH (chemin) (réinstallez-le si nécessaire).
Docker ne fonctionne pas sous WindowsRedémarrez Docker Desktop et vérifiez que WSL 2 backend est activé.
Le conteneur IRIS ne démarre pasExécutez docker logs iris-fhir pour vérifier les erreurs.
Impossible d'accéder à l'API FHIR Vérifiez que le conteneur est en cours d'exécution (docker ps).

Merci de votre aide. J'attends vos commentaires avec impatience!

0
0 30
Article Sylvain Guilbaud · Mai 19, 2025 3m read

Bonjour à la communauté,

Vous cherchez un moyen d'intégrer InterSystems IRIS® for Health à votre équipe ? Exploitez tout le potentiel de cette plateforme grâce à ces ressources de formation InterSystems. Grâce à une combinaison de formations en ligne et en présentiel, vous pouvez accompagner différents rôles au sein de votre organisation et préparer votre équipe à la réussite.

Ressources d'intégration pour chaque rôle

Développeurs

0
1 52
Article Sylvain Guilbaud · Mai 19, 2025 3m read

Bonjour à la communauté,

Exploitez tout le potentiel d'InterSystems IRIS et accompagnez votre équipe dans son intégration grâce à la gamme complète de ressources de formation InterSystems, disponibles en ligne et en présentiel, adaptées à chaque rôle de votre organisation. Les développeurs, administrateurs système, analystes de données et intégrateurs peuvent rapidement se familiariser avec la solution.

Ressources d'intégration pour chaque rôle

Développeurs

0
0 54
Article Sylvain Guilbaud · Mai 9, 2025 9m read

L'indication de requête parallèle augmente les performances de certaines requêtes sur les systèmes multiprocesseurs par le biais du traitement parallèle. L'optimiseur SQL détermine les cas où cela est bénéfique. Sur les systèmes à un seul processeur, cette indication n'a aucun effet.

Le traitement parallèle peut être géré par:

  1.  Définition de l'option auto parallel pour l'ensemble du système.
    
  2. L'utilisation du mot-clé %PARALLEL dans la clause FROM de certaines requêtes.
    

Le mot clé %PARALLEL est ignoré lorsqu'il est appliqué aux requêtes suivantes:

  1. Les requêtes INSERT, UPDATE et DELETE (cette fonctionnalité ne s'applique qu'aux requêtes SELECT)
  2. Les requêtes impliquant des fonctions ou des variables spécifiques au processus
  3. Une sous-requête corrélée à une requête englobante.
  4. Une sous-requête contenant des prédicats complexes, tels que les prédicats FOR SOME et FOR SOME %ELEMENT.

En plus des raisons mentionnées précédemment, voici quelques raisons pour lesquelles le traitement parallèle des requêtes peut être ignoré:

  • Certaines requêtes complexes ne bénéficient pas d'un traitement parallèle, même si elles semblent en bénéficier au départ.
  • Certaines configurations et paramètres de base de données ne supportent pas le traitement %PARALLEL.
  • Les dépendances et les relations au sein de la structure des données peuvent empêcher une parallélisation efficace.

Dans des scénarios suivants, %PARALLEL n'effectuera pas de traitement parallèle:

  1.  La requête inclut à la fois les clauses TOP et ORDER BY, en optimisant le temps le plus rapide pour atteindre la première ligne.
    
  2.  La requête fait référence à une vue et renvoie un identifiant de vue.
    
  3.  La requête utilise des formats de stockage personnalisés ou des tables GLOBAL TEMPORARY ainsi que des tables avec un stockage de référence global étendu.
    
  4.  La requête accède à une table avec une sécurité au niveau de la ligne.
    
  5.  Les données sont stockées dans une base de données distante.
    
  6.  La collation NLS au niveau du processus ne correspond pas à la collation NLS de tous les globaux impliqués.
    

Pour plus de détails sur les options, les considérations et les restrictions, reportez-vous à Configure Parallel Query Processing (Interystems Documentation) et Specify Optimization Hints in Queries (Configuration du traitement parallèle des requêtes (Documentation Interystems) et Spécifier les conseils d'optimisation dans les requêtes). Ce sujet a été récemment exploré dans le cadre d'une discussion au sein de la Communauté de développeurs d'InterSystems (DC), qui a inspiré cet article sur IRIS, Cache et Ensemble.

InterSystems IRIS supporte le traitement parallèle à la fois pour le SQL intégré, le SQL dynamique et le SQL dans les QueryMethods. Lorsque le mot-clé %PARALLEL est utilisé dans la clause FROM d'une requête pour suggérer un traitement parallèle. L'optimiseur SQL déterminera si la requête peut bénéficier d'un traitement parallèle et l'appliquera le cas échéant.

Pour utiliser efficacement le traitement %PARALLEL dans InterSystems IRIS, plusieurs paramètres et restrictions doivent être pris en compte, tant au niveau du système qu'au niveau de la requête, afin d'en tirer tous les avantages.

Dans le cas où vous essayez d'obtenir le traitement %PARALLEL à l'aide du traitement parallèle des requêtes à l'échelle du système et que le mode adaptatif est désactivé, vous pouvez activer le traitement parallèle des requêtes à l'échelle du système via le Portail de gestion ou $SYSTEM.SQL.Util.SetOption() Exemple

USER>w ##class(%SYSTEM.SQL.Util).GetOption("AutoParallel")
0
USER>d ##class(%SYSTEM.SQL.Util).SetOption("AutoParallel",1,.oldParVal)
 
USER>w ##class(%SYSTEM.SQL.Util).GetOption("AutoParallel")
1
USER>zw oldParVal
oldParVal=0

Autres aspects importants à prendre en compte lors de la mise en œuvre de la fonctionnalité %PARALLEL.

  •   Lorsque le [AdaptiveMode](https://docs.intersystems.com/iris20242/csp/docbook/DocBook.UI.Page.cls?KEY=RACS_AdaptiveMode) est activé, le traitement parallèle automatique est appliqué à toutes les requêtes SELECT, en les accompagnant de la mention %PARALLEL. Cependant, toutes les requêtes ne peuvent pas utiliser le traitement parallèle, car l'optimiseur SQL peut en décider autrement.
    
  •   Lorsque nous essayons d'utiliser cette fonctionnalité %PARALLEL, nous devons également prendre en compte le paramètre  [AutoParallelThreshold](https://docs.intersystems.com/iris20242/csp/docbook/Doc.View.cls?KEY=RACS_AutoParallelThreshold) (la valeur par défaut est 3200) et ce paramètre n'est pas utile dans le cas où [AutoParallel](https://docs.intersystems.com/iris20242/csp/docbook/DocBook.UI.Page.cls?KEY=RACS_AutoParallel) est désactivé.
    
  •   Le paramètre AutoParallelThreshold détermine si une requête est exécutée en parallèle, les valeurs les plus élevées réduisant les chances de traitement en parallèle. La valeur par défaut est 3200, elle peut être ajustée via $SYSTEM.SQL.Util.SetOption("AutoParallelThreshold",n,.oldval).
    
  •   Dans les environnements partagés, le traitement parallèle est utilisé pour toutes les requêtes, quel que soit le seuil, lorsque le mode adaptatif AdaptiveMode est activé.
    
  •   Lorsque le mode AdaptiveMode est activé (défini à 1) et que la fonctionnalité AutoParallel est désactivée, le Mode adaptatif remplace le paramètre AutoParallel et active le traitement parallèle.
    

Exemple: Exemple de classe avec 100 000 enregistrements remplis

 Class SQLClass.MyTest Extends (%Persistent, %Populate)
  {
    
    Property Name As %String(MAXLEN = 255);
    
    Property Age As %Integer(MAXVAL = 100, MINVAL = 1);
    
    Property Address As %String(MAXLEN = 255);
    
    Property City As %String(MAXLEN = 255);
    
    Property State As %String(MAXLEN = 255);
    
    Property Zip As %String(MAXLEN = 255);
    
    Property Country As %String(MAXLEN = 255);
    
    Property Comment As %String(MAXLEN = 255);
    
    Property Hobby As %String(MAXLEN = 255);
    
    Property JobTitle As %String(MAXLEN = 255);
    
    Property Company As %String(MAXLEN = 255);
    
    Property PhoneNumber As %String(MAXLEN = 255);
    
    Property Email As %String(MAXLEN = 255);
    
    Property Gender As %String(MAXLEN = 1);
    
    Property Ethnicity As %String(MAXLEN = 255);
    
    Property Race As %String(MAXLEN = 255);
    
    Property Religion As %String(MAXLEN = 255);
    
    Property MaritalStatus As %String(MAXLEN = 255);
    
    Property Children As %Integer(MAXVAL = 10, MINVAL = 0);
    
    Property Income As %Integer(MAXVAL = 100000, MINVAL = 0);
    
    Property Occupation As %String(MAXLEN = 255);
    
    Property Education As %String(MAXLEN = 255);
    
    Property HomePhone As %String(MAXLEN = 255);
    
    Property MobilePhone As %String(MAXLEN = 255);
    
    Property WorkPhone As %String(MAXLEN = 255);
    
    Property WorkEmail As %String(MAXLEN = 255);
    
    Property HomeEmail As %String(MAXLEN = 255);
    
    Property HomeAddress As %String(MAXLEN = 255);
    
    Property HomeCity As %String(MAXLEN = 255);
    
    Property HomeState As %String(MAXLEN = 255);
    
    Property HomeZip As %String(MAXLEN = 255);
    
    Property HomeCountry As %String(MAXLEN = 255);
    
    Property WorkAddress As %String(MAXLEN = 255);
    
    Property WorkCity As %String(MAXLEN = 255);
    
    Property WorkState As %String(MAXLEN = 255);
    
    Property WorkZip As %String(MAXLEN = 255);
    
    Property WorkCountry As %String(MAXLEN = 255);
    
    Property WorkPhoneNumber As %String(MAXLEN = 255);
    
    Property WorkMobilePhone As %String(MAXLEN = 255);
    
    Property WorkFax As %String(MAXLEN = 255);
    
    Property WorkWebsite As %String(MAXLEN = 255);
    
    Property WorkComments As %String(MAXLEN = 255);
    
    
    Index IdxAge On Age;
}

Test n° 1Exemple d'exécution sans % PARALLEL (pour afficher 10 000 enregistrements en SMP)

select * from SQLClass.MyTest where age>40
  • 3.2069 secondes
  • 10404 références globales
  • 3325407 commandes exécutées

Exemple d'exécution avec %PARALLEL(pour afficher 10 000 enregistrements dans SMP)

select * from %PARALLEL SQLClass.MyTest where age>40
  • 2.8681 secondes
  • 10404 références globales
  • 3325407 commandes exécutées

Test n° 2 :Exemple d'exécution sans % PARALLEL (pour afficher 1 enregistrement en SMP)

select COUNT(Children),MAX(Children),MIN(Children),AVG(Children) from SQLClass.MyTest where age>10
  • 0.4037 secondes
  • 46559 références globales
  • 1459936 commandes exécutées

Exemple d'exécution avec %PARALLEL (pour afficher 1 enregistrement en SMP)

select COUNT(Children),MAX(Children),MIN(Children),AVG(Children) from %PARALLEL SQLClass.MyTest where age>10
  • 0.0845 secondes
  • 46560 références globales
  • 1460418 commandes exécutées

Exemple avec SQL intégré

ClassMethod embeddedSQL() As %Status
{
    // w ##Class(SQLClass.MyTest).embeddedSQL()
    Set sc = $$$OK
    DO ClearBuffers^|"%SYS"|GLOBUFF()
    set stime=$p($zts,",",2)
    &sql(select COUNT(Children),MAX(Children),MIN(Children),AVG(Children) from SQLClass.MyTest where age>10)
    w:'SQLCODE "Without %Parallel : ",($p($zts,",",2)-stime),!
    DO ClearBuffers^|"%SYS"|GLOBUFF()
    set stime=$p($zts,",",2)
    &sql(select COUNT(Children),MAX(Children),MIN(Children),AVG(Children) from %PARALLEL SQLClass.MyTest where age>10)
    w:'SQLCODE "With %Parallel : ",($p($zts,",",2)-stime),!
    Return sc
}

Résultats (SQL intégré) : USER> D ##Class(SQLClass.MyTest).embeddedSQL() 5466 blocs supprimés Sans %Parallel : .355737 5217 blocs supprimés Avec %Parallel : .3407056

USER>

Exemple avec SQL dynamique

ClassMethod dynamicSQL() As %Status
{
     // w ##Class(SQLClass.MyTest).dynamicSQL()
    Set sc = $$$OK
    DO ClearBuffers^|"%SYS"|GLOBUFF()
    set stime=$p($zts,",",2), recCnt=0
    Set rs=##class(%ResultSet).%New()
    Set sc=rs.Prepare("select COUNT(Children),MAX(Children),MIN(Children),AVG(Children) from SQLClass.MyTest where age>10")
    Set sc=rs.Execute()
    While(rs.Next()) {
	 	w "COUNT(Children) : ",rs.GetData(1),"; MAX(Children) : ",rs.GetData(2),"; MIN(Children) : ",rs.GetData(3),"; AVG(Children) : ",rs.GetData(4),!
    }
    w "Without %Parallel : ",($p($zts,",",2)-stime),!!!
    DO ClearBuffers^|"%SYS"|GLOBUFF()
    set stime=$p($zts,",",2), recCnt=0
    Set sc=rs.Prepare("select COUNT(Children),MAX(Children),MIN(Children),AVG(Children) from SQLClass.MyTest where age>10")
    Set sc=rs.Execute()
    While(rs.Next()) {
	 	w "COUNT(Children) : ",rs.GetData(1),"; MAX(Children) : ",rs.GetData(2),"; MIN(Children) : ",rs.GetData(3),"; AVG(Children) : ",rs.GetData(4),!
	}
    w "With %Parallel : ",($p($zts,",",2)-stime),!
    Return sc
}

Résultats (SQL dynamique): USER>d ##Class(SQLClass.MyTest).dynamicSQL() 22163 blocs supprimés NOMBRE(Enfants) : 89908; MAX(Enfants) : 10; MIN(Enfants) : 0; AVG(Enfants) : 5.021989144458780086 Sans %Parallel : .4036913

5721 blocs supprimés NOMBRE(Enfants) : 89908; MAX(Enfants) : 10; MIN(Enfants) : 0; AVG(Enfants) : 5.021989144458780086 Avec %Parallel : .3693442

0
0 37
Article Sylvain Guilbaud · Avr 25, 2025 22m read

TLS, qu'est-ce que c'est ?

La TLS (Transport Layer Security ou "Sécurité de la couche de transport"), qui succède à SSL (Secure Sockets Layer ou "Couche de sockets sécurisée"), fournit de la sécurité (c'est-à-dire le chiffrement et l'authentification) sur une connexion TCP/IP. Si vous avez déjà remarqué le "s" sur les URLs "https" vous avez reconnu une connexion HTTP "sécurisée" par SSL/TLS. Dans le passé, seules les pages de connexion/autorisation sur le web utilisaient TLS, mais dans l'environnement hostile d'Internet d'aujourd'hui, les meilleures pratiques indiquent que nous devrions sécuriser toutes les connexions avec TLS.

Pourquoi utiliser TLS?

Alors, pourquoi mettre en œuvre TLS pour les connexions HL7 ? Alors que les violations de données, les rançongiciels et les vulnérabilités sont de plus en plus fréquents, chaque mesure que vous prenez pour renforcer la sécurité de ces précieuses sources de données devient plus cruciale. La TLS est une méthode éprouvée et bien comprise pour protéger les données en transit.

TLS fournit deux fonctionnalités principales qui nous sont bénéfiques : 1) le chiffrement et 2) l'authentification.

Chiffrement

Le chiffrement transforme les données en cours de transfert de sorte que seules les deux parties engagées dans la communication peuvent lire/comprendre les informations échangées. Dans la plupart des cas, seules les applications impliquées dans la connexion TLS peuvent interpréter les données transférées. Cela signifie que les acteurs malveillants opérant sur les serveurs ou réseaux de communication ne pourront pas lire les données, même s'ils parviennent à capturer les paquets TCP bruts à l'aide d'un renifleur de paquets (wiretap, wireshark, tcpdump, etc.).

Without TLS

Authentification

L'authentification garantit que chaque partie communique avec la partie prévue et non avec un imposteur. En s'appuyant sur l'échange de certificats (et la vérification de la preuve de propriété associée qui s'est produite lors d'un handshake TLS), lorsque vous utilisez TLS, vous pouvez être sûr que vous échangez des données avec une partie de confiance. Plusieurs attaques consistent à tromper un serveur pour qu'il communique avec un acteur malveillant en redirigeant le trafic vers le mauvais serveur (par exemple, l'emploi de DNS et d'ARP poisoning) Lorsque TLS est impliqué, les imposteurs doivent non seulement rediriger le trafic, mais aussi voler les certificats et les clés appartenant à la partie de confiance.

L'authentification protège non seulement contre les attaques intentionnelles de pirates informatiques ou de acteurs malveillants, mais aussi contre les erreurs de configuration accidentelles qui pourraient envoyer des données vers le ou les mauvais systèmes. Par exemple, si vous attribuez accidentellement l'adresse IP d'une connexion HL7 à un serveur qui n'utilise pas le certificat attendu, la vérification de la négociation TLS échouera avant l'envoi de données vers ce mauvais serveur.

Vérification d'hôte

Lors de la vérification, les clients ont la possibilité d'effectuer une vérification d'hôte. Cette vérification compare l'adresse IP ou le nom d'hôte utilisé dans la connexion avec les adresses IP et les noms d'hôte intégrés dans le certificat. Si cette vérification est activée et que l'adresse IP/l'hôte de la connexion ne correspond pas à une adresse IP/un hôte figurant dans le certificat, le handshake TLS échouera. Vous trouverez les adresses IP et les noms d'hôte dans les champs X.509 « Subject » et « Subject Alternative Name » présentés ci-dessous.

Preuve de la propriété d'un certificat avec une clé privée

Pour prouver la propriété des certificats échangés avec TLS, vous devez également avoir accès à la clé privée liée à la clé publique intégrée au certificat. Nous ne discuterons pas de la cryptographie employée pour la preuve de propriété avec une clé privée, mais vous devez savoir que l'accès à la clé privée de votre certificat est nécessaire pendant le handshake TLS.

TLS mutuel

Pour la plupart des connexions https établies par votre navigateur web, seul le certificat d'authenticité du serveur web est vérifié. Normalement, les serveurs web n'authentifient pas le client avec des certificats. Au lieu de cela, la plupart des serveurs web s'appuient sur l'authentification du client au niveau de l'application (formulaires de connexion, cookies, mots de passe, etc.).

Avec HL7, il est préférable que les deux côtés de la connexion soient authentifiés. Lorsque les deux côtés sont authentifiés, on parle de «TLS mutuel». Avec le TLS mutuel, le serveur et le client échangent leurs certificats et l'autre côté vérifie les certificats fournis avant de poursuivre la connexion et l'échange de données.

X.509 Certificats

X.509 Champs du certificat

Pour fournir le cryptage et l'authentification, les informations sur la clé publique et l'identité de chaque partie sont échangées dans des certificats [X.509] (https://en.wikipedia.org/wiki/X.509). Vous trouverez ci-dessous certains champs courants d'un certificat X.509 qui nous intéresseront:

  • Serial Number: numéro unique à un CA qui identifie ce certificat spécifique
  • Subject Public Key Info: clé publique du propriétaire
  • Subject: nom distinctif (DN) du serveur/service représenté par ce certificat
    • Ce champ peut être vide si des "Subject Alternative Names" (noms alternatifs du sujet) sont fournis.
  • Issuer: nom distinctif (DN) du CA qui a émis/signé ce certifica
  • Validity Not Before: date de mise en vigueur de ce certificat
  • Validity Not After: date d'expiration de ce certificat
  • Basic Constraints: indique s'il s'agit d'un CA ou non
  • Key Usage: l'utilisation prévue de la clé publique fournie par ce certificat
    • Valeurs d'exemple: digitalSignature, contentCommitment, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign, cRLSign, encipherOnly, decipherOnly
  • Extended Key Usage: utilisations supplémentaires prévues de la clé publique fournie par ce certificat
    • Valeurs d'exemple: serverAuth, clientAuth, codeSigning, emailProtection, timeStamping, OCSPSigning, ipsecIKE, msCodeInd, msCodeCom, msCTLSign, msEFS
    • Pour les connexions TLS mutuelles, les deux modes d'utilisation serverAuth et clientAuth sont nécessaires.
  • Subject Key Identifier: identifie la clé publique du sujet fournie par ce certificat
  • Authority Key Identifier: identifie la clé publique du fournisseur utilisée pour vérifier ce certificat
  • Subject Alternative Name: contient un ou plusieurs noms alternatifs pour ce sujet
    • Les noms DNS et les adresses IP sont des noms alternatifs fréquemment fournis dans ce champ.
    • Subject Alternative Name est parfois abrégé en SAN.
    • Le nom DNS ou l'adresse IP utilisés dans la connexion doivent figurer dans cette liste ou dans le Common Name du Subject pour que la vérification de l'hôte soit réussie.

Noms distingués

Les champs Subject (Sujet) et Issuer (Émetteur) d'un certificat X.509 sont définis comme des Distinguished Names (DN, Noms Distingués). Les noms distingués sont constitués de plusieurs attributs, chaque attribut ayant le format <attr>=<value>. Voici une liste non exhaustive des attributs courants que l'on trouve dans les champs Subject et Issuer

AbréviationNomExempleRemarques
CNNom communCN=server1.domain.comLe nom de domaine complet (FQDN) d'un serveur/service
CPaysC=USCode pays à deux caractères
STÉtat (ou province)ST=MassachusettsNom complet de l'état/province
LLocalitéL=CambridgeVille, région, etc.
OOrganisationO=Best CorporationNom de l'organisation
OUUnité opérationnelleOU=FinanceDépartment, division, etc.

Selon les exemples du tableau ci-dessus, le DN complet pour cet exemple serait C=US, ST=Massachusetts, L=Cambridge, O=Best Corporation, OU=Finance, CN=server1.domain.com

Notez que le Common Name (nom commun) trouvé dans le Subject (sujet) est utilisé lors de la vérification de l'hôte et correspond normalement au nom de domaine complet (FQDN) du serveur ou du service associé au certificat. Les Subject Alternative Names (noms alternatifs du sujet) du certificat peuvent également être utilisés lors de la vérification de l'hôte.

Expiration du certificat

Les champs Validity Not Before (Validité avant la date) et Validity Not After (Validité après la date) du certificat fournissent une plage de dates entre lesquelles le certificat est valide

Normalement, les certificats feuille ont une validité d'un ou deux ans (bien que les sites Web soient encouragés à réduire leurs délais d'expiration à des périodes beaucoup plus courtes). Les autorités de certification ont généralement un délai d'expiration de plusieurs années.

L'expiration des certificats est une fonctionnalité TLS nécessaire mais peu pratique. Avant d'ajouter TLS à vos connexions HL7, assurez-vous d'avoir un plan pour remplacer les certificats avant leur expiration. Une fois qu'un certificat expire, vous ne pourrez plus établir de connexion TLS à l'aide de celui-ci.

Formats de certificat X.509

Les champs de ces certificats X.509 (ainsi que d'autres) sont structurés suivant le format ASN.1 et généralement enregistrés dans l'un des formats de fichier suivants :

Exemple d'encodage PEM d'un certificat X.509:

-----BEGIN CERTIFICATE-----
MIIEVTCCAz2gAwIBAgIQMm4hDSrdNjwKZtu3NtAA9DANBgkqhkiG9w0BAQsFADA7
MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNlcnZpY2VzMQww
CgYDVQQDEwNXUjIwHhcNMjUwMTIwMDgzNzU0WhcNMjUwNDE0MDgzNzUzWjAZMRcw
FQYDVQQDEw53d3cuZ29vZ2xlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA
BDx/pIz8HwLWsWg16BG6YqeIYBGof9fn6z6QwQ2v6skSaJ9+0UaduP4J3K61Vn2v
US108M0Uo1R1PGkTvVlo+C+jggJAMIICPDAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0l
BAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU3rId2EvtObeF
NL+Beadr56BlVZYwHwYDVR0jBBgwFoAU3hse7XkV1D43JMMhu+w0OW1CsjAwWAYI
KwYBBQUHAQEETDBKMCEGCCsGAQUFBzABhhVodHRwOi8vby5wa2kuZ29vZy93cjIw
JQYIKwYBBQUHMAKGGWh0dHA6Ly9pLnBraS5nb29nL3dyMi5jcnQwGQYDVR0RBBIw
EIIOd3d3Lmdvb2dsZS5jb20wEwYDVR0gBAwwCjAIBgZngQwBAgEwNgYDVR0fBC8w
LTAroCmgJ4YlaHR0cDovL2MucGtpLmdvb2cvd3IyLzlVVmJOMHc1RTZZLmNybDCC
AQMGCisGAQQB1nkCBAIEgfQEgfEA7wB2AE51oydcmhDDOFts1N8/Uusd8OCOG41p
wLH6ZLFimjnfAAABlIMTadcAAAQDAEcwRQIgf6SEH+xVO+nGDd0wHlOyVTbmCwUH
ADj7BJaSQDR1imsCIQDjJjt0NunwXS4IVp8BP0+1sx1BH6vaxgMFOATepoVlCwB1
AObSMWNAd4zBEEEG13G5zsHSQPaWhIb7uocyHf0eN45QAAABlIMTaeUAAAQDAEYw
RAIgBNtbWviWZQGIXLj6AIEoFKYQW4pmwjEfkQfB1txFV20CIHeouBJ1pYp6HY/n
3FqtzC34hFbgdMhhzosXRC8+9qfGMA0GCSqGSIb3DQEBCwUAA4IBAQCHB09Uz2gM
A/gRNfsyUYvFJ9J2lHCaUg/FT0OncW1WYqfnYjCxTlS6agVUPV7oIsLal52ZfYZU
lNZPu3r012S9C/gIAfdmnnpJEG7QmbDQZyjF7L59nEoJ80c/D3Rdk9iH45sFIdYK
USAO1VeH6O+kAtFN5/UYxyHJB5sDJ9Cl0Y1t91O1vZ4/PFdMv0HvlTA2nyCsGHu9
9PKS0tM1+uAT6/9abtqCBgojVp6/1jpx3sx3FqMtBSiB8QhsIiMa3X0Pu4t0HZ5j
YcAkxtIVpNJ8h50L/52PySJhW4gKm77xNCnAhAYCdX0sx76eKBxB4NqMdCR945HW
tDUHX+LWiuJX
-----END CERTIFICATE-----

Comme vous pouvez le voir, l'encodage PEM ajoute -----BEGIN CERTIFICATE----- et -----END CERTIFICATE----- aux données ASN.1 du certificat encodées en base64.

Établir la confiance avec les autorités de certification

Sur l'Internet ouvert, il serait impossible pour votre navigateur Web de connaître et de faire confiance au certificat de chaque site Web. Il y en a tout simplement trop! Pour contourner ce problème, votre navigateur Web délègue la confiance à un ensemble prédéterminé d'autorités de certification (AC). Les autorités de certification sont des entités qui vérifient qu'une personne demandant un certificat pour un site Web ou un domaine est bien propriétaire et responsable du serveur, du domaine ou des activités commerciales associés à la demande de certificat. Une fois que l'autorité de certification a vérifié un propriétaire, elle est en mesure d'émettre le certificat demandé.

Chaque autorité de certification est représentée par un ou plusieurs certificats X.509. Ces certificats CA sont utilisés pour signer tous les certificats émis par la CA. Si vous regardez dans le champ Issuer (Émetteur) d'un certificat X.509, vous trouverez une référence au certificat CA qui a créé et signé ce certificat.

Si un certificat est créé sans autorité de certification, il est appelé certificat auto-signé. Vous savez qu'un certificat est auto-signé si les champs Subject (Sujet) et Issuer (Émetteur) du certificat sont identiques.

En général, la CA crée un certificat root (racine) auto-signé avec une longue fenêtre d'expiration. Ce certificat racine est ensuite utilisé pour générer quelques autorités de certification intermédiaires, qui ont une fenêtre d'expiration légèrement plus courte. La CA racine sera sécurisée et rarement utilisée après la création des CA intermédiaires. Les CA intermédiaires seront utilisées pour émettre et signer les certificats leaf (feuille) au quotidien.

Les CA intermédiaires sont créées au lieu d'utiliser directement la CA racine afin de minimiser l'impact en cas de violation ou de mauvaise gestion d'un certificat. Si une seule CA intermédiaire est compromise, l'entreprise aura toujours les autres CA disponibles pour continuer à fournir le service.

Chaînes de certificats

Un certificat de connexion et tous les certificats CA impliqués dans l'émission et la signature de ce certificat peuvent être organisés en une structure appelée chaîne de certificats. Cette chaîne de certificats (décrite ci-dessous) sera utilisée pour vérifier et approuver le certificat de connexion.

Si vous suivez le certificat feuille d'une connexion jusqu'à la CA émettrice (en utilisant le champ Issuer) puis, à partir de cette CA, jusqu'à son émetteur (et ainsi de suite, jusqu'à ce que vous atteigniez un certificat racine auto-signé), vous aurez parcouru la chaîne de certificats.

Construction d'une chaîne de certificats

Faire confiance à un certificat

Votre navigateur Web et votre système d'exploitation conservent généralement une liste d'autorités de certification approuvées. Lors de la configuration d'une interface HL7 ou d'une autre application, vous dirigerez probablement votre interface vers un fichier CA-bundle contenant une liste de CA approuvées. Ce fichier contiendra généralement une liste d'un ou plusieurs certificats CA encodés au format PEM. Par exemple:

# Probablement, une CA intermédiaire
-----BEGIN CERTIFICATE-----
MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF
...
rqXRfboQnoZsG4q5WTP468SQvvG5
-----END CERTIFICATE-----

# Probablement, une CA racine
-----BEGIN CERTIFICATE-----
MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV
...
WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
-----END CERTIFICATE-----

Lorsque votre navigateur Web (ou l'interface HL7) tente d'établir une connexion TLS, il utilise cette liste de certificats CA de confiance pour déterminer s'il fait confiance au certificat échangé lors du handshake TLS.

Le processus commence par le certificat racine et traverse la chaîne de certificats jusqu'au certificat CA suivant. Si le certificat CA n'est pas trouvé dans le magasin de confiance ou le fichier CA-bundle, le certificat racine n'est pas considéré comme fiable et la connexion TLS échoue.

Si le certificat CA ou le fichier CA-bundle est trouvé dans le magasin de confiance, le processus continue en remontant la chaîne de certificats, en vérifiant que chaque CA se trouvant sur le chemin est dans le magasin de confiance. Une fois que le certificat CA racine au sommet de la chaîne est vérifié (ainsi que tous les certificats CA intermédiaires se trouvant sur le chemin), le processus peut approuver le certificat feuille du serveur.

Détermination de la confiance

Le handshake TLS

Pour ajouter TLS à une connexion TCP/IP (comme un flux HL7), le client et le serveur doivent effectuer un handshake TLS après que la connexion TCP/IP a été établie. Ce handshake implique de s'accorder sur les chiffrements/méthodes de chiffrement, de s'accorder sur la version TLS, d'échanger des certificats X.509, de prouver la propriété de ces certificats et de valider que chaque partie fait confiance à l'autre.

Les étapes principales d'un handshake TLS sont les suivantes:

  1. Le client établit une connexion TCP/IP avec le serveur.
  2. Le client lance le handshake TLS.
  3. Le serveur envoie son certificat (et la preuve de sa propriété) au client.
  4. Le client vérifie le certificat du serveur.
  5. En cas de TLS mutuel, le client envoie son certificat (et la preuve de sa propriété) au serveur.
  6. En cas de TLS mutuel, le serveur vérifie le certificat du client.
  7. Le client et le serveur s'échangent des données encryptées.

Handshake TLS

1. Le client établit une connexion TCP/IP avec le serveur.

À l'étape n° 1, le client et le serveur effectuent un handshake TCP à la procédure de base « ternaire » [TCP 3-way handshake] (https://en.wikipedia.org/wiki/Transmission_Control_Protocol#Connection_establishment) pour établir une connexion TCP/IP entre eux. Dans un handshake à la procédure de base ternaire:

  1. Le client envoie un paquet SYN.
  2. Le serveur envoie un paquet SYN-ACK.
  3. Le client envoie un paquet ACK.

Une fois ce handshake terminé, la connexion TCP/IP est établie. L'étape suivante consiste à lancer le handshake TLS.

2. Le client lance le handshake TLS.

Une fois la connexion TCP établie, l'une des parties doit agir en tant que client et lancer le handshake TLS. Généralement, le processus qui a initié la connexion TCP est également responsable du lancement du handshake TLS, mais cela peut être inversé dans de rares cas.

Pour lancer le handshake TLS, le client envoie un message ClientHello au serveur. Ce message contient diverses options utilisées pour négocier les paramètres de sécurité de la connexion avec le serveur.

3. Le serveur envoie son certificat (et la preuve de sa propriété) au client.

Après avoir reçu le message ClientHello du client, le serveur répond à son tour par un message ServerHello. Celui-ci inclut les paramètres de sécurité négociés.

Après le message ServerHello, le serveur envoie également un message Certificate et CertificateVerify au client. Cela permet de partager la chaîne de certificats X.509 avec le client et de fournir la preuve de propriété de la clé privée associée au certificat.

4. Le client vérifie le certificat du serveur.

Une fois que le client a reçu les messages ServerHello, Certificate et CertificateVerify, il vérifie que le certificat est valide et approuvé (en comparant les CAs aux fichiers CA-bundle approuvés, au magasin de certificats du système opérationnel ou au magasin de certificats du navigateur web). Le client effectue également toute vérification de l'hôte (voir ci-dessus) pour s'assurer que l'adresse de connexion correspond aux adresses/IP du certificat.

5. S'il s'agit d'une connexion TLS mutuelle, le client envoie son certificat (et la preuve de propriété) au serveur.

S'il s'agit d'une connexion TLS mutuelle (déterminée par l'envoi d'un message CertificateRequest par le serveur), le client enverra un message Certificate incluant sa chaîne de certificats, puis un message CertificateVerify pour prouver qu'il est le propriétaire de la clé privée associée.

6. S'il s'agit d'une connexion TLS mutuelle, le serveur vérifie le certificat client.

Là encore, s'il s'agit d'une connexion TLS mutuelle, le serveur vérifie que la chaîne de certificats envoyée par le client est valide et approuvée.

7. Le client et le serveur s'échangent des données encryptées.

Si la négociation TLS se déroule sans erreur, le client et le serveur s'échangent des messages Finished (Terminé) pour achever la négociation. Après cela, les données encryptées peuvent être échangées entre le client et le serveur.

Configuration de TLS sur les interfaces HL7

Félicitations d'être arrivé jusqu'ici ! Maintenant que vous savez en quoi consiste TLS, comment procéderiez-vous pour mettre en œuvre le protocole TLS sur vos connexions HL7 ? De manière générale, voici les étapes à suivre pour configurer TLS sur vos connexions HL7.

  1. Choisissez une autorité de certification.
  2. Créez une clé et une demande de signature de certificat.
  3. Obtenez votre certificat auprès de votre CA.
  4. Obtenez la chaîne de certificats pour votre pair.
  5. Créez une configuration SSL pour la connexion.
  6. Ajoutez la configuration SSL à l'interface, faites rebondir l'interface et vérifiez le flux de messages.

1. Choisissez une autorité de certification.

La procédure que vous utiliserez pour obtenir un certificat et une clé pour votre serveur dépendra largement des politiques de sécurité de votre entreprise. Dans la plupart des cas, votre certificat sera signé par l'une des autorités de certification suivantes:

  1. Votre certificat sera signé par une CA interne à l'entreprise.
  • C'est mon option préférée, car votre entreprise dispose déjà de l'infrastructure nécessaire pour gérer les certificats et les CAs. Il vous suffit de travailler avec l'équipe qui possède cette infrastructure pour obtenir votre propre certificat pour vos interfaces HL7.
  1. Votre certificat sera signé par une CA publique.
  • Cette option est intéressante dans le sens où la CA publique dispose également de toute l'infrastructure nécessaire pour maintenir les certificats et les CAs. Cette option est sans doute exagérée pour la plupart des interfaces HL7, car les CA publiques fournissent généralement des certificats pour l'Internet ouvert ; les interfaces HL7 ont tendance à se connecter via un intranet privé, et non via l'Internet public.
  • L'obtention de certificats auprès d'une CA publique peut également entraîner des frai.
  1. Votre certificat sera signé par une CA que vous créerez et maintiendrezvous-même.
  • Cette option peut vous convenir, mais malheureusement, cela signifie que vous supportez la charge de la maintenance et de la sécurisation de votre configuration CA et de votre logiciel.
  • Vous l'utilisez à vos risques et périls!
  • Cette option est la plus complexe. Préparez-vous à une courbe d'apprentissage abrupte.
  • Vous pouvez utiliser des progiciels open source éprouvés pour gérer votre CA et vos certificats. La suite OpenSSL est une excellente option. Les autres options sont EJBCA, step-ca et cfssl.

2. Créez une clé et une demande de signature de certificat.

Après avoir choisi votre CA, l'étape suivante consiste à créer une clé privée et une demande de signature de certificat (CSR) . La manière dont vous générez la clé et la CSR dépendra de la politique de votre entreprise et de la CA choisie. Pour l'instant, nous allons simplement parler des étapes de manière générale.

Lors de la génération d'une clé privée, la clé publique associée est également générée. La clé publique sera intégrée à votre CSR et à votre certificat signé. Ces deux clés seront utilisées pour prouver la propriété de votre certificat signé lors de l'établissement d'une connexion TLS.

ATTENTION! Veillez à enregistrer votre clé privée dans un endroit sûr (de préférence dans un format protégé par un mot de passe). Si vous perdez cette clé, votre certificat ne sera plus utilisable. Si quelqu'un d'autre accède à cette clé, il pourra se faire passer pour votre serveur.

La demande de signature de certificat inclura des informations sur votre serveur, votre entreprise, votre clé publique, la manière d'utiliser le certificat, etc. Elle inclura également la preuve que vous possédez la clé privée associée. Cette CSR sera ensuite fournie à votre CA pour générer et signer votre certificat.

REMARQUE: lors de la création de la CSR, assurez-vous de demander une Extended Key Usage (utilisation étendue de la clé) à la fois pour serverAuth et clientAuth, si vous utilisez le TLS mutuel. La plupart des CA sont habituées à signer des certificats avec uniquement la clé serverAuth. Malheureusement, cela signifie que le certificat ne peut pas être utilisé comme certificat client dans une connexion TLS mutuelle.

3. Obtenez votre certificat auprès de votre CA.

Après avoir créé votre clé et votre CSR, soumettez la CSR à votre autorité de certification. Après avoir effectué plusieurs vérifications, votre CA devrait être en mesure de vous fournir un certificat signé et la chaîne de certificats associée. Ce certificat et cette chaîne doivent être enregistrés au format PEM. Si la CA a fourni votre certificat dans un format différent, vous devrez le convertir à l'aide d'un outil tel qu'OpenSSL.

4. Obtenez la chaîne de certificats pour votre homologue.

Les étapes précédentes étaient axées sur l'obtention d'un certificat pour votre serveur. Vous devriez pouvoir utiliser ce certificat (et la clé associée) avec chaque connexion HL7 vers/depuis ce serveur. Vous devrez également obtenir les chaînes de certificats pour chacun des systèmes/homologues auxquels vous vous connecterez.

Les chaînes de certificats de chaque homologue devront être enregistrées dans un fichier au format PEM. Ce CA-bundle n'aura pas besoin de contenir les certificats feuille ; il doit uniquement contenir les certificats CA intermédiaires et racine.

Veillez à fournir à votre homologue un CA-bundle contenant vos CA intermédiaires et racine. Cela lui permettra de faire confiance à votre certificat lorsque vous établirez une connexion.

5. Créez une configuration SSL pour la connexion.

Dans Health Connect d'InterSystems, il vous faudra créer des configurations SSL client et serveur pour chaque système auquel votre serveur se connectera. Ces configurations SSL dirigeront vers le fichier CA-bundle du système associé et vers les fichiers clé et de certificat de votre serveur.

Les configurations SSL client sont utilisées lors des opérations pour lancer le handshake TLS. Les configurations SSL serveur sont utilisées sur les services pour répondre aux handshakes TLS. Si un système dispose à la fois de services entrants et de services sortants, il faudra configurer à la fois une configuration SSL client et une configuration SSL serveur pour ce système.

Pour créer une configuration SSL client:

  1. Accédez à System Administration > Security > SSL/TLS Configurations (Administration système > Sécurité > Configurations SSL/TLS).
  2. Appuyeze sur Create New Configuration (Créer une nouvelle configuration).
  3. Donnez un Configuration Name (Nom de configuration) et une Description (Description) à votre configuration SSL.
  4. Assurez-vous que votre configuration SSL est Enabled (Activée).
  5. Choisissez Client comme Type.
  6. Choisissez Require (Obligatoire) pour le champ Server certificate verification (Vérification du certificat serveur). Cela effectue une vérification de l'hôte sur la connexion.
  7. Dirigez le champ File (Fichier) contenant le(s) certificat(s) CA de confiance vers le fichier CA-bundle contenant les CA intermédiaires et racines (au format PEM) du système auquel vous vous connectez.
  8. Dirigez le champ File (Fichier) contenant le certificat de ce client vers le fichier contenant le certificat X.509 de votre serveur au format PEM.
  9. Dirigez le champ File (Fichier) contenant la clé privée associée vers le fichier contenant la clé privée de votre certificat.
  10. Le Private key type (type de clé privée) sera très probablement RSA (chiffrement RSA). Cela devrait correspondre au type de votre clé privée.
  11. Si votre clé privée est protégée par un mot de passe (comme cela devrait être le cas), saisissez le mot de passe dans les champs Private key password (mot de passe de la clé privée) et Private key password (confirm) (confirmer le mot de passe de la clé privée).
  12. Vous pouvez probablement laisser les autres champs à leurs valeurs par défaut.

Pour créer une configuration de serveur SSL:

  1. Go to System Administration > Security > SSL/TLS Configurations.
  2. Appuyeze sur Create New Configuration (Créer une nouvelle configuration).
  3. Donnez un Configuration Name (Nom de configuration) et une Description (Description) à votre configuration SSL.
  4. Assurez-vous que votre configuration SSL est Enabled (Activée).
  5. Choisissez Server comme Type.
  6. Choisissez Require (Obligatoire) pour le champ Client certificate verification (Vérification du certificat client). Cela permettra de s'assurer que le TLS mutuel est exécuté.
  7. Dirigez File containing trusted Certificate Authority certificate(s) (Fichier contenant le(s) certificat(s) de l'autorité de certification de confiance) vers le fichier CA-bundle contenant les CA intermédiaires et racines (au format PEM) du système auquel vous vous connectez.
  8. Dirigez File containing this server's certificate (Fichier contenant le certificat de ce serveur) vers le fichier contenant le certificat X.509 de votre serveur au format PEM.
  9. Dirigez File containing associated private key (Fichier contenant la clé privée associée) vers le fichier contenant la clé privée de votre certificat.
  10. Le Private key type (type de clé privée) sera très probablement RSA (chiffrement RSA). Cela devrait correspondre au type de votre clé privée.
  11. Si votre clé privée est protégée par un mot de passe (comme cela devrait être le cas), saisissez le mot de passe dans les champs Private key password (mot de passe de la clé privée) et Private key password (confirm) (confirmer le mot de passe de la clé privée).
  12. Vous pouvez probablement laisser les autres champs à leurs valeurs par défaut.

configuration de configuration SSL

6. Ajoutez la configuration SSL à l'interface, relancez l'interface et vérifiez le flux de messages.

Une fois que vous avez créé les configurations SSL client et serveur, vous êtes prêt à activer TLS sur les interfaces. Pour chaque service ou opération, choisissez la configuration SSL associée dans le menu déroulant Connection Settings > SSL Configuration (Paramètres de connexion > Configuration SSL) qui se trouve dans l'onglet Settings (Paramètres) de l'interface.

Après avoir relancé l'interface, vous verrez la connexion se rétablir. Lorsqu'un nouveau message est transféré, un statut Completed (Terminé) indique que TLS fonctionne. Si TLS ne fonctionne pas, la connexion sera interrompue à chaque tentative de message.

Pour vous aider à déboguer les problèmes avec TLS, il se peut que vous ayez besoin d'utiliser des outils tels que tcpdump, Wireshark ou l'utilitaire s_client d'OpenSSL.

Conclusion

Nous avons fait une analyse très approfondie du sujet SSL/TLS. Il y a tellement d'autres informations qui n'ont pas été incluses dans cet article. J'espère que cet article vous a fourni un aperçu suffisant du fonctionnement de TLS pour que vous puissiez rechercher les détails et obtenir plus d'informations si nécessaire.

Si vous recherchez une ressource approfondie sur TLS, consultez le site Web d'Ivan Ristić, fiestyduck.com et son livre, Bulletproof TLS and PKI. J'ai trouvé que ce livre était une excellente ressource pour en savoir plus sur l'utilisation de TLS.

0
0 48
Article Sylvain Guilbaud · Avr 22, 2025 3m read

Rubrique FAQ InterSystems

Les variables globales temporaires stockées dans les bases de données IRISTEMP/CACHETEMP sont utilisées lorsqu'un processus n'a pas besoin de stocker des données indéfiniment, mais requiert les performances élevées des variables globales. Les bases de données IRISTEMP/CACHETEMP ne sont pas journalisées ; leur utilisation ne crée donc pas de fichiers journaux.

Le système utilise les bases de données IRISTEMP/CACHETEMP pour le stockage temporaire et les utilisateurs peuvent y accéder à cette fin.

0
0 30
Article Sylvain Guilbaud · Mars 28, 2025 54m read

Depuis l'introduction d'Embedded Python, il y a toujours eu un doute sur ses performances par rapport à ObjectScript et J'en ai discuté à plusieurs reprises avec @Guillaume Rongier , eh bien, profitant du fait que je faisais une petite application pour capturer les données des concours publics en Espagne et pouvoir effectuer des recherches en utilisant les capacités de VectorSearch, j'ai vu l'opportunité de réaliser un petit test.

Données pour le test

Les informations relatives aux concours publics sont fournies mensuellement dans des fichiers XML à partir de cette URL  et le format typique des informations d'un concours est le suivant:

 
Spoiler
<entry>
        <id>https://contrataciondelestado.es/sindicacion/licitacionesPerfilContratante/15070121</id>
        <link href="https://contrataciondelestado.es/wps/poc?uri=deeplink:detalle_licitacion&amp;idEvl=zVQ4gDdr3Y436J9Lctlsuw%3D%3D"/>
        <summary type="text">Id licitación: C54243062800; Órgano de Contratación: Dirección General de Carreteras; Importe: 3088367.31 EUR; Estado: RES</summary>
        <title>39-MU-6280; 54.408/24. Actuaciones para el desarrollo del plan de acción contra el ruido de la Fase II en la Región de Murcia. Plan de Recuperación, Transformación y Resiliencia, Next Generation EU.</title>
        <updated>2024-12-19T07:58:39.502+01:00</updated>
        <cac-place-ext:ContractFolderStatus>
            <cbc:ContractFolderID>C54243062800</cbc:ContractFolderID>
            <cbc-place-ext:ContractFolderStatusCode listURI="https://contrataciondelestado.es/codice/cl/2.04/SyndicationContractFolderStatusCode-2.04.gc" languageID="es">RES</cbc-place-ext:ContractFolderStatusCode>
            <cac-place-ext:LocatedContractingParty>
                <cbc:ContractingPartyTypeCode listURI="http://contrataciondelestado.es/codice/cl/2.10/ContractingAuthorityCode-2.10.gc">1</cbc:ContractingPartyTypeCode>
                <cbc:ActivityCode listURI="http://contrataciondelestado.es/codice/cl/2.10/ContractingAuthorityActivityCode-2.10.gc">1</cbc:ActivityCode>
                <cbc:BuyerProfileURIID>https://contrataciondelestado.es/wps/poc?uri=deeplink:perfilContratante&amp;idBp=bbqeQ9uN6YE%3D</cbc:BuyerProfileURIID>
                <cac:Party>
                    <cbc:WebsiteURI>http://www.fomento.es</cbc:WebsiteURI>
                    <cac:PartyIdentification>
                        <cbc:ID schemeName="DIR3">E00124905</cbc:ID>
                    </cac:PartyIdentification>
                    <cac:PartyIdentification>
                        <cbc:ID schemeName="NIF">S2817015G</cbc:ID>
                    </cac:PartyIdentification>
                    <cac:PartyIdentification>
                        <cbc:ID schemeName="ID_PLATAFORMA">10000170000108</cbc:ID>
                    </cac:PartyIdentification>
                    <cac:PartyName>
                        <cbc:Name>Dirección General de Carreteras</cbc:Name>
                    </cac:PartyName>
                    <cac:PostalAddress>
                        <cbc:CityName>Madrid</cbc:CityName>
                        <cbc:PostalZone>28071</cbc:PostalZone>
                        <cac:AddressLine>
                            <cbc:Line>Paseo de la Castellana 67 - Despacho B750</cbc:Line>
                        </cac:AddressLine>
                        <cac:Country>
                            <cbc:IdentificationCode listURI="http://contrataciondelestado.es/codice/cl/2.08/CountryIdentificationCode-2.08.gc">ES</cbc:IdentificationCode>
                            <cbc:Name>España</cbc:Name>
                        </cac:Country>
                    </cac:PostalAddress>
                    <cac:Contact>
                        <cbc:Name>Dirección General de Carreteras</cbc:Name>
                        <cbc:Telephone>915978341</cbc:Telephone>
                        <cbc:Telefax>915978547</cbc:Telefax>
                        <cbc:ElectronicMail>dgc.licitaciones@fomento.es</cbc:ElectronicMail>
                    </cac:Contact>
                </cac:Party>
                <cac-place-ext:ParentLocatedParty>
                    <cac:PartyName>
                        <cbc:Name>Dirección General de Carreteras</cbc:Name>
                    </cac:PartyName>
                    <cac-place-ext:ParentLocatedParty>
                        <cac:PartyName>
                            <cbc:Name>Secretaría General de Transporte Terrestre</cbc:Name>
                        </cac:PartyName>
                        <cac-place-ext:ParentLocatedParty>
                            <cac:PartyName>
<cbc:Name>Secretaría de Estado de Transportes y Movilidad Sostenible</cbc:Name>
                            </cac:PartyName>
                            <cac-place-ext:ParentLocatedParty>
<cac:PartyIdentification>
    <cbc:ID schemeName="DIR3">E05229701</cbc:ID>
</cac:PartyIdentification>
<cac:PartyName>
    <cbc:Name>Ministerio de Transportes y Movilidad Sostenible</cbc:Name>
</cac:PartyName>
<cac-place-ext:ParentLocatedParty>
    <cac:PartyIdentification>
        <cbc:ID schemeName="DIR3">EA9999999</cbc:ID>
    </cac:PartyIdentification>
    <cac:PartyName>
        <cbc:Name>ADMINISTRACIÓN GENERAL DEL ESTADO</cbc:Name>
    </cac:PartyName>
    <cac-place-ext:ParentLocatedParty>
        <cac:PartyName>
            <cbc:Name>Sector Público</cbc:Name>
        </cac:PartyName>
    </cac-place-ext:ParentLocatedParty>
</cac-place-ext:ParentLocatedParty>
                            </cac-place-ext:ParentLocatedParty>
                        </cac-place-ext:ParentLocatedParty>
                    </cac-place-ext:ParentLocatedParty>
                </cac-place-ext:ParentLocatedParty>
            </cac-place-ext:LocatedContractingParty>
            <cac:ProcurementProject>
                <cbc:Name>39-MU-6280; 54.408/24. Actuaciones para el desarrollo del plan de acción contra el ruido de la Fase II en la Región de Murcia. Plan de Recuperación, Transformación y Resiliencia, Next Generation EU.</cbc:Name>
                <cbc:TypeCode listURI="http://contrataciondelestado.es/codice/cl/2.08/ContractCode-2.08.gc">3</cbc:TypeCode>
                <cbc:SubTypeCode listURI="http://contrataciondelestado.es/codice/cl/1.04/WorksContractCode-1.04.gc">4500</cbc:SubTypeCode>
                <cbc:MixContractIndicator>false</cbc:MixContractIndicator>
                <cac:BudgetAmount>
                    <cbc:EstimatedOverallContractAmount currencyID="EUR">3706040.77</cbc:EstimatedOverallContractAmount>
                    <cbc:TotalAmount currencyID="EUR">3736924.45</cbc:TotalAmount>
                    <cbc:TaxExclusiveAmount currencyID="EUR">3088367.31</cbc:TaxExclusiveAmount>
                </cac:BudgetAmount>
                <cac:RequiredCommodityClassification>
                    <cbc:ItemClassificationCode listURI="http://contrataciondelestado.es/codice/cl/2.04/CPV2008-2.04.gc">45233000</cbc:ItemClassificationCode>
                </cac:RequiredCommodityClassification>
                <cac:RealizedLocation>
                    <cbc:CountrySubentity>Región de Murcia</cbc:CountrySubentity>
                    <cbc:CountrySubentityCode listURI="http://contrataciondelestado.es/codice/cl/2.08/NUTS-2021.gc">ES62</cbc:CountrySubentityCode>
                    <cac:Address>
                        <cac:Country>
                            <cbc:IdentificationCode listURI="http://contrataciondelestado.es/codice/cl/2.08/CountryIdentificationCode-2.08.gc">ES</cbc:IdentificationCode>
                            <cbc:Name>España</cbc:Name>
                        </cac:Country>
                    </cac:Address>
                </cac:RealizedLocation>
                <cac:PlannedPeriod>
                    <cbc:DurationMeasure unitCode="MON">6</cbc:DurationMeasure>
                </cac:PlannedPeriod>
            </cac:ProcurementProject>
            <cac:TenderResult>
                <cbc:ResultCode listURI="http://contrataciondelestado.es/codice/cl/2.09/TenderResultCode-2.09.gc">9</cbc:ResultCode>
                <cbc:Description>Por ser la ofertas más ventajosa</cbc:Description>
                <cbc:AwardDate>2024-11-26</cbc:AwardDate>
                <cbc:ReceivedTenderQuantity>20</cbc:ReceivedTenderQuantity>
                <cbc:LowerTenderAmount currencyID="EUR">2388215.74</cbc:LowerTenderAmount>
                <cbc:HigherTenderAmount currencyID="EUR">2764090</cbc:HigherTenderAmount>
                <cbc:SMEsReceivedTenderQuantity>6</cbc:SMEsReceivedTenderQuantity>
                <cbc:SMEAwardedIndicator>false</cbc:SMEAwardedIndicator>
                <cac:Contract>
                    <cbc:IssueDate>2024-12-18</cbc:IssueDate>
                </cac:Contract>
                <cac:WinningParty>
                    <cac:PartyIdentification>
                        <cbc:ID schemeName="NIF">A17013863</cbc:ID>
                    </cac:PartyIdentification>
                    <cac:PartyName>
                        <cbc:Name>CONSTRUCCIONES RUBAU, S.A.</cbc:Name>
                    </cac:PartyName>
                    <cac:PhysicalLocation>
                        <cac:Address>
                            <cbc:CityName>Madrid</cbc:CityName>
                            <cbc:PostalZone>28046</cbc:PostalZone>
                            <cac:Country>
<cbc:IdentificationCode listURI="http://contrataciondelestado.es/codice/cl/2.08/CountryIdentificationCode-2.08.gc" name="España">ES</cbc:IdentificationCode>
                            </cac:Country>
                        </cac:Address>
                    </cac:PhysicalLocation>
                </cac:WinningParty>
                <cac:AwardedTenderedProject>
                    <cac:LegalMonetaryTotal>
                        <cbc:TaxExclusiveAmount currencyID="EUR">2469149.66</cbc:TaxExclusiveAmount>
                        <cbc:PayableAmount currencyID="EUR">2987671.09</cbc:PayableAmount>
                    </cac:LegalMonetaryTotal>
                </cac:AwardedTenderedProject>
            </cac:TenderResult>
            <cac:TenderingTerms>
                <cbc:RequiredCurriculaIndicator>false</cbc:RequiredCurriculaIndicator>
                <cbc:VariantConstraintIndicator>false</cbc:VariantConstraintIndicator>
                <cbc:FundingProgramCode listURI="http://contrataciondelestado.es/codice/cl/2.08/FundingProgramCode-2.08.gc" name="Financiación con fondos de la UE">EU</cbc:FundingProgramCode>
                <cbc:FundingProgramCode listURI="http://contrataciondelestado.es/codice/cl/2.08/FundingProgramCode-2.08.gc" name="Asociado al Plan de Recuperación, Transformación y Resiliencia">PRTR</cbc:FundingProgramCode>
                <cbc:FundingProgram>NEXT GENERATION EU</cbc:FundingProgram>
                <cbc:ProcurementNationalLegislationCode listURI="https://contrataciondelestado.es/codice/cl/2.08/ProcurementNationalLegislationCode-2.08.gc">3</cbc:ProcurementNationalLegislationCode>
                <cbc:ReceivedAppealQuantity>0</cbc:ReceivedAppealQuantity>
                <cac:RequiredFinancialGuarantee>
                    <cbc:GuaranteeTypeCode listURI="http://contrataciondelestado.es/codice/cl/1.04/GuaranteeTypeCode-1.04.gc">3</cbc:GuaranteeTypeCode>
                    <cbc:AmountRate>5</cbc:AmountRate>
                </cac:RequiredFinancialGuarantee>
                <cac:RequiredFinancialGuarantee>
                    <cbc:GuaranteeTypeCode listURI="http://contrataciondelestado.es/codice/cl/1.04/GuaranteeTypeCode-1.04.gc">2</cbc:GuaranteeTypeCode>
                    <cbc:AmountRate>5</cbc:AmountRate>
                </cac:RequiredFinancialGuarantee>
                <cac:ProcurementLegislationDocumentReference>
                    <cbc:ID>N/A</cbc:ID>
                </cac:ProcurementLegislationDocumentReference>
                <cac:TendererQualificationRequest>
                    <cac:RequiredBusinessClassificationScheme>
                        <cbc:ID>RequiredBusinessProfileCode</cbc:ID>
                        <cac:ClassificationCategory>
                            <cbc:CodeValue>G6-5</cbc:CodeValue>
                        </cac:ClassificationCategory>
                    </cac:RequiredBusinessClassificationScheme>
                    <cac:FinancialEvaluationCriteria>
                        <cbc:EvaluationCriteriaTypeCode listURI="http://contrataciondelestado.es/codice/cl/2.0/FinancialCapabilityTypeCode-2.0.gc">ZZZ</cbc:EvaluationCriteriaTypeCode>
                        <cbc:Description>Se acreditará con la clasificación</cbc:Description>
                    </cac:FinancialEvaluationCriteria>
                    <cac:SpecificTendererRequirement>
                        <cbc:RequirementTypeCode listURI="http://contrataciondelestado.es/codice/cl/2.08/DeclarationTypeCode-2.08.gc">1</cbc:RequirementTypeCode>
                        <cbc:Description>Capacidad de obrar</cbc:Description>
                    </cac:SpecificTendererRequirement>
                </cac:TendererQualificationRequest>
                <cac:AwardingTerms>
                    <cac:AwardingCriteria>
                        <cbc:AwardingCriteriaTypeCode listURI="http://contrataciondelestado.es/codice/cl/2.0/AwardingCriteriaCode-2.0.gc">SUBJ</cbc:AwardingCriteriaTypeCode>
                        <cbc:Description>Juicio de valor</cbc:Description>
                        <cbc:Note>Criterios evaluables mediante juicio de valor.</cbc:Note>
                        <cbc:AwardingCriteriaSubTypeCode listURI="http://contrataciondelestado.es/codice/cl/2.09/AwardingCriteriaNotAutomaticallyEvaluatedSubTypeCode-2.09.gc">99</cbc:AwardingCriteriaSubTypeCode>
                        <cbc:WeightNumeric>49</cbc:WeightNumeric>
                    </cac:AwardingCriteria>
                    <cac:AwardingCriteria>
                        <cbc:AwardingCriteriaTypeCode listURI="http://contrataciondelestado.es/codice/cl/2.0/AwardingCriteriaCode-2.0.gc">OBJ</cbc:AwardingCriteriaTypeCode>
                        <cbc:Description>Precio</cbc:Description>
                        <cbc:AwardingCriteriaSubTypeCode listURI="http://contrataciondelestado.es/codice/cl/2.09/AwardingCriteriaAutomaticallyEvaluatedSubTypeCode-2.09.gc">1</cbc:AwardingCriteriaSubTypeCode>
                        <cbc:WeightNumeric>45.9</cbc:WeightNumeric>
                    </cac:AwardingCriteria>
                    <cac:AwardingCriteria>
                        <cbc:AwardingCriteriaTypeCode listURI="http://contrataciondelestado.es/codice/cl/2.0/AwardingCriteriaCode-2.0.gc">OBJ</cbc:AwardingCriteriaTypeCode>
                        <cbc:Description>Incremento de gastos de ensayos</cbc:Description>
                        <cbc:Note>El licitador deberá consignar el porcentaje total de gastos de ensayo al que se compromete</cbc:Note>
                        <cbc:AwardingCriteriaSubTypeCode listURI="http://contrataciondelestado.es/codice/cl/2.09/AwardingCriteriaAutomaticallyEvaluatedSubTypeCode-2.09.gc">2</cbc:AwardingCriteriaSubTypeCode>
                        <cbc:WeightNumeric>5.1</cbc:WeightNumeric>
                    </cac:AwardingCriteria>
                </cac:AwardingTerms>
                <cac:TenderRecipientParty>
                    <cbc:EndpointID>https://contrataciondelestado.es</cbc:EndpointID>
                </cac:TenderRecipientParty>
                <cac:Language>
                    <cbc:ID>es</cbc:ID>
                </cac:Language>
            </cac:TenderingTerms>
            <cac:TenderingProcess>
                <cbc:ProcedureCode listURI="https://contrataciondelestado.es/codice/cl/2.07/SyndicationTenderingProcessCode-2.07.gc">1</cbc:ProcedureCode>
                <cbc:UrgencyCode listURI="http://contrataciondelestado.es/codice/cl/1.04/DiligenceTypeCode-1.04.gc">1</cbc:UrgencyCode>
                <cbc:ContractingSystemCode listURI="http://contrataciondelestado.es/codice/cl/2.08/ContractingSystemTypeCode-2.08.gc">0</cbc:ContractingSystemCode>
                <cbc:SubmissionMethodCode listURI="http://contrataciondelestado.es/codice/cl/1.04/TenderDeliveryCode-1.04.gc">1</cbc:SubmissionMethodCode>
                <cbc:OverThresholdIndicator>false</cbc:OverThresholdIndicator>
                <cac:DocumentAvailabilityPeriod>
                    <cbc:EndDate>2024-07-11</cbc:EndDate>
                    <cbc:EndTime>19:00:00</cbc:EndTime>
                </cac:DocumentAvailabilityPeriod>
                <cac:TenderSubmissionDeadlinePeriod>
                    <cbc:EndDate>2024-07-11</cbc:EndDate>
                    <cbc:EndTime>19:00:00</cbc:EndTime>
                </cac:TenderSubmissionDeadlinePeriod>
                <cac:AuctionTerms>
                    <cbc:AuctionConstraintIndicator>false</cbc:AuctionConstraintIndicator>
                </cac:AuctionTerms>
            </cac:TenderingProcess>
            <cac:LegalDocumentReference>
                <cbc:ID>10 39-MU-6280_PCAP1 COMPLETO.pdf</cbc:ID>
                <cac:Attachment>
                    <cac:ExternalReference>
                        <cbc:URI>https://contrataciondelestado.es/wps/wcm/connect/PLACE_es/Site/area/docAccCmpnt?srv=cmpnt&amp;cmpntname=GetDocumentsById&amp;source=library&amp;DocumentIdParam=e5192f55-437c-4336-bcae-fa5ed89ed137</cbc:URI>
                        <cbc:DocumentHash>TaNyzb5w+ClWd4GazjY4A6j0n90=</cbc:DocumentHash>
                    </cac:ExternalReference>
                </cac:Attachment>
            </cac:LegalDocumentReference>
            <cac:TechnicalDocumentReference>
                <cbc:ID>13 39-MU-6280 ENLACES PROYECTO.pdf</cbc:ID>
                <cac:Attachment>
                    <cac:ExternalReference>
                        <cbc:URI>https://contrataciondelestado.es/wps/wcm/connect/PLACE_es/Site/area/docAccCmpnt?srv=cmpnt&amp;cmpntname=GetDocumentsById&amp;source=library&amp;DocumentIdParam=5f34283e-8f1f-4111-83bc-6ee7c627f85a</cbc:URI>
                        <cbc:DocumentHash>C+IvGwHro7JPdvGq0G0Zw5qd4jQ=</cbc:DocumentHash>
                    </cac:ExternalReference>
                </cac:Attachment>
            </cac:TechnicalDocumentReference>
            <cac:AdditionalDocumentReference>
                <cbc:ID>39-MU-6280.xml</cbc:ID>
                <cac:Attachment>
                    <cac:ExternalReference>
                        <cbc:URI>https://contrataciondelestado.es/wps/wcm/connect/PLACE_es/Site/area/docAccCmpnt?srv=cmpnt&amp;cmpntname=GetDocumentsById&amp;source=library&amp;DocumentIdParam=f0de9e5c-3148-464a-9d1d-6755d7ce3e97</cbc:URI>
                        <cbc:DocumentHash>Mo8VWVuBQYTANqJQ8I2ydW6lmkM=</cbc:DocumentHash>
                    </cac:ExternalReference>
                </cac:Attachment>
            </cac:AdditionalDocumentReference>
            <cac:AdditionalDocumentReference>
                <cbc:ID>Instrucciones DEUC.pdf</cbc:ID>
                <cac:Attachment>
                    <cac:ExternalReference>
                        <cbc:URI>https://contrataciondelestado.es/wps/wcm/connect/PLACE_es/Site/area/docAccCmpnt?srv=cmpnt&amp;cmpntname=GetDocumentsById&amp;source=library&amp;DocumentIdParam=f8ffa4b2-7c67-41f4-ad8c-490a1cd924e4</cbc:URI>
                        <cbc:DocumentHash>2XK7llhvbjvUXAN8KlDbZhtleg8=</cbc:DocumentHash>
                    </cac:ExternalReference>
                </cac:Attachment>
            </cac:AdditionalDocumentReference>
            <cac:AdditionalDocumentReference>
                <cbc:ID>39-MU-6280.pdf</cbc:ID>
                <cac:Attachment>
                    <cac:ExternalReference>
                        <cbc:URI>https://contrataciondelestado.es/wps/wcm/connect/PLACE_es/Site/area/docAccCmpnt?srv=cmpnt&amp;cmpntname=GetDocumentsById&amp;source=library&amp;DocumentIdParam=d52c9815-e054-4aea-aed0-b0a43a9c77d5</cbc:URI>
                        <cbc:DocumentHash>B7+m/6Aq3+UHDBARLcj9tZQJivo=</cbc:DocumentHash>
                    </cac:ExternalReference>
                </cac:Attachment>
            </cac:AdditionalDocumentReference>
            <cac-place-ext:ValidNoticeInfo>
                <cbc-place-ext:NoticeTypeCode listURI="https://contrataciondelestado.es/codice/cl/2.11/TenderingNoticeTypeCode-2.11.gc">DOC_CN</cbc-place-ext:NoticeTypeCode>
                <cac-place-ext:AdditionalPublicationStatus>
                    <cbc-place-ext:PublicationMediaName>BOE</cbc-place-ext:PublicationMediaName>
                    <cac-place-ext:AdditionalPublicationDocumentReference>
                        <cbc:IssueDate>2024-06-13</cbc:IssueDate>
                    </cac-place-ext:AdditionalPublicationDocumentReference>
                </cac-place-ext:AdditionalPublicationStatus>
            </cac-place-ext:ValidNoticeInfo>
            <cac-place-ext:ValidNoticeInfo>
                <cbc-place-ext:NoticeTypeCode listURI="https://contrataciondelestado.es/codice/cl/2.11/TenderingNoticeTypeCode-2.11.gc">DOC_CAN_ADJ</cbc-place-ext:NoticeTypeCode>
                <cac-place-ext:AdditionalPublicationStatus>
                    <cbc-place-ext:PublicationMediaName>Perfil del contratante</cbc-place-ext:PublicationMediaName>
                    <cac-place-ext:AdditionalPublicationDocumentReference>
                        <cbc:IssueDate>2024-11-27</cbc:IssueDate>
                    </cac-place-ext:AdditionalPublicationDocumentReference>
                </cac-place-ext:AdditionalPublicationStatus>
            </cac-place-ext:ValidNoticeInfo>
            <cac-place-ext:ValidNoticeInfo>
                <cbc-place-ext:NoticeTypeCode listURI="https://contrataciondelestado.es/codice/cl/2.11/TenderingNoticeTypeCode-2.11.gc">DOC_CD</cbc-place-ext:NoticeTypeCode>
                <cac-place-ext:AdditionalPublicationStatus>
                    <cbc-place-ext:PublicationMediaName>Perfil del contratante</cbc-place-ext:PublicationMediaName>
                    <cac-place-ext:AdditionalPublicationDocumentReference>
                        <cbc:IssueDate>2024-06-06</cbc:IssueDate>
                    </cac-place-ext:AdditionalPublicationDocumentReference>
                </cac-place-ext:AdditionalPublicationStatus>
            </cac-place-ext:ValidNoticeInfo>
            <cac-place-ext:ValidNoticeInfo>
                <cbc-place-ext:NoticeTypeCode listURI="https://contrataciondelestado.es/codice/cl/2.11/TenderingNoticeTypeCode-2.11.gc">DOC_CN</cbc-place-ext:NoticeTypeCode>
                <cac-place-ext:AdditionalPublicationStatus>
                    <cbc-place-ext:PublicationMediaName>Perfil del contratante</cbc-place-ext:PublicationMediaName>
                    <cac-place-ext:AdditionalPublicationDocumentReference>
                        <cbc:IssueDate>2024-06-06</cbc:IssueDate>
                    </cac-place-ext:AdditionalPublicationDocumentReference>
                </cac-place-ext:AdditionalPublicationStatus>
            </cac-place-ext:ValidNoticeInfo>
            <cac-place-ext:ValidNoticeInfo>
                <cbc-place-ext:NoticeTypeCode listURI="https://contrataciondelestado.es/codice/cl/2.11/TenderingNoticeTypeCode-2.11.gc">DOC_FORM</cbc-place-ext:NoticeTypeCode>
                <cac-place-ext:AdditionalPublicationStatus>
                    <cbc-place-ext:PublicationMediaName>Perfil del contratante</cbc-place-ext:PublicationMediaName>
                    <cac-place-ext:AdditionalPublicationDocumentReference>
                        <cbc:IssueDate>2024-12-19</cbc:IssueDate>
                    </cac-place-ext:AdditionalPublicationDocumentReference>
                </cac-place-ext:AdditionalPublicationStatus>
            </cac-place-ext:ValidNoticeInfo>
            <cac-place-ext:GeneralDocument>
                <cac-place-ext:GeneralDocumentDocumentReference>
                    <cbc:ID>2024-808fc79b-06f9-4f36-9949-667977a5f86d</cbc:ID>
                    <cbc:DocumentTypeCode listURI="http://contrataciondelestado.es/codice/cl/2.08/GeneralContractDocuments-2.08.gc">14</cbc:DocumentTypeCode>
                    <cac:Attachment>
                        <cac:ExternalReference>
                            <cbc:URI>https://contrataciondelestado.es/wps/wcm/connect/PLACE_es/Site/area/docAccCmpnt?srv=cmpnt&amp;cmpntname=GetDocumentsById&amp;source=library&amp;DocumentIdParam=2024-808fc79b-06f9-4f36-9949-667977a5f86d</cbc:URI>
                            <cbc:FileName>Informe sobre las ofertas incursas en presunción de anormalidad</cbc:FileName>
                        </cac:ExternalReference>
                    </cac:Attachment>
                </cac-place-ext:GeneralDocumentDocumentReference>
            </cac-place-ext:GeneralDocument>
            <cac-place-ext:GeneralDocument>
                <cac-place-ext:GeneralDocumentDocumentReference>
                    <cbc:ID>2024-715e4b11-0c10-44d2-9127-81a56418ae13</cbc:ID>
                    <cbc:DocumentTypeCode listURI="http://contrataciondelestado.es/codice/cl/2.08/GeneralContractDocuments-2.08.gc">12</cbc:DocumentTypeCode>
                    <cac:Attachment>
                        <cac:ExternalReference>
                            <cbc:URI>https://contrataciondelestado.es/wps/wcm/connect/PLACE_es/Site/area/docAccCmpnt?srv=cmpnt&amp;cmpntname=GetDocumentsById&amp;source=library&amp;DocumentIdParam=2024-715e4b11-0c10-44d2-9127-81a56418ae13</cbc:URI>
                            <cbc:FileName>Acta del órgano de asistencia</cbc:FileName>
                        </cac:ExternalReference>
                    </cac:Attachment>
                </cac-place-ext:GeneralDocumentDocumentReference>
            </cac-place-ext:GeneralDocument>
            <cac-place-ext:GeneralDocument>
                <cac-place-ext:GeneralDocumentDocumentReference>
                    <cbc:ID>2024-f761442b-7a0c-44b8-9bd5-7c09187c8ebe</cbc:ID>
                    <cbc:DocumentTypeCode listURI="http://contrataciondelestado.es/codice/cl/2.08/GeneralContractDocuments-2.08.gc">1</cbc:DocumentTypeCode>
                    <cac:Attachment>
                        <cac:ExternalReference>
                            <cbc:URI>https://contrataciondelestado.es/wps/wcm/connect/PLACE_es/Site/area/docAccCmpnt?srv=cmpnt&amp;cmpntname=GetDocumentsById&amp;source=library&amp;DocumentIdParam=2024-f761442b-7a0c-44b8-9bd5-7c09187c8ebe</cbc:URI>
                            <cbc:FileName>Actos públicos informativos o de aperturas de ofertas</cbc:FileName>
                        </cac:ExternalReference>
                    </cac:Attachment>
                </cac-place-ext:GeneralDocumentDocumentReference>
            </cac-place-ext:GeneralDocument>
            <cac-place-ext:GeneralDocument>
                <cac-place-ext:GeneralDocumentDocumentReference>
                    <cbc:ID>2024-526c90fa-93d8-4db8-8feb-dc96d6a5bfce</cbc:ID>
                    <cbc:DocumentTypeCode listURI="http://contrataciondelestado.es/codice/cl/2.08/GeneralContractDocuments-2.08.gc">12</cbc:DocumentTypeCode>
                    <cac:Attachment>
                        <cac:ExternalReference>
                            <cbc:URI>https://contrataciondelestado.es/wps/wcm/connect/PLACE_es/Site/area/docAccCmpnt?srv=cmpnt&amp;cmpntname=GetDocumentsById&amp;source=library&amp;DocumentIdParam=2024-526c90fa-93d8-4db8-8feb-dc96d6a5bfce</cbc:URI>
                            <cbc:FileName>Acta del órgano de asistencia</cbc:FileName>
                        </cac:ExternalReference>
                    </cac:Attachment>
                </cac-place-ext:GeneralDocumentDocumentReference>
            </cac-place-ext:GeneralDocument>
            <cac-place-ext:GeneralDocument>
                <cac-place-ext:GeneralDocumentDocumentReference>
                    <cbc:ID>2024-1a70d65c-0e41-4302-a9be-4a39a072903e</cbc:ID>
                    <cbc:DocumentTypeCode listURI="http://contrataciondelestado.es/codice/cl/2.08/GeneralContractDocuments-2.08.gc">12</cbc:DocumentTypeCode>
                    <cac:Attachment>
                        <cac:ExternalReference>
                            <cbc:URI>https://contrataciondelestado.es/wps/wcm/connect/PLACE_es/Site/area/docAccCmpnt?srv=cmpnt&amp;cmpntname=GetDocumentsById&amp;source=library&amp;DocumentIdParam=2024-1a70d65c-0e41-4302-a9be-4a39a072903e</cbc:URI>
                            <cbc:FileName>Acta del órgano de asistencia</cbc:FileName>
                        </cac:ExternalReference>
                    </cac:Attachment>
                </cac-place-ext:GeneralDocumentDocumentReference>
            </cac-place-ext:GeneralDocument>
            <cac-place-ext:GeneralDocument>
                <cac-place-ext:GeneralDocumentDocumentReference>
                    <cbc:ID>2024-84248c0b-2d31-4307-9e10-0642ab0ac3aa</cbc:ID>
                    <cbc:DocumentTypeCode listURI="http://contrataciondelestado.es/codice/cl/2.08/GeneralContractDocuments-2.08.gc">12</cbc:DocumentTypeCode>
                    <cac:Attachment>
                        <cac:ExternalReference>
                            <cbc:URI>https://contrataciondelestado.es/wps/wcm/connect/PLACE_es/Site/area/docAccCmpnt?srv=cmpnt&amp;cmpntname=GetDocumentsById&amp;source=library&amp;DocumentIdParam=2024-84248c0b-2d31-4307-9e10-0642ab0ac3aa</cbc:URI>
                            <cbc:FileName>Acta del órgano de asistencia</cbc:FileName>
                        </cac:ExternalReference>
                    </cac:Attachment>
                </cac-place-ext:GeneralDocumentDocumentReference>
            </cac-place-ext:GeneralDocument>
            <cac-place-ext:GeneralDocument>
                <cac-place-ext:GeneralDocumentDocumentReference>
                    <cbc:ID>2024-476ebe20-72eb-479e-9815-05fc29efd728</cbc:ID>
                    <cbc:DocumentTypeCode listURI="http://contrataciondelestado.es/codice/cl/2.08/GeneralContractDocuments-2.08.gc">12</cbc:DocumentTypeCode>
                    <cac:Attachment>
                        <cac:ExternalReference>
                            <cbc:URI>https://contrataciondelestado.es/wps/wcm/connect/PLACE_es/Site/area/docAccCmpnt?srv=cmpnt&amp;cmpntname=GetDocumentsById&amp;source=library&amp;DocumentIdParam=2024-476ebe20-72eb-479e-9815-05fc29efd728</cbc:URI>
                            <cbc:FileName>Acta del órgano de asistencia</cbc:FileName>
                        </cac:ExternalReference>
                    </cac:Attachment>
                </cac-place-ext:GeneralDocumentDocumentReference>
            </cac-place-ext:GeneralDocument>
            <cac-place-ext:GeneralDocument>
                <cac-place-ext:GeneralDocumentDocumentReference>
                    <cbc:ID>2024-96d6f24e-8931-47c4-9feb-a87ed7bddcbe</cbc:ID>
                    <cbc:DocumentTypeCode listURI="http://contrataciondelestado.es/codice/cl/2.08/GeneralContractDocuments-2.08.gc">13</cbc:DocumentTypeCode>
                    <cac:Attachment>
                        <cac:ExternalReference>
                            <cbc:URI>https://contrataciondelestado.es/wps/wcm/connect/PLACE_es/Site/area/docAccCmpnt?srv=cmpnt&amp;cmpntname=GetDocumentsById&amp;source=library&amp;DocumentIdParam=2024-96d6f24e-8931-47c4-9feb-a87ed7bddcbe</cbc:URI>
                            <cbc:FileName>Informe de valoración de los criterios de adjudicación cuantificables mediante juicio de valor</cbc:FileName>
                        </cac:ExternalReference>
                    </cac:Attachment>
                </cac-place-ext:GeneralDocumentDocumentReference>
            </cac-place-ext:GeneralDocument>
            <cac-place-ext:GeneralDocument>
                <cac-place-ext:GeneralDocumentDocumentReference>
                    <cbc:ID>2024-3f052589-4887-46d8-9a0a-f821ced98d8a</cbc:ID>
                    <cbc:DocumentTypeCode listURI="http://contrataciondelestado.es/codice/cl/2.08/GeneralContractDocuments-2.08.gc">1</cbc:DocumentTypeCode>
                    <cac:Attachment>
                        <cac:ExternalReference>
                            <cbc:URI>https://contrataciondelestado.es/wps/wcm/connect/PLACE_es/Site/area/docAccCmpnt?srv=cmpnt&amp;cmpntname=GetDocumentsById&amp;source=library&amp;DocumentIdParam=2024-3f052589-4887-46d8-9a0a-f821ced98d8a</cbc:URI>
                            <cbc:FileName>Actos públicos informativos o de aperturas de ofertas</cbc:FileName>
                        </cac:ExternalReference>
                    </cac:Attachment>
                </cac-place-ext:GeneralDocumentDocumentReference>
            </cac-place-ext:GeneralDocument>
            <cac-place-ext:GeneralDocument>
                <cac-place-ext:GeneralDocumentDocumentReference>
                    <cbc:ID>f8896783-0abc-48ab-87c1-cf1f5cd7e367</cbc:ID>
                    <cbc:DocumentTypeCode listURI="http://contrataciondelestado.es/codice/cl/2.08/GeneralContractDocuments-2.08.gc">11</cbc:DocumentTypeCode>
                    <cac:Attachment>
                        <cac:ExternalReference>
                            <cbc:URI>https://contrataciondelestado.es/wps/wcm/connect/PLACE_es/Site/area/docAccCmpnt?srv=cmpnt&amp;cmpntname=GetDocumentsById&amp;source=library&amp;DocumentIdParam=f8896783-0abc-48ab-87c1-cf1f5cd7e367</cbc:URI>
                            <cbc:FileName>Documento de aprobación del expediente</cbc:FileName>
                        </cac:ExternalReference>
                    </cac:Attachment>
                </cac-place-ext:GeneralDocumentDocumentReference>
            </cac-place-ext:GeneralDocument>
            <cac-place-ext:GeneralDocument>
                <cac-place-ext:GeneralDocumentDocumentReference>
                    <cbc:ID>d93158fa-158c-4ddf-a037-ea8ae1372ddb</cbc:ID>
                    <cbc:DocumentTypeCode listURI="http://contrataciondelestado.es/codice/cl/2.08/GeneralContractDocuments-2.08.gc">8</cbc:DocumentTypeCode>
                    <cac:Attachment>
                        <cac:ExternalReference>
                            <cbc:URI>https://contrataciondelestado.es/wps/wcm/connect/PLACE_es/Site/area/docAccCmpnt?srv=cmpnt&amp;cmpntname=GetDocumentsById&amp;source=library&amp;DocumentIdParam=d93158fa-158c-4ddf-a037-ea8ae1372ddb</cbc:URI>
                            <cbc:FileName>Acuerdo de iniciación del expediente</cbc:FileName>
                        </cac:ExternalReference>
                    </cac:Attachment>
                </cac-place-ext:GeneralDocumentDocumentReference>
            </cac-place-ext:GeneralDocument>
            <cac-place-ext:GeneralDocument>
                <cac-place-ext:GeneralDocumentDocumentReference>
                    <cbc:ID>daffb941-c1b3-415a-950a-bcf2c51f13fa</cbc:ID>
                    <cbc:DocumentTypeCode listURI="http://contrataciondelestado.es/codice/cl/2.08/GeneralContractDocuments-2.08.gc">ZZZ</cbc:DocumentTypeCode>
                    <cac:Attachment>
                        <cac:ExternalReference>
                            <cbc:URI>https://contrataciondelestado.es/wps/wcm/connect/PLACE_es/Site/area/docAccCmpnt?srv=cmpnt&amp;cmpntname=GetDocumentsById&amp;source=library&amp;DocumentIdParam=daffb941-c1b3-415a-950a-bcf2c51f13fa</cbc:URI>
                            <cbc:FileName>Enlace nuevo a proyecto</cbc:FileName>
                        </cac:ExternalReference>
                    </cac:Attachment>
                </cac-place-ext:GeneralDocumentDocumentReference>
            </cac-place-ext:GeneralDocument>
        </cac-place-ext:ContractFolderStatus>
    </entry>
 

Comme vous le voyez, chaque concours a des dimensions considérables et dans chaque fichier, nous pouvons trouver environ 450 concours. Cette dimension ne permet pas d'utiliser une classe d'ObjectScript pour le mappage (on pourrait... mais je ne suis pas prêt à le faire).  

Codes pour les tests

Mon idée est de capturer uniquement les champs pertinents pour les recherches ultérieures. Pour ce faire, j'ai créé la classe suivante qui nous servira à stocker les informations capturées:

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

Property IdLicitacion As%String(MAXLEN = 200);Property Titulo As%String(MAXLEN = 2000);Property URL As%String(MAXLEN = 1000);Property Resumen As%String(MAXLEN = 2000);Property TituloVectorizado As%Vector(DATATYPE = "DECIMAL", LEN = 384);Property Contratante As%String(MAXLEN = 2000);Property URLContratante As%String(MAXLEN = 2000);Property ValorEstimado As%Numeric(STORAGEDEFAULT = "columnar");Property ImporteTotal As%Numeric(STORAGEDEFAULT = "columnar");Property ImporteTotalSinImpuestos As%Numeric(STORAGEDEFAULT = "columnar");Property FechaAdjudicacion As%Date;Property Estado As%String;Property Ganador As%String(MAXLEN = 200);Property ImporteGanador As%Numeric(STORAGEDEFAULT = "columnar");Property ImporteGanadorSinImpuestos As%Numeric(STORAGEDEFAULT = "columnar");Property Clasificacion As%String(MAXLEN = 10);Property Localizacion As%String(MAXLEN = 200); Index IndexContratante On Contratante; Index IndexGanador On Ganador; Index IndexClasificacion On Clasificacion; Index IndexLocalizacion On Localizacion; Index IndexIdLicitation On IdLicitacion [ PrimaryKey ]; }

Pour capturer les données à l'aide d'Embedded Python, j'ai utilisé la bibliothèque  xml.etree.ElementTree  qui nous permet d'extraire les valeurs nœud par nœud. Voici la méthode Python que j'ai utilisée pour le mappage du XML:

Method ReadXML(xmlPath As %String) As %String [ Language = python ]
{
    import xml.etree.ElementTree as ET
    import iris
    import pandas as pd
<span class="hljs-keyword">try</span> :
    tree = ET.parse(xmlPath)
    root = tree.getroot()
    <span class="hljs-keyword">for</span> entry <span class="hljs-keyword">in</span> root.iter(<span class="hljs-string">"{http://www.w3.org/2005/Atom}entry"</span>):
        licitacion = {<span class="hljs-string">"titulo"</span>: <span class="hljs-string">""</span>, <span class="hljs-string">"resumen"</span>: <span class="hljs-string">""</span>, <span class="hljs-string">"idlicitacion"</span>: <span class="hljs-string">""</span>, <span class="hljs-string">"url"</span>: <span class="hljs-string">""</span>, <span class="hljs-string">"contratante"</span>: <span class="hljs-string">""</span>, <span class="hljs-string">"urlcontratante"</span>: <span class="hljs-string">""</span>, <span class="hljs-string">"estado"</span>: <span class="hljs-string">""</span>, <span class="hljs-string">"valorestimado"</span>: <span class="hljs-string">""</span>, <span class="hljs-string">"importetotal"</span>: <span class="hljs-string">""</span>, <span class="hljs-string">"importetotalsinimpuestos"</span>: <span class="hljs-string">""</span>, <span class="hljs-string">"clasificacion"</span>: <span class="hljs-string">""</span>, <span class="hljs-string">"localizacion"</span>: <span class="hljs-string">""</span>, <span class="hljs-string">"fechaadjudicacion"</span>: <span class="hljs-string">""</span>, <span class="hljs-string">"ganador"</span>: <span class="hljs-string">""</span>, <span class="hljs-string">"importeganadorsinimpuestos"</span>: <span class="hljs-string">""</span>, <span class="hljs-string">"importeganador"</span>: <span class="hljs-string">""</span>}
        <span class="hljs-keyword">for</span> tags <span class="hljs-keyword">in</span> entry:
            <span class="hljs-keyword">if</span> tags.tag == <span class="hljs-string">"{http://www.w3.org/2005/Atom}title"</span>:
                licitacion[<span class="hljs-string">"titulo"</span>] = tags.text
            <span class="hljs-keyword">if</span> tags.tag == <span class="hljs-string">"{http://www.w3.org/2005/Atom}summary"</span>:
                licitacion[<span class="hljs-string">"resumen"</span>] = tags.text
            <span class="hljs-keyword">if</span> tags.tag == <span class="hljs-string">"{http://www.w3.org/2005/Atom}id"</span>:
                licitacion[<span class="hljs-string">"idlicitacion"</span>] = tags.text
            <span class="hljs-keyword">if</span> tags.tag == <span class="hljs-string">"{http://www.w3.org/2005/Atom}link"</span>:
                licitacion[<span class="hljs-string">"url"</span>] = tags.attrib[<span class="hljs-string">"href"</span>]
            <span class="hljs-keyword">if</span> tags.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice-place-ext:schema:xsd:CommonAggregateComponents-2}ContractFolderStatus"</span>:
                <span class="hljs-keyword">for</span> detailTags <span class="hljs-keyword">in</span> tags:
                    <span class="hljs-keyword">if</span> detailTags.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice-place-ext:schema:xsd:CommonAggregateComponents-2}LocatedContractingParty"</span>:
                        <span class="hljs-keyword">for</span> infoContractor <span class="hljs-keyword">in</span> detailTags:
                            <span class="hljs-keyword">if</span> infoContractor.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}Party"</span>:
                                <span class="hljs-keyword">for</span> contractorDetails <span class="hljs-keyword">in</span> infoContractor:
                                    <span class="hljs-keyword">if</span> contractorDetails.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}PartyName"</span> :
                                        <span class="hljs-keyword">for</span> name <span class="hljs-keyword">in</span> contractorDetails:
                                            licitacion[<span class="hljs-string">"contratante"</span>] = name.text
                                    <span class="hljs-keyword">elif</span> contractorDetails.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice:schema:xsd:CommonBasicComponents-2}WebsiteURI"</span>:
                                        licitacion[<span class="hljs-string">"urlcontratante"</span>] = contractorDetails.text
                    <span class="hljs-keyword">elif</span> detailTags.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice-place-ext:schema:xsd:CommonAggregateComponents-2}ContractFolderStatusCode"</span>:
                        licitacion[<span class="hljs-string">"estado"</span>] = detailTags.text
                    <span class="hljs-keyword">elif</span> detailTags.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}ProcurementProject"</span>:
                        <span class="hljs-keyword">for</span> infoProcurement <span class="hljs-keyword">in</span> detailTags:
                            <span class="hljs-keyword">if</span> infoProcurement.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}BudgetAmount"</span>:
                                <span class="hljs-keyword">for</span> detailBudget <span class="hljs-keyword">in</span> infoProcurement:
                                    <span class="hljs-keyword">if</span> detailBudget.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice:schema:xsd:CommonBasicComponents-2}EstimatedOverallContractAmount"</span>:
                                        licitacion[<span class="hljs-string">"valorestimado"</span>] = detailBudget.text
                                    <span class="hljs-keyword">elif</span> detailBudget.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice:schema:xsd:CommonBasicComponents-2}TotalAmount"</span>:
                                        licitacion[<span class="hljs-string">"importetotal"</span>] = detailBudget.text
                                    <span class="hljs-keyword">elif</span> detailBudget.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice:schema:xsd:CommonBasicComponents-2}TaxExclusiveAmount"</span>:
                                        licitacion[<span class="hljs-string">"importetotalsinimpuestos"</span>] = detailBudget.text
                            <span class="hljs-keyword">elif</span> infoProcurement.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}RequiredCommodityClassification"</span>:
                                <span class="hljs-keyword">for</span> detailClassification <span class="hljs-keyword">in</span> infoProcurement:
                                    <span class="hljs-keyword">if</span> detailClassification.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice:schema:xsd:CommonBasicComponents-2}ItemClassificationCode"</span>:
                                        licitacion[<span class="hljs-string">"clasificacion"</span>] = detailClassification.text
                            <span class="hljs-keyword">elif</span> infoProcurement.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}RealizedLocation"</span>:
                                <span class="hljs-keyword">for</span> detailLocalization <span class="hljs-keyword">in</span> infoProcurement:
                                    <span class="hljs-keyword">if</span> detailLocalization.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice:schema:xsd:CommonBasicComponents-2}CountrySubentity"</span>:
                                        licitacion[<span class="hljs-string">"localizacion"</span>] = detailLocalization.text
                    <span class="hljs-keyword">elif</span> detailTags.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}TenderResult"</span>:
                        <span class="hljs-keyword">for</span> infoResult <span class="hljs-keyword">in</span> detailTags:
                            <span class="hljs-keyword">if</span> infoResult.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice:schema:xsd:CommonBasicComponents-2}AwardDate"</span>:
                                licitacion[<span class="hljs-string">"fechaadjudicacion"</span>] = infoResult.text
                            <span class="hljs-keyword">elif</span> infoResult.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}WinningParty"</span>:
                                <span class="hljs-keyword">for</span> detailWinner <span class="hljs-keyword">in</span> infoResult:
                                    <span class="hljs-keyword">if</span> detailWinner.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}PartyName"</span>:
                                        <span class="hljs-keyword">for</span> detailName <span class="hljs-keyword">in</span> detailWinner:
                                            <span class="hljs-keyword">if</span> detailName.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice:schema:xsd:CommonBasicComponents-2}Name"</span>:
                                                licitacion[<span class="hljs-string">"ganador"</span>] = detailName.text
                            <span class="hljs-keyword">elif</span> infoResult.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}AwardedTenderedProject"</span>:
                                <span class="hljs-keyword">for</span> detailTender <span class="hljs-keyword">in</span> infoResult:
                                    <span class="hljs-keyword">if</span> detailTender.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice:schema:xsd:CommonAggregateComponents-2}LegalMonetaryTotal"</span>:
                                        <span class="hljs-keyword">for</span> detailWinnerAmount <span class="hljs-keyword">in</span> detailTender:
                                            <span class="hljs-keyword">if</span> detailWinnerAmount.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice:schema:xsd:CommonBasicComponents-2}TaxExclusiveAmount"</span>:
                                                licitacion[<span class="hljs-string">"importeganadorsinimpuestos"</span>] = detailWinnerAmount.text
                                            <span class="hljs-keyword">elif</span> detailWinnerAmount.tag == <span class="hljs-string">"{urn:dgpe:names:draft:codice:schema:xsd:CommonBasicComponents-2}PayableAmount"</span>:
                                                licitacion[<span class="hljs-string">"importeganador"</span>] = detailWinnerAmount.text
        iris.cls(<span class="hljs-string">"Ens.Util.Log"</span>).LogInfo(<span class="hljs-string">"Inquisidor.BP.XMLToLicitacion"</span>, <span class="hljs-string">"VectorizePatient"</span>, <span class="hljs-string">"Terminado mapeo "</span>+licitacion[<span class="hljs-string">"titulo"</span>])
        <span class="hljs-keyword">if</span> licitacion.get(<span class="hljs-string">"importeganador"</span>) <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">None</span> <span class="hljs-keyword">and</span> licitacion.get(<span class="hljs-string">"importeganador"</span>) <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-string">""</span>:
            iris.cls(<span class="hljs-string">"Ens.Util.Log"</span>).LogInfo(<span class="hljs-string">"Inquisidor.BP.XMLToLicitacion"</span>, <span class="hljs-string">"VectorizePatient"</span>, <span class="hljs-string">"Lanzando insert "</span>+licitacion[<span class="hljs-string">"titulo"</span>])
            stmt = iris.sql.prepare(<span class="hljs-string">"INSERT INTO INQUISIDOR_Object.Licitacion (Titulo, Resumen, IdLicitacion, URL, Contratante, URLContratante, Estado, ValorEstimado, ImporteTotal, ImporteTotalSinImpuestos, Clasificacion, Localizacion, FechaAdjudicacion, Ganador, ImporteGanadorSinImpuestos, ImporteGanador) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,TO_DATE(?,'YYYY-MM-DD'),?,?,?)"</span>)
            <span class="hljs-keyword">try</span>:
                rs = stmt.execute(licitacion[<span class="hljs-string">"titulo"</span>], licitacion[<span class="hljs-string">"resumen"</span>], licitacion[<span class="hljs-string">"idlicitacion"</span>], licitacion[<span class="hljs-string">"url"</span>], licitacion[<span class="hljs-string">"contratante"</span>], licitacion[<span class="hljs-string">"urlcontratante"</span>], licitacion[<span class="hljs-string">"estado"</span>], licitacion[<span class="hljs-string">"valorestimado"</span>], licitacion[<span class="hljs-string">"importetotal"</span>], licitacion[<span class="hljs-string">"importetotalsinimpuestos"</span>], licitacion[<span class="hljs-string">"clasificacion"</span>], licitacion[<span class="hljs-string">"localizacion"</span>], licitacion[<span class="hljs-string">"fechaadjudicacion"</span>], licitacion[<span class="hljs-string">"ganador"</span>], licitacion[<span class="hljs-string">"importeganadorsinimpuestos"</span>], licitacion[<span class="hljs-string">"importeganador"</span>])
            <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> err:
                iris.cls(<span class="hljs-string">"Ens.Util.Log"</span>).LogInfo(<span class="hljs-string">"Inquisidor.BP.XMLToLicitacion"</span>, <span class="hljs-string">"VectorizePatient"</span>, repr(err))
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Success"</span>
<span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> err:
    iris.cls(<span class="hljs-string">"Ens.Util.Log"</span>).LogInfo(<span class="hljs-string">"Inquisidor.BP.XMLToLicitacion"</span>, <span class="hljs-string">"VectorizePatient"</span>, repr(err))
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Error"</span>

}

Une fois le mappage terminé, nous procédons à un simple insert avec l'enregistrement.

Pour le mappage en utilisant ObjectScript, j'ai utilisé la fonctionnalité %XML.TextReader , voyons la méthode:

Method OnRequest(pRequest As Ens.StreamContainer, Output pResponse As Ens.Response) As%Status
{
    set filename = pRequest.OriginalFilename

    set status=##class(%XML.TextReader).ParseFile(filename,.textreader)
    //check statusif$$$ISERR(status) {do$System.Status.DisplayError(status) quit}
    set tStatement = ##class(%SQL.Statement).%New()
    //iterate through document, node by nodewhile textreader.Read()
    {        

        if ((textreader.NodeType = "element") && (textreader.Depth = 2) && (textreader.Path = "/feed/entry")) {
            if ($DATA(licitacion))
            {                
                if (licitacion.ImporteGanador '= ""){
                    //set sc = licitacion.%Save()set myquery = "INSERT INTO INQUISIDOR_Object.LicitacionOS (Titulo, Resumen, IdLicitacion, URL, Contratante, URLContratante, Estado, ValorEstimado, ImporteTotal, ImporteTotalSinImpuestos, Clasificacion, Localizacion, FechaAdjudicacion, Ganador, ImporteGanadorSinImpuestos, ImporteGanador) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"set qStatus = tStatement.%Prepare(myquery)
                    if qStatus '= 1 {
                        write"%Prepare failed:"do$System.Status.DisplayError(qStatus)
                        quit
                    }
                    set rset = tStatement.%Execute(licitacion.Titulo, licitacion.Resumen, licitacion.IdLicitacion, licitacion.URL, licitacion.Contratante, licitacion.URLContratante, licitacion.Estado, licitacion.ValorEstimado, licitacion.ImporteTotal, licitacion.ImporteTotalSinImpuestos, licitacion.Clasificacion, licitacion.Localizacion, licitacion.FechaAdjudicacion, licitacion.Ganador, licitacion.ImporteGanadorSinImpuestos, licitacion.ImporteGanador)
                }                
            }
            set licitacion = ##class(Inquisidor.Object.LicitacionOS).%New()
        }        

        if (textreader.Path = "/feed/entry/title"){
            if (textreader.Value '= ""){
                set licitacion.Titulo = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/summary"){
            if (textreader.Value '= ""){
                set licitacion.Resumen = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/id"){
            if (textreader.Value '= ""){
                set licitacion.IdLicitacion = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/link"){            
            if (textreader.MoveToAttributeName("href")) {
                set licitacion.URL = textreader.Value                
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cbc-place-ext:ContractFolderStatusCode"){
            if (textreader.Value '= ""){
                set licitacion.Estado = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac-place-ext:LocatedContractingParty/cac:Party/cac:PartyName"){
            if (textreader.Value '= ""){
                set licitacion.Contratante = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac-place-ext:LocatedContractingParty/cac:Party/cbc:WebsiteURI"){
            if (textreader.Value '= ""){
                set licitacion.URLContratante = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac:ProcurementProject/cac:BudgetAmount/cbc:EstimatedOverallContractAmount"){
            if (textreader.Value '= ""){
                set licitacion.ValorEstimado = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac:ProcurementProject/cac:BudgetAmount/cbc:TotalAmount"){
            if (textreader.Value '= ""){
                set licitacion.ImporteTotal = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac:ProcurementProject/cac:BudgetAmount/cbc:TaxExclusiveAmount"){
            if (textreader.Value '= ""){
                set licitacion.ImporteTotalSinImpuestos = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac:ProcurementProject/cac:RequiredCommodityClassification/cbc:ItemClassificationCode"){
            if (textreader.Value '= ""){
                set licitacion.Clasificacion = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac:ProcurementProject/cac:RealizedLocation/cbc:CountrySubentity"){
            if (textreader.Value '= ""){
                set licitacion.Localizacion = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac:TenderResult/cbc:AwardDate"){
            if (textreader.Value '= ""){
                set licitacion.FechaAdjudicacion = $System.SQL.Functions.TODATE(textreader.Value,"YYYY-MM-DD")
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac:TenderResult/cac:WinningParty/cac:PartyName/cbc:Name"){
            if (textreader.Value '= ""){
                set licitacion.Ganador = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac:TenderResult/cac:AwardedTenderedProject/cac:LegalMonetaryTotal/cbc:TaxExclusiveAmount"){
            if (textreader.Value '= ""){
                set licitacion.ImporteGanadorSinImpuestos = textreader.Value
            }
        }
        if (textreader.Path = "/feed/entry/cac-place-ext:ContractFolderStatus/cac:TenderResult/cac:AwardedTenderedProject/cac:LegalMonetaryTotal/cbc:PayableAmount"){
            if (textreader.Value '= ""){
                set licitacion.ImporteGanador = textreader.Value
            }
        }       
    }    
    // set resultEmbeddings = ..GenerateEmbeddings()Quit$$$OK
}

Les deux codes n'enregistreront dans la base de données que les concours qui ont déjà été résolus (pour lesquels le montant total gagnant a été communiqué).

Configuration de la production

Avec nos méthodes mises en œuvre dans les processus métier correspondants, il ne nous reste plus qu'à configurer la production pour notre test, ce qui nous permettra d'alimenter les deux méthodes. Il suffit d'ajouter deux services métier qui se limiteront à capturer les fichiers avec les informations XML et à les transmettre aux processus métier.

Pour éviter toute interférence lors de la capture et de la transmission des informations aux processus métier, nous allons créer deux services métier. La production se présentera comme suit:

Pour le test, nous allons introduire les concours publics du mois de février, soit un total de 91 fichiers avec 1,30 Go de données. Voyons comment se comportent les deux codes.

À vos marques...

Prêts...

C'est parti!

Résultats du mappage XML en ObjectScript

Commençons par le temps nécessaire au code ObjectScript pour mapper les 91 fichiers:

Le premier fichier a été démarré à 21:11:15, voyons quand le dernier fichier a été mappé:

Si nous regardons les détails du dernier message, nous pouvons voir la date finale du traitement:

L'heure finale est 21:17:43, ce qui donne un temps de traitement de 6 minutes et 28 secondes.

Résultats du mappage XML en Embedded Python

Répétez la même opération avec le processus qui utilise Python:

Début à 21:11:15 comme dans le cas précédent, voyons quand l'opération s'est terminée:

Voyons le message en détail pour connaître la fin exacte:

L'heure finale était 21:12:03, ce qui nous amène à un total de 48 secondes.

Eh bien, le gagnant est connu ! Dans ce round, Embedded Python a battu ObjectScript, du moins en ce qui concerne l'analyse XML. Si vous avez des suggestions ou des améliorations à apporter au code des deux méthodes, je vous encourage à les mettre dans les commentaires et je répéterai les tests pour vérifier les améliorations possibles.

Ce que nous pouvons dire, c'est qu'en ce qui concerne la supériorité évidente de la performance d'ObjectScript par rapport à Python... le mythe est brisé!

myth-busted – Mike Raffety, DTM, PID

0
0 35
Question Sylvain Guilbaud · Mars 7, 2025

En utilisant IRIS 2024.1.2 lorsque je monte la base de données de routine en lecture seule, j'obtiens une erreur lors de l'exécution de requêtes SQL dynamique.

Quelles globales doivent être mappées dans une base de données en lecture-écriture afin de permettre l'écriture des plans d'exécutions ?

0
0 40
Article Sylvain Guilbaud · Fév 28, 2025 1m read

Bonjour,

Comme il m'a fallu un certain temps pour comprendre d'où venait le problème, je voudrais partager cette expérience, afin que vous ne tombiez pas dans le même piège.

Je viens de remarquer que si vous nommez votre package "code" (tout en minuscules), dans une classe utilisant du python intégré en utilisant [Language = python], vous aurez l'erreur suivante :

 <THROW> *%Exception.PythonException <PYTHON EXCEPTION> 246 <class 'ModuleNotFoundError'>: No module named 'code.basics'; 'code' is not a package

0
0 25
Article Sylvain Guilbaud · Fév 26, 2025 7m read

L'utilisation de Embedded Python lors de la création d'une solution basée sur InterSystems peut ajouter des fonctionnalités très puissantes et approfondies à votre boîte à outils.

J'aimerais partager un exemple de cas d'utilisation que j'ai rencontré : l'activation d'un CDC (Change Data Capture) pour une collection mongoDB - la capture de ces modifications, leur digestion via un flux d'interopérabilité et, pour finir, la mise à jour d'un EMR via une API REST.

0
0 39
Article Sylvain Guilbaud · Fév 21, 2025 6m read

InterSystems est à l'avant-garde de la technologie des bases de données depuis sa création, en étant à l'origine d'innovations qui surpassent régulièrement ses concurrents comme Oracle, IBM et Microsoft. En se concentrant sur une conception de noyau efficace et en adoptant une approche sans compromis des performances des données, InterSystems s'est taillé une place de choix dans les applications critiques, garantissant fiabilité, rapidité et évolutivité.

Une histoire d'excellence technique

0
0 66
Article Sylvain Guilbaud · Jan 31, 2025 1m read

Dans un environnement conteneurisé, vous pouvez gérer l'heure de votre conteneur via la variable TZ ou via les répertoires /etc/timezone et /etc/localtime :

environment:
      - TZ=Europe/Paris
volumes:
    - "/etc/timezone:/etc/timezone:ro"
    - "/etc/localtime:/etc/localtime:ro"

Vous pouvez retrouver des exemples complets ici :

IRIS Community

IRISHealth_Community

IRIS production

IRISHealth production

0
0 29
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
Article Sylvain Guilbaud · Jan 29, 2025 3m read

Lors du dernier concours InterSystems "Bringing Ideas to Reality", j'ai parcouru le portail d'idées à la recherche de problèmes d'interface utilisateur à traiter. 

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

J'ai implémenté les idées suivantes dans l'extension de navigateur IRIS Whiz browser extension, so if you use the management portal to help with your day-to-day integration management this extension could be for you!

Fonctionnalité ajoutée: Rafraîchissement de la file d'attente

Iris a désormais une liste déroulante de rafraîchissement automatique pour la page des files d'attente (Queues). Cette option permet de rafraîchir la file d'attente à l'intervalle sélectionné. Cette fonctionnalité ne s'applique pas à Ensemble, qui en dispose déjà.

C'est utile si vous avez un concours de clics à venir et que vous avez besoin du repos de votre doigt de clic.

Implémenté à partir de l'idée: https://ideas.intersystems.com/ideas/DPI-I-487

 

Fonctionnalité ajoutée : Exportation de la recherche au format CSV

Dans la page de la visionneuse de messages Message Viewer, vous pouvez cliquer sur le bouton Iris Whiz Export pour télécharger une copie CSV des données contenues actuellement dans votre table de recherche.

Utile si vous voulez faire une analyse rapide de vos données sans utiliser la nouvelle page Chart.JS que j'ai mis une éternité à créer (voir ici en action!).

Implémenté à partir de l'idée: https://ideas.intersystems.com/ideas/DPI-I-566

 

Fonctionnalité ajoutée : Tri de la file d'attente des pages de production

Ajout d'options de tri pour l'onglet "file d'attente" de la page de production. Le tri par défaut est le nombre d'erreurs. Cliquez sur l'en-tête d'un table pour passer de l'ordre de tri asc à l'ordre de tri desc. Utilisez la barre de recherche pour trouver rapidement des éléments.

Utile si vous ne voulez pas faire défiler sur l'écran pour accéder à la plus grande file d'attente.

Implémenté à partir de l'idée: https://ideas.intersystems.com/ideas/DPI-I-628

 

Fonctionnalité Ajoutée: Ordre insensible à la casse de la liste déroulante des catégories

Permet de classer par ordre alphabétique la liste déroulante des catégories dans la page de production, quel que soit la casse. Sans cela, l'ordre est dépendant de la casse.

Utile si vous voulez trouver des choses dans la liste des catégories, sans avoir à tout recatégoriser dans la même casse pour y parvenir.

Implémenté à partir de l'idée: https://ideas.intersystems.com/ideas/DPI-I-625

Bonus! 

Il existe également un taux de rafraîchissement dans l'onglet de la visionneuse de messages dans la page de production.  Cela rafraîchira également votre onglet de file d'attente si vous sélectionnez un intervalle et naviguez vers l'onglet de la file d'attente. 

Si l'une de ces idées vous plaît, téléchargez l'extension de navigateur et communiquez moi vos commentaires. Vous trouverez une vidéo d'installation sur la liste d'OpenExchange que je vous recommande de regarder car vous devrez en réaliser une partie pour que la plupart des fonctionnalités fonctionnent!

0
0 36
Article Sylvain Guilbaud · Jan 2, 2025 10m read

Votre mission

Imaginons que vous êtes un espion international qui consacre sa vie à protéger les habitants de notre planète contre les dangers. La mission suivante vous est confiée:

Cher agent IRIS,

Nous sommes désolés d'interrompre vos vacances aux Bahamas, mais notre agent à Londres vient de nous informer qu'une "bombe à explosion retardée" est prête à exploser dans une zone très peuplée de Los Angeles. Selon nos informateurs, la "bombe à explosion retardée" devrait se déclencher à 15h14 aujourd'hui.

Dépêchez-vous, les citoyens comptent sur vous!

Le problème

Alors que vous vous précipitez et vous préparez à partir pour Los Angeles, vous vous rendez compte qu'il vous manque un renseignement essentiel : cette "bombe à explosion retardée" explosera-t-elle à 15h14 heure des Bahamas ou à 15h14 heure de Los Angeles? ...ou peut-être même à 15h14 heure de Londres.

Vous comprenez bien vite que l'heure fournie (15h14) ne vous donne pas assez de renseignements pour déterminer le moment où vous devez vous rendre à Los Angeles.

L'heure indiquée (15h14) était ambiguë. Vous avez besoin de plus de renseignements pour déterminer une heure exacte.

Quelques solutions

Lorsque vous réfléchissez au problème, vous vous rendez compte qu'il existe des méthodes pour surmonter l'ambiguïté de l'heure indiquée qui vous a été fournie:

  1. Votre informateur aurait pu vous fournir le lieu où l'heure locale était 3:14 PM. Par exemple, Los Angeles, les Bahamas, ou Londres.

  2. Votre informateur aurait pu utiliser un standard telle que l'UTC (temps universel coordonné) pour vous fournir un décalage par rapport à un lieu convenu (tel que Greenwich, Londres).

La fin heureuse

Vous téléphonez à votre informateur et confirmez que l'heure fournie était bien 15h14 heure de Los Angeles. Vous pouvez vous rendre à Los Angeles, désamorcer la "bombe à explosion retardée" avant 15h14, et retourner rapidement aux Bahamas pour y terminer vos vacances.

Le but

Quel est donc le but de cet exercice de réflexion? Je doute qu'aucun d'entre nous soit confronté au problème présenté ci-dessus, mais si vous travaillez avec une application ou un code qui déplace des données d'un lieu à un autre (en particulier si les lieux se trouvent dans des fuseaux horaires différents), vous devez savoir comment gérer les dates-heures et les fuseaux horaires.

Les fuseaux horaires, C'EST DUR!

Eh bien, les fuseaux horaires ne sont pas si mauvais. Ce sont l'heure d'été et les périmètres politiques qui rendent les fuseaux horaires si difficiles à gérer.

Je croyais avoir toujours compris l'idée "générale" des fuseaux horaires: le globe est divisé en tranches verticales par fuseau horaire, où chaque fuseau horaire est en retard d'une heure par rapport au fuseau horaire situé à l'Est.

Les fuseaux horaires sur la carte du monde (simplification)

Bien que cette simplification soit valable pour de nombreux endroits, il existe malheureusement de nombreuses exceptions à cette règle.

Fuseaux horaires du monde (Wikipedia) Référence: Fuseaux horaires du monde (Wikipedia)

Représentation du temps UTC ("l'origine")

Pour simplifier la communication de l'heure spécifique, le monde a décidé d'utiliser l'UTC (le temps universel coordonné). Ce standard fixe "l'origine" à la longitude 0° qui passe par Greenwich, Londres.

Définition du "décalage"

En utilisant le temps universel coordonné (UTC) comme base, tous les autres fuseaux horaires peuvent être définis par rapport à l'UTC. Cette relation est appelée décalage UTC.

Si vous connaissez l'heure locale et le décalage, vous n'avez plus d'heure ambiguë (comme dans notre exemple d'espionnage ci-dessus) ; vous avez une heure définie et spécifique, sans ambiguïté.

Le format type utilisé pour indiquer le décalage UTC est le suivant: ±HHMM[SS[.ffffff]].

  • Le symbole moins - indique un décalage à l'ouest de l'UTC.
  • Le signe plus + indique un décalage à l'est de l'UTC..
  • HH indique les heures (avec un zéro initial)
  • MM indique les minutes (avec un zéro initial)
  • SS indique les secondes (avec un zéro initial)
  • .ffffff indique les fractions de secondes

Par exemple, en Amérique, le fuseau horaire de l'Est (EST) est défini comme -0500 UTC. Cela signifie que toutes les localités situées dans le fuseau horaire EST sont en retard de 5 heures par rapport à l'UTC. Si l'heure est 9:00 PM à UTC, l'heure locale à EST est 4:00 PM.

Dans le fuseau horaire central occidental d'Australie (ACWST), le décalage est défini comme +0845 UTC. Si l'heure est 21h00 à UTC, l'heure locale d'EST est 16h00.

Heure d'été

Revenons aux cartes des fuseaux horaires ci-dessus. Vous y trouverez de nombreux fuseaux horaires qui suivent les frontières politiques des pays et des régions. Cela complique légèrement le calcul des fuseaux horaires, mais c'est assez facile à comprendre.

Malheureusement, il y a un autre élément à prendre en compte lorsque l'on travaille avec des heures et des fuseaux horaires. Regardons Los Angeles.

Cette carte indique que le décalage UTC pour Los Angeles est de -8 en Standard Time. L'heure normale est généralement suivie pendant les mois d'hiver, tandis que l'heure d'été est généralement suivie pendant les mois d'été.

L'heure d'été (en anglais; Daylight Savings Time (DST) avance les horloges d'un fuseau horaire donné (généralement d'une heure pendant les mois d'été). Les régions politiques peuvent choisir de suivre l'heure d'été pour plusieurs raisons (économies d'énergie, meilleure utilisation de la lumière du jour, etc.) La difficulté et la complexité de l'heure d'été résident dans le fait que l'heure d'été n'est pas appliquée de manière uniforme dans le monde entier. Selon votre situation géographique, votre région peut suivre ou non l'heure d'été.

Base de données de fuseaux horaires

La combinaison des frontières politiques et de l'heure d'été augmentant considérablement la complexité de la détermination d'une heure spécifique, une base de données des fuseaux horaires est nécessaire pour faire correspondre correctement les heures locales à des heures spécifiques par rapport à l'UTC. La base de données des fuseaux horaires de l'IANA (Internet Assigned Numbers Authority) est la source commune de renseignements sur les fuseaux horaires utilisée par les systèmes d'exploitation et les langages de programmation.

La base de données comprend les noms et alias de tous les fuseaux horaires, des renseignements sur le décalage, des renseignements sur l'utilisation de l'heure d'été, les abréviations des fuseaux horaires et les plages de données auxquelles les différentes règles s'appliquent.

Des copies et des renseignements concernant la base de données sur les fuseaux horaires sont disponibles sur le site web de l'IANA.

La plupart des systèmes UNIX disposent d'une copie de la base de données qui est mise à jour par le gestionnaire de paquetages du système d'exploitation (généralement installé dans /usr/share/zoneinfo). Certains langages de programmation ont la base de données intégrée. D'autres la rendent disponible par le biais d'une bibliothèque ou peuvent lire la copie de la base de données du système.

Noms et identifiants des fuseaux horaires

La base de données des fuseaux horaires contient un grand nombre de noms et d'alias pour des fuseaux horaires spécifiques. La plupart des saisies incluent un pays (ou un continent) et une grande ville dans le nom. Par exemple:

  • Amérique/New_York
  • Amérique/Los_Angeles
  • Europe/Rome
  • Australie/Melbourne

Conversion et formatage à l'aide d'ObjectScript

Ainsi, voici ce que nous savons:

  • les heures locales (heures ambiguës sans décalage ni emplacement)
  • les décalages UTC (le décalage relatif d'un horodatage ou d'un emplacement par rapport à l'« origine » UTC à Greenwich, Londres)
  • l'heure d'été (une tentative d'aider la civilisation au détriment des décalages de fuseaux horaires)
  • base de données des fuseaux horaires (qui contient des renseignements sur les fuseaux horaires et le respect de l'heure d'été dans de nombreux lieux et régions).

Sachant cela, comment pouvons-nous travailler avec les heures-dates et les fuseaux horaires en ObjectScript?

***Note: Je crois que toutes les affirmations suivantes sont vraies à propos d'ObjectScript, mais n'hésitez pas à me faire savoir si j'ai mal expliqué comment ObjectScript fonctionne avec les fuseaux horaires et les décalages.

Variables et fonctions intégrées

Si vous avez besoin de convertir des horodatages entre différents formats dans le fuseau horaire du processus exécutant IRIS, les fonctionnalités intégrées d'ObjectScript devraient être suffisantes. Voici une brève liste des variables/fonctions relatives au temps dans ObjectScript:

  • $ZTIMESTAMP / $ZTS

    • Format interne d'IRIS en tant que valeur UTC (décalage +0000).
    • Format: ddddd,sssss.fffffff
  • $NOW(tzmins)

    • Heure locale du système actuel avec le décalage de tzmins donné par rapport à UTC.
    • Heure d'été n'est pas prise en compte.
    • Par défaut, tzmins est basé sur la variable $ZTIMEZONE.
    • Format: ddddd,sssss.fffffff
  • $HOROLOG

    • Heure locale actuelle du système (basée sur $ZTIMEZONE), avec prise en compte de l'heure d'été.
    • Format: ddddd,sssss.fffffff
  • $ZTIMEZONE

    • Renvoie ou définit le décalage UTC local du système en minutes.
  • $ZDATETIME() / $ZDT()

    • Conversion du format $HOROLOG en un format d'affichage spécifique.
    • Peut être utilisé pour la conversion de l'heure locale du système à l'heure UTC (+0000).
  • $ZDATETIMEH() / $ZDTH()

    • Conversion d'une chaîne de date au format interne $HOROLOG.
    • Peut être utilisé pour convertir l'heure UTC (+0000) en heure locale.

Pour autant que je sache, ces fonctionnalités ne peuvent manipuler les dates qu'en utilisant le fuseau horaire du système local. Il ne semble pas y avoir de moyen de travailler avec des fuseaux horaires arbitraires en ObjectScript.

Accès à la bibliothèque tz sur Open Exchange

Pour faciliter la conversion vers et depuis des fuseaux horaires arbitraires, j'ai créé la bibliothèque de conversion de fuseaux horaires ObjectScript tz - ObjectScript Time Zone Conversion Library.

Cette bibliothèque accède à la base de données des fuseaux horaires installée sur votre système afin d'offrir un support pour la conversion des horodatages entre les fuseaux horaires et les formats.

Par exemple, si vous disposez de l'heure locale de Los Angeles (Amérique/Los_Angeles), vous pouvez la convertir dans le fuseau horaire utilisé aux Bahamas (Amérique/New_York) ou dans le fuseau horaire utilisé à Londres (Europe/Londres):

USER>zw ##class(tz.Ens).TZ("2024-12-20 3:14 PM", "America/Los_Angeles", "America/New_York")
"2024-12-20 06:14 PM"

USER>zw ##class(tz.Ens).TZ("2024-12-20 3:14 PM", "America/Los_Angeles", "Europe/London")
"2024-12-20 11:14 PM"

Si l'on vous donne un horodatage avec un décalage, vous pouvez le convertir en heure locale à Eucla, Australie (Australie/Eucla), même si vous ne connaissez pas le fuseau horaire d'origine:

USER>zw ##class(tz.Ens).TZ("2024-12-20 08:00 PM -0500", "Australia/Eucla")
"2024-12-21 09:45 AM +0845"

Si vous travaillez avec des messages HL7, la bibliothèque tz dispose de plusieurs méthodes associées aux règles d'interopérabilité et aux DTL pour vous aider à convertir facilement les fuseaux horaires, les heures locales, les heures avec décalage, etc.:

// Conversion de l'heure locale d'un fuseau horaire à l'autre 	 
set datetime = "20240102033045"
set newDatetime = ##class(tz.Ens).TZ(datetime,"America/New_York","America/Chicago")

// Conversion de l'heure locale en décalage 	 
set datetime = "20240102033045"
set newDatetime = ##class(tz.Ens).TZOffset(datetime,"America/Chicago","America/New_York")

// Conversion du décalage en heure locale 	 
set datetime = "20240102033045-0500"
set newDatetime = ##class(tz.Ens).TZLocal(datetime,"America/Chicago")

// Conversion vers un format alternatif au HL7 	 
set datetime = "20240102033045-0500"
set newDatetime = ##class(tz.Ens).TZ(datetime,"America/Chicago",,"%m/%d/%Y %H:%M:%S %z")

Résumé

Je vous remercie de m'avoir suivi dans ce "voyage à travers le monde" où nous avons rencontré des fuseaux horaires, l'heure d'été, les cartes du monde et les "bombes à explosion retardée". J'espère que ce travail vous a permis d'éclairer (et de simplifier) les nombreuses complexités liées à l'utilisation des dates et des fuseaux horaires.

Consultez la bibliothèque tz - ObjectScript Time Zone Conversion Library et faites-moi savoir si vous avez des questions (ou des corrections/clarifications à propos de quelque chose que j'ai dit ici.

Merci!

Références / Liens intéressants

0
0 85
Article Sylvain Guilbaud · Déc 26, 2024 3m read

Traduit du concours d'articles de la communauté espagnole.

Suite au dernier concours de programmation sur OEX j'ai eu une observation surprenante.
Il existait des applications presque exclusives basées sur l'IA en combinaison avec des modules Py précuits.
Mais en creusant plus profondément, tous les exemples utilisaient les mêmes éléments techniques d'IRIS.

Du point de vue d'IRIS, c'était à peu près la même chose que la recherche de texte
ou la recherche d'images ou d'autres motifs.  Cela s'est terminé par des méthodes presque échangeables.

1
0 40