0 Abonnés · 15 Publications

Angular (ou AngularJS) est un framework d'application web front-end open source basé sur JavaScript, principalement maintenu par Google et par une communauté de particuliers et d'entreprises. Il permet de relever de nombreux défis liés au développement d'applications monopages.

Site officiel.

Article Lorenzo Scalese · Août 22, 2024 5m read

Dans l'article précédent, nous avons présenté l'application d[IA]gnosis développée pour soutenir le codage des diagnostics CIM-10. Dans le présent article, nous verrons comment InterSystems IRIS for Health nous fournit les outils nécessaires à la génération de vecteurs à partir de la liste des codes CIM-10 au moyen d'un modèle de langage pré-entraîné, à leur stockage et à la recherche ultérieure de similitudes sur tous ces vecteurs générés.

Introduction

L'une des principales fonctionnalités apparues avec le développement des modèles d'IA est ce que nous appelons RAG (Retrieval-Augmented Generation), qui nous permet d'améliorer les résultats des modèles LLM en incorporant un contexte au modèle. Dans notre exemple, le contexte est donné par l'ensemble des diagnostics CIM-10, et pour les utiliser, nous devons d'abord les vectoriser.

Comment vectoriser notre liste de diagnostics?

SentenceTransformers et Embedded Python

Pour la génération de vecteurs, nous avons utilisé la bibliothèque Python SentenceTransformers qui facilite grandement la vectorisation de texte libre à partir de modèles pré-entraînés. Extrait de leur propre site web:

Le module "Sentence Transformers" (alias SBERT) SBERT) est un module Python intégré qui permet d'accéder, d'utiliser et d'entraîner des modèles incorporés de texte et d'image à la pointe de la technologie. Il peut être utilisé pour calculer des embeddings à l'aide de modèles Sentence Transformer (quickstart) ou pour calculer des scores de similarité à l'aide de modèles Cross-Encoder (quickstart). Cela ouvre la voie à un large éventail d'applications, notamment la  recherche sémantique, la  similarité textuelle sémantique, et l' extraction de paraphrases.

Parmi tous les modèles développés par la communauté SentenceTransformers, nous avons trouvé BioLORD-2023-M, un modèle pré-entraîné qui génère des vecteurs de 786 dimensions.

Ce modèle a été entraîné à l'aide de BioLORD, une nouvelle stratégie de pré-entraînement visant à produire des représentations significatives pour les expressions cliniques et les concepts biomédicaux.

Les méthodologies de pointe maximisent la similarité de la représentation des noms se référant au même concept et évitent l'effondrement grâce à l'apprentissage contrastif. Cependant, les noms biomédicaux n'étant pas toujours explicites, il en résulte parfois des représentations non sémantiques.

BioLORD résout ce problème en fondant ses représentations de concepts sur des définitions, ainsi que sur de courtes descriptions dérivées d'un graphe de connaissances multirelationnel composé d'ontologies biomédicales. Grâce à cette base, notre modèle produit des représentations de concepts plus sémantiques qui correspondent mieux à la structure hiérarchique des ontologies. BioLORD-2023 établit un nouvel état de l'art en matière de similarité textuelle pour les expressions cliniques (MedSTS) et les concepts biomédicaux (EHR-Rel-B).

Comme vous pouvez le voir dans sa propre définition, ce modèle est pré-entraîné avec des concepts médicaux qui seront utiles lors de la vectorisation de nos codes ICD-10 et du texte brut.

Pour notre projet, nous téléchargerons ce modèle afin d'accélérer la création des vecteurs:

if not os.path.isdir('/shared/model/'):
    model = sentence_transformers.SentenceTransformer('FremyCompany/BioLORD-2023-M')            
    model.save('/shared/model/')

Lorsque nous sommes à notre ordinateur, nous pouvons introduire les textes à vectoriser dans des listes afin d'accélérer le processus. Voyons comment vectoriser les codes CIM-10 que nous avons précédemment enregistrés dans notre classe ENCODER.Object.Codes.

st = iris.sql.prepare("SELECT TOP 50 CodeId, Description FROM ENCODER_Object.Codes WHERE VectorDescription is null ORDER BY ID ASC ")
resultSet = st.execute()
df = resultSet.dataframe()

if (df.size > 0): model = sentence_transformers.SentenceTransformer("/shared/model/") embeddings = model.encode(df['description'].tolist(), normalize_embeddings=True)

df[<span class="hljs-string">'vectordescription'</span>] = embeddings.tolist()

stmt = iris.sql.prepare(<span class="hljs-string">"UPDATE ENCODER_Object.Codes SET VectorDescription = TO_VECTOR(?,DECIMAL) WHERE CodeId = ?"</span>)
<span class="hljs-keyword">for</span> index, row <span class="hljs-keyword">in</span> df.iterrows():
    rs = stmt.execute(str(row[<span class="hljs-string">'vectordescription'</span>]), row[<span class="hljs-string">'codeid'</span>])

else: flagLoop = False

Comme vous pouvez le voir, nous extrayons d'abord les codes stockés dans notre table de codes CIM-10 que nous n'avons pas encore vectorisés mais que nous avons enregistrés dans une étape précédente après les avoir extraits du fichier CSV, puis nous extrayons la liste des descriptions à vectoriser et en utilisant la bibliothèque Python sentence_transformers nous allons récupérer notre modèle et générer les embeddings associés.

Enfin, nous mettons à jour le code CIM-10 avec la description vectorisée en exécutant la commande UPDATE. Comme vous pouvez le voir, le résultat retourné par le modèle est vectorisé par la commande TO_VECTOR de SQL dans IRIS.

Utilisation dans IRIS

Très bien, nous avons déjà notre code Python, il nous suffit donc de l'inclure dans une classe qui étend Ens.BusinessProcess et de l'inclure dans notre production, puis de le connecter au Business Service chargé de récupérer le fichier CSV et le tour est joué!

Voyons à quoi ressemblera ce code dans notre production:

Comme vous pouvez le voir, nous avons notre service d'entreprise avec l'adaptateur EnsLib.File.InboundAdapter qui nous permettra de collecter le fichier de code et de le rediriger vers notre processus d'entreprise dans lequel nous effectuerons toutes les opérations de vectorisation et de stockage, ce qui se traduira par un ensemble d'enregistrements comme le suivant:

Notre application est maintenant prête à rechercher des correspondances possibles avec les textes que nous lui transmettons!

Dans le prochain article...

Dans le prochain article, nous montrerons comment le front-end de l'application développée en Angular 17 est intégré à notre production dans IRIS for Health et comment IRIS reçoit les textes à analyser, les vectorise et recherche des similitudes dans la table des codes CIM-10.

À ne pas manquer!

0
1 48
Article Lorenzo Scalese · Août 20, 2024 8m read

Avec l'introduction des types de données vectorielles et de la fonctionnalité de recherche vectorielle dans IRIS, tout un univers de possibilités de développement d'applications s'ouvre et un exemple de ces applications est celui que j'ai récemment vu publié dans un appel d'offres public du Ministère régional de la santé de Valence demandant un outil d'aide au codage de la CIM-10 à l'aide de modèles d'IA.

Comment pourrions-nous mettre en œuvre une application similaire à celle demandée? Voyons ce dont nous aurions besoin:

  1. Liste des codes CIM-10, que nous utiliserons comme contexte de notre application RAG pour rechercher des diagnostics dans les textes bruts.
  2. Un modèle entraîné pour vectoriser les textes dans lesquels nous allons rechercher des équivalences dans les codes CIM-10.
  3. Les bibliothèques Python nécessaires à l'ingestion et à la vectorisation des codes CIM-10 et des textes.
  4. Un front-end convivial qui prend en charge les textes sur lesquels nous recherchons des diagnostics possibles.
  5. L'orchestration des requêtes reçues du front-end.

Que propose IRIS pour répondre à ces besoins?

  1. Importation CSV, soit en utilisant la fonctionnalité RecordMapper, soit directement en utilisant Embedded Python.
  2. Embedded Python nous permet d'implémenter le code Python nécessaire pour générer les vecteurs à l'aide du modèle sélectionné.
  3. Publication d'API REST à invoquer à partir de l'application front-end.
  4. Les productions d'interopérabilité qui permettent le suivi des informations au sein d'IRIS.

Il ne reste plus qu'à voir l'exemple développé:

d[IA]gnosis

Associé à cet article vous avez accès à l'application développée, les prochains articles présenteront en détail la mise en œuvre de chacune des fonctionnalités, de l'utilisation du modèle au stockage des vecteurs, en passant par l'utilisation des recherches vectorielles.

Passons en revue l'application:

Importation des codes CIM-10

L'écran de configuration indique le format que doit suivre le fichier CSV contenant les codes CIE-10 que nous allons importer. Le processus de chargement et de vectorisation consomme beaucoup de temps et de ressources, c'est pourquoi le déploiement du conteneur Docker configure non seulement la mémoire RAM utilisable par Docker mais aussi la mémoire disque au cas où les besoins dépasseraient la RAM allouée:

# iris  iris:    init:true    container_name:iris    build:      context:.      dockerfile:iris/Dockerfile    ports:      -52774:52773      -51774:1972    volumes:    -./shared:/shared    environment:    -ISC_DATA_DIRECTORY=/shared/durable    command:--check-capsfalse--ISCAgentfalse    mem_limit:30G    memswap_limit:32G

Le fichier contenant les codes ICD-10 est disponible dans le chemin du projet /shared/cie10/icd10.csv, une fois que 100% est atteint, l'application sera prête à être utilisée.

Dans notre application, nous avons défini deux fonctionnalités différentes pour le codage des diagnostics, l'une basée sur les messages HL7 reçus dans le système et l'autre basée sur des textes bruts.

Saisie des diagnostics via HL7

Le projet contient une série de messages HL7 prêts à être testés, il suffit de copier le fichier /shared/hl7/messagesa01_en.hl7 dans le dossier /shared/HL7In et la production associée en extraira le diagnostic pour l'afficher dans l'application web:

L'écran de demande de diagnostic permet de voir tous les diagnostics reçus via la messagerie HL7. Pour leur codage CIM-10, il suffit de cliquer sur la loupe pour afficher une liste des codes CIM-10 les plus proches du diagnostic reçu:

Une fois sélectionné, le diagnostic et le code CIM-10 associé apparaissent dans la liste. En cliquant sur le bouton avec l'icône de l'enveloppe, un message est généré en utilisant l'original et en incluant le nouveau code sélectionné dans le segment du diagnostic:

MSH|^~\&|HIS|HULP|EMPI||||ADT^A08|592956|P|2.5.1
EVN|A01|
PID|||1556655212^^^SERMAS^SN~922210^^^HULP^PI||GARCÍA PÉREZ^JUAN^^^||20150403|M|||PASEO PEDRO ÁLVAREZ 1951 CENTRO^^LEGANÉS^MADRID^28379^SPAIN||555283055^PRN^^JUAN.GARCIA@YAHOO.COM|||||||||||||||||N|
PV1||N
DG1|1||O10.91^Hypertension préexistante non spécifiée compliquant la grossesse^CIE10-ES|Hypertension gestationnelle||A||

Ce message se trouve dans le chemin /shared/HL7Out

Captures d'écran de diagnostic en texte brut

Dans l'option Analyseur de texte, l'utilisateur peut inclure un texte brut sur lequel un processus d'analyse sera effectué. L'application recherchera des tuples de 3 mots lemmatisés (en éliminant les articles, les pronoms et d'autres mots peu pertinents). Une fois analysé, le système affichera le texte pertinent souligné et les diagnostics possibles localisés:

Une fois l'analyse effectuée, elle peut être consultée à tout moment à partir de l'historique de l'analyse.

Historique des analyses

Toutes les analyses effectuées sont enregistrées et peuvent être consultées à tout moment, en visualisant tous les codes CIM-10 possibles:

Dans le prochain article...

Nous verrons comment, en utilisant Embedded Python, nous utilisons un modèle LLM spécifique pour la vectorisation des codes CIM-10 qui nous serviront de contexte et des textes bruts.

Si vous avez des questions ou des suggestions, n'hésitez pas à écrire un commentaire dans l'article.

Avec l'introduction des types de données vectorielles et de la fonctionnalité de recherche vectorielle dans IRIS, tout un univers de possibilités de développement d'applications s'ouvre et un exemple de ces applications est celui que j'ai récemment vu publié dans un appel d'offres public du Ministère régional de la santé de Valence demandant un outil d'aide au codage de la CIM-10 à l'aide de modèles d'IA.

Comment pourrions-nous mettre en œuvre une application similaire à celle demandée? Voyons ce dont nous aurions besoin:

  1. Liste des codes CIM-10, que nous utiliserons comme contexte de notre application RAG pour rechercher des diagnostics dans les textes bruts.
  2. Un modèle entraîné pour vectoriser les textes dans lesquels nous allons rechercher des équivalences dans les codes CIM-10.
  3. Les bibliothèques Python nécessaires à l'ingestion et à la vectorisation des codes CIM-10 et des textes.
  4. Un front-end convivial qui prend en charge les textes sur lesquels nous recherchons des diagnostics possibles.
  5. L'orchestration des requêtes reçues du front-end.

Que propose IRIS pour répondre à ces besoins?

  1. Importation CSV, soit en utilisant la fonctionnalité RecordMapper, soit directement en utilisant Embedded Python.
  2. Embedded Python nous permet d'implémenter le code Python nécessaire pour générer les vecteurs à l'aide du modèle sélectionné.
  3. Publication d'API REST à invoquer à partir de l'application front-end.
  4. Les productions d'interopérabilité qui permettent le suivi des informations au sein d'IRIS.

Il ne reste plus qu'à voir l'exemple développé:

d[IA]gnosis

Associé à cet article vous avez accès à l'application développée, les prochains articles présenteront en détail la mise en œuvre de chacune des fonctionnalités, de l'utilisation du modèle au stockage des vecteurs, en passant par l'utilisation des recherches vectorielles.

Passons en revue l'application:

Importation des codes CIM-10

L'écran de configuration indique le format que doit suivre le fichier CSV contenant les codes CIE-10 que nous allons importer. Le processus de chargement et de vectorisation consomme beaucoup de temps et de ressources, c'est pourquoi le déploiement du conteneur Docker configure non seulement la mémoire RAM utilisable par Docker mais aussi la mémoire disque au cas où les besoins dépasseraient la RAM allouée:

# iris  iris:    init:true    container_name:iris    build:      context:.      dockerfile:iris/Dockerfile    ports:      -52774:52773      -51774:1972    volumes:    -./shared:/shared    environment:    -ISC_DATA_DIRECTORY=/shared/durable    command:--check-capsfalse--ISCAgentfalse    mem_limit:30G    memswap_limit:32G

Le fichier contenant les codes ICD-10 est disponible dans le chemin du projet /shared/cie10/icd10.csv, une fois que 100% est atteint, l'application sera prête à être utilisée.

Dans notre application, nous avons défini deux fonctionnalités différentes pour le codage des diagnostics, l'une basée sur les messages HL7 reçus dans le système et l'autre basée sur des textes bruts.

Saisie des diagnostics via HL7

Le projet contient une série de messages HL7 prêts à être testés, il suffit de copier le fichier /shared/hl7/messagesa01_en.hl7 dans le dossier /shared/HL7In et la production associée en extraira le diagnostic pour l'afficher dans l'application web:

L'écran de demande de diagnostic permet de voir tous les diagnostics reçus via la messagerie HL7. Pour leur codage CIM-10, il suffit de cliquer sur la loupe pour afficher une liste des codes CIM-10 les plus proches du diagnostic reçu:

Une fois sélectionné, le diagnostic et le code CIM-10 associé apparaissent dans la liste. En cliquant sur le bouton avec l'icône de l'enveloppe, un message est généré en utilisant l'original et en incluant le nouveau code sélectionné dans le segment du diagnostic:

MSH|^~\&|HIS|HULP|EMPI||||ADT^A08|592956|P|2.5.1
EVN|A01|
PID|||1556655212^^^SERMAS^SN~922210^^^HULP^PI||GARCÍA PÉREZ^JUAN^^^||20150403|M|||PASEO PEDRO ÁLVAREZ 1951 CENTRO^^LEGANÉS^MADRID^28379^SPAIN||555283055^PRN^^JUAN.GARCIA@YAHOO.COM|||||||||||||||||N|
PV1||N
DG1|1||O10.91^Hypertension préexistante non spécifiée compliquant la grossesse^CIE10-ES|Hypertension gestationnelle||A||

Ce message se trouve dans le chemin /shared/HL7Out

Captures d'écran de diagnostic en texte brut

Dans l'option Analyseur de texte, l'utilisateur peut inclure un texte brut sur lequel un processus d'analyse sera effectué. L'application recherchera des tuples de 3 mots lemmatisés (en éliminant les articles, les pronoms et d'autres mots peu pertinents). Une fois analysé, le système affichera le texte pertinent souligné et les diagnostics possibles localisés:

Une fois l'analyse effectuée, elle peut être consultée à tout moment à partir de l'historique de l'analyse.

Historique des analyses

Toutes les analyses effectuées sont enregistrées et peuvent être consultées à tout moment, en visualisant tous les codes CIM-10 possibles:

Dans le prochain article...

Nous verrons comment, en utilisant Embedded Python, nous utilisons un modèle LLM spécifique pour la vectorisation des codes CIM-10 qui nous serviront de contexte et des textes bruts.

Si vous avez des questions ou des suggestions, n'hésitez pas à écrire un commentaire dans l'article.

0
1 40
Article Lorenzo Scalese · Juil 2, 2024 7m read

Enfin et avec un peu de retard, nous concluons cette série d'articles sur notre moteur de Workflow en montrant un exemple de connexion que nous pourrions établir à partir d'une application mobile.

Dans l'article précédent, nous avons présenté un exemple d'application permettant un contrôle détaillé d'une pathologie chronique telle que l'hypertension, tant pour le patient que pour son médecin associé. Dans cet exemple, le patient pourra accéder à une application web à partir de son téléphone portable (en fait, à une page web conçue pour s'adapter à cet appareil) dans laquelle il recevra des notifications basées sur les mesures que le tensiomètre portable envoie à l'instance IRIS.

Par conséquent, nous aurons deux accès différents à notre instance IRIS:

  • Accès utilisateur à partir d'une application mobile.
  • Accès à l'appareil pour soumettre les lectures de tension artérielle.

Dans cet article, nous verrons le premier d'entre eux qui permet aux patients de gérer les tâches que leurs lectures génèrent.

Application mobile de connexion - IRIS

Pour réaliser cette connexion, le plus simple est de configurer une application web dans IRIS et pour ce faire, nous y accéderons à partir du portail de gestion, System Administration -> Security -> Applications -> Web Applications (Administration du système > Sécurité > Applications > Applications web):

Ensuite, dans la liste affichée, nous cliquerons sur Create new application (Créer une nouvelle application), ce qui ouvrira un écran comme le suivant:

Sur cet écran, configurons les champs suivants:

  • Nom: dans ce champ, nous définirons l'URL à publier pour donner accès à notre fonctionnalité déployée dans IRIS.
  • Espace de Noms: l'espace de noms auquel nous voulons que l'application web soit associée, ce qui nous permettra plus tard de profiter des fonctionnalités des productions d'interopérabilité.
  • REST: Nous sélectionnerons cette option car ce que nous allons publier est une API REST pour autoriser les connexions HTTP.
  • Classe de répartition: Classe ObjectScript qui recevra l'appel HTTP et décidera quoi en faire.
  • Utilisation de l'authentification JWT: en cochant cette option, les points de terminaison /login et /logout seront activés sur l'URL que nous avons définie pour notre application, ce qui nous permettra d'obtenir un jeton Web JSON afin d'authentifier nos appels via IRIS.
  • Paramètres de sécurité -> Méthodes d'authentification autorisées: nous allons définir un mot de passe pour sécuriser nos appels.

Jetons un coup d'œil à notre classe Workflow.WS.Service:

Class Workflow.WS.Service Extends%CSP.REST
{

Parameter HandleCorsRequest = 0;Parameter CHARSET = "utf-8"; XData UrlMap [ XMLNamespace = "https://www.intersystems.com/urlmap" ] { <Routes> <Route Url="/getTasks" Method="GET" Call="GetTasks" /> <Route Url="/saveTask" Method="POST" Call="SaveTask" /> </Routes> }

ClassMethod OnHandleCorsRequest(url As%String) As%Status { set url = %request.GetCgiEnv("HTTP_REFERER") set origin = $p(url,"/",1,3) // origin = "http(s)://origin.com:port"// ici vous pouvez vérifier les origines spécifiques// sinon, toutes les origines seront autorisées (utile uniquement lors du développement)do%response.SetHeader("Access-Control-Allow-Credentials","true") do%response.SetHeader("Access-Control-Allow-Methods","GET,POST,PUT,DELETE,OPTIONS") do%response.SetHeader("Access-Control-Allow-Origin",origin) do%response.SetHeader("Access-Control-Allow-Headers","Access-Control-Allow-Origin, Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control") quit$$$OK }

ClassMethod GetTasks() As%Status { Try { Do##class(%REST.Impl).%SetContentType("application/json") If '##class(%REST.Impl).%CheckAccepts("application/json") Do##class(%REST.Impl).%ReportRESTError(..#HTTP406NOTACCEPTABLE,$$$ERROR($$$RESTBadAccepts)) QuitDo##class(%REST.Impl).%SetStatusCode("200") set sql = "SELECT %Actions, %Message, %Priority, %Subject, TaskStatus_TimeCreated, ID FROM EnsLib_Workflow.TaskResponse WHERE TaskStatus_AssignedTo = ? AND TaskStatus_IsComplete = 0"set statement = ##class(%SQL.Statement).%New(), statement.%ObjectSelectMode = 1set status = statement.%Prepare(sql) if ($$$ISOK(status)) { set resultSet = statement.%Execute($USERNAME) if (resultSet.%SQLCODE = 0) { set tasks = [] while (resultSet.%Next() '= 0) { set task = {"actions": "", "message": "", "priority": "", "subject": "", "creation": "", "id": ""} set task.actions = resultSet.%GetData(1) set task.message = resultSet.%GetData(2) set task.priority = resultSet.%GetData(3) set task.subject = resultSet.%GetData(4) set task.creation = resultSet.%GetData(5) set task.id = resultSet.%GetData(6) do tasks.%Push(task) }
} } set result = {"username": ""} set result.username = $USERNAMEDo##class(%REST.Impl).%WriteResponse(tasks)

} <span class="hljs-keyword">Catch</span> (ex) {
    <span class="hljs-keyword">Do</span> <span class="hljs-keyword">##class</span>(<span class="hljs-built_in">%REST.Impl</span>).<span class="hljs-built_in">%SetStatusCode</span>(<span class="hljs-string">"400"</span>)
    <span class="hljs-keyword">return</span> ex.DisplayString()
}

<span class="hljs-keyword">Quit</span> <span class="hljs-built_in">$$$OK</span>

}

ClassMethod SaveTask() As%Status { Try { Do##class(%REST.Impl).%SetContentType("application/json") If '##class(%REST.Impl).%CheckAccepts("application/json") Do##class(%REST.Impl).%ReportRESTError(..#HTTP406NOTACCEPTABLE,$$$ERROR($$$RESTBadAccepts)) Quit// Lecture du corps de l'appel http avec les données relatives à la personneset dynamicBody = {}.%FromJSON(%request.Content)

    <span class="hljs-keyword">set</span> task = <span class="hljs-keyword">##class</span>(EnsLib.Workflow.TaskResponse).<span class="hljs-built_in">%OpenId</span>(dynamicBody.<span class="hljs-built_in">%Get</span>(<span class="hljs-string">"id"</span>))
    <span class="hljs-keyword">set</span> sc = task.CompleteTask(dynamicBody.action)

    <span class="hljs-keyword">if</span> <span class="hljs-built_in">$$$ISOK</span>(sc) {	        
        <span class="hljs-keyword">Do</span> <span class="hljs-keyword">##class</span>(<span class="hljs-built_in">%REST.Impl</span>).<span class="hljs-built_in">%SetStatusCode</span>(<span class="hljs-string">"200"</span>)
        <span class="hljs-keyword">Do</span> <span class="hljs-keyword">##class</span>(<span class="hljs-built_in">%REST.Impl</span>).<span class="hljs-built_in">%WriteResponse</span>({<span class="hljs-string">"result"</span>: <span class="hljs-string">"success"</span>})         
	}	
    
} <span class="hljs-keyword">Catch</span> (ex) {
    <span class="hljs-keyword">Do</span> <span class="hljs-keyword">##class</span>(<span class="hljs-built_in">%REST.Impl</span>).<span class="hljs-built_in">%SetStatusCode</span>(<span class="hljs-string">"400"</span>)
    <span class="hljs-keyword">Do</span> <span class="hljs-keyword">##class</span>(<span class="hljs-built_in">%REST.Impl</span>).<span class="hljs-built_in">%WriteResponse</span>({<span class="hljs-string">"result"</span>: <span class="hljs-string">"error"</span>})
}

<span class="hljs-keyword">Quit</span> <span class="hljs-built_in">$$$OK</span>

}

}

Comme vous pouvez le voir, nous résoudrons toute la logique dont nous avons besoin à partir de notre WS, mais je ne recommande pas de procéder de cette manière, car nous perdons la traçabilité possible en envoyant les requêtes reçues à la production configurée dans l'espace de noms Namespace.

En jetant un coup d'œil à la section URLMap, nous verrons que nous avons 2 points de terminaison configurés dans notre WS:

  • getTasks: pour récupérer toutes les tâches en attente de l'utilisateur (si vous voyez le SQL utilisé, nous passons le nom d'utilisateur directement à partir de la variable générée lors de la connexion).
  • saveTask: pour recevoir la réponse de l'utilisateur à la tâche et la terminer en exécutant la méthode CompleteTaks de la classe EnsLib.Workflow.TaskResponse.

De la part d'IRIS, tout serait déjà configuré pour que l'application externe se connecte.

Test de l'application externe avec le moteur de Workflow

Tout d'abord nous allons simuler l'envoi d'un message de HL7 vers notre production en copiant le fichier /shared/hl7/message_1_1.hl7 vers le chemin /shared/in, examinons donc le message que nous envoyons:

MSH|^~\&|HIS|HULP|EMPI||20240402111716||ADT^A08|346831|P|2.5.1
EVN|A08|20240402111716
PID|||07751332X^^^MI^NI~900263^^^HULP^PI||LÓPEZ CABEZUELA^ÁLVARO^^^||19560121|F|||PASEO MARIO FERNÁNDEZ 2581 DERECHA^^MADRID^MADRID^28627^SPAIN||555819817^PRN^^ALVARO.LOPEZ@VODAFONE.COM|||||||||||||||||N|
PV1||N
OBX|1|NM|162986007^Pulso^SNM||72|^bpm|||||F|||20240402111716
OBX|2|NM|105723007^Temperatura^SNM||37|^Celsius|||||F|||20240402111716
OBX|3|NM|163030003^Presión sanguínea sistólica^SNM||142|^mmHg|||||F|||20240402111716
OBX|4|NM|163031004^Presión sanguínea diastólica^SNM||83|^mmHg|||||F|||20240402111716

Pour ceux d'entre vous qui ne se rappellent pas les articles précédents, nous avions défini dans notre BPL qu'une alerte serait générée lorsque la pression systolique dépassait 140 ou la pression diastolique dépassait 90, par conséquent, ce message devrait générer une tâche d'alerte pour notre patient avec DNI (Document National d'Identification) 07751332X et une autre tâche automatique qui restera ouverte jusqu'à ce qu'IRIS reçoive le nouveau message.

Vérifions notre application mobile:

Deux tâches sont générées : la première est définie comme manuelle et dépend de l'utilisateur ; la seconde est définie comme automatique et, pour qu'elle disparaisse, l'utilisateur doit effectuer une nouvelle lecture avec son tensiomètre . Si nous examinons l'appel HTTP, nous pouvons voir comment nous y avons inclus le JWT:

Si l'utilisateur clique sur le bouton Accepter de la tâche en attente, le flux restera en attente de la lecture du tensiomètre et ne passera pas aux étapes suivantes. Une fois que la tâche manuelle est acceptée et qu'une nouvelle lecture du tensiomètre est reçue, dans laquelle les limites marquées sont à nouveau dépassées, le système génère deux nouvelles tâches d'avertissement, l'une pour le patient et l'autre pour le médecin associé, afin de l'avertir d'une crise possible:

Parfait! Nous avons déjà mis en place notre système de notification des patients de manière simple et rapide.

Conclusion

Comme vous l'avez vu dans cette série d'articles, la fonctionnalité du moteur de Workflow ainsi que les capacités d'interopérabilité d'InterSystems IRIS offrent un potentiel incroyable pour la mise en œuvre de processus métier que peu d'autres solutions métier peuvent fournir. Il est vrai que certaines connaissances techniques peuvent être nécessaires pour en tirer le maximum, mais le jeu en vaut vraiment la chandelle.

0
0 60
Article Lorenzo Scalese · Juin 20, 2024 7m read

Dans notre article précédent, nous avons présenté les concepts généraux ainsi que le problème que nous voulions résoudre en utilisant le moteur de tâches intégré dans InterSystems IRIS. Dans l'article d'aujourd'hui, nous verrons comment configurer une production d'interopérabilité pour fournir une solution.

Configuration du moteur de workflow

Tout d'abord, nous allons définir les rôles des tâches à gérer. Dans notre exemple, nous allons définir deux types de tâches:

  • AutomaticBloodPressureRole: pour créer des tâches automatiques qui ne nécessitent aucune intervention de la part de l'utilisateur.
  • ManualBloodPressureRole:ManualBloodPressureRole: pour créer les tâches à valider manuellement par l'utilisateur.

Il ne faudra pas assigner des utilisateurs à ces rôles puisque nous le ferons plus tard, au fur et à mesure que nous recevrons des messages HL7 de différents patients.

Nous n'avons pas non plus besoin d'ajouter les utilisateurs d'IRIS au Workflow puisque nous le ferons par code à partir de la production.

Configuration de la production

Pour notre exemple, nous allons créer une production dans un NAMESPACE (espace de noms) créé ad-hoc et que nous avons appelé WORKFLOW (flux de travail). C'est dans cette production que nous inclurons les éléments métier dont nous avons besoin.

Commençons par expliquer les composants les plus simples:

Services métier

  • HL7_File_IN: responsable de la collecte des fichiers HL7 à partir d'un chemin d'accès spécifique au serveur qui simulera la réception de messages en provenance d'un dispositif médical.

Opérations métier

  • AutomaticBloodPressureRole: composant de la classe EnsLib.Workflow.Operation, avec le nom d'un des rôles définis que nous utiliserons pour générer les tâches qui n'impliqueront pas d'interaction directe avec l'utilisateur dans notre Workflow.
  • ManualBloodPressureRole: similaire à l'opération métier précédente, mais dans ce cas, les tâches que nous générons nécessiteront que l'utilisateur intervienne directement pour les achever.

Voyons maintenant en détail le processus opérationnel qui gérera le flux d'informations ainsi que la création et la gestion des tâches impliquées dans notre cycle.

Workflow.BP.BloodPressurePlan

Ce processus métier est de type BPL et sera responsable de la création des tâches liées aux soins du patient et de la saisie de la réponse, à la fois du patient et de l'appareil médical qui enverra les informations relatives aux niveaux de tension artérielle.

Début du processus:

Ici, le flux effectue les opérations suivantes:

  1. Contrôle de l'utilisateur: vérifie l'utilisateur reçu dans le message ADT^A08 et, s'il existe, poursuit le flux, sinon crée l'utilisateur dans IRIS et l'affecte aux rôles définis dans le flux de travail. Pour assigner des utilisateurs au rôle, elle lance la commande suivante :
    /// Création d'utilisateurs de Workflowset sc = ##class(EnsLib.Workflow.UserDefinition).CreateUser(username)
    /// Assignation du rôle à l'utilisateur du Workflowset sc = ##class(EnsLib.Workflow.RoleDefinition).AddUserToRole(role, username)
  2. Obtention d'une tâche automatique: Puisque dans ce flux nous allons gérer à la fois des tâches automatiques et manuelles, nous devons vérifier s'il y a des tâches automatiques en attente pour l'utilisateur concernant le patient reçu. Ce point est important car si des tâches automatiques sont en suspens, cela signifie que nous avons eu une lecture antérieure dont les valeurs dépassent les niveaux normaux. Nous effectuerons le contrôle directement dans la table TaskResponse où toutes les tâches créées sont sauvegardées, la requête sera la suivante:
    &sql(SELECTIDINTO :taskId FROM EnsLib_Workflow.TaskResponse WHERE TaskStatus_AssignedTo = :username AND TaskStatus_IsComplete = 0AND %RoleName = :role)
  3. Obtention du nombre d'OBX: nous récupérons le nombre de segments OBX à partir de notre message HL7.
  4. Contrôle des valeurs OBX: nous parcourons les segments OBX et n'extrayons que les données relatives à la tension artérielle.
  5. âches en attente?: Comme nous l'avons dit au point 2, nous vérifions si nous avons une tâche automatique en attente ou non.

Première lecture de la tension artérielle:

Dans cette partie du flux, nous traiterons les messages qui ne sont pas générés par une première lecture dépassant les niveaux maximums de tension artérielle définis.

  1. Contrôle de la tension: Nous vérifions si les niveaux de tension artérielle dépassent les limites préétablies ; si ce n'est pas le cas, le processus s'achève sans qu'il soit nécessaire de faire quoi que ce soit d'autre. Sinon, il sera nécessaire de créer les tâches nécessaires.
  2. Création d'une tâche manuelle: La tension artérielle a dépassé les niveaux de sécurité définis, il est donc nécessaire de créer une tâche manuelle qui informera le patient de la situation et lui indiquera qu'il doit effectuer une deuxième lecture. Voyons la configuration de cette activité.     
    1. Nous allons invoquer l'opération métier ManualBloodPressureRole qui sera responsable de la génération de la tâche en y passant un message de type EnsLib.Workflow.TaskRequest.
    2. Nous définirons les actions que le patient peut effectuer en les séparant par des virgules dans callrequest.%Actions, dans ce cas nous n'avons défini que l'action "Accepter".
    3. Nous configurons le message qui sera affiché au patient dans callrequest.%Message.
    4. Nous attribuons la tâche créée au patient en définissant son nom d'utilisateur dans callrequest.%UserName.
    5. Enfin, nous indiquerons un sujet ou un titre dans callequest.%Subject et une priorité (entre 1 et 3) dans callrequest.%Priority.
  3. Création d'une tâche automatique: comme pour la configuration de l'activité précédente, sauf que cette fois-ci, nous l'enverrons à l'opération métier AutomaticBloodPressureRole. Cette tâche sera celle qui restera en attente de la deuxième mesure de la tension artérielle du patient et qui fera en sorte que tout nouveau message reçu par la production pour ce patient ne génère pas de nouvelles tâches en obtenant un faux dans l'activité Pending task? ("Tâche en attente").
  4. Attente des tâches: Cette activité met le flux de tâches en attente jusqu'à ce que la tâche et le manuel soient achevés, c'est-à-dire jusqu'à ce que le patient supprime l'alarme créée par ManualBloodPressureRole et que la production IRIS reçoive un deuxième message du dispositif médical.
  5. Contrôle en cas d'avertissement: Nous aborderons cette activité une fois que les tâches précédentes en attente auront été achevées. À ce stade, nous vérifions si l'achèvement de la tâche automatique a reçu une nouvelle lecture avec des niveaux qui dépassent les valeurs maximales de tension artérielle configurées (la tâche sera achevée avec une action d'avertissement) ou non. Si la mesure est inférieure aux valeurs maximales, le processus s'achève. Si les mesures dépassent les valeurs maximales, vous passerez à l'activité suivante.
  6. Tâche d'avertissement pour le médecin: Une tâche manuelle est créée pour informer le médecin assigné au patient que ce dernier est peut-être en train de traverser une crise.
  7. Tâche d'avertissement pour le patient: nous informons le patient, au moyen d'une nouvelle tâche manuelle, que son médecin est au courant de la situation.

Deuxième lecture de la tension artérielle

Cette partie du flux ne sera accessible que lorsqu'une tâche automatique précédemment créée n'a pas été achevée.

  1. Contrôle de la tension: nous validons à nouveau le message reçu pour savoir si les valeurs de la tension artérielle dépassent la limite ou non.
  2. Clôturer la tâche normale: Si les niveaux ne dépassent pas les valeurs maximales, nous allons achever la tâche en attente avec la méthode CompleteTask fournie par la classe EnsLib.Workflow.TaskResponse que notre tâche instancie. Le paramètre utilisé indiquera l'action entreprise, dans ce cas nous indiquerons qu'il s'agit d'une fausse alarme en passant la valeur FalseAlarm, nous pourrions définir n'importe quel autre valeur à notre guise.
  3. Achevement de la tâche d'avertissement: Comme dans le cas précédent, sauf que dans ce cas l'action que nous transmettons au moteur de Workflow sera Avertissement et qu'il s'agira de la valeur que l'activité " Contrôle en cas d'avertissement " que nous avons vue précédemment vérifiera.

Comme vous pouvez le voir dans le diagramme suivant (il n'est pas littéral mais vous pouvez vous faire une idée de la manière dont il fonctionnera), notre flux de tâches fonctionnera avec deux "fils" ou "processus":

Le premier fil/processus restera ouvert au cas où il serait nécessaire d'attendre une seconde lecture et c'est le second fil/processus qui provoquera la réactivation du premier fil ouvert après réception de ladite seconde lecture, concluant ainsi le flux de tâches défini.

Conclusion

Comme vous pouvez le constater, la configuration d'un flux de tâches dans une BPL est assez simple et vous pouvez simuler des tâches automatiques et manuelles en toute facilité. Dans le prochain et dernier article, nous verrons comment les utilisateurs peuvent interagir avec leurs tâches à partir d'une application externe.

Merci à tous pour votre attention!

0
0 48
Article Lorenzo Scalese · Juin 18, 2024 6m read

Cela fait un certain temps que j'ai l'intention de faire une sorte de démonstration de concept avec la fonctionnalité Workflow (flux de travail), qui, comme beaucoup d'autres fonctionnalités disponibles dans IRIS, tend à passer inaperçue aux yeux de nos clients (et je fais ici mon mea culpa). C'est pourquoi j'ai décidé il y a quelques jours de développer un exemple de configuration et d'exploitation de cette fonctionnalité en la connectant à une interface utilisateur développée en Angular.

Pour ne pas faire un article trop long et le rendre plus accessible, je vais le diviser en 3 parties. Dans ce premier article, je présenterai la fonctionnalité de Workflow ainsi que l'exemple que nous allons résoudre. Le deuxième article détaillera la configuration et l'implémentation de la production qui sera responsable de la gestion du Workflow. Enfin, nous montrerons comment accéder aux informations disponibles dans notre Workflow via une application Web.

Moteur de Workflow dans InterSystems IRIS

Pour expliquer ce qu'est cette fonctionnalité de Workflow, rien de mieux que de copier la description qui en est faite dans la documentation d'IRIS.

Un système de gestion des flux de travail automatise la répartition des tâches entre les utilisateurs. L'automatisation de la distribution des tâches selon une stratégie prédéfinie rend l'attribution des tâches plus efficace et l'exécution des tâches plus responsable. Un exemple typique est celui d'une application de service d'assistance qui reçoit des rapports de problèmes de la part des clients, envoie ces rapports aux membres des organisations appropriées pour qu'ils prennent des mesures et, une fois le problème résolu, communique les résultats au client.

Le moteur de workflow dans InterSystems IRIS offre un niveau de fonctionnalité bien plus élevé que les systèmes de gestion de workflow traditionnels et autonomes.

Où trouvons - nous les fonctionnalités de Workflow dans notre IRIS? Très simple, depuis le Portail de Gestion:

Expliquons brièvement chacune des options disponibles avant d'entrer dans l'exemple de projet.

Les rôles de Workflow

Cette option fait référence aux rôles dans lesquels nous classerons les utilisateurs qui utiliseront la fonctionnalité. La définition du rôle est quelque peu confuse, je préfère la voir plutôt comme des types de tâches que nous pouvons créer à partir de notre production. Le nom attribué à ces rôles doit correspondre aux noms des Opérations Métier que nous déclarerons plus tard dans notre production pour créer les tâches associées à ces rôles.

Utilisateurs de Workflow

Les utilisateurs IRIS qui peuvent être assignés à différentes tâches seront associés à un ou plusieurs rôles. Les utilisateurs doivent être enregistrés auprès d'IRIS.

Les tâches de Workflow

Liste des tâches créées dans le système avec leurs informations associées. À partir de cette fenêtre, des tâches peuvent être attribuées aux différents utilisateurs correspondant au rôle de tâche.

Quelles sont ces tâches? C'est très simple, les tâches sont des instances de la classe EnsLib.Workflow.TaskRequest, l'objet de ce type sera envoyé depuis le Processus Métier dans lequel il a été généré vers une Opération Métier de la classe EnsLib.Workflow.Operation et avec le nom du rôle que nous avons précédemment créé. Cette action créera à son tour une instance de la classe EnsLib.Workflow.TaskResponse. L'Opération Métier ne renverra pas de réponse tant que la méthode CompleteTask de l'instance de classe TaskResponse n'aura pas été exécutée.

Liste de travail de Workflow

Semblable à la fonctionnalité précédente, il nous montre également une liste de tâches avec les informations qui leur sont associées.

Exemple de Workflow

Le projet que vous trouverez associé à cet article présente un exemple simplifié de solution à un problème typique des organisations de santé tel que le traitement des patients chroniques. Ces types de patients nécessitent un contrôle continu et une surveillance stricte pour lesquels, dans de nombreux cas, les professionnels impliqués dans les soins ne disposent pas des moyens les plus appropriés.

Dans l'exemple, nous allons voir comment nous pouvons surveiller les patients souffrant d'hypertension. Nous supposerons que les patients prennent leur tension artérielle quotidiennement avec un tensiomètre qui enverra la lecture au serveur où notre InterSystems IRIS est installé. Si la tension artérielle dépasse 140 pour la systolique et 90 pour la diastolique, le patient est informé qu'il faudra reprendre la tension artérielle après un certain temps et, si la tension dépasse à nouveau les limites, le patient et le médecin sont informés de la situation afin qu'ils puissent décider des mesures à prendre.

Pour ce faire, nous allons diviser ce flux de tâches en deux étapes:

Étape 1: Prise quotidienne de la tension artérielle.

  1. Réception dans la production de messages HL7 avec des mesures de la pression artérielle.
  2. Vérifier l'existence de l'utilisateur dans le système (et le créer s'il n'existe pas), puis enregistrer l'utilisateur dans les rôles impliqués dans le workflow.
  3. Extraction des données de tension artérielle et comparaison avec le critère d'alerte.
  4. Si les données dépassent les critères d'alerte:
    1. Création d'une tâche pour informer le patient de la situation et lui demander une nouvelle mesure de la tension artérielle.
    2. Création d'une tâche automatique pour gérer la deuxième lecture de la tension artérielle.
  5. Si les données ne dépassent pas les critères d'alerte, le processus est clôturé jusqu'à la réception de la prochaine lecture le lendemain.

Étape 2: Réception de la deuxième lecture après que la première lecture dépasse les valeurs d'alerte.

  1. Réception du message HL7 avec la deuxième lecture de données.
  2. En attente d'une récupération automatique des tâches.
  3. Si les niveaux de tension artérielle dépassent les critères d'alerte:
    1. La tâche automatique est clôturée en indiquant l'état d'alerte.
    2. Une tâche manuelle est créée pour le médecin qui signale la situation du patient.
    3. Une tâche manuelle est créée pour le patient, l'avertissant de la situation et du fait que son médecin a été informé.
  4. Si les niveaux ne dépassent pas les critères d'alerte:
    1. La tâche automatique est clôturée indiquant qu'il n'y a pas de danger.

Dans le prochain article, nous entrerons dans les détails de la conception de notre organigramme afin de refléter ce qui a été dit plus haut.

Interface utilisateur

Comme vous l'avez vu dans les captures d'écran ci-jointes, l'interface utilisateur fournie par InterSystems IRIS pour la gestion des tâches est, pour le moins, assez spartiate, mais l'un des avantages que nous avons avec IRIS est que nous pouvons créer notre propre API REST pour gérer notre flux de tâches d'une manière très simple, nous discuterons de ce point dans notre dernier article.

Pour offrir une interface conviviale aux utilisateurs, nous avons développé une petite application web en Angular en utilisant Angular Material qui nous permettra de créer une interface qui simule une application mobile.

Cette application mobile se connectera à InterSystems IRIS à l'aide de JSON Web Tokens (jetons Web JSON) et effectuera des requêtes HTTP vers une application web spécifique publiée dans IRIS, de manière à ce que les actions entreprises par les différents acteurs sur les tâches soient reflétées dans le flux de tâches défini.

Dans le chapitre suivant...

Après cette introduction à la fonctionnalité Workflow, dans notre prochain article nous montrerons comment implémenter le flux de tâches nécessaire pour traiter l'exemple que nous avons présenté en utilisant une BPL, nous configurerons l'ensemble de la production et nous verrons quelles sont les principales classes impliquées dans le Workflow (EnsLib.Workflow.TaskRequest et EnsLib.Workflow.TaskResponse).

Merci à tous pour votre attention!

0
0 61
Article Lorenzo Scalese · Juin 14, 2024 5m read

Nous concluons cette série d'articles SMART On FHIR avec Auth0 et le référentiel FHIR d'InterSystems IRIS en passant en revue notre application développée en Angular 16.

Rappelons à quoi ressemble l'architecture définie pour notre solution:

Notre application qui servira de front-end correspond à la deuxième colonne et comme vous pouvez le voir, elle sera en charge de deux choses:

  1. Redirigez la demande de connexion vers Auth0 et recevez la réponse.
  2. Envoyez et recevez la réponse des requêtes via REST envoyées au serveur FHIR.

Angular

Angular est un cadre d'application web développé en TypeScript, open source, maintenu par Google, utilisé pour créer et maintenir des applications web mono-page. Cette conception "applications web mono-page" permet de concevoir des applications beaucoup plus dynamiques pour l'utilisateur. Comme nous l'avons déjà expliqué dans le premier article, nous allons utiliser le serveur NGINX comme serveur d'application et reverse proxy qui évitera les problèmes dérivés de CORS en modifiant les en-têtes d'appel pour qu'ils correspondent à ceux du serveur.

Conception de l'application

Nous avons conçu notre application avec Angular Material pour simuler la conception d'une application mobile. Dans notre exemple, l'application est destinée à enregistrer une série de données de patients telles que la fréquence cardiaque, la pression artérielle et le poids et pour cela nous allons envoyer deux types de ressources FHIR à notre serveur, la première sera de type Patient avec laquelle l'utilisateur enregistrera ses données ; La seconde correspondra à la ressource Observation dans laquelle nous enverrons chacun des types de données que nous allons envoyer.

Cette application permettra à l'utilisateur de voir un graphique de l'évolution des données enregistrées.

Écran de connexion

Lorsque l'utilisateur accède à l'itinéraire https:\\localhost, l'écran initial s'affichera à partir duquel on peut demander à se connecter.

 

En cliquant sur le bouton de connexion, l'application redirigera automatiquement l'utilisateur vers la page Auth0 activée pour l'API configurée:

Après avoir introduit notre nom d'utilisateur et notre mot de passe, Auth0 nous demandera d'autoriser l'application à accéder à nos données. Une fois l'accès aux données confirmé, Auth0 nous redirigera vers l'adresse URL que nous avons indiqué lors du processus de configuration. Une fois le jeton d'accès généré, la bibliothèque Auth0 se chargera de l'inclure dans l'en-tête de tous les appels que nous lançons au serveur. On peut le voir dans l'image suivante :

Écran initial

Une fois connecté, la première communication avec notre serveur FHIR permettra de demander les informations disponibles pour l'utilisateur connecté. Pour cela, nous utiliserons une requête par paramètre en envoyant un appel GET du type suivant:

https://localhost:8443/smart/fhir/r5/Patient?email=lperezra%40intersystems.com

La réponse du serveur sera une ressource de type Bundle (paquet) avec les informations suivantes:

{
    "resourceType":"Bundle",
    "id":"8c5b1efd-cfdd-11ee-a06b-0242ac190002",
    "type":"searchset",
    "timestamp":"2024-02-20T10:48:14Z",
    "total":0,
    "link":[
        {
            "relation":"self",
            "url":"https://localhost:8443/smart/fhir/r5/Patient?email=lperezra%40intersystems.com"
        }
    ]
}

Comme nous pouvons le voir, nous avons un total de 0 patients avec cette adresse e-mail, donc notre application nous montrera un écran initial à partir duquel nous pouvons enregistrer nos données.

 

Comme vous pouvez le voir, le champ email est déjà rempli avec l'email de l'utilisateur connecté, car comme vous l'avez vu dans la requête initiale, il va être utilisé comme identifiant. Une fois le formulaire rempli, nous allons envoyer un appel du type suivant via POST:

https://localhost:8443/smart/fhir/r5/Patient

With the message body formed by a Patient resource:

{
    "resourceType":"Patient",
    "birthDate":"1982-03-08",
    "gender":"male",
    "identifier":[
        {
            "type":{
                "text":"ID"
            },
            "value":"12345678A"
        }
    ],
    "name":[
        {
            "family":"PÉREZ RAMOS",
            "given":[
                "LUIS ÁNGEL"
                ]
        }
    ],
    "telecom":[
        {
            "system":"phone",
            "value":"600102030"
        },
        {
            "system":"email",
            "value":"lperezra@intersystems.com"
        }
    ]
}

Les données du patient étant enregistrées sur notre serveur, la requête du patient renvoie maintenant un résultat, ce qui nous permet d'enregistrer les différentes données d'observation. Voyons à quoi ressemble l'écran initial:

Observations screen

De la même manière que nous avons envoyé les données du patient, nous enverrons les résultats d'observation à partir de leurs écrans spécifiques:

Pour chaque ressource envoyée au serveur, l'application ajoute un nouveau point dans le diagramme:

Pour ce faire, elle lancera une requête au serveur demandant les ressources type Observation (résultats d'observation) appartenant à l'utilisateur:

https://localhost/smart/fhir/r5/Observation?patient=Patient/1

Et le serveur retournera à nouveau une ressource type Bundle (paquet) avec tous les résultats d'observations enregistrés pour le patient:

Avec le résultat obtenu, l'application extrait toutes les valeurs numériques et construit les diagrammes pertinents.

Conclusion

Comme vous l'avez vu dans cet article et dans les deux précédents, la conception et la création d'applications SMART On FHIR n'est pas très complexe et nous permet de construire rapidement et de manière agile des applications qui profitent de toutes les fonctionnalités disponibles sur notre serveur FHIR.

Une application de ce type ne requiert pas la mise au point d'un back-end complexe pour gérer les opérations type CRUD sur les données et, grâce à l'utilisation d'OAuth2, il n'est pas nécessaire de gérer les utilisateurs à partir de l'application, cette fonctionnalité étant déléguée à Auth0 ou au serveur d'authentification et d'autorisation choisi par l'utilisateur.

Avec SMART On FHIR, nous pouvons, d'une manière simple et aisée, mettre les outils nécessaires à la gestion des données cliniques à la disposition des patients et des professionnels de la santé.

TMerci infiniment pour votre attention!

0
0 55
Article Lorenzo Scalese · Juin 12, 2024 7m read

Dans l'article précédent, nous avons présenté l'architecture de notre projet SMART On FHIR, il est donc temps de passer aux choses sérieuses et de commencer à configurer tous les éléments qui seront nécessaires.

Nous commençons avec Auth0.

Configuration de l'Auth0

Commençons par créer un compte Auth0 avec un email valide, une fois enregistré il nous faut créer notre première application, et nous le ferons à partir du menu de gauche:

Application menu

Dans notre exemple, l'application sera de type application web monopage car il s'agit d'une application développée dans Angular 16. Nous sélectionnons cette option et cliquons sur Create (Créer).

Single Page Web Application

Dans l'écran suivant, il faut définir les champs suivants:

ATTENTION! Les adresses URL doivent toutes être HTTPS, c'est l'une des exigences pour les connexions OAuth2.

Avec cela, nous avons configuré les adresses URL dont Auth0 a besoin pour rediriger l'utilisateur après le processus d'authentification et d'autorisation. Si vous voyez les URL, elles n'ont pas de port défini, c'est parce qu'avec le déploiement du projet Angular dans Docker via NGINX, nous avons indiqué qu'il sera accessible via le port HTTPS par défaut, 443. Vous pouvez mettre le nom qui vous convient le mieux.

Application configuration

Pour la configuration ultérieure de notre projet Angular, indiquez les valeurs que nous trouvons à la fois dans Domain et Client ID (identifiant de domaine et identifiant de client).

Notre application étant configurée, il est temps de définir l'API qui recevra les requêtes de notre application Angular et nous le ferons à nouveau à partir du menu de gauche:

Cette option nous montrera un nouvel écran pour saisir toutes les données nécessaires:

API configuration

Pour se connecter correctement, il vous faudra définir un identifiant pour l'API qui sera ensuite utilisé par l'application Angular comme "environnement". Comme vous pouvez le voir, il est recommandé de saisir une adresse URL, mais il n'est pas nécessaire que cette adresse soit fonctionnelle, puisqu'elle ne servira que d'identifiant. Dans notre cas, nous pouvons définir:

https://localhost/smart/fhir/r5

Enfin, nous configurons un algorithme de signature RS256 et passons à l'onglet Permissions, où nous définissons le périmètre de FHIR pour les utilisateurs connectés

API permission

Si vous souhaitez approfondir le sujet des contextes FHIR, vous pouvez consulter l'URL de la page officielle en cliquant ici. Pour notre exemple, nous avons défini le périmètre de l'utilisateur/*.* qui permet à l'utilisateur validé d'effectuer des opérations CRUD sur toutes les ressources du serveur FHIR.

Parfait! Nous venons de configurer notre compte Auth0 pour qu'il fonctionne comme un serveur OAuth2.

Configuration de l'application Angular

Je souhaiterais développer l'application en Angular 17, qui introduit pas mal de changements, mais malheureusement la documentation associée à Auth0 et ses bibliothèques ne sont que pour Angular 16, j'ai donc décidé de suivre un chemin facile et je l'ai développée dans la version 16.

Pour configurer notre projet Angular, il suffit d'ouvrir la page app.module.ts et de rechercher le fragment de code suivant:

Examinons la signification de chaque paramètre à configurer:

  • domain: correspond à la valeur Domain générée lors de la création de notre application dans Auth0.
  • clientId: identique à ce qui précède, mais avec la valeur Client ID générée.
  • audience: correspond à l'URL que nous avons configuré comme identifiant de notre API.
  • uri: avec cette valeur, nous indiquons à la bibliothèque TypeScript Auth0 d'intercepter tous les appels que nous faisons aux adresses URL qui contiennent cette URI et d'inclure l'Access_token qu'Auth0 renverra lorsque nous validerons (en ajoutant le paramètre Authorization à l'en-tête de l'appel: Bearer....).

Une fois ces valeurs modifiées, notre application Angular est configurée pour fonctionner avec Auth0. Dans le prochain article, nous verrons plus en détail comment nous invoquerons Auth0 depuis notre interface utilisateur.

Configuration d'InterSystems IRIS for Health

Ce projet est configuré pour installer automatiquement le serveur FHIR pendant le processus de déploiement, afin de nous épargner une étape. Dans notre cas, nous avons défini l'URI/smart/fhir/r5 comme point de terminaison de notre serveur FHIR. Pour ceux d'entre vous qui ne sont pas familiers avec la terminologie FHIR, r5 fait référence à la dernière version de FHIR, disponible depuis des mois sur IRIS.

Pour configurer notre instance IRIS, nous devons d'abord démarrer notre Docker et déployer les 3 conteneurs disponibles dans le projet. Il nous suffira d'exécuter la commande suivante dans le terminal depuis la racine du projet:

docker-compose up -d --build

Cela nous permettra de construire et d'exécuter les conteneurs contenus dans le projet. Pour les utilisateurs de Windows, si vous utilisez Docker Desktop, un écran comme celui-ci devrait s'afficher:

Docker Desktop

Voici nos 3 conteneurs:

  • iris: avec l'instance IRIS dans laquelle se trouve notre serveur FHIR.
  • smart-ui: avec le code de notre application web déployée depuis le serveur NGINX configuré à son tour pour que toutes les connexions se fassent via SSL avec les certificats associés.
  • webgateway: avec son serveur Apache associé (rappelons que depuis la version officielle 2023.1, le serveur Web privé (Private Web Server) a été supprimé, bien qu'il soit encore disponible dans les versions communautaires).

Passerelle Web

Je le répète encore une fois, pour utiliser OAuth2 avec notre serveur FHIR, il est obligatoire que toutes les connexions se fassent par HTTPS, le serveur Apache doit donc être configuré pour n'accepter que les appels de ce type, si vous jetez un œil au fichier /webgateway/shared/CSP.conf vous pouvez voir la section suivante responsable de la configuration du serveur Apache:

# SECTION SSL #
# Activez SSL/TLS (https://) sur le serveur Web Apache.
# L'utilisateur est responsable de fournir des certificats SSL valides.
LoadModule ssl_module /usr/lib/apache2/modules/mod_ssl.so
LoadModule headers_module /usr/lib/apache2/modules/mod_headers.so
<VirtualHost *:443>
SSLEngine on
SSLCertificateFile "/webgateway-shared/apache_webgateway.cer"
SSLCertificateKeyFile "/webgateway-shared/apache_webgateway.key"
Header add ACCESS-CONTROL-ALLOW-ORIGIN "*"
</VirtualHost>

Vous pouvez voir comment la configuration est faite pour que les appels passent par le port 443, c'est-à-dire que l'adresse URL de notre webgateway serait https://webgateway et que tous les appels que nous lançons depuis notre application web vers notre serveur FHIR devraient être redirigés vers cette adresse URL (webgateway est le masque attribué au réseau du conteneur Docker créé par ce dernier).

Tous les appels au serveur depuis Angular viendront avec l'URL https://localhost/smart/fhir/r5 et le serveur NGINX sera en charge de rediriger ce localhost vers le webgateway. Si vous ouvrez le fichier /smart-ui/nginx.conf, vous pourrez voir la configuration suivante:

 

Dans cette configuration, nous voyons que notre application web écoutera sur le port 443 et que tous les appels qui ont la valeur / dans l'URL seront gérés par l'application Angular, tandis que ceux qui incluent /smart/ seront redirigés vers https://webgateway.

Faites attention avec proxy_set_header, qui sera la valeur qui permettra d'éviter les maux de tête avec CORS. Pour éviter que notre passerelle Web ne rejette les appels provenant d'autres serveurs, nous devons modifier la valeur de l'en-tête Host pour la configurer avec l'adresse de la passerelle Web.

InterSystems IRIS

Maintenant il faut configurer notre IRIS pour qu'il fonctionne avec Auth0, pour cela il faut le configurer en tant que client OAuth2. Pour ce faire, il suffit d'accéder au Portail de gestion avec les identifiants superuser/SYS et d'accéder à l'option System Administration > Security > OAuth 2.0 > Client, puis de cliquer sur Create Server Description et de remplir l'endpoint Issuer avec la valeur Domain que nous avons obtenue au moment de la création de l'application à Auth0 (https://[MY_DOMAIN]/). Soyez prudent! L'adresse URL doit se terminer par"/". Enfin, nous sélectionnons la configuration SSL/TLS et cliquons sur Discover and Save (Découvrir et Sauvegarder):

IRIS client

Notre instance IRIS récupère automatiquement les informations dont elle a besoin auprès d'Auth0.

Issuer endpoint

Il suffit d'ajouter un client au serveur précédemment configuré. En appuyant sur Client Configuration (Configuration client), nous accéderons à un nouvel écran où nous définirons le nom de l'application et du client. Ce nom de client sera celui que nous utiliserons plus tard pour configurer notre serveur FHIR.

Serveur FHIR

The last step to finish the configuration of our project is to tell our FHIR server which OAuth2 client will be used for the connection. Pour accéder à la configuration, nous allons ouvrir le Portail de Gestion et sélectionnez Health > FHIR > Configuration du FHIR > Configuration du serveur et nous allons ouvrir le point de terminaison qui s'affiche à l'écran, nous irons à la fin de celui-ci et cliquerons sur Edit (Modifier). Enfin, nous ajoutons dans le champ de nom du client OAuth OAuth Client Name le nom avec lequel nous avons créé la configuration du client.

FHIR OAuth Configuration

Conclusion

Nous avons déjà configuré notre projet, dans le prochain article nous étudierons comment notre application Angular interagit avec chacun des acteurs.

Je vous remercie de votre attention!

0
0 57
Article Lorenzo Scalese · Juin 10, 2024 4m read

Introduction

J'ai récemment participé à une séance pratique formidablement organisée par @Patrick Jamieson au cours de laquelle une application Angular a été configurée avec un serveur IRIS FHIR en suivant les protocoles définis par SMART On FHIR. J'ai trouvé cela très intéressant et j'ai donc décidé de développer ma propre application Angular et de profiter ainsi de ce que j'ai appris en la publiant au sein de la communauté.

SMART On FHIR

Voyons ce que Google nous dit sur SMART On FHIR:

SMART on FHIR est un standard de données qui permet aux applications d'accéder aux informations contenues dans les systèmes de dossiers médicaux électroniques (DME). Un développeur d'application peut écrire une application unique qui se connecte à n'importe quel système de DME adopté selon ce standard.

Les principaux concepts que nous allons traiter dans SMART On FHIR sont:

  • Authentification et autorisation déléguées par OAuth2 ou OpenID.
  • Gestion des ressources FHIR dans le contexte défini.
  • Communications HTTPS.

Architecture de notre projet

Pour cet exercice, nous avons configuré les éléments suivants à la fois dans Docker et dans le service Auth0:

  • Une application développée en Angular qui servira de front-end, et qui a été développée selon les principes de SMART On FHIR.
  • Serveur web NGINX et serveur proxy inverse qui publiera notre application développée en Angular.
  • Auth0 nous fournira le service d'authentification et d'autorisation via OAuth2.
  • InterSystems IRIS dans lequel nous déploierons notre serveur FHIR et auquel nous nous connecterons via la passerelle Web incluant un serveur Apache déjà disponible dans son image Docker.

Auth0

Bien que nous puissions déléguer l'authentification et l'autorisation des utilisateurs à un autre serveur IRIS déployé à cet effet, nous allons à cette occasion utiliser le service offert par Auth0.

Qu'est-ce que Auth0?

Auth0 est un service qui nous fournit l'ensemble du mécanisme pour gérer l'autorisation et l'authentification de nos plateformes.

Auth0 dispose également de bibliothèques spécifiques dans différentes langues pour pouvoir s'intégrer facilement à n'importe quel projet, c'est donc toujours une option à prendre en compte pour les développements basés sur SMART On FHIR.

Intégration d'Auth0 dans notre application

L'utilisation d'OAuth2 étant une condition requise pour l'utilisation de SMART On FHIR, cela implique l'inclusion d'un serveur OAuth2 dans le processus habituel d'authentification, d'autorisation et d'accès à l'application. Dans le diagramme suivant, nous pouvons voir la route suivie par les informations envoyées au système avec le service Auth0:

Analysons le processus:

  • Login request:
    1. Demande de connexion: L'utilisateur accède à l'application dans son navigateur Internet et demande à se connecter.
    2. Demande de connexion: L'application Angular transmet la demande au service Auth0.
    3. Page de connexion: Auth0 envoie une redirection vers votre propre page au navigateur Internet de l'utilisateur.
  • Authentification sur Auth0:
    1. Informations d'identification de l'utilisateur: L'utilisateur saisit son adresse électronique et le mot de passe avec lequel il est enregistré dans Auth0.
    2. Authentification & Autorisation: Auth0 valide les données et génère un Access_token comprenant le contexte attribué à l'utilisateur.
    3. Réponse & redirection de l'Access_token: Auth0 redirige la réponse vers l'URL indiquée dans la configuration du projet en incluant le jeton généré.
    4. Écran du patient: L'application Angular montre à l'utilisateur la page d'enregistrement de ses données personnelles.
  • Enregistrement des Ressources FHIR:
    1. Enregistrer le patient: l'utilisateur remplit le formulaire avec ses données, et l'application Angular transforme le formulaire en un objet JSON au format de la ressource FHIR Patient.
    2. Requête POST: l'application Angular envoie un appel HTTP POST au serveur FHIR déployé dans IRIS en incluant l'access_token comme jeton d'authentification dans l'en-tête de la requête
    3. Réponse POST: après avoir reçu la demande POST via Web Gateway, IRIS vérifie la validité du jeton et le contexte de la demande. Si tout est correct, il valide la ressource reçue et l'enregistre sur le serveur FHIR, en renvoyant un HTTP 201 indiquant la création de la nouvelle ressource et en joignant l'identifiant attribué à la nouvelle ressource dans un en-tête.
    4. Opération réussie: L'application Angular redirigera l'utilisateur vers l'écran présentant les principales fonctionnalités.

Une fois connecté, la bibliothèque Auth0 intégrée au projet sera chargée d'intercepter toutes les requêtes que nous faisons à notre serveur FHIR pour y inclure le jeton d'accès reçu d'Auth0.

A venir...

Dans les prochains articles, nous allons voir comment il faut configurer chacun des systèmes concernés et enfin comment les connecter à notre application Angular. Pour ceux qui ne peuvent pas attendre, vous pouvez consulter le README.md présent sur le GitHub associé au projet OpenExchange lié à cet article, qui explique en détail comment configurer à la fois Auth0 et InterSystems IRIS.

Awfully Good: Stay Tuned (1992) with John Ritter

0
0 79
Article Iryna Mykhailova · Oct 9, 2023 5m read

En profitant de l'application Quiniela ML et comme nous l'avons dit dans l'article précédent, nous allons expliquer comment nous pouvons réaliser une authentification JWT entre notre frontend développé en Angular et notre backend développé en InterSystems IRIS.

Je vous rappelle l'architecture de notre projet QuinielaML :

Pour les applications web, le développement de l'administration et de la gestion de l'accès des utilisateurs est généralement un processus compliqué, mais dans notre cas, InterSystems IRIS le simplifie en nous fournissant toute l'infrastructure dont nous avons besoin.

Authentification par jeton Web JSON

IRIS permet aux applications web connectées à l'instance déployée de se connecter via JWT de manière simple et directe.

Rappelons le cycle de vie d'une demande d'authentification JWT :

Flux d'authentification du client-serveur JWT

En ce qui nous concerne, le client sera notre application frontend développée en Angular et le serveur sera notre instance IRIS.

Ainsi, le processus commence par l'envoi de la demande de connexion du client au serveur. Le serveur valide les données de l'utilisateur en générant un jeton au format JSON qu'il renvoie à l'application cliente. Le client peut à son tour valider le jeton reçu et doit ensuite l'inclure dans l'en-tête de ses appels au serveur afin d'obtenir les ressources demandées.

Comme nous l'avons dit, IRIS facilite la gestion de l'authentification via JWT et pour cela il nous fournit les endpoints suivants à utiliser par notre application client :

  • /login — Un appel à ce point de terminaison avec l'authentification HTTP de base ou avec des informations d'identification valides dans le corps de la demande renvoie un jeton d'accès et un jeton d'actualisation qui peuvent être utilisés dans des demandes ultérieures.
  • /logout — Un appel à ce point de terminaison, s'il n'utilise pas Group-By-ID, invalide le jeton d'accès fourni et le jeton d'actualisation associé. S'il utilise Group-By-ID, toutes les sessions du groupe By-ID actuel sont invalidées.
  • /refresh — Un appel à ce point de terminaison émet une nouvelle paire de jetons d'accès et d'actualisation lorsque l'appel est effectué avec un jeton d'actualisation valide. Cela invalide la paire de jetons d'accès et d'actualisation précédente.
  • /revoke — Si le Group-By-ID n'est pas utilisé, ceci est fonctionnellement le même que /logout. Si l'on utilise Group-By-ID, cela révoque uniquement la paire de jetons d'accès et d'actualisation en cours.
  • Configuration frontale de la connexion

    Comme nous l'avons déjà précisé, nous avons développé le frontend de notre application en utilisant Angular. Depuis notre page HTML login.component.html, nous avons configuré un formulaire qui accepte le nom d'utilisateur et le mot de passe et nous procédons, depuis la classe TypeScript, au lancement de l'invocation depuis le composant de services que nous avons défini.

    Ici, nous voyons comment nous invoquons l'appel au service à partir de login.component.ts :

    onSubmit(): void {
        const { username, password } = this.form;
    
        this.authService.login(username, password).subscribe({
          next: data => {
            this.storageService.save(data.access_token)
            this.isLoggedIn = true;
            this.router.navigate(['home']);
          },
          error: err => {
          }
        });
    }

    Voyons maintenant l'appel que nous lançons depuis le service auth.service.ts vers notre backend :

    login(username: string, password: string): Observable<any> {
        returnthis.http.post<Response>(
          AUTH_API + 'login',
          {
            "user": username,
            "password": password,
          },
          httpOptions
        )
    }

    Comme vous le voyez, nous avons défini notre appel au point de terminaison login à partir duquel nous récupérerons la réponse et stockerons l'access_token pour authentifier ultérieurement nos appels.

    Le stockage du jeton reçu en réponse sera effectué par le service storage.service.ts :

    save(userToken: string): void {
        window.sessionStorage.setItem(USER_KEY, userToken);
    }

    Avec notre jeton stocké, il nous suffit d'intercepter chaque requête envoyée à IRIS et d'insérer le jeton dans l'en-tête. Pour ce faire, nous allons définir un HttpInterceptor dans la classe auth.interceptor.ts.

    intercept(req: HttpRequest<any>, next: HttpHandler) {
        // Get the auth token from the service.const authToken = this.storageService.getToken();
    
        // Clone the request and replace the original headers with// cloned headers, updated with the authorization.if (authToken !== ''){
            req = req.clone({
                headers: req.headers.set('Authorization', 'Bearer ' + authToken)
              });
        }
        // send cloned request with header to the next handler.return next.handle(req);
    }

    Configuration de l'authentification JWT dans IRIS

    Pour que notre IRIS puisse fournir ces services, nous devons configurer correctement notre application web et pour cela nous y accéderons à partir de l'option de menu Administration -> Security -> Applications -> Web Applications (Administration -> Sécurité -> Applications -> Applications web).

    Lorsque nous sommes dans la configuration de l'application, nous devons marquer l'utilisation de l'authentification JWT et nous pouvons définir le délai d'attente pour le jeton d'accès et le jeton d'actualisation :

    Avec cette configuration, nous n'aurons besoin de rien d'autre pour commencer à utiliser ce mode d'authentification dans nos applications web.

    Un commentaire sur la configuration ou l'utilisation de l'authentification JWT est toujours bienvenu si vous avez des questions à ce sujet.

    Le prochain article présentera la configuration de notre projet QuinielaML pour effectuer du web scraping en utilisant Embedded Python.

    0
    0 181
    Article Iryna Mykhailova · Oct 6, 2023 5m read

    Bienvenue chers membres de la Communauté à la présentation et au premier article d'un petit projet qui présentera les capacités d'InterSystems IRIS à fournir une fonctionnalité de sauvegarde complète pour une application web développée en Angular. Dans cet article, nous nous contenterons de présenter le concept ainsi que les fonctionnalités d'InterSystems IRIS utilisées de manière générale, en allant plus en détail dans les articles suivants.

    Bienvenue à QuinielaML !

    Introduction

    Peut-être avez-vous déjà entendu parler des fonctionnalités d'InterSystems IRIS comme Embedded Python et IntegratedML, mais certainement n'avez-vous pas eu l'occasion de les utiliser ou vous ne savez tout simplement pas comment intégrer ces fonctionnalités dans votre quotidien. Profitant du début de la saison de football en Espagne, j'ai pensé que ce serait une excellente occasion d'expliquer toutes ces fonctionnalités d'InterSystems IRIS en développant une application qui nous aide à prédire les résultats du football et à devenir millionnaires avec le Quiniela.

    Qu'est-ce que Quiniela ?

    Lord Of The Rings Smeagol GIF - Lord Of The Rings Smeagol Gollum - DDécouvrez et partagez des

    Pour ceux d'entre vous qui ne connaissent pas ce terme, la Quiniela est un type de pari sportif très célèbre en Espagne car, pendant plusieurs années, il s'agissait du seul format de pari sportif légal.

    Grosso modo, la Quiniela est un type de pari sportif qui consiste à deviner qui sera le vainqueur de 15 matchs à chaque tour (10 matchs de première division et 5 matchs de deuxième division). La victoire de l'équipe locale est marquée par un 1, celle de l'équipe visiteuse par un 2 et l'égalité par un X.

    Voici un billet de la Quiniela.

    Quiniela Elige8, el nuevo juego asociado a la quiniela. - Loteria Cano

    Comment prédire les résultats du Quiniela ?

    Nous ne pouvons pas vraiment prédire un résultat, mais nous pouvons estimer la probabilité de chaque résultat en fonction de certaines variables. Ici, nous avons pris en compte les éléments suivants :

    1. Résultats historiques : résultats des matches entre deux équipes depuis 2000.
    2. Série de victoires de l'équipe locale : L'équipe qui reçoit a gagné les 3 derniers matchs à domicile.
    3. Série de victoires de l'équipe jouant à l'extérieur : Victoire de l'équipe jouant à l'extérieur lors des derniers matches joués à l'extérieur.
    4. Arbitre : résultat des équipes selon l'arbitre.

    Comment obtenir toutes ces données pour construire notre modèle de prédiction ?

    Le web scraping à la rescousse ! Toutes ces données historiques sont accessibles sur le web, il nous suffira donc d'implémenter à partir de notre IRIS un moyen d'obtenir ces données depuis un site web particulier, pour notre exemple nous avons utilisé BDFutbol, qui nous fournit des données concernant tous les matchs joués historiquement à la fois en première et en deuxième division.

    Comment pouvons-nous effectuer un tel web scraping ? Très simplement, avec Embedded Python, il nous suffit d'importer les bibliothèques Python beautifulsoup4 et requests dans notre serveur IRIS et nous serons en mesure d'obtenir les informations nécessaires à partir du site web en question.

    Architecture du projet

    En tant qu'ancien développeur, j'ai choisi Angular pour développer la partie interface utilisateur et ainsi éviter de trop me compliquer la vie. Il est évident que le choix du backend se portera sur InterSystems IRIS avec la fonctionnalité IntegratedML incluse dans sa version communautaire.

    Voici un petit schéma de l'architecture choisie :

    Comment le déployer et le former :

    L'ensemble du projet est Dockerisé, donc il vous suffit de télécharger le code source depuis GitHub sur votre ordinateur local, d'installer Docker et Docker Compose et d'exécuter les instructions suivantes depuis le terminal, dans le chemin d'accès où vous avez téléchargé le projet :

    docker-compose build
    docker-compose up -d

    Une image IRIS d'InterSystems sera automatiquement déployée sur le port 52774 (en utilisant l'accès habituel au portail de gestion) et l'application Angular directement à cette URL.

    Accès à QuinielaML

    Une fois la page de connexion ouverte, il faut saisir le nom d'utilisateur et le mot de passe :

    superuser / SYS

    Celle-ci sera redirigée directement vers l'écran des prédictions, mais il est nécessaire d'afficher le menu latéral en cliquant sur l'icône de l'application en haut à gauche :

    Et choisir **Gestion des données ** dans le menu :

    Nous accédons ainsi à l'écran à partir duquel nous pouvons importer les données du site web BDFutbol, les préparer pour calculer les stries et modifier les valeurs textuelles en format numérique, et enfin créer le modèle de données et le former à l'aide des données préparées.

    Vous devez commencer par la première option à gauche et vous déplacer dans la direction des flèches, en attendant que l'étape précédente soit terminée :

    Génération de prédictions :

    Une fois le modèle formé, il ne nous reste plus qu'à entrer les matchs de la journée que nous voulons prédire. Pour ce faire, nous cliquerons sur l'icône de l'application en haut à gauche et choisirons Prédiction de résultats

    Sur cet écran, il suffit de remplir les champs en haut pour générer une prédiction adéquate, il suffit d'ajouter les données des équipes pour générer la prédiction, mais plus il y a de données, mieux c'est.

    La partie frontend n'a pas été entièrement revue, donc vous pouvez trouver un bug, n'hésitez pas à me le faire savoir pour que je puisse l'améliorer.

    Pour chaque match que vous enregistrez, vous verrez automatiquement la prédiction du résultat fournie par le modèle. Une fois la ronde terminée, vous pouvez entrer le résultat pour avoir une idée de la prédiction approximative en cliquant sur le match en question.

    C'est bien ! Vous avez déjà tout pour devenir millionnaire avec la Quiniela et InterSystems IRIS. Dans le prochain article, nous expliquerons comment ouvrir une session depuis Angular avec InterSystems IRIS via JWT.

    À la prochaine fois !

    2
    0 184
    Article Iryna Mykhailova · Mai 10, 2023 6m read

    De nombreux facteurs influencent la qualité de vie des gens, et l'un des plus importants est le sommeil. La qualité de notre sommeil détermine notre capacité à fonctionner pendant la journée et affecte notre santé mentale et physique. Un sommeil de bonne qualité est essentiel à notre santé et à notre bien-être général. Par conséquent, en analysant les indicateurs précédant le sommeil, nous pouvons déterminer la qualité de notre sommeil. C'est précisément la fonctionnalité de l'application Sheep's Galaxy.

    Sheep's Galaxy est un exemple d'application qui fonctionne avec les technologies IntegratedML et IRIS Cloud SQL d'InterSystems et qui fournit à l'utilisateur un outil d'analyse et d'amélioration de la qualité du sommeil. L'analyse du sommeil prend en compte des facteurs tels que les niveaux de bruit, l'éclairage de la pièce, la durée du sommeil, la consommation de caféine, etc., ce qui permet à l'utilisateur de reconsidérer ses habitudes en matière de sommeil et de créer des conditions optimales pour le sommeil à l'avenir.

    Présentation vidéo :

    https://www.youtube.com/watch?v=eZ9Wak831x4&ab_channel=MariaGladkova

    L'application est basée sur les technologies suivantes :

    Partie Frontend :

    Pour construire cette application, nous avons utilisé la structure Angular. Il nous a aidé à créer une application simple à page unique. Nous avons utilisé Angular v15, et tous les composants Angular ont été implémentés en tant que standalones pour rationaliser l'expérience de création. Nous n'avons pas utilisé de modules Angular et c'est une bonne pratique pour faire évoluer une application dans le futur si nécessaire. Nous avons également utilisé l'architecture des composants intelligents (Smart Component Architecture) - tous les composants de notre application frontale sont divisés en composants "intelligents" et "muets". Ce concept nous aide à séparer le code de logique métier et le code de présentation entre ces composants. Toute la Logique métier et les demandes adressées au serveur sont conservées dans les services isolés. Pour traiter les données de notre backend, nous utilisons RxJS - une bibliothèque pour composer des programmes asynchrones et basés sur des événements en utilisant des séquences observables. Pour styliser notre application, nous avons utilisé Angular Material - il s'agit d'une bibliothèque de composants d'interface utilisateur que les développeurs peuvent utiliser dans leurs projets Angular pour accélérer le développement d'interfaces utilisateur élégantes et cohérentes. Cette bibliothèque offre un grand nombre de composants d'interface utilisateur réutilisables et magnifiques - nous en avons ajouté quelques-uns comme les cartes, les entrées, les tableaux de données, les sélecteurs de date, et bien d'autres encore. Nous présentons ci-dessous une vue d'ensemble du flux de travail typique d'un utilisateur. Tout d'abord, l'utilisateur passe par le processus d'enregistrement, s'il l'utilise pour la première fois, ou par l'écran d'autorisation.

    image

    À l'aide de cette application, l'utilisateur entre des renseignements sur son sommeil, tels que son niveau d'activité pendant la journée, le nombre de tasses de café, son confort de sommeil, son niveau de stress et la quantité d'émotions positives, ainsi que la lumière de la pièce et l'heure du coucher.

    image

    Après chaque saisie de données, l'utilisateur reçoit une notification sur la qualité de son sommeil. Ces données sont ensuite analysées à l'aide d'algorithmes d'apprentissage automatique afin de fournir aux utilisateurs des informations sur leurs habitudes de sommeil.

    image

    Partie Backend :

    Fastapi est un framework python basé sur deux technologies : Pydantic et Starlette. Il a les fonctionnalités suivantes :

    • Il est basé sur des standards ouverts : OpenAPI, schéma JSON, OAuth2 ;
    • Documentation automatique de l'API en swagger ;
    • Implémentation des dépendances ;
    • Il utilise les fonctionnalités de python moderne : annotation de type, asyncio ;
    • Il supporte le code synchrone et asynchrone ;

    La structure du projet consiste en des routeurs avec des points d'extrémité, des modèles pour chaque entité et des services de traitement.

    Chaque point d'extrémité apparaît dans la documentation atomique à /docs et les champs des points d'extrémité ont une relation avec les modèles de données dans la base de données.

    image

    Les modèles pydantiques valident automatiquement les données entrantes et sortantes.

    image

    Le processus de traitement des données des utilisateurs repose sur le protocole, qui vous permet de traiter les données en toute sécurité.

    image

    Le processus d'interaction avec la base de données est mis en œuvre par la connexion SQL d'IRIS à l'aide de DB API.

    image

    IRIS Cloud SQL avec IntegratedML :

    Tout d'abord, vous devez vous connecter au portail InterSystems Cloud Services. Vous devez ensuite créer un nouveau déploiement IRIS Cloud SQL. Veillez à inclure IntegratedML lorsque vous créez un nouveau déploiement. Lorsqu'il est prêt, vous pouvez obtenir les paramètres de connexion à utiliser dans docker-compose.yml :

    image

    En ouvrant le menu "IntegratedML Tools", vous avez accès à la création, à l'entraînement et à la validation de votre modèle, ainsi qu'à la possibilité de générer des prédictions sur un champ sélectionné dans le tableau de votre modèle.

    image

    Dans notre application, nous prédisons la qualité du sommeil sur la base des données de l'utilisateur. Pour ce faire, nous remplissons les champs de la section Prédiction comme suit :

    image

    Dans la requête générée, le champ prediction contient une prédiction de la qualité du sommeil, le champ probability_quality (probabilité de qualité) contient la probabilité que le sommeil soit " de bonne qualité ".

    Liens :

    Pour en savoir plus sur notre projet ou l'utiliser comme modèle pour vos futurs travaux : https://openexchange.intersystems.com/package/Sheep%E2%80%99s-Galaxy

    Remerciements :

    Notre équipe tient à remercier InterSystems et Banksia Global de nous avoir donné l'occasion de travailler avec une technologie de pointe sur des questions importantes.

    Développeurs du projet :

    0
    0 53
    Article Guillaume Rongier · Jan 20, 2023 12m read

    Intersystems IRIS for Health offre un excellent support pour la norme sectorielle FHIR. Les principales caractéristiques sont :

    1. Serveur FHIR
    2. Base de données FHIR
    3. API REST et ObjectScript pour les opérations CRUD sur les ressources FHIR (patient, questionnaire, vaccins, etc.)

    Cet article explique comment utiliser chacune de ces fonctionnalités, et présente un front-end angulaire permettant de créer et d'afficher des ressources FHIR de type Quiz.

    Étape 1 - Déploiement de votre serveur FHIR à l'aide d'InterSystems IRIS for Health

    Pour créer votre serveur FHIR, il faut ajouter les instructions suivantes dans le fichier iris.script ( à partir de : https://openexchange.intersystems.com/package/iris-fhir-template)

        zn "HSLIB"
        set namespace="FHIRSERVER"
        Set appKey = "/fhir/r4"
        Set strategyClass = "HS.FHIRServer.Storage.Json.InteractionsStrategy"
        set metadataPackages = $lb("hl7.fhir.r4.core@4.0.1")
        set importdir="/opt/irisapp/src"
    
        //Install a Foundation namespace and change to it
        Do ##class(HS.HC.Util.Installer).InstallFoundation(namespace)
        zn namespace
    
        // Install elements that are required for a FHIR-enabled namespace
        Do ##class(HS.FHIRServer.Installer).InstallNamespace()
    
        // Install an instance of a FHIR Service into the current namespace
        Do ##class(HS.FHIRServer.Installer).InstallInstance(appKey, strategyClass, metadataPackages)
    
        // Configure FHIR Service instance to accept unauthenticated requests
        set strategy = ##class(HS.FHIRServer.API.InteractionsStrategy).GetStrategyForEndpoint(appKey)
        set config = strategy.GetServiceConfigData()
        set config.DebugMode = 4
        do strategy.SaveServiceConfigData(config)
    
        zw ##class(HS.FHIRServer.Tools.DataLoader).SubmitResourceFiles("/opt/irisapp/fhirdata/", "FHIRServer", appKey)
    
        do $System.OBJ.LoadDir("/opt/irisapp/src","ck",,1)
    
        zn "%SYS"
        Do ##class(Security.Users).UnExpireUserPasswords("*")
    
        zn "FHIRSERVER"
        zpm "load /opt/irisapp/ -v":1:1
    
        //zpm "install fhir-portal"
        halt
    

    En utilisant la classe utilitaire HS.FHIRServer.Installer, vous pouvez créer votre serveur FHIR.

    Étape 2 - Utilisez l'API FHIR REST ou ObjectScript pour lire, mettre à jour, supprimer et trouver des données FHIR

    Je préfère utiliser la classe ObjectScript HS.FHIRServer.Service pour faire toutes les opérations CRUD.

    Pour obtenir toutes les données FHIR provenant d'un type de ressource (comme le questionnaire) :

    /// Retreive all the records of questionnaire
    ClassMethod GetAllQuestionnaire() As %Status
    {
    
        set tSC = $$$OK
        Set %response.ContentType = ..#CONTENTTYPEJSON
        Set %response.Headers("Access-Control-Allow-Origin")="*"
    
        Try {
            set fhirService = ##class(HS.FHIRServer.Service).EnsureInstance(..#URL)
            set request = ##class(HS.FHIRServer.API.Data.Request).%New()
            set request.RequestPath = "/Questionnaire/"
            set request.RequestMethod = "GET"
            do fhirService.DispatchRequest(request, .pResponse)
            set json = pResponse.Json
            set resp = []
            set iter = json.entry.%GetIterator()
            while iter.%GetNext(.key, .value) { 
              do resp.%Push(value.resource)
            }
            
            write resp.%ToJSON()    
        } Catch Err {
            set tSC = 1
            set message = {}
            set message.type= "ERROR"
            set message.details = "Error on get all questionnairies"       
        }
        
        Quit tSC
    }

    Pour obtenir un élément de données spécifique du référentiel de données FHIR (comme une occurrence de questionnaire) :

    /// Retreive a questionnaire by id
    ClassMethod GetQuestionnaire(id As %String) As %Status
    {
    
        set tSC = $$$OK
        Set %response.ContentType = ..#CONTENTTYPEJSON
        Set %response.Headers("Access-Control-Allow-Origin")="*"
    
        Try {
            set fhirService = ##class(HS.FHIRServer.Service).EnsureInstance(..#URL)
            set request = ##class(HS.FHIRServer.API.Data.Request).%New()
            set request.RequestPath = "/Questionnaire/"_id
            set request.RequestMethod = "GET"
            do fhirService.DispatchRequest(request, .pResponse)
            write pResponse.Json.%ToJSON()    
        } Catch Err {
            set tSC = 1
            set message = {}
            set message.type= "ERROR"
            set message.details = "Error on get the questionnaire"       
        }
        
        Quit tSC
    }

    Pour créer une nouvelle occurrence de ressource FHIR (comme un nouveau questionnaire) :

    /// Create questionnaire
    ClassMethod CreateQuestionnaire() As %Status
    {
      set tSC = $$$OK
      Set %response.ContentType = ..#CONTENTTYPEJSON
      Set %response.Headers("Access-Control-Allow-Origin")="*"
    
      Try {
        set fhirService = ##class(HS.FHIRServer.Service).EnsureInstance(..#URL)
        set request = ##class(HS.FHIRServer.API.Data.Request).%New()
        set request.RequestPath = "/Questionnaire/"
        set request.RequestMethod = "POST"
        set data = {}.%FromJSON(%request.Content)
        set data.resourceType = "Questionnaire"
        set request.Json = data
        do fhirService.DispatchRequest(request, .response)
        write response.Json.%ToJSON()
      } Catch Err {
        set tSC = 1
        set message = {}
        set message.type= "ERROR"
        set message.details = "Error on create questionnaire"
      }
      
      Return tSC
    }

    Pour mettre à jour une ressource FHIR (comme un questionnaire) :

    /// Update a questionnaire
    ClassMethod UpdateQuestionnaire(id As %String) As %Status
    {
      set tSC = $$$OK
      Set %response.ContentType = ..#CONTENTTYPEJSON
      Set %response.Headers("Access-Control-Allow-Origin")="*"
    
      Try {
        set fhirService = ##class(HS.FHIRServer.Service).EnsureInstance(..#URL)
        set request = ##class(HS.FHIRServer.API.Data.Request).%New()
        set request.RequestPath = "/Questionnaire/"_id
        set request.RequestMethod = "PUT"
        set data = {}.%FromJSON(%request.Content)
        set data.resourceType = "Questionnaire"
        set request.Json = data
        do fhirService.DispatchRequest(request, .response)
        write response.Json.%ToJSON()
      }Catch Err {
        set tSC = 1
        set message = {}
        set message.type= "ERROR"
        set message.details = "Error on update questionnaire"
      }
      Return tSC
    }
    

    Pour supprimer un occurrence de ressource FHIR (comme un questionnaire) :

    /// Delete a questionnaire by id
    ClassMethod DeleteQuestionnaire(id As %String) As %Status
    {
    
        set tSC = $$$OK
        Set %response.ContentType = ..#CONTENTTYPEJSON
        Set %response.Headers("Access-Control-Allow-Origin")="*"
    
        Try {
            set fhirService = ##class(HS.FHIRServer.Service).EnsureInstance(..#URL)
            set request = ##class(HS.FHIRServer.API.Data.Request).%New()
            set request.RequestPath = "/Questionnaire/"_id
            set request.RequestMethod = "DELETE"
            do fhirService.DispatchRequest(request, .pResponse)
        } Catch Err {
            set tSC = 1
            set message = {}
            set message.type= "ERROR"
            set message.details = "Error on delete the questionnaire"       
        }
        
        Quit tSC
    }
    

    Comme vous pouvez le voir, pour créer, il faut utiliser POST, pour mettre à jour, il faut utiliser PUT, pour supprimer, il faut utiliser DELETE et pour lancer une requête, il faut utiliser le verbe GET.

    Étape 3 - Créez un client en Angular pour utiliser votre application de serveur FHIR.

    J'ai créé une application angulaire en utilisant PrimeNG et en installant le paquet npm install --save @types/fhir. Ce paquet a tous les types FHIR mappé à TypeScript.

    Classe de contrôleur en Angular :

    import { Component, OnInit, ViewEncapsulation } from '@angular/core';
    import { ActivatedRoute, Router } from '@angular/router';
    import { Period, Questionnaire } from 'fhir/r4';
    import { ConfirmationService, MessageService, SelectItem } from 'primeng/api';
    import { QuestionnaireService } from './questionnaireservice';
    
    const QUESTIONNAIREID = 'questionnaireId';
    
    @Component({
        selector: 'app-questionnaire',
        templateUrl: './questionnaire.component.html',
        providers: [MessageService, ConfirmationService],
        styleUrls: ['./questionnaire.component.css'],
        encapsulation: ViewEncapsulation.None
    })
    export class QuestionnaireComponent implements OnInit {
    
        public questionnaire: Questionnaire;
        public questionnairies: Questionnaire[];
        public selectedQuestionnaire: Questionnaire;
        public questionnaireId: string;
        public sub: any;
        public publicationStatusList: SelectItem[];
        
        constructor(
            private questionnaireService: QuestionnaireService,
            private router: Router,
            private route: ActivatedRoute,
            private confirmationService: ConfirmationService,
            private messageService: MessageService){
                this.publicationStatusList = [
                    {label: 'Draft', value: 'draft'},
                    {label: 'Active', value: 'active'},
                    {label: 'Retired', value: 'retired'},
                    {label: 'Unknown', value: 'unknown'}
                ]
            }
    
        ngOnInit() {
            this.reset();
            this.listQuestionnaires();
            this.sub = this.route.params.subscribe(params => {
    
                this.questionnaireId = String(+params[QUESTIONNAIREID]);
    
                if (!Number.isNaN(this.questionnaireId)) {
                    this.loadQuestionnaire(this.questionnaireId);
                }
            });
        }
    
        private loadQuestionnaire(questionnaireId) {
            this.questionnaireService.load(questionnaireId).subscribe(response => {
                this.questionnaire = response;
                this.selectedQuestionnaire = this.questionnaire;
                if(!response.effectivePeriod) {
                    this.questionnaire.effectivePeriod = {};
                }
            }, error => {
                console.log(error);
                this.messageService.add({ severity: 'error', summary: 'Error', detail: 'Error on load questionnaire.' });
            });
        }
    
    
        public loadQuestions() {
            if(this.questionnaire && this.questionnaire.id) {
                this.router.navigate(['/question', this.questionnaire.id]);
            } else {
                this.messageService.add({ severity: 'warn', summary: 'Warning', detail: 'Choose a questionnaire.' });
            }
        }
    
        private listQuestionnaires() {
            this.questionnaireService.list().subscribe(response => {
                this.questionnairies = response;
                this.reset();
            }, error => {
                console.log(error);
                this.messageService.add({ severity: 'error', summary: 'Error', detail: 'Error on load the questionnaries.' });
            });
        }
    
        public onChangeQuestionnaire() {
            if (this.selectedQuestionnaire && !this.selectedQuestionnaire.id) {
                this.messageService.add({ severity: 'warn', summary: 'Warning', detail: 'Select a questionnaire.' });
            } else {
                if(this.selectedQuestionnaire && this.selectedQuestionnaire.id) {
                    this.loadQuestionnaire(this.selectedQuestionnaire.id);
                }
            }
        }
    
        public reset() {
            this.questionnaire = {};
            this.questionnaire.effectivePeriod = {};
        }
    
    
        public save() {
    
            if(this.questionnaire.id && this.questionnaire.id != "") {
                this.questionnaireService.update(this.questionnaire).subscribe(
                    (resp) => {
                        this.messageService.add({
                            severity: 'success',
                            summary: 'Success', detail: 'Questionnaire saved.'
                        });
                        this.listQuestionnaires()
                        this.loadQuestionnaire(this.questionnaire.id);
                    },
                    error => {
                        console.log(error);
                        this.messageService.add({
                            severity: 'error',
                            summary: 'Error', detail: 'Error on save the questionnaire.'
                        });
                    }
                );
            } else {
                this.questionnaireService.save(this.questionnaire).subscribe(
                    (resp) => {
                        this.messageService.add({
                            severity: 'success',
                            summary: 'Success', detail: 'Questionnaire saved.'
                        });
                        this.listQuestionnaires()
                        this.loadQuestionnaire(resp.id);
                    },
                    error => {
                        console.log(error);
                        this.messageService.add({
                            severity: 'error',
                            summary: 'Error', detail: 'Error on save the questionnaire.'
                        });
                    }
                );
            }
            
        }
        
        public delete(id: string) {
    
            if (!this.questionnaire || !this.questionnaire.id) {
                this.messageService.add({ severity: 'warn', summary: 'Warning', detail: 'Select a questionnaire.' });
            } else {
                this.confirmationService.confirm({
                    message: 'Do you confirm?',
                    accept: () => {
                        this.questionnaireService.delete(id).subscribe(
                            () => {
                                this.messageService.add({ severity: 'success', summary: 'Success', detail: 'Questionnaire deleted.' });
                                this.listQuestionnaires();
                                this.reset();
                            },
                            error => {
                                console.log(error);
                                this.messageService.add({ severity: 'error', summary: 'Error', detail: 'Error on delete questionnaire.' });
                            }
                        );
                    }
                });
            }
        }
       
    }
    

    Fichier HTML Angular

    Classe de Service Angular

    import { Injectable } from '@angular/core';
    import { HttpClient, HttpHeaders } from '@angular/common/http';
    import { Observable } from 'rxjs';
    import { environment } from 'src/environments/environment';
    import { take } from 'rxjs/operators';
    import { Questionnaire } from 'fhir/r4';
    
    @Injectable({
      providedIn: 'root'
    })
    export class QuestionnaireService {
    
      private url = environment.host2 + 'questionnaire';
    
      constructor(private http: HttpClient) { }
    
      public save(Questionnaire: Questionnaire): Observable {
        return this.http.post(this.url, Questionnaire).pipe(take(1));
      }
    
      public update(Questionnaire: Questionnaire): Observable {
        return this.http.put(`${this.url}/${Questionnaire.id}`, Questionnaire).pipe(take(1));
      }
    
      public load(id: string): Observable {
        return this.http.get(`${this.url}/${id}`).pipe(take(1));
      }
    
      public delete(id: string): Observable {
        return this.http.delete(`${this.url}/${id}`).pipe(take(1));
      }
    
      public list(): Observable {
        return this.http.get(this.url).pipe(take(1));
      }
    
    }
    

    Step 4 - Application in action

    1. Aller à l'application https://openexchange.intersystems.com/package/FHIR-Questionnaires.

    2. Clone/git dépose le dépôt dans n'importe quel répertoire local.

    $ git clone https://github.com/yurimarx/fhir-questions.git
    

    3. Ouvrir le terminal dans ce répertoire et exécutez :

    $ docker-compose up -d
    1. Ouvrir l'application web : http://localhost:52773/fhirquestions/index.html

    Voici quelques illustrations :

    0
    0 101
    Article Sergei Sarkisian · Juil 5, 2022 10m read

    Avant de commencer à aborder des sujets intermédiaires et avancés, je voudrais résumer quelques points plus généraux. Ils sont subjectifs, bien sûr, et je serai heureux d'en discuter si vous avez une autre opinion ou de meilleurs arguments pour l'un d'entre eux.

    La liste n'est pas exhaustive et c'est voulu, car je couvrirai certains sujets dans de futurs articles.

    Conseil 1. Suivez le guide de style officiel

    Angular est assez strict en termes de limitation de l'architecture possible d'une application, mais il y a encore de nombreux endroits qui vous permettent de faire les choses à votre façon. L'imagination des développeurs est sans limite, mais elle rend parfois le travail difficile pour ceux qui travaillent sur le projet avec vous ou après vous.

    L'équipe Angular fait un bon travail en maintenant l'architecture Angular elle-même et ses bibliothèques, donc ils savent certainement comment créer une base de code stable et supportable.

    Je recommande de suivre leur guide de style officiel et de ne s'en écarter que si les choses ne fonctionnent pas de cette façon. Cela rendra les choses plus faciles quand vous arriverez à un nouveau projet ou si quelqu'un arrive dans votre projet.

    Rappelez-vous, un code et une architecture d'application supportables, stables et faciles à comprendre sont plus importants que des solutions intelligentes mais cryptiques que personne ne pourra rattraper (peut-être même vous à l'avenir).

    Guide de style officiel d'Angular : https://angular.io/guide/styleguide

    Conseil 2. Envisagez d'acheter le livre Angular Ninja

    Vous pouvez penser que c'est de la pub, mais voilà : c'est un très bon livre avec tous les principaux concepts d'Angular et le prix est à votre convenance. Vous pouvez également choisir combien d'argent ira aux auteurs et combien ira à une œuvre de charité.

    L'un des auteurs de ce livre est membre d'Angular teem, donc c'est certainement la source d'information la plus fiable sur Angular après la documentation officielle. Vous pouvez voir les chapitres du livre sur la page du livre et lire des exemples de chapitres pour décider si le livre en vaut la peine.

    De plus, le livre est mis à jour avec la sortie d'une nouvelle version d'Angular et vous obtenez toutes les mises à jour du livre gratuitement.

    Le blog Ninja Squad lui-même est une très bonne source d'informations sur Angular, avec des articles et des nouvelles sur les nouvelles versions, les meilleures pratiques, les fonctionnalités expérimentales et plus encore.

    Le livre Angular Ninja : https://books.ninja-squad.com/angular

    Conseil 3. Lisez la documentation officielle

    Avant de vous plonger dans l'écriture du code de votre application, il est bon de lire la documentation et les guides officiels, surtout si vous démarrez votre projet sur une version d'Angular que vous n'avez jamais utilisée auparavant. Les choses sont dépréciées tout le temps, les tutoriels et les guides sur Internet peuvent être dépassés et vous pouvez finir par augmenter votre dette technique au lieu d'utiliser les nouvelles meilleures pratiques et fonctionnalités.

    C'est aussi une bonne habitude de vérifier pour quelle version le guide a été écrit. S'il s'agit d'une version antérieure de plus de deux ans à celle que vous utilisez, il est préférable de vérifier si les choses ont changé depuis.

    Documentation officielle : https://angular.io

    Conseil 4. Envisagez d'utiliser Angular CDK même si vous n'utilisez pas Angular Material.

    Je l'aborderai plus en détail dans de futurs articles, mais je sais que de nombreux développeurs Angular ne connaissent même pas Angular CDK.

    Angular CDK est une bibliothèque de directives et de classes de base utiles qui peuvent vous aider à développer de meilleures applications. Par exemple, elle contient des éléments tels que FocusTrap, Drag & Drop, VirtualScroll et d'autres qui peuvent être facilement ajoutés à vos composants.

    Angular CDK : https://material.angular.io/cdk/categories

    Conseil 5. Fixez vos dépendances dans package.json

    Il n'est pas particulièrement lié à Angular et il peut être important dans tout projet. Lorsque vous faites npm install --save <quelque chose> , il sera ajouté à votre package.json avec ^ ou ~ au début de la version du package. Cela signifie que votre projet sera capable d'utiliser n'importe quelle version mineure/patch de la même version majeure de la dépendance. Cela se passera mal à l'avenir avec une probabilité de presque 100%. J'ai été confronté à ce problème dans différents projets à de nombreuses reprises. Le temps passe et une nouvelle version mineure d'une dépendance sort et votre application ne se construit plus. Parce qu'un auteur de cette dépendance a mis à jour ses dépendances en version mineure (ou même en patch) et maintenant elles sont en conflit avec votre build. Ou peut-être qu'il y a un nouveau bug dans la nouvelle version mineure de votre dépendance et vous n'avez aucun problème dans votre code (parce que vous avez installé vos dépendances avant la nouvelle version), mais toute autre personne essayant d'installer et de construire votre projet à partir du référentiel sera confrontée au bug dont vous n'avez même pas connaissance (et vous ne serez pas en mesure de le reproduire sur votre machine).

    package-lock.json existe pour résoudre ce problème et pour aider les développeurs à avoir le même ensemble de dépendances à travers le projet. Idéalement, il devrait être livré au dépôt, mais de nombreux développeurs décident d'ajouter ce fichier à .gitignore. Et s'il n'est plus là, vous pouvez vous retrouver avec les problèmes ci-dessus. Il est donc préférable de ne pas faire aveuglément confiance à la résolution de vos dépendances et de la fixer sur la version spécifique, de la scanner régulièrement pour détecter les vulnérabilités (avec npm audit), puis de la mettre à jour et de la tester manuellement.

    Conseil 6. Essayez de mettre votre application à niveau vers la nouvelle version d'Angular dès que vous le pouvez.

    Angular évolue en permanence et de nouvelles versions sortent tous les 6 mois environ. En gardant votre application à jour, vous pourrez utiliser toutes les nouvelles fonctionnalités, y compris des constructions plus rapides et d'autres optimisations. En outre, la mise à niveau vers la prochaine version majeure d'Angular ne devrait pas poser de gros problèmes dans ce cas. Mais si vous avez 5 versions de retard et que votre application est de taille moyenne ou grande, le processus de mise à jour vers la dernière version peut être difficile car Angular n'a pas de schéma pour mettre à niveau les applications sautant des versions intermédiaires d'Angular. Vous devrez mettre à jour toutes les versions intermédiaires une par une, en vérifiant que l'application fonctionne sur chaque version. La mise à jour directe sans schéma Angular CLI est possible, mais peut aussi être délicate. Je vous suggère donc de garder votre application fraîche et à jour.

    Conseil 7. Essayez de réduire votre liste de dépendances

    Il est facile de prendre l'habitude d'introduire de nouvelles dépendances dans votre application lorsque vous avez besoin de nouvelles fonctionnalités. Mais avec chaque dépendance, la complexité de votre application augmente et le risque d'une soudaine avalanche de dettes techniques s'accroît.

    Si vous décidez d'ajouter une nouvelle dépendance dans votre application, pensez à ceci :

    • La dépendance est-elle bien supportée ?
    • Combien de personnes l'utilisent ?
    • Quelle est la taille de l'équipe de développement ?
    • A quelle vitesse ils ferment les problèmes sur GitHub ?
    • La documentation de la dépendance est-elle bien rédigée ?
    • A quelle vitesse est-elle mise à jour après la sortie d'une nouvelle version d'Angular ?
    • Quel est l'impact de cette dépendance sur les performances et la taille du bundle de votre application ?
    • Quelle sera la difficulté de remplacer cette dépendance si elle est abandonnée ou dépréciée à l'avenir ?

    Si la réponse à certaines de ces questions est négative, envisagez de vous en débarrasser ou au moins de la remplacer par quelque chose de plus mature et de mieux supporté.

    Conseil 8. N'utilisez pas <any> comme type "temporaire".

    Encore une fois, il est facile de commencer à utiliser n'importe quel type au fur et à mesure que vous écrivez votre logique d'entreprise, parce que l'écriture de types appropriés pourrait prendre du temps, et vous devez terminer votre tâche dans ce sprint. Les types peuvent être ajoutés plus tard, bien sûr. Mais c'est une pente glissante pour augmenter la dette technique.

    L'architecture de votre application et les types doivent être définis avant d'écrire toute logique métier. Vous devez clairement comprendre quels objets vous aurez et où ils seront utilisés. La spécification avant le code, n'est-ce pas ? (Tom Demarco a écrit un livre à ce sujet avant qu'il ne devienne courant : https://www.amazon.com/Deadline-Novel-About-Project-Management/dp/0932633390).

    Si vous écrivez le code sans types prédéfinis, vous risquez de vous retrouver avec une architecture d'application moins bonne et des fonctions qui utilisent des objets très similaires mais différents. Vous devrez donc soit créer des types différents pour chaque fonction (ce qui aggravera encore les choses), soit passer votre temps à écrire des spécifications, des types ET du refactoring, ce qui est une perte de temps par rapport à ce qui aurait été fait auparavant.

    Conseil 9. Prenez le temps de comprendre le processus de construction

    Angular fait un excellent travail pour faciliter le travail du développeur en ce qui concerne la construction du projet. Mais les options par défaut ne sont pas toujours les meilleures dans tous les cas.

    Passez votre temps à comprendre comment le processus de construction fonctionne dans Angular, quelles sont les différences entre les constructions de développement et de production, quelles options Angular a pour les constructions (comme les optimisations, les cartes de source, le regroupement et plus).

    Conseil 10. Examinez le contenu de votre liasse.

    Toutes les bibliothèques ne fournissent pas de tree-shaking et nous ne faisons pas toujours les importations de la bonne manière, il y a donc toujours une chance que quelque chose de redondant soit empaqueté avec notre application.

    C'est donc une bonne habitude d'examiner le contenu de votre bundle de temps en temps.

    Il y a de bons articles décrivant le processus en utilisant webpack-bundle-analyzer , donc je ne le couvrirai pas ici, mais voici un lien pour l'un d'entre eux : https://www.digitalocean.com/community/tutorials/angular-angular-webpack-bundle-analyzer

    Je couvrirai ce sujet plus en détail plus tard dans la série.

    Conseil 11. Utilisez la propriété providedIn des services au lieu d'importer le service dans le module.

    De nombreux exemples de code Angular sur Internet utilisent l'importation des services dans les modules. Mais ce n'est pas la façon préférée de déclarer les services depuis Angular 6 ou 7. Nous devrions utiliser la propriété providedIn du décorateur @Injectable pour permettre le tree-shaking et un meilleur bundle de nos applications. Angular est suffisamment intelligent pour comprendre dans quel bundle le service doit être inclus, quand il doit être initialisé et combien d'instances du service doivent être créées.

    Il y a trois valeurs que providedIn accepte. La plupart du temps, root est suffisant, mais il en existe deux autres :

    • root : le service sera singleton dans la portée de l'application
    • any : une instance du service sera créée pour tous les modules chargés avec empressement, avec une instance différente créée pour chaque module paresseux.
    • platform : le service sera singleton pour toutes les applications tournant sur la même page.

    Conseil 12. N'oubliez pas les principales règles d'exécution

    • Utilisez trackBy pour les collections afin de réduire les opérations de redécoupage et l'exécution de JavaScript.
    • Utilisez la stratégie de détection des changements onPush partout où vous le pouvez (je l'aborderai dans l'article dédié).
    • Effectuez les calculs lourds en dehors de ngZone
    • Utilisez des modèles d'accélération et de ralentissement avec vos événements pour éviter les appels inutiles au serveur et l'inondation d'événements.
    • Utilisez le défilement virtuel pour afficher de grands ensembles de données.
    • Utiliser des pure pipes pour la transformation des données dans vos templates
    • Utilisez la compilation AoT
    • Chargez paresseusement les parties de votre application qui ne sont pas nécessaires à son démarrage.
    • Évitez les calculs et les appels de fonction dans vos modèles.

    Merci de votre lecture ! J'espère que certains de ces conseils vous ont été utiles. Si vous avez des commentaires et des remarques, s'il vous plaît, faites-le moi savoir dans les commentaires, je serai heureux de discuter 😃. A bientôt !

    0
    0 174
    Article Sergei Sarkisian · Juil 3, 2022 9m read

    Bonjour, je m'appelle Sergei Sarkisian et je crée des fronts Angular depuis plus de 7 ans en travaillant chez InterSystems. Comme Angular est un framework très populaire, nos développeurs, clients et partenaires le choisissent souvent comme partie de la pile pour leurs applications.

    J'aimerais commencer une série d'articles qui couvriront différents aspects d'Angular : concepts, comment faire, meilleures pratiques, sujets avancés et plus encore. Cette série s'adressera aux personnes qui connaissent déjà Angular et ne couvrira pas les concepts de base. Comme je suis en train d'établir la feuille de route des articles, je voudrais commencer par mettre en évidence certaines fonctionnalités importantes de la dernière version d'Angular.

    Formes strictement typées

    Il s'agit probablement de la fonctionnalité d'Angular la plus demandée ces dernières années. Avec Angular 14, les développeurs peuvent désormais utiliser toutes les fonctionnalités de vérification stricte des types de TypeScript avec les formulaires réactifs d'Angular.

    La classe FormControl est maintenant générique et prend le type de la valeur qu'elle contient.

    /* Avant Angular 14 */
    const untypedControl = new FormControl(true);
    untypedControl.setValue(100); // est définie, aucune erreur
    
    // Maintenant
    const strictlyTypedControl = new FormControl<boolean>(true);
    strictlyTypedControl.setValue(100); // vous recevrez le message d'erreur de vérification de type ici
    
    // Également dans Angular 14
    const strictlyTypedControl = new FormControl(true);
    strictlyTypedControl.setValue(100); // vous recevrez le message d'erreur de vérification de type ici
    

    Comme vous le voyez, le premier et le dernier exemple sont presque identiques, mais les résultats sont différents. Cela se produit parce que dans Angular 14 la nouvelle classe FormControl infère les types à partir de la valeur initiale fournie par le développeur. Ainsi, si la valeur true a été fournie, Angular définit le type boolean | null pour ce FormControl. La valeur nullable est nécessaire pour la méthode .reset() qui annule les valeurs si aucune valeur n'est fournie.

    Une ancienne classe FormControl non typée a été convertie en UntypedFormControl (il en est de même pour UntypedFormGroup, UntypedFormArray et UntypedFormBuilder) qui est pratiquement un alias pour FormControl<any>. Si vous effectuez une mise à jour depuis une version précédente d'Angular, toutes les mentions de votre classe FormControl seront remplacées par la classe UntypedFormControl par Angular CLI.

    Les classes Untyped* sont utilisées avec des objectifs spécifiques :

    1. Faire en sorte que votre application fonctionne absolument comme elle l'était avant la transition de la version précédente (rappelez-vous que le nouveau FormControl déduira le type de la valeur initiale).
    2. S'assurer que toutes les utilisations de FormControl<any> sont prévues. Donc vous devrez changer tout UntypedFormControl en FormControl<any> par vous-même.
    3. Pour fournir aux développeurs plus de flexibilité (nous couvrirons ceci ci-dessous)

    Rappelez-vous que si votre valeur initiale est null alors vous devrez explicitement spécifier le type de FormControl. Aussi, il y a un bug dans TypeScript qui exige de faire la même chose si votre valeur initiale est false.

    Pour le groupe de formulaire, vous pouvez aussi définir l'interface et juste passer cette interface comme type pour le FormGroup. Dans ce cas, TypeScript déduira tous les types à l'intérieur du FormGroup.

    interface LoginForm {
        email: FormControl<string>;
        password?: FormControl<string>;
    }
    
    const login = new FormGroup<LoginForm>({
        email: new FormControl('', {nonNullable: true}),
        password: new FormControl('', {nonNullable: true}),
    });
    

    La méthode de FormBuilder .group() a maintenant un attribut générique qui peut accepter votre interface prédéfinie comme dans l'exemple ci-dessus où nous avons créé manuellement FormGroup :

    interface LoginForm {
        email: FormControl<string>;
        password?: FormControl<string>;
    }
    
    const fb = new FormBuilder();
    const login = fb.group<LoginForm>({
        email: '',
        password: '',
    });
    

    Comme notre interface n'a que des types primitifs non nuls, elle peut être simplifiée avec la nouvelle propriété nonNullable FormBuilder (qui contient l'instance de la classe NonNullableFormBuilder qui peut aussi être créée directement) :

    const fb = new FormBuilder();
    const login = fb.nonNullable.group({
        email: '',
        password: '',
    })
    

    ❗ Notez que si vous utilisez un FormBuilder non Nullable ou si vous définissez une option nonNullable dans le FormControl, alors lorsque vous appelez la méthode .reset(), elle utilisera la valeur initiale du FormControl comme valeur de réinitialisation.

    Aussi, il est très important de noter que toutes les propriétés dans this.form.value seront marquées comme optionnelles. Comme ceci :

    const fb = new FormBuilder();
    const login = fb.nonNullable.group({
        email: '',
        password: '',
    });
    
    // login.value
    // {
    //   email?: string;
    //   password?: string;
    // }
    

    Cela se produit parce que lorsque vous désactivez un FormControl à l'intérieur du FormGroup, la valeur de ce FormControl sera supprimée de form.value.

    const fb = new FormBuilder();
    const login = fb.nonNullable.group({
        email: '',
        password: '',
    });
    
    login.get('email').disable();
    console.log(login.value);
    
    // {
    //   password: ''
    // }
    

    Pour obtenir l'objet complet du formulaire, vous devez utiliser la méthode .getRawValue() :

    const fb = new FormBuilder();
    const login = fb.nonNullable.group({
        email: '',
        password: '',
    });
    
    login.get('email').disable();
    console.log(login.getRawValue());
    
    // {
    //   email: '',
    //   password: ''
    // }
    

    Avantages des formulaires strictement dactylographiés :

    1. Toute propriété et méthode retournant les valeurs du FormControl / FormGroup est maintenant strictement typée. Par exemple, value, getRawValue(), valueChanges.
    2. Toute méthode permettant de modifier la valeur d'un FormControl est maintenant sécurisée par le type : setValue(), patchValue(), updateValue().
    3. Les FormControls sont maintenant strictement typés. Cela s'applique également à la méthode .get() de FormGroup. Cela vous empêchera également d'accéder aux FormControls qui n'existent pas au moment de la compilation.

    Nouvelle classe FormRecord

    L'inconvénient de la nouvelle classe FormGroup est qu'elle a perdu sa nature dynamique. Une fois définie, vous ne serez pas en mesure d'ajouter ou de supprimer des FormControls à la volée.

    Pour résoudre ce problème, Angular présente une nouvelle classe - FormRecord. FormRecord est pratiquement le même que FormGroup, mais il est dynamique et tous ses FormControls doivent avoir le même type.

    folders: new FormRecord({
      home: new FormControl(true, { nonNullable: true }),
      music: new FormControl(false, { nonNullable: true })
    });
    
    // Ajouter un nouveau FormContol au groupe
    this.foldersForm.get('folders').addControl('videos', new FormControl(false, { nonNullable: true }));
    
    // Cela entraînera une erreur de compilation car le contrôle est de type différent.
    this.foldersForm.get('folders').addControl('books', new FormControl('Some string', { nonNullable: true }));
    

    Et comme vous le voyez, ceci a une autre limitation - tous les FormControls doivent être du même type. Si vous avez vraiment besoin d'un FormGroup à la fois dynamique et hétérogène, vous devriez utiliser la classe UntypedFormGroup pour définir votre formulaire.

    Composants sans module (autonomes)

    Cette fonctionnalité est toujours considérée comme expérimentale, mais elle est intéressante. Elle vous permet de définir des composants, des directives et des tuyaux sans les inclure dans un module.

    Le concept n'est pas encore totalement au point, mais nous sommes déjà capables de construire une application sans ngModules.

    Pour définir un composant autonome, vous devez utiliser la nouvelle propriété standalone dans le décorateur Composant/Pipe/Directive :

    @Component({
      selector: 'app-table',
      standalone: true,
      templateUrl: './table.component.html'
    })
    export class TableComponent {
    }
    

    Dans ce cas, ce composant ne peut être déclaré dans aucun NgModule. Mais il peut être importé dans les NgModules et dans d'autres composants autonomes.

    Chaque autonome composant/pipe/directive a maintenant un mécanisme pour importer ses dépendances directement dans le décorateur :

    @Component({
      standalone: true,
      selector: 'photo-gallery',
      // un module existant est importé directement dans un composant autonome
    	// CommonModule importé directement pour utiliser les directives Angular standard comme *ngIf
      // le composant autonome déclaré ci-dessus est également importé directement
      imports: [CommonModule, MatButtonModule, TableComponent],
      template: `
        ...
        <button mat-button>Next Page</button>
    		<app-table *ngIf="expression"></app-table>
      `,
    })
    export class PhotoGalleryComponent {
    }
    

    Comme je l'ai mentionné ci-dessus, vous pouvez importer des composants autonomes dans n'importe quel ngModule existant. Il n'est plus nécessaire d'importer l'intégralité d'un module partagé, nous ne pouvons importer que les éléments dont nous avons réellement besoin. C'est également une bonne stratégie pour commencer à utiliser de nouveaux composants autonomes:

    @NgModule({
      declarations: [AppComponent],
      imports: [BrowserModule, HttpClientModule, TableComponent], // import our standalone TableComponent
      bootstrap: [AppComponent]
    })
    export class AppModule {}
    

    Vous pouvez créer un composant autonome avec Angular CLI en tapant:

    ng g component --standalone user
    
    

    Application Bootstrap sans module

    Si vous voulez vous débarrasser de tous les ngModules dans votre application, vous devrez amorcer votre application différemment. Angular a une nouvelle fonction pour cela que vous devez appeler dans le fichier main.ts:

    bootstrapApplication(AppComponent);
    

    Le second paramètre de cette fonction vous permettra de définir les providers dont vous avez besoin dans votre application. Comme la plupart des fournisseurs existent généralement dans les modules, Angular (pour l'instant) exige d'utiliser une nouvelle fonction d'extraction importProvidersFrom pour eux :

    bootstrapApplication(AppComponent, { providers: [importProvidersFrom(HttpClientModule)] });
    

    Chargement paresseux de la route des composants autonomes:

    Angular dispose d'une nouvelle fonction de route de chargement paresseux, loadComponent, qui existe exactement pour charger des composants autonomes:

    {
      path: 'home',
      loadComponent: () => import('./home/home.component').then(m => m.HomeComponent)
    }
    

    loadChildren ne vous permet plus seulement de charger paresseusement un ngModule, mais aussi de charger les routes des enfants directement à partir du fichier des routes :

    {
      path: 'home',
      loadChildren: () => import('./home/home.routes').then(c => c.HomeRoutes)
    }
    

    Quelques notes au moment de la rédaction de l'article

    • La fonctionnalité des composants autonomes est encore au stade expérimental. Elle sera bien meilleure à l'avenir avec le passage à Vite builder au lieu de Webpack, un meilleur outillage, des temps de construction plus rapides, une architecture d'application plus robuste, des tests plus faciles et plus encore. Mais pour l'instant, beaucoup de ces éléments sont manquants, nous n'avons donc pas reçu le paquet complet, mais au moins nous pouvons commencer à développer nos applications avec le nouveau paradigme Angular en tête.
    • Les IDE et les outils Angular ne sont pas encore tout à fait prêts à analyser statiquement les nouvelles entités autonomes. Comme vous devez importer toutes les dépendances dans chaque entité autonome, si vous manquez quelque chose, le compilateur peut aussi le manquer et vous faire échouer au moment de l'exécution. Cela s'améliorera avec le temps, mais pour l'instant, les développeurs doivent accorder plus d'attention aux importations.
    • Il n'y a pas d'importations globales présentées dans Angular pour le moment (comme cela se fait dans Vue, par exemple), donc vous devez importer absolument chaque dépendance dans chaque entité autonome. J'espère que ce problème sera résolu dans une prochaine version, car l'objectif principal de cette fonctionnalité est de réduire le boilerplate et de rendre les choses plus faciles.

    C'est tout pour aujourd'hui. A bientôt !

    0
    0 424