0 Abonnés · 38 Publications

Health Level-7 ou HL7 désigne un ensemble de normes internationales relatives au transfert de données cliniques et administratives entre des applications logicielles utilisées par divers prestataires de services de santé.

En savoir plus

Article Sylvain Guilbaud · Oct 29, 2025 14m read

Aperçu

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

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

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

Conditions préalables

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

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

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

Mode d'emploi

Le processus de configuration est simple:

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

Configuration

Avant la compilation et l'exécution:

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

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

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

Mise à l'essai de l'application

Ajout d'enregistrements

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

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

Mise à jour de l'enregistrement

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

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

Suppression de l'enregistrement

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

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

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

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

Recherche de l'enregistrement

Code / Ressources

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

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

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

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

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

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

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

</body> 

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

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

 

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

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

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

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

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

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

}

Conclusion

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

0
0 17
Article Lorenzo Scalese · Jan 6, 2025 6m read

Salut la Communauté,

image
Dans cet article, je présenterai mon application iris-HL7v2Gen.

IRIS-HL7v2Gen est une application CSP qui facilite la génération dynamique de messages de test HL7. Ce processus est essentiel pour tester, déboguer et intégrer les systèmes de données de soins de santé. L'application permet aux utilisateurs de générer une grande variété de types de messages HL7, de valider leur structure par rapport aux spécifications HL7, d'explorer la hiérarchie des messages et de transmettre les messages par TCP/IP aux systèmes de production. Ces fonctionnalités sont particulièrement utiles dans les contextes où la conformité aux normes HL7 est obligatoire pour assurer l'interopérabilité entre différents organismes ou systèmes de soins de santé.

Fonctionnalités de l'application

  • Génération Dynamique de Messages HL7: Création instantanée de messages HL7 pour une gamme de types de messages, facilitant ainsi les tests complets.
  • Exploration de la structure des messages: Visualisation de la structure des messages générés sur la base des spécifications HL7.
  • Visualisation des jeux de valeurs: Visualisation des jeux de valeurs codées prédéfinis pour des champs spécifiques.
  • Validation des messages: Validation des messages par rapport aux normes HL7 pour garantir la conformité.
  • Communication TCP/IP: Transmission facile de messages à la production à l'aide de paramètres TCP/IP.
  • Prise en charge d'un grand nombre de types de messages: Prise en charge de 184 types de messages HL7, garantissant la polyvalence pour les différents besoins d'intégration des soins de santé.
  • ClassMethod: Génération d'un message de test par l'invocation d'une méthode de classe
  • Version prise en charge: Actuellement, la version 2.5 de HL7 est prise en charge
0
0 55
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 Iryna Mykhailova · Déc 13, 2024 3m read

Comme beaucoup d'autres se retrouvent probablement, nous étions obligés de faire un mappage de données en direct dans notre moteur d'interface, ce que nous ne voulions vraiment pas faire, mais nous n'avions pas de bon choix alternatif. Nous voulons uniquement conserver les mappages aussi longtemps que nécessaire, puis purger les lignes expirées en fonction d'une valeur TTL. Nous avions en fait 4 cas d'utilisation pour cela nous-mêmes avant de créer cela. Cas d'utilisation :

0
0 38
Annonce Irène Mykhailova · Oct 23, 2024

Salut les Développeurs !

Nous avons le plaisir de vous inviter à participer au Hospitals on FHIR User Day, un événement passionnant dédié à l'interopérabilité des systèmes de santé.

📅 Dates: 25 - 26 novembre, 2024

📌 Lieu : Bluepoint Conference Centre Brussels, Blvd Auguste Reyers 80, 1030 Brussels

Register – Hospitals On FHIR Users Days – Bluepoint Conference Centre  Brussels, Blvd Auguste Reyers 80

0
0 52
Article Lorenzo Scalese · Août 8, 2024 3m read

Parfois, nous devons convertir le message FHIR en HL7 V2, par exemple pour enregistrer un patient dans le système PACS.
Dans cet article, les étapes à suivre pour obtenir les résultats souhaités en utilisant la production du serveur IRIS FHIR seront expliquées.

Voici les étapes à suivre:

  1. Assurez-vous que la production du serveur FHIR est démarrée.
  2. Enregistrez le service métier avec le point de terminaison FHIRServer.
  3. Définissez les processus métier pour convertir les messages FHIR en SDA, puis convertissez SDA en HL7 v2.
  4. Publiez la ressource JSON sur le point de terminaison FHIRServer et obtenez la réponse HL7 V2.

Examinons les étapes en détail.
 

Étape 1. Assurez-vous que la production du serveur FHIR est démarrée

Ouvrez la page de production et assurez-vous que la Production est démarrée. À l'étape suivante, nous devons nous assurer que le service commercial HS.FHIRServer.Interop.Service est enregistré auprès de FHIRServer


Step 2. Étape 2. Enregistrez le service métier avec le point de terminaison FHIRServer.

Depuis le portail de gestion, cliquez sur l'onglet Health (Santé)

Cliquez ensuite sur Configuration FHIR dans la liste, puis cliquez sur Server Configuration (configuration du serveur)

Sélectionnez le point de terminaison et assurez-vous que HS.FHIRServer.Interop.Service (service commercial défini dans la production) est défini sous le nom Service Config Name.

Étape 3. Définissez les processus métier pour convertir les messages FHIR en SDA, puis convertissez SDA en HL7 v2.

Définissez le rpocessus métier (le processus Solution.BP.Process est déjà défini dans l'application).
Veuillez noter que nous appliquons ici une condition selon laquelle le point de terminaison doit contenir une augmentation hl7 pour procéder à la conversion, sinon il est considéré comme une demande FHIR normale.


Transmettez ensuite le message au processus FHIR_SDA qui le convertira en SDA.
FHIR_SDA est dérivé d'une classe Solution.BP.Process définie par l'utilisateur.
Après conversion FHIR_SDA, le processus transmet le message au processus SDA_HL7
Le processus SDA_HL7 est dérivé d'une classe Solution.BP.SDATransformProcess définied  par l'utilisateur qui convertit le message SDA en message HL7 V2.


Définissez les processus de production comme suit:


Étape 4. Publiez la ressource JSON sur le point de terminaison FHIRServer et obtenez la réponse HL7 V2.

Depuis Postman, appelez le point de terminaison FHIRServer à l'aide de la méthode Post.
Notre point de terminaison FHIRServer est ci-dessous:
http://localhost:32783/csp/fhirserver/fhir/r4/hl7

REMARQUE:Notre point de terminaison FHIRServer est http://localhost:32783/csp/fhirserver/fhir/r4/, mais nous passons hl7 comme argument pour détecter à partir du processus métier de production qu'il ne s'agit pas d'une demande Post FHIR normale mais d'une demande de transformation du message FHIR.

Il s'agit de la même fonctionnalité que celle que nous pouvons utiliser à partir de nos applications Web. 
Sélectionnez la ressource patient, puis cliquez sur la ressource dans la liste, sélectionnez l'onglet HL7 FHIR ou Détails de la ressource, et cliquez sur le bouton "Transformer FHIR en HL7 V2"

L'application obtiendra le message de transformation HL7 V2 à l'aide de la production du serveur FHIR.



Transformation de HL7 V2 à FHIR

Sélectionnez "HL7 to FHIR" dans le menu et entrez les données HL7 V2. Cliquez sur le bouton de conversion pour transformer le message HL7 en message FHIR

La transformation de HL7 en FHIR utilise également la production pour convertir le message HL7 V2 en message FHIR.

Le service métier HL7_Http_Service envoie le message HL7 au processus HL7_SDA, puis HL7_SDA envoie les données SDA au processus SDA_FHIR, qui les convertit finalement en FHIR

Pour plus de détails et une révision du code, veuillez visiter la page d'application de l'échange ouvert iris-fhir-lab .
Merci!

0
0 69
Article Iryna Mykhailova · Juil 17, 2024 4m read

Parmi les points problématiques de la maintenance des interfaces HL7 figure la nécessité d'effectuer un test de régression fiable lors du déploiement dans de nouveaux environnements et après les mises à jour. La classe %UnitTest permet de créer des tests unitaires et de les intégrer au code de l'interface. Les données de test peuvent également être enregistrées au sein de la classe de test unitaire, ce qui permet de réaliser rapidement et facilement des tests de fumée et des tests de régression.

##Ressources:

##Scénario: Une classe de test unitaire sera créée pour chaque flux entrant en fonction des exigences en matière de profilage, de routage et de mappage des données.

  • Exigences relatives aux échantillons:*

Functional Requirements

Pour tester chaque scénario, nous devons lancer un échantillon pour chaque type d'événement et confirmer les règles de routage. Il nous faut également confirmer les exigences spécifiques en matière de mappage et trouver des exemples de données pour chaque scénario.

##Production HL7

L'objectif de ce UnitTest-RuleSet est de tester un pipeline HL7 : Service métierRègles de routageTransformation.

Dans cet exemple créé pour la production illustrée ci-dessous, le flux de processus ADT passe par plusieurs sauts supplémentaires. Une classe étendant UnitTest.RuleSet.HL7 a été modifiée pour permettre de traiter des flux de processus plus complexes.

Flux de processus: FromHSROUTER.TESTAdtFromTESTAdt.ReorgSegmentProcessRouteTESTAdtTEST.ADTTransformHS.Gateway.HL7.InboundProcess

HL7 Production

##Création d'une Classe de Test Unitaire

Créez une classe TestAdtUnitTest qui étend UnitTest.RuleSet.HL7. (UnitTest.RuleSet.Example dans le référentiel UnitTest-RuleSet contient un exemple d'une telle classe.

*Remarque: Dans cet exemple, tout a été déplacé sous HS.Local dans HSCUSTOM pour faciliter les tests. *

Signature:Class HS.Local.EG.UnitTest.TestAdtUnitTest Extends HS.Local.Util.UnitTest.RuleSet.HL7

##Aperçu de la structure des classes

La classe de test unitaire est organisée de la manière suivante:

Unit Test Class Layout

##Paramètres de configuration

Ces paramètres de classe sont utilisés pour configurer les tests.

// Espace de noms où la production est située
Parameter Namespace = "EGTEST";

// Répertoire de base pour les tests unitaires

Parameter TestDirectory = "/tmp/unittest";

// nom du sous-répertoire pour les tests unitaires

Parameter TestSuite = "TEST-HL7ADT";

/// Remplacement pour un schéma différent
Parameter HL7Schema = "2.5.1:ADT_A01";

/// Remplacement par le nom du service existant en production
Parameter SourceBusinessServiceName = "FromHSROUTER.TESTAdt";

/// Remplacer par le nom du moteur de routage de processus métier existant en production
Parameter TargetConfigName = "FromTESTAdt.ReorgSegmentProcess";

/// Nom du processus de routage primaire en production
Parameter PrimaryRoutingProcessName = "RouteTESTAdt";

/// Nom du processus de routage secondaire en production
Parameter SecondaryRoutingProcessName = "TEST.ADTTransform";

Remarque: Les processus de routage primaire et secondaire sont référencés dans les différentes méthodes de test afin de tester la sortie et les résultats de deux processus de routage enchaînés.

##Création d'exemples de messages d'entrée

Pour créer une classe de test unitaire réutilisable, il faut enregistrer des échantillons anonymes pour chaque type d'événement et tout message supplémentaire nécessaire à la réalisation des scénarios de test dans la classe de test unitaire, dans des blocs XDATA au bas du fichier.

  • Exemple du bloc XDATA:*

Le nom *SourceMessageA01 * est utilisé pour référencer le bloc XDATA spécifique.

XData SourceMessageA01 { <![CDATA[ MSH|^~\&|Epic|TEST||TEST|20230911060119|RUBBLE|ADT^A01|249509431|P|2.2 EVN|A01|20230911060119||ADT_EVENT|RUBBLE^MATTHEW^BARNEY^D^^^^^TEST^^^^^UH|20230911060000|PHL^PHL^ADTEDI ZVN||LS23450^LS-23450^^^^RTL PID|1|000163387^^^MRN^MRN|000163387^^^MRN^MRN||FLINTSTONE^ANNA^WILMA||19690812|F|MURRAY^ANNA~FLINTSTONE^ANNA^R~FLINTSTONE^ANNA|B|100 BEDROCK WAY^^NORTH CHARLESTON^SC^29420-8707^US^P^^CHARLESTON|CHAR|(555)609-0969^P^PH^^^555^6090969~^NET^Internet^ANNAC1@YAHOO.COM~(555)609-0969^P^CP^^^555^6090969||ENG|M|CHR|1197112023|260-61-5801|||1|||||Non Veteran|||N ZPD||MYCH||AC|||N||N PD1|||TEST HOLLINGS CANCER CENTER^^10003|1134107873^LINK^MICHAEL^J^^^^^EPIC^^^^PNPI ROL|1|UP|GENERAL|1134107873^LINK^MICHAEL^J^^^^^EPIC^^^^PNPI|20211115 NK1|1|GABLE^BETTY|PARENT||(555)763-5651^^PH^^^555^7635651||Emergency Contact 1 NK1|2|FLINTSTONE^FRED|Spouse|100 Bedrock way^^REMBERT^SC^29128^US|(888)222-2222^^PH^^^888^2222222|(888)222-3333^^PH^^^888^2223333|Emergency Contact 2 PV1|1|O|R1OR^RTOR^07^RT^R^^^^TEST RT OR|EL|||1386757342^HALSTEAD^LUCINDA^A.^^^^^EPIC^^^^PNPI|1386757342^HALSTEAD^LUCINDA^A.^^^^^EPIC^^^^PNPI||OTO||||PHYS|||1386757342^HALSTEAD^LUCINDA^A.^^^^^EPIC^^^^PNPI|SO||BCBS|||||||||||||||||||||ADMCONF|||20230911060000 PV2||PRV||||||20230911||||HOSP ENC|||||||||N|N||||||||||N ZPV||||||||||||20230911060000 OBX|1|NM|PRIMARYCSN|1|1197112023||||||F AL1|1|DA|900525^FISH CONTAINING PRODUCTS^DAM|3|Anaphylaxis|20210823 AL1|2|DA|568^PEANUT^HIC|3|Anaphylaxis|20221209 AL1|3|DA|12753^TREE NUT^HIC|3|Anaphylaxis|20221209 AL1|4|DA|1193^TREE NUTS^DAM|3|Anaphylaxis|20130524 AL1|5|DA|1554^HYDROCODONE^HIC||Other|20210728 AL1|6|DA|3102^POLLEN EXTRACTS^HIC||Other|20201204 AL1|7|DA|11754^SHELLFISH DERIVED^HIC||Other|20210728 DG1|1|I10|Q85.02^Neurofibromatosis, type 2^I10|Neurofibromatosis, type 2||ADMISSION DIAGNOSIS (CODED) DG1|2|I10|D33.3^Benign neoplasm of cranial nerves^I10|Benign neoplasm of cranial nerves||ADMISSION DIAGNOSIS (CODED) DG1|3|I10|J38.01^Paralysis of vocal cords and larynx, unilateral^I10|Paralysis of vocal cords and larynx, unilateral||ADMISSION DIAGNOSIS (CODED) DG1|4||^NF2 (neurofibromatosis 2) [Q85.02]|NF2 (neurofibromatosis 2) [Q85.02]||ADMISSION DIAGNOSIS (TEXT) DG1|5||^Acoustic neuroma [D33.3]|Acoustic neuroma [D33.3]||ADMISSION DIAGNOSIS (TEXT) DG1|6||^Unilateral complete paralysis of vocal cord [J38.01]|Unilateral complete paralysis of vocal cord [J38.01]||ADMISSION DIAGNOSIS (TEXT) GT1|1|780223|FLINTSTONE^ANNA^WILMA^^^^L||100 BEDROCK WAY^^NORTH CHARLESTON^SC^29420-8707^US^^^CHARLESTON|(555)609-0969^P^PH^^^555^6090969~(555)763-5651^P^CP^^^555^7635651||19690812|F|P/F|SL|248-61-5801|||||^^^^^US|||Full ZG1||||1 IN1|1|BL90^BCBS/STATE EMP^PLANID||BCBS STATE|ATTN CLAIMS PROCESSING^PO BOX 100605^COLUMBIA^SC^29260-0605||(800)444-4311^^^^^800^4444311|002038404||||20140101||NPR||FLINTSTONE^THOMAS^^V|Sp|19661227|3310 DUBIN RD^^NORTH CHARLESTON^SC^29420^US|||1|||||||||||||1087807|ZCS49984141|||||||M|^^^^^US|||BOTH IN3|1|||2||20230911|20230911|RUBBLE^MATTHEW^BARNEY^D|||NOT|||||(800)999-0000^^^^^800^9990000~(888)444-5555^^^^^888^4445555 ZIN|||||||FLINTSTONE^THOMAS^^V|||||16871492 ]]> }

Exemple de code permettant d'extraire des données d'un bloc XDATA pour les utiliser dans le cadre de tests :

set xdata=##class(%Dictionary.CompiledXData).%OpenId(..%ClassName(1)_"||"_XDataName,0)

Voici un exemple de code de ** GetMessage**, une méthode d'assistance utilisée pour lire et renvoyer les données d'un bloc XDATA en fonction du nom du bloc.

ClassMethod GetMessage(XDataName As %String) As EnsLib.HL7.Message { #dim SourceMessage as EnsLib.HL7.Message set xdata=##class(%Dictionary.CompiledXData).%OpenId(..%ClassName(1)_"||"XDataName,0) quit:'$IsObject(xdata) $$$NULLOREF set lines="" while 'xdata.Data.AtEnd { set line=$ZSTRIP(xdata.Data.ReadLine(),"<w") continue:line="" continue:$Extract(line,1)="<" // ignorer les balises XML ouvrantes ou fermantes et commencer la balise CData continue:$Extract(line,1)="]" // ignorer ]]> closing CDATA set lines=lines($S($L(lines)=0:"",1:$C(..#NewLine)))_line } set SourceMessage=##class(EnsLib.HL7.Message).ImportFromString(lines,.tSC) quit:$$$ISERR(tSC) $$$NULLOREF set SourceMessage.DocType=..#HL7Schema set tSC=SourceMessage.PokeDocType(..#HL7Schema) quit SourceMessage }

##Création de méthodes de test

La classe de test unitaire contient également une méthode de test qui configure chaque test de manière programmatique, injecte le message dans la production et vérifie que le message transformé qui en résulte correspond à ce qui est attendu.

L'exemple de test contient les méthodes de test suivantes :

  • TestMessageA01
  • TestMessageA02
  • TestMessageA03
  • TestMessageA04
  • TestMessageA05
  • TestMessageA06
  • TestMessageA08
  • TestMessageA28
  • TestMessageA31
  • TestCorrectAssigningAuthorityForA01
  • TestEncoutnerNumberPresent
  • TestPD1LocationMapped

Exemple de méthode: TestMessageA01 Méthode permettant de vérifier que les messages A01 sont traités sans erreur et routés vers la transformation correcte.

Method TestMessageA01() { Set ReturnA01 = ..#SecondaryRoutingProcessName_":HS.Local.EG.ProfSvcs.Router.Base.ADT.TransformA01" #dim message as EnsLib.HL7.Message

    // Télécharger un nouveau message HL7 par UnitTest
set ..HL7Message=..GetMessage("SourceMessageA01")
quit:'$IsObject(..HL7Message) $$$ERROR(5001,"Failed to correlate Xdata for Source Message")

set routingProcess = ..#PrimaryRoutingProcessName

set message=..HL7Message.%ConstructClone(1)
do message.PokeDocType(message.DocType)
do message.SetValueAt("SYSA","MSH:3.1")
set expectSuccess=1
set expectReturn="send:"_ReturnA01
set expectReason="rule#8"
do ..SendMessageToRouter(message,"TestMessageA01",routingProcess, expectSuccess, expectReturn, expectReason)

}

Remarque: SendMessageToRouter() est une méthode mise en œuvre dans le paquet UnitTest-RuleSet.

Exemple de méthode: TestEncounterNumberPresent

La méthode ci-dessous vérifie la présence de valeurs spécifiques dans le message après la transformation.

Method TestEncounterNumberPresent()
{
	#dim message as EnsLib.HL7.Message

    // Télécharger un nouveau message HL7 par UnitTest
set ..HL7Message=..GetMessage("SourceMessageA01")
quit:'$IsObject(..HL7Message) $$$ERROR(5001,"Failed to correlate Xdata for Source Message")

// source du processus de transformation
set routingProcess = ..#SecondaryRoutingProcessName
set message=..HL7Message.%ConstructClone(1)
do message.PokeDocType(message.DocType)
do message.SetValueAt("SYSA","MSH:3.1")
// Vérifier que la sortie PV1:19 n'est pas vide
set expectSuccess=1
set expectElement="PV1:19"
set expectReturnVal="1197112023"
do ..SendMessageReturnOutput(message,"TestMessageA01", routingProcess, expectSuccess, expectElement, expectReturnVal)
 }

Remarque: SendMessageReturnOutput() est une méthode modifiée pour renvoyer le message résultatif pour évaluation.

##Comment exécuter le UnitTest

Les mises en œuvre de la classe %UnitTest dépend de la globale ^UnitTestRoot pour l'emplacement des dossiers de travail des tests unitaires.

Il y a deux paramètres utilisés pour définir les dossiers:

 // Répertoire de base pour les tests unitaires
 Parameter TestDirectory = "/tmp/unittest";

 // nom du sous-répertoire pour les tests unitaires
 Parameter TestSuite = "TEST-HL7ADT";

Dans ce cas, la classe s'attend à ce que le dossier : /tmp/unittest/TEST-HL7ADT soit présent dans l'environnement où la classe de test unitaire est exécutée.

En outre, la mise en œuvre personnalisée définit la globale ^UnitTestRoot et passe à l'espace de noms spécifique dans le paramètre d'espace de noms Namespace.

Pour le lancement depuis le terminal:

HSCUSTOM> do ##class(HS.Local.EG.UnitTest.TestAdtUnitTest).Debug()

Les résultats du lancement seront envoyés à la console. Si l'un des tests échoue, tout le test est considéré comme ayant échoué. Outre le terminal, on peut également consulter les résultats à partir du portail de gestion. Pour ouvrir l'URL, utilisez le lien figurant dans le résultat.

##Extrait du résultat de la Console

TestMessageA31 passed TestPD1LocationMapped() begins ... 14:50:20.104:...t.TestAdtUnitTest: TestPD1Location Sent LogMessage:SessionId was 130 LogMessage:Source Config Name name is :TEST.ADTTransform LogMessage:Message Body Id was 94 LogMessage:Expect Success is 0 LogMessage:Testing for value PV1:39 LogMessage:Found value AssertNotEquals:Expect No Match:TestPD1Location:ReturnValue= (failed) <<==== FAILED TEST-HL7ADT:HS.Local.EG.UnitTest.TestAdtUnitTest:TestPD1LocationMapped LogMessage:Duration of execution: .033106 sec. TestPD1LocationMapped failed HS.Local.EG.UnitTest.TestAdtUnitTest failed Skipping deleting classes TEST-HL7ADT failed

Use the following URL to view the result: http://192.168.1.229:52773/csp/sys/%25UnitTest.Portal.Indices.cls?Index=3&$NAMESPACE=EGTEST Some tests FAILED in suites: TEST-HL7ADT

##Affichage des résultats dans le portail de gestion

Utilisez l'URL affichée sur le terminal et copiez-la dans un navigateur pour voir les résultats. En cliquant sur le nom du test, vous obtiendrez des détails supplémentaires.

Unit Test in Management Portal

En cliquant sur le lien de chaque test, vous obtiendrez plus d'informations sur le test :

UnitTest Results

##Conclusion La création de classes de tests unitaires pour chaque interface HL7 et leur intégration dans le code de l'interface permettent de documenter le processus de test et les échantillons de données, ainsi que de tester rapidement la régression de toute mise à jour ou modification des données ou de leur mise en œuvre.

Lors du déploiement, la classe de test unitaire sert de test de fumée autonome pour confirmer que l'interface est complète et fonctionnelle.

##Considérations supplémentaires

  • Le cadre UnitTest_RuleSet peut être adapté aux tests unitaires du CCDA. Les tests de routage sont relativement semblables à l'exemple HL7. Pour répondre aux exigences de mappage, il faut mettre en œuvre la classe de test unitaire pour gérer XSLT et XPaths.

  • Les responsables de l'assurance qualité peuvent recueillir des exigences et des échantillons de données pour configurer la classe de test unitaire avant le début de la mise en œuvre.

  • Les tests unitaires pour toutes les interfaces peuvent être regroupés dans le gestionnaire de tests unitaires (Unit Test Manager), de sorte que l'ensemble des tests peut être exécuté en une seule fois pour valider la configuration et le code existants.

0
0 50
Article Sylvain Guilbaud · Mars 15, 2024 2m read

InterSystems rubrique FAQ 

Vous pouvez utiliser la classe %IndexBuilder pour effectuer la reconstruction d'index dans plusieurs processus parallèles.

Voici un exemple dans le but de définir l'index standard HomeStateIdx pour la colonne Home_State (informations de l'État de l'adresse du domicile) de Sample.Person.

Les étapes sont les suivantes:

1. Masquez le nom de l'index à ajouter/reconstruire à partir de l'optimiseur de requêtes.

>write$system.SQL.SetMapSelectability("Sample.Person","HomeStateIdx",0)
1
0
0 65
Article Sylvain Guilbaud · Fév 15, 2024 6m read

Dans cet article, nous allons voir comment utiliser le service de messagerie instantanée WhatsApp depuis InterSystems IRIS pour envoyer des messages à différents destinataires. Pour ce faire, nous devons créer et configurer un compte dans Meta et configurer une opération métier pour envoyer les messages que nous souhaitons.

Examinons chacune de ces étapes plus en détail.

Créer un compte sur Meta

C'est peut-être le point le plus compliqué de toute la configuration, puisque nous devrons configurer une série de comptes jusqu'à ce que nous puissions avoir la fonctionnalité de messagerie.

0
0 107
Article Pierre LaFay · Jan 7, 2024 8m read

Introduction

Cet article vise à explorer le fonctionnement du système FHIR-PEX et a été développé, en tirant parti des capacités d'InterSystems IRIS.

En rationalisant l'identification et le traitement des examens médicaux dans les centres de diagnostic clinique, notre système vise à améliorer l'efficacité et la précision des flux de travail de soins de santé. En intégrant les normes FHIR à la base de données InterSystems IRIS Java-PEX, le système aide les professionnels de santé avec des capacités de validation et de routage, contribuant ainsi à améliorer la prise de décision et les soins aux patients.

how it works

  • Interopérabilité IRIS : Reçoit les messages au standard FHIR, garantissant l'intégration et la compatibilité avec les données de santé.

  • Traitement de l'information avec 'PEX Java' : Traite les messages au format FHIR et les dirige vers des sujets Kafka en fonction de règles configurées globalement dans la base de données, facilitant ainsi le traitement et le routage efficaces des données, en particulier pour les examens dirigés vers la quarantaine.

  • Gestion des retours Kafka via un backend Java externe : Interprète uniquement les examens dirigés vers la quarantaine, permettant au système de gérer les retours de Kafka via un backend Java externe. Il facilite la génération d'informations pronostiques pour les professionnels de la santé grâce à l'IA générative, en s'appuyant sur les consultations des résultats d'examens précédents pour les patients respectifs.

Development

Grâce au PEX (Production EXtension) d'InterSystems, un outil d'extensibilité permettant d'améliorer et de personnaliser le comportement du système, nous avons élaboré une Opération Métier. Ce composant est chargé de traiter les messages entrants au format FHIR au sein du système. Comme exemple suivant :

import com.intersystems.enslib.pex.*;
import com.intersystems.jdbc.IRISObject;
import com.intersystems.jdbc.IRIS;
import com.intersystems.jdbc.IRISList;
import com.intersystems.gateway.GatewayContext;

import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.*;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

public class KafkaOperation extends BusinessOperation {
// Connection to InterSystems IRIS
private IRIS iris;

// Connection to Kafka
private Producer<Long, String> producer;

// Kafka server address (comma separated if several)
public String SERVERS;

// Name of our Producer
public String CLIENTID;

/// Path to Config File
public String CONFIG;

public void OnInit() throws Exception {
[...]
}

public void OnTearDown() throws Exception {
[...]
}

public Object OnMessage(Object request) throws Exception {
    IRISObject req = (IRISObject) request;
    LOGINFO("Received object: " + req.invokeString("%ClassName", 1));

    // Create record
    String value = req.getString("Text");
    String topic = getTopicPush(req);
    final ProducerRecord<Long, String> record = new ProducerRecord<>(topic, value);

    // Send new record
    RecordMetadata metadata = producer.send(record).get();

    // Return record info
    IRISObject response = (IRISObject)(iris.classMethodObject("Ens.StringContainer","%New",topic+"|"+metadata.offset()));
    return response;
}

private Producer<Long, String> createProducer() throws IOException {
[...]
}

private String getTopicPush(IRISObject req) {
[...]
}

[...]
}

`

Au sein de l'application, la méthode getTopicPush se charge d'identifier le sujet auquel le message sera envoyé.

La détermination du sujet auquel le message sera envoyé dépend de l'existence d'une règle dans la globale "quarantineRule", telle que lue dans IRIS.

String code = FHIRcoding.path("code").asText();
String system = FHIRcoding.path("system").asText();

IRISList quarantineRule = iris.getIRISList("quarantineRule",code,system);

 String reference = quarantineRule.getString(1);
 String value = quarantineRule.getString(2);

 String observationValue = fhir.path("valueQuantity").path("value").asText()

When the global ^quarantineRule exists, validation of the FHIR object can be validated.

private boolean quarantineValueQuantity(String reference, String value, String observationValue) {
    LOGINFO("quarantine rule reference/value: " + reference + "/" + value);
    double numericValue = Double.parseDouble(value);
    double numericObservationValue = Double.parseDouble(observationValue);

    if ("<".equals(reference)) {
        return numericObservationValue < numericValue;
    }
    else if (">".equals(reference)) {
        return numericObservationValue > numericValue;
    }
    else if ("<=".equals(reference)) {
        return numericObservationValue <= numericValue;
    }
    else if (">=".equals(reference)) {
        return numericObservationValue >= numericValue;
    }
    
    return false;
}

Exemple pratique :

Lors de la définition d'une globale, telle que :

Set ^quarantineRule("59462-2","http://loinc.org") = $LB(">","500") 

Ceci établit une règle pour le code "59462-2" et le système ""http://loinc.org"" dans la globale ^quarantineRule , spécifiant une condition dans laquelle la valeur supérieure à 500 est définie comme quarantaine. Dans l'application, la méthode getTopicPush peut ensuite utiliser cette règle pour déterminer le sujet approprié pour envoyer le message en fonction du résultat de la validation.

Compte tenu de l'affectation, le JSON ci-dessous serait envoyé en quarantaine car il correspond à la condition spécifiée en ayant :

 {
          "system": "http://loinc.org",
          "code": "59462-2",
          "display": "Testosterone"
}

"valueQuantity": { "value": 550, "unit": "ng/dL", "system": "http://unitsofmeasure.org", "code": "ng/dL" }

FHIR Observation:

{
    "resourceType": "Observation",
    "id": "3a8c7d54-1a2b-4c8f-b54a-3d2a7efc98c9",
    "status": "final",
    "category": [
      {
        "coding": [
          {
            "system": "http://terminology.hl7.org/CodeSystem/observation-category",
            "code": "laboratory",
            "display": "laboratory"
          }
        ]
      }
    ],
    "code": {
      "coding": [
        {
          "system": "http://loinc.org",
          "code": "59462-2",
          "display": "Testosterone"
        }
      ],
      "text": "Testosterone"
    },
    "subject": {
      "reference": "urn:uuid:274f5452-2a39-44c4-a7cb-f36de467762e"
    },
    "encounter": {
      "reference": "urn:uuid:100b4a8f-5c14-4192-a78f-7276abdc4bc3"
    },
    "effectiveDateTime": "2022-05-15T08:45:00+00:00",
    "issued": "2022-05-15T08:45:00.123+00:00",
    "valueQuantity": {
      "value": 550,
      "unit": "ng/dL",
      "system": "http://unitsofmeasure.org",
      "code": "ng/dL"
    }
}

L'application Java Quarkus

Après envoi sur le sujet souhaité, une application Quarkus Java a été construite pour recevoir les examens en quarantaine. @ApplicationScoped public class QuarentineObservationEventListener {

@Inject
PatientService patientService;

@Inject
EventBus eventBus;

@Transactional
@Incoming("observation_quarantine")
public CompletionStage<Void> onIncomingMessage(Message<QuarentineObservation> quarentineObservationMessage) {
	var quarentineObservation = quarentineObservationMessage.getPayload();
	var patientId = quarentineObservation.getSubject()
			.getReference();
	var patient = patientService.addObservation(patientId, quarentineObservation);
	publishSockJsEvent(patient.getId(), quarentineObservation.getCode()
			.getText());
	return quarentineObservationMessage.ack();
}

private void publishSockJsEvent(Long patientId, String text) {
	eventBus.publish("monitor", MonitorEventDto.builder()
			.id(patientId)
			.message(" is on quarentine list by observation ." + text)
			.build());
}
 }

Ce segment du système est chargé de conserver les informations reçues de Kafka, de les stocker dans les observations du patient dans la base de données et de notifier l'événement au moniteur.

The monitor

Enfin, le moniteur du système est chargé de fournir une visualisation frontale simple. Cela permet aux professionnels de la santé d’examiner les données des patients/examens et de prendre les mesures nécessaires.

Implementation of langchainPT

Grâce au moniteur, le système permet aux professionnels de la santé de demander des recommandations à l'IA générative.

@Unremovable
@Slf4j
@ApplicationScoped
public class PatientRepository {
	@Tool("Get anamnesis information for a given patient id")
	public Patient getAnamenisis(Long patientId) {
		log.info("getAnamenisis called with id " + patientId);
		Patient patient = Patient.findById(patientId);
		return patient;
	}

	@Tool("Get the last clinical results for a given patient id")
	public List<Observation> getObservations(Long patientId) {
		log.info("getObservations called with id " + patientId);
		Patient patient = Patient.findById(patientId);
		return patient.getObservationList();
	}

}

suivre la mise en œuvre de Langchain4j

@RegisterAiService(chatMemoryProviderSupplier = RegisterAiService.BeanChatMemoryProviderSupplier.class, tools = {PatientRepository.class})
public interface PatientAI {

	@SystemMessage("""
			You are a health care assistant AI. You have to recommend exams for patients based on history information.
			""")
	@UserMessage("""
			 Your task is to recommend clinical exams for the patient id {patientId}.

			 To complete this task, perform the following actions:
			 1 - Retrieve anamnesis information for patient id {patientId}.
			 2 - Retrieve the last clinical results for patient id {patientId}, using the property 'name' as the name of exam and 'value' as the value.
			 3 - Analyse results against well known conditions of health care.

			 Answer with a **single** JSON document containing:
			 - the patient id in the 'patientId' key
			 - the patient weight in the 'weight' key
			 - the exam recommendation list in the 'recommendations' key, with properties exam, reason and condition.
			 - the 'explanation' key containing an explanation of your answer, especially about well known diseases.

			Your response must be just the raw JSON document, without ```json, ``` or anything else.
			 """)
	String recommendExams(Long patientId);
}

Le système peut ainsi aider les professionnels de santé à prendre des décisions et à mener des actions.

Video demo

VIDEO

Authors

NOTE:

L'application https://openexchange.intersystems.com/package/fhir-pex participe actuellement au concours InterSystems Java 2023. N'hésitez pas à explorer davantage la solution et n'hésitez pas à nous contacter si vous avez des questions ou avez besoin d'informations supplémentaires. Nous vous recommandons d'exécuter l'application dans votre environnement local pour une expérience pratique. Merci pour l'opportunité 😀!

0
0 124
Article Adeline Icard · Oct 20, 2023 8m read

Bonjour à toutes et à tous !

 

InterSystems a sponsorisé le FHIR User Day qui a eu lieu lors de la semaine européenne de la e-santé. Et je voudrais partager avec vous mes notes de cet événement. Mais avant ça, j'ai le plaisir à vous infirmer que la journée a été entièrement filmée. Les vidéos des différentes interventions seront transmises dans les semaines à venir. Restez à l'écoute!

Sans perte de temps, voici toute l'info.

0
0 158
Article Sylvain Guilbaud · Oct 19, 2023 6m read

Cet article a été écrit en réponse à un message de la communauté qui demandait si Python pouvait créer des messages HL7 de manière dynamique.

Conditions préalables et configuration

Utilisez un espace de noms compatible avec l'intégration.
Remarque : l'espace de noms USER n'est pas activé pour l'interopérabilité par défaut.
Si nécessaire, créez un nouvel espace de noms interopérable afin d'explorer les fonctionnalités.

# Passage à
ZN "[Espace de nom pour l'interopérabilité]"

# Lancement d'un shell interactif Python :
Exécuter $SYSTEM.Python.Shell()

Début du script

#Chargement des dépendances

import datetime as dt
import uuid

# L'heure actuelle du cache est exprimée au format AAAAMMJJHHMMss.
hl7_datetime_now=dt.datetime.now().strftime('%Y%m%d%H%M%S')

# Création d'un message HL7
hl7=iris.cls("EnsLib.HL7.Message")._New()

# Définition du type de document
# 2.5.1:ORU_R01 - Envoi non sollicité d'un message d'observation
hl7.PokeDocType("2.5.1:ORU_R01")

Les structures de ces messages sont accessibles à partir du portail de gestion

 

Création du MSH (segment d'en-tête de message)

// Segment MSH
hl7.SetValueAt('OutApp','MSH:SendingApplication')
hl7.SetValueAt('OutFac','MSH:SendingFacility')
hl7.SetValueAt('InApp','MSH:ReceivingApplication')
hl7.SetValueAt('InFac','MSH:ReceivingFacility')
hl7.SetValueAt(hl7_datetime_now,'MSH:DateTimeOfMessage')
hl7.SetValueAt('ORU','MSH:MessageType.MessageCode')
hl7.SetValueAt('R01','MSH:MessageType.TriggerEvent')
hl7.SetValueAt('ORU_R01','MSH:MessageType.MessageStructure')
hl7.SetValueAt(str(uuid.uuid4()),'MSH:MessageControlID')
hl7.SetValueAt('2.5.1','MSH:ProcessingID')

Fonction d'échappement et d'annulation ("Escape" ou "Unescape")

Les documents HL7 sont structurés en segments
Chaque segment est divisé en Éléments par un délimiteur ("|") et en Éléments répétitifs ("~").
Un élément comporte un délimiteur "^" et un sous-délimiteur "&".
Lorsque le délimiteur apparaît en tant que contenu textuel, il est échappé par "" et d'autres caractères qui remplacent les délimiteurs.
Le caractère "&" est souvent problématique, car il peut apparaître fréquemment dans les messages et entraîner une troncature lors de la lecture par le système de réception.
Un segment HL7 dispose d'une méthode intégrée pour échapper le contenu en fonction des délimiteurs choisis pour un message.
Un modèle commun est d'obtenir une référence au premier segment

# Exécution de cette ligne; la variable "msh" sera utilisée ultérieurement.
> msh=hl7.GetSegmentAt(1)

Ensuite, on peut appeler Escape (échappement) par exemple avec la chaîne brute de Python :

> msh.Escape(r"a&b~c^d")
'a\\T\\b\\R\\c\\S\\d'

Le segment peut également être utilisé pour annuler la fonction Unescape, par exemple :

> msh.Unescape('a\\T\\b\\R\\c\\S\\d')
'a&b~c^d'

Ainsi, lors de la définition d'un contenu qui devrait comporter des caractères de délimitation, ces caractères peuvent être échappés pour le message

hl7.SetValueAt(msh.Escape(r"a&b~c^d"),'MSH:ReceivingFacility')

Lors de la récupération du contenu, il est possible d'annuler l'echappement

msh.Unescape(hl7.GetValueAt('MSH:ReceivingFacility'))

Pour cet exemple, il suffit de rétablir la valeur précédente de msh.

hl7.SetValueAt('InFac','MSH:ReceivingFacility')

Pour la revérification du segment existant :

> hl7.GetValueAt('MSH')
'MSH|^~\\&|OutApp|OutFac|InApp|InFac|20230610100040||ORU^R01^ORU_R01|2dfab415-51aa-4c75-a7e7-a63aedfb53cc|2.5.1'

Segment démographique (PID)

# Préfixe du chemin virtuel pour l'identifiant PID
seg='PIDgrpgrp(1).PIDgrp.PID:'
hl7.SetValueAt('1',seg+'SetIDPID')
hl7.SetValueAt('12345',seg+'PatientIdentifierList(1).IDNumber')
hl7.SetValueAt('MRN',seg+'PatientIdentifierList(1).AssigningAuthority')
hl7.SetValueAt('MR',seg+'PatientIdentifierList(1).IdentifierTypeCode')
hl7.SetValueAt(msh.Escape('Redfield'), seg+'PatientName(1).FamilyName')
hl7.SetValueAt(msh.Escape('Claire') ,seg+'PatientName(1).GivenName')
hl7.SetValueAt('19640101',seg+'DateTimeofBirth')
hl7.SetValueAt('F',seg+'AdministrativeSex')
hl7.SetValueAt(msh.Escape('Umbrella Corporation') ,seg+'PatientAddress.StreetAddress')
hl7.SetValueAt(msh.Escape('Umbrella Drive') ,seg+'PatientAddress.OtherDesignation')
hl7.SetValueAt(msh.Escape('Raccoon City') ,seg+'PatientAddress.City')
hl7.SetValueAt(msh.Escape('MO') ,seg+'PatientAddress.StateorProvince')
hl7.SetValueAt(msh.Escape('63117') ,seg+'PatientAddress.ZiporPostalCode')

Revérification du contenu du segment PID

> hl7.GetValueAt(seg[0:-1])
'PID|1||12345^^^MRN^MR||Redfield^Claire||19640101|F|||Umbrella Corporation^Umbrella Drive^Raccoon City^MO^63117'

Segment de contrôle d'ordre

seg='PIDgrpgrp(1).ORCgrp(1).ORC:'
hl7.SetValueAt('RE',seg+'OrderControl')
hl7.SetValueAt('10003681',seg+'PlacerOrderNumber')
hl7.SetValueAt('99001725',seg+'FillerOrderNumber')
hl7.SetValueAt('AG104',seg+'OrderingProvider')
hl7.SetValueAt('L43',seg+'EnterersLocation')

Revérification du contenu des segments ORC (segments de contrôle d'ordre)

> hl7.GetValueAt(seg[0:-1])
'ORC|RE|10003681|99001725|||||||||AG104|L43'

 

Demande d'observation

seg='PIDgrpgrp(1).ORCgrp(1).OBR:'
hl7.SetValueAt('1',seg+'SetIDOBR')
hl7.SetValueAt('10003681',seg+'PlacerOrderNumber')
hl7.SetValueAt('99001725',seg+'FillerOrderNumber')
hl7.SetValueAt('20210428100729',seg+'ResultsRptStatusChngDateTime')
hl7.SetValueAt('F',seg+'ResultStatus')
hl7.SetValueAt('U',seg+'QuantityTiming.Priority')

 

Observation OBX/Résultat

seg='PIDgrpgrp(1).ORCgrp(1).OBXgrp(1).OBX:'
hl7.SetValueAt('1',seg+'SetIDOBX')
hl7.SetValueAt('TX',seg+'ValueType')
hl7.SetValueAt('V8132',seg+'ObservationIdentifier.Identifier')
hl7.SetValueAt(msh.Escape('G-Virus') , seg+'ObservationIdentifier.Identifier')
hl7.SetValueAt(msh.Escape('17.8 log10') ,seg+'ObservationValue')
hl7.SetValueAt(msh.Escape('RNA copies/mL') ,seg+'Units')
hl7.SetValueAt('F',seg+'ObservationResultStatus')
hl7.SetValueAt('20210428100729',seg+'DateTimeoftheObservation')
hl7.SetValueAt('AG001',seg+'ResponsibleObserver.IDNumber')
hl7.SetValueAt('Birkin',seg+'ResponsibleObserver.FamilyName')
hl7.SetValueAt('William',seg+'ResponsibleObserver.GivenName')
hl7.SetValueAt('AG001',seg+'ResponsibleObserver.IDNumber')
hl7.SetValueAt('UXL43',seg+'EquipmentInstanceIdentifier')

 

NTE - Remarques et commentaires

seg='PIDgrpgrp(1).ORCgrp(1).OBXgrp(1).NTE(1):'
hl7.SetValueAt('1',seg+'SetIDNTE')
hl7.SetValueAt(msh.Escape('Expected late onset Hyphema. Contain but do not approach.') ,seg+'Comment')

 

Affichage du message complet sur le terminal

> print(hl7.OutputToString())

MSH|^~\&|OutApp|OutFac|InApp|InFac|20230610141201||ORU^R01^ORU_R01|2dfab415-51aa-4c75-a7e7-a63aedfb53cc|2.5.1
PID|1||12345^^^MRN^MR||Redfield^Claire||19640101|F|||Umbrella Corporation^Umbrella Drive^Raccoon City^MO^63117
ORC|RE|10003681|99001725|||||||||AG104|L43
OBR|1|10003681|99001725|||||||||||||||||||20210428100729|||F||^^^^^U
OBX|1|TX|G-Virus||17.8 log10|RNA copies/mL|||||F|||20210428100729||AG001^Birkin^William||UXL43
NTE|1||Expected late onset Hyphema. Contain but do not approach.

Pièges

Si le contenu d'un élément comporte une valeur telle que "8@%SYS.Python", il est probable que la valeur "string" ou la propriété "string" ait été demandée à la place.

Par exemple, uuid a été enveloppé par "str" dans la structure de MSH.

0
1 122
Annonce Irène Mykhailova · Sept 12, 2023

Bonjour la communauté !

Je souhaite partager avec vous une solution intéressante qui vous permettra de réaliser des tests d'intégration pour vos applications de santé. Laissez-moi vous présenter Pandora - un logiciel de test de serveur FHIR conçu par Fyrstain, offrant des solutions de pointe pour les tests d'interopérabilité dans le domaine de la santé.

 

1
1 176
Question ED Coder · Jan 25, 2023

Salut,

J'ai un message ORU, qui a un segment EVN que je veux supprimer et envoyer le message à travers. J'ai essayé de cloner la demande comme ci-dessous :

s newreq = request.constructClone
s changereq = newreq.RemoveSegmentAt("EVN")
s request  = changereq

mais cela échoue.

Existe-t-il un moyen de supprimer un segment en toute sécurité et de le transmettre ? J'apprécierais votre aide à ce sujet.

Merci,

Eric

2
0 100
Annonce Irène Mykhailova · Oct 31, 2022

Salut la Сommunauté !

Nous sommes heureux de vous annoncer notre nouveau sujet pour les prochains mois ! Nous continuerons à parler de la santé et de l'utilisation des produits InterSystems pour faciliter la capture des données nécessaires, leur normalisation, leur stockage en toute sécurité et leur exploitation pratique. Lisez cet article pour en savoir plus !

20
0 99
Question Ewan Whyte · Déc 15, 2022

J'essaie d'obtenir un compte de type de message spécifique avec une entrée spécifique et j'ai pensé que je pourrais construire la requête dans Message Viewer mais cela ne fournit pas de comptes (pour autant que je sache). Ainsi, lorsque je prends le SQL à partir de "Show Query", il omet les critères de segment comme le montre le code ci-dessous.

J'ai attaché les critères qui ont été exclus. Est-ce possible ?

Merci

1
0 109
Article Lorenzo Scalese · Déc 21, 2022 4m read

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


Les stipulations de base de cet exemple sont les suivantes :

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

  

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


PDF Processing

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

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

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

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


HL7 Processing

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

=== BPL ===

Le BPL accomplit les tâches suivantes :

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

 

=== DTL ===

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

 

=== DTL Code Action ===

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

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

   Try {

     Set pdfStreamObj = pdfStreamContainer.StreamGet()

   }

   Catch {

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

      Quit

   }

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

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

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


Sample Production

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

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

 

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

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

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

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

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

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

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

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

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

0
0 102
Article Lorenzo Scalese · Déc 16, 2022 4m read

Au fil des ans, je me suis souvent retrouvé dans la nécessité de créer plusieurs messages HL7 à partir d'un seul message entrant. Il s'agit généralement d'une commande ou d'un résultat provenant d'un laboratoire. Chaque fois que j'ai abordé ce problème, j'ai essayé de repartir de zéro en pensant que la tentative précédente aurait pu être mieux faite.

Récemment, le besoin est réapparu et j'ai pu créer une solution dont je n'avais pas honte. Mon principal souci était que je me retrouvais toujours à m'enterrer dans une BPL, ou à utiliser ObjectScript et à tenter de modifier des messages en utilisant la méthode SetValueAt pour la classe de messages HL7.

Problème

Lorsque le Système A traite plusieurs commandes pour un seul patient, le résultat est transmis dans un seul message avec un ORCgrp répété, contenant les segments OBR et OBX. Le Système B ne peut recevoir qu'un seul OBR par message.

Approche

Développez un processus ObjectScript qui divisera un message unique en plusieurs en fonction du nombre de répétitions ORCgrp, et ce, en manipulant uniquement le message HL7 avec un DTL.

Exécution

Le code (partie 1)

Pour commencer, nous avons besoin d'une classe qui étend Ens.BusinessProcess, et qui attend un message HL7 sur demande :

Class Demo.Processes.MessageSplitter Extends Ens.BusinessProcess{
Method OnRequest(pRequest As EnsLib.HL7.Message) As %Status{
  Quit $$$OK
   }
}

 

L'étape suivante consiste à boucler le message en fonction du nombre de répétitions ORCgrp. Pour cela, 2 choses sont nécessaires :

  1. Le nombre de répétitions
  2. Une boucle de type For

Pour obtenir le nombre de répétitions, nous pouvons récupérer le compte du message en utilisant le code suivant :

Set ORCCount = pRequest.GetValueAt("PIDgrpgrp(1).ORCgrp(*)")

 

Cela va définir la variable "ORCCount" en fonction du nombre de répétitions.

En combinant cela avec une boucle type For et une trace pour voir la sortie, cela ressemble à ceci :

Method OnRequest(pRequest As EnsLib.HL7.Message) As %Status{
   Set ORCCount = pRequest.GetValueAt("PIDgrpgrp(1).ORCgrp(*)")
   For i=1:1:ORCCount {
       $$$TRACE("This is loop number: "_i)
   }
   Quit $$$OK
}

 

En faisant passer un message avec deux répétitions ORCgrp par ce processus à partir d'une production, nous obtenons :

Comme vous pouvez le voir, nous obtenons deux traces.

A partir de là, nous devons être en mesure d'appeler une transformée, et aussi d'indiquer à cette transformée quelle itération de l'ORCgrp elle doit retourner. Pour cela, nous allons utiliser le paramètre "aux", parfois négligé, pour les classes de transformée.

 

La transformée

La transformée elle-même peut être très simple. Tout ce que nous voulons pour cela est le suivant :

  1. Copier l'en-tête MSH tout en rendant le MessageControlID unique à votre message splitté
  2. Définir le premier ORCgrp du message cible à l'itération sur laquelle nous sommes depuis la source.
  3. Définir la valeur Target SetIDOBR à " 1 ", car elle sera toujours la première dans le message cible.

Pour cela, notre transformée devrait ressembler à ceci :

Mais attendez - où aux. StringValue obtient-il des données ?

Pour le savoir, il faut revenir au code...

Le code (partie 2)

Nous pouvons transmettre une classe à la transformée via le paramètre aux, et pour ce scénario, je vais juste utiliser un conteneur string. Nous allons également envoyer le résultat de la transformation à une cible afin de voir les résultats :

Class Demo.Processes.MessageSplitter Extends Ens.BusinessProcess{

Property TargetConfigName As Ens.DataType.ConfigName;
Parameter SETTINGS = "TargetConfigName";

Method OnRequest(pRequest As EnsLib.HL7.Message) As %Status{

   Set ORCCount = pRequest.GetValueAt("PIDgrpgrp(1).ORCgrp(*)")
    For i=1:1:ORCCount {
        Set contextString = ##class(Ens.StringContainer).%New()
       Set contextString.StringValue = i
       $$$QuitOnError(##Class(Demo.Transformations.R01Split).Transform(pRequest,.SplitR01,.contextString))
        $$$QuitOnError(..SendRequestAsync(..TargetConfigName,SplitR01,0))
    }
   Quit $$$OK
 }
}

 

Ensuite, dans la production, nous définissons une destination en utilisant la nouvelle option de paramètres que nous avons créée avec la propriété et le paramètre en tête de la classe, et nous verrons quelque chose comme ceci :

 

Conclusion

Comme je l'ai dit au début de cet article, j'ai toujours l'impression de développer une solution à ce type de problème, et ensuite je regarde en arrière et je pense que cela pourrait être mieux fait. Cette solution ne fait pas exception, mais elle est certainement meilleure que les itérations précédentes.

Pour améliorer cela à court terme, je vais ajouter des commentaires descriptifs à l'ObjectScript. A plus long terme, j'aimerais pouvoir ajouter un paramètre pour la classe de transformation afin qu'elle puisse être contrôlée depuis l'extérieur de l'ObjectScript.

De façon générale, je considère qu'il s'agit d'une approche que j'adopterai pour les prochains développements et que je ne partirai pas complètement de zéro.

(PS - Je suis heureux de partager le code pour cela, mais je ne peux que joindre un pdf à cet article. Cela semble un peu léger pour être quelque chose pour Open Exchange, mais s'il y a un intérêt pour moi de le télécharger là ou ailleurs, s'il vous plaît faites le moi savoir)

0
0 65
Article Iryna Mykhailova · Déc 2, 2022 9m read

Salut la Communauté!

Parce que je n'avais aucune idée de comment construire une solution d'intégration pour HL7 et que je ne savais pas par où commencer, j'ai décidé de suivre le cours Building Basic HL7 Integrations with InterSystems sur le portail d’apprentissage en ligne InterSystems Learning pour avoir au moins l'idée par où commencer. Après avoir tout étudié, j'ai décidé que ce serait peut-être une bonne idée de partager mes pensées et mes réflexions à ce cours avec tout le monde.

0
1 271
Article Iryna Mykhailova · Nov 30, 2022 4m read

Dans l'article précédent, nous avons vu le contenu du message ORM et du message de réponse ORU. Examinons maintenant de plus près le message ACK.

Chaque fois qu'une application réceptrice accepte un message et consomme les données du message, elle est censée renvoyer un message ACKnowledgement (ACK) à l'application émettrice. L'application émettrice est censée continuer à envoyer un message jusqu'à ce qu'elle ait reçu un message ACK. C'est une partie importante de la norme HL7, le protocole de la confirmation de réception.

0
0 719