Recherche

Effacer le filtre
Article
Lorenzo Scalese · Sept 5, 2022

Présentation de FHIRaaS

# Introduction Cet article vise à donner un aperçu du service FHIR Accelerator Service (FHIRaaS) piloté par l'implémentation de l'application iris-on-fhir, disponible dans OEX développé pour le concours FHIRaaS. Un tutoriel de base vous guidera dans la configuration d'un déploiement FHIRaaS fonctionnel, comprenant une clé API et un serveur OAuth 2.0. Une bibliothèque permettant d'utiliser les ressources FHIR par le biais de FHIRaaS est également brièvement évoquée. Enfin, certaines fonctionnalités de l'application iris-on-fhir sont présentées dans des articles séparés. Vous pouvez consulter le code complet sur le référentiel github de l'application. Ce contenu sera présenté dans une série de 3 artciles. Ce premier article semble un peu gros, mais ne vous inquiétez pas, c'est parce que j'ai mis beaucoup d'images pour vous aider dans vos étapes de configuration. # FHIRaaS IRIS fournit déjà un environnement API FHIR [intégré dans IRIS for Health et IRIS Health Connect](https://www.intersystems.com/fhir/#our-products-support-fhir). Mais si vous souhaitez profiter de l'environnement fiable, sécurisé et à faible maintenance offert par les services en nuage, vous pouvez désormais compter sur [InterSystems IRIS FHIR Accelerator Service (FHIRaaS)](https://docs.intersystems.com/components/csp/docbook/Doc.View.cls?KEY=FAS_intro). FHIRaaS est une infrastructure FHIR prête à l'emploi basée sur des services en nuage. Il vous suffit de mettre en place un déploiement et de commencer à utiliser l'API FHIR dans vos applications, quelle que soit leur nature : client JS (SMART on FHIR), backend traditionnel ou applications sans serveur. Pour demander votre essai gratuit de FHIRaaS, veuillez contacter InterSystems. ## Configuration du déploiement Après la connexion, cliquez sur le bouton "CREATE NEW DEPLOYMENT". ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/0LIauF5BKe.png) Vous devrez passer par quelques étapes. La première consiste à choisir la taille du déploiement. Au moment de la rédaction de cet article, FHIRaaS ne propose qu'une seule option. Il suffit donc d'appuyer sur le bouton "CONTINUE". ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/SW7lHUh3kY.png) L'étape suivante consiste à choisir le fournisseur de services en nuage qui sera utilisé. Là encore, une seule option était disponible au moment de la rédaction de cet article : AWS. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/lDpNEO2zC9.png) La dernière configuration est juste le nom du déploiement. Il y a quelques règles pour ce nom, et l'interface FHIRaaS vous alerte lorsqu'un nom invalide est fourni. Notez également que vous ne pouvez pas changer ce nom après la configuration. Cliquez sur le bouton "CONTINUE" après avoir sélectionné le nom. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/E8CCKdytt0.png) Enfin, révisez votre configuration et démarrez votre déploiement FHIRaaS en cliquant sur le bouton "CREATE". ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/21dbwDwCMw.png) Si tout se passe bien, vous recevrez un message sympa indiquant que votre déploiement FHIRaaS est en cours de développement. Attendez quelques minutes jusqu'à la fin de ce processus. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/HYLcwQzfEj.png) Après quelques minutes, votre déploiement FHIRaaS est prêt à être utilisé. Il suffit d'appuyer sur le bouton de déploiement et de commencer à l'utiliser. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/IZ8FY5q5mI.png) Après avoir cliqué sur le bouton de déploiement, l'onglet "Aperçu" est présenté. Notez que vous disposez de plusieurs onglets. Dans cet article, seuls les onglets "OAuth 2.0", "Credentials" et "API Development" seront couverts. Mais il ne s'agit que d'un aperçu, les autres ne sont pas du tout compliqués et vous pouvez facilement les explorer. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/q5eUnTOeBu.png) ## Contrôle d'accès FHIRaaS prend en charge deux méthodes de contrôle d'accès : Clé API et OAuth 2.0. Passons en revue chacune d'entre elles. ### Clé d'API Les clés API sont des jetons générés par FHIRaaS qui permettent à vos applications d'interagir avec l'API sans interaction avec l'utilisateur. Cette méthode est donc idéale pour la communication entre le serveur FHIRaaS et les applications tierces autorisées, comme une API de chatbot, par exemple. Pour créer une clé API, accédez d'abord à l'onglet "Credentials", puis cliquez sur le bouton "CRÉER UNE CLÉ API". ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/0ziV95PjEE.png) Choisissez un nom pour votre clé API et cliquez sur le bouton "ADD". ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/UALIEz359m.png) Génial ! Votre clé API a été créée ! Copiez-la et enregistrez-la dans un endroit sûr - vous ne pourrez plus accéder à cette information. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/4xhW0b3JYP.png) Après la création, vous ne pouvez que supprimer une clé API. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/9rDImo7h6G.png) Pour utiliser cette clé Api, il suffit d'ajouter un en-tête x-api-key à une requête HTTP. Par exemple : ``` curl -X GET "https://fhir.lrwvcusn.static-test-account.isccloud.io/Patient" -H "accept: application/fhir+json" -H "x-api-key: your-apy-key" ``` ### OAuth 2.0 - Création d'un serveur OAuth 2.0 et ajout d'utilisateurs à celui-ci Comme nous l'avons dit précédemment, la clé API permet d'utiliser l'API sans interaction avec l'utilisateur. Mais, lorsque vous créez une application pour vos utilisateurs, OAuth 2.0 et OpenID Connect sont aujourd'hui la norme industrielle pour l'authentification et l'autorisation. Une petite remarque : bien que vous puissiez utiliser OAuth 2.0 et OpenID Connect indépendamment, il est tout à fait normal de voir les deux fonctionner côte à côte. Ainsi, lorsque je parle d'OAuth 2.0, je veux dire OAuth 2.0 pour l'autorisation et OpenID Connect pour l'authentification. Alors, configurons un serveur OAuth 2.0. Tout d'abord, sélectionnez l'onglet "OAuth 2.0", puis cliquez sur le bouton "CREATE AUTHENTICATION SERVER" ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/9WdzcWGKZp.png) L'étape suivante consiste à choisir un nom pour votre serveur OAuth 2.0 et à sélectionner le fournisseur d'identité (IdP) à utiliser. Au moment où cet article a été écrit, FHIRaaS ne supportait que AWS Cognito comme IdP. Il suffit donc d'appuyer sur le bouton "CREATE". ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/WEiVaUhl7o.png) Si votre demande a été réussie, un message vous sera envoyé, comme dans l'image ci-dessous. Maintenant vous pouvez ajouter des utilisateurs à votre IdP en cliquant sur le bouton "ADD USER". ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/cRn6llnRR4.png) Vous serez redirigé vers l'onglet "Credentials". Pour ajouter un utilisateur, cliquez sur le bouton "CREATE USER". ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/m25cp1qZ6n.png) Saisissez le nom d'utilisateur et son mot de passe, puis cliquez sur le bouton "CREER". ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/kK2bESQh8u.png) Si tout se passe bien, vous pouvez maintenant voir un utilisateur créé dans votre IdP. Cet utilisateur peut maintenant se connecter à des applications autorisées par ce serveur OAuth 2.0. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/kYkKtw4z5Z.png) ### OAuth 2.0 - Ajout d'applications au serveur OAuth 2.0 Après la création d'un serveur OAuth 2.0 et l'ajout d'utilisateurs à celui-ci, ces utilisateurs peuvent utiliser les applications autorisées par ce serveur. Maintenant, nous allons ajouter une application au serveur. Tout d'abord, accédez à l'onglet "OAuth 2.0", sélectionnez "Application", puis cliquez sur le bouton "CREATE APPLICATION". ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/dWype5WvmQ.png) Ensuite, choisissez un nom pour votre application dans le serveur et le serveur OAuth 2.0 qui vient d'être créé. Les URLs doivent diriger vers votre application. L'"URL de redirection" est l'adresse de destination lorsque les utilisateurs se connectent avec succès. L'"URL de déconnexion" est la page vers laquelle les utilisateurs sont redirigés, lorsqu'ils utilisent l'IdP pour se déconnecter. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/6EkFXqnxhM.png) Vous pouvez rediriger vers localhost pendant le développement, mais, bien sûr, pour la production, vous devez fournir une URL accessible par l'Internet. Les dernières étapes consistent à choisir les ressources FHIR (domaines d'application) que les utilisateurs doivent accepter de partager avec l'application. Pour ce test simple, toutes les ressources sont demandées, mais dans les applications réelles, vous pouvez contrôler chaque ressource FHIR, comme si l'application pouvait juste lire, juste écrire ou les deux. Si les utilisateurs ne sont pas d'accord avec cette demande d'autorisation, le serveur OAuth 2.0 refusera l'accès à ces ressources. Après avoir correctement configuré les domaines, appuyez sur le bouton "CREER". ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/P11B258GjR.png) Si tout se passe bien, vous verrez apparaître un message vert. Maintenant, vous pouvez vérifier les paramètres de votre nouvelle application ou la supprimer en appuyant sur la case de l'application. Vous pouvez également créer plusieurs applications dont vous avez besoin. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/iG9QBPWndU.png) ## Développement d'API Parmi les fonctionnalités intéressantes de FHIRaaS, citons l'onglet Développement API. Il vous fournit un explorateur de spécification OpenAPI de l'API FHIR, vous permettant d'essayer facilement toutes les fonctionnalités FHIR. Pour y accéder, cliquez sur l'onglet "API Development". Après son chargement, vous pouvez sélectionner la ressource FHIR que vous souhaitez explorer. Notez que FHIRaaS fournit la version R4 pour les ressources FHIR. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/zx7NVHKnFH.png) Maintenant, nous allons nous authentifier pour utiliser l'outil. Vous devez d'abord créer une clé API. ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/IxnjaDcllj.gif) Bien, maintenant, nous allons avoir tous les patients dans cette instance de FHIRaaS : ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/gVVKWw6OzC.gif) Comme vous pouvez le voir dans l'animation ci-dessus, vous pouvez effectuer toutes les opérations CRUD sur la ressource Patient - de même pour toutes les autres ressources disponibles. Ce qui est bien ici, c'est que vous n'avez pas besoin de connaître toute la structure des ressources pour essayer d'effectuer des opérations sur elles. Par exemple, si vous souhaitez créer un nouveau patient, l'outil vous fournit un modèle pour cette ressource : ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/ib5mlqUBF3.gif) Vous disposez de la même fonctionnalité pour les autres ressources FHIR. À la fin de la page, l'outil vous offre une belle vue de toutes les ressources liées, sous forme de schémas : ![](https://raw.githubusercontent.com/diashenrique/iris-on-fhir/master/image/article1/hhhQhcTTys.gif) # Conclusion Dans cet article, nous abordons certains aspects de FHIRaaS et mettons en place un déploiement fonctionnel Dans le prochain article, nous examinerons quelques exemples simples de son utilisation dans des applications. Pour suivre l'actualité, ouvrez l'annonce!
Article
Guillaume Rongier · Mai 6, 2022

2021.2 Fonctionnalité SQL en vedette - Statistiques avancées de tables

Voici le troisième article de notre courte série sur les innovations d'IRIS SQL qui offrent une expérience plus adaptative et plus performante aux analystes et aux applications requérant des données relationnelles sur IRIS. Il s'agit peut-être du dernier article de cette série pour 2021.2, mais nous prévoyons plusieurs autres améliorations dans ce domaine. Dans cet article, nous allons approfondir un peu plus les statistiques de tableaux supplémentaires que nous commençons à rassembler dans cette version : Histogrammes Que signifie le mot "histogramme" ? Un histogramme est une représentation approximative de la distribution des données d'un champ numérique (ou de manière plus générale des données qui ont un ordre précis). Il est utile de connaître la valeur la plus petite, la plus grande et la moyenne d'un tel champ, mais cela ne vous dit pas grand-chose sur la façon dont les données sont réparties entre ces trois points. C'est là qu'intervient l'histogramme, qui divise la plage de valeurs en "godets" et compte le nombre de valeurs de champ qui apparaissent dans chaque godet. Il s'agit d'une définition assez souple et vous pouvez toujours choisir de prendre la taille des godets de telle sorte que les godets soient également "larges" en termes de valeurs de champ, ou également "larges" en termes de nombre de valeurs échantillonnées couvertes. Dans ce dernier cas, chaque godet contient le même pourcentage de valeurs et les godets représentent donc des percentiles. Le graphique ci-dessus trace un histogramme pour le champ EventData de l'ensemble de données [Aviation Demo dataset](https://github.com/intersystems/Samples-Aviation), en utilisant la même largeur de godet exprimée en nombre de jours. Pourquoi aurais-je besoin d'un histogramme ? Supposons que vous cherchiez dans cet ensemble de données tous les événements antérieurs à 2004 dans l'État de Californie : SELECT * FROM Aviation.Event WHERE EventDate < '2004-05-01' AND LocationCountry = 'California' Dans notre article précédent sur [Choix du plan d'exécution](https://fr.community.intersystems.com/post/20212-fonctionnalit%C3%A9-sql-en-vedette-choix-du-plan-dex%C3%A9cution), nous avons déjà discuté comment capturer la sélectivité et les valeurs aberrantes potentielles pour un champ comme LocationCountry dans les statistiques de la table. Mais de telles statistiques pour les valeurs de champs individuels ne sont pas très pratiques pour cette condition `<` sur EventDate. Pour calculer la sélectivité de cette condition, vous devez agréger la sélectivité de toutes les valeurs possibles de EventDate jusqu'au 1er mai 2004, ce qui peut être une requête assez exigeante en soi plutôt que le genre d'estimation rapide que vous pouvez vous permettre au moment de la planification de la requête. C'est là que les histogrammes sont utiles. Reprenons notre histogramme pour la distribution des valeurs EventDate, en divisant cette fois les données en 16 sections de même taille, chacune contenant 6,667 % des données. De cette façon, les choses se traduisent plus facilement en percentiles et en nombres de sélectivité que nous pouvons utiliser pour les estimations du coût des requêtes. Pour lire ce tableau, regardons la quatrième ligne : 20 % des valeurs (3 godets de 6,667 % chacun) précèdent la limite inférieure de ce godet du 22 juin 2003, et il contient 6,667 % de valeurs supplémentaires, jusqu'au 19 septembre 2003. Godet Percentile Valeur 0% 21/12/2001 1 7% 02/07/2002 2 13% 19/01/2003 3 20% 22/06/2003 4 27% 19/09/2003 5 33% 30/12/2003 6 40% 01/10/2004 7 47% 01/10/2005 8 53% 20/08/2006 9 60% 14/01/2007 10 67% 02/04/2008 11 73% 14/05/2008 12 80% 29/11/2008 13 87% 01/06/2010 14 93% 30/10/2011 15 100% 26/09/2012 La date de coupure utilisée dans l'exemple de requête ci-dessus (1er mai 2004) se trouve dans le cinquième godet, et comporte entre 33 % et 40 % des valeurs précédant cette date. Au fur et à mesure que les godets deviennent plus petits, nous pouvons considérer que la distribution _à l'intérieur_ de ceux-ci est approximativement uniforme et simplement interpoler entre les limites inférieure et supérieure, ce qui dans ce cas conduit à une sélectivité d'environ 37%, que nous pouvons utiliser dans notre estimation du coût de la requête. Voici une autre façon de visualiser notre utilisation des histogrammes, en les traçant comme un barre de distribution cumulative. Nous pouvons voir comment la ligne tracée pour le 1er mai 2004 sur l'axe X (les valeurs), se traduit par ~37% sur l'axe Y. L'exemple ci-dessus utilise une condition d'intervalle avec juste une limite supérieure pour plus de clarté, mais l'approche fonctionne évidemment aussi bien lorsqu'on utilise une limite inférieure ou une condition d'intervalle (par exemple en utilisant le prédicat BETWEEN). À partir de la version 2021.2, nous collectons des histogrammes dans le cadre des statistiques de tables pour tout champ organisé, y compris les chaînes de caractères, et nous les utilisons pour estimer la sélectivité des plages dans le cadre du RTPC. De nombreuses requêtes du monde réel impliquent une condition de plage sur les champs de date (et autres). Nous sommes donc convaincus que cette amélioration d'IRIS SQL sera bénéfique à la planification des requêtes pour bon nombre de nos clients et, comme toujours, nous sommes impatients de connaître vos expériences.
Article
Iryna Mykhailova · Mai 5, 2022

CheatSheet pour travailler avec les globales

Quand on travaille avec les globales, on voit qu’il n’y a pas mantes fonction en ObjectScript (COS) à utiliser. C’est aussi le cas avec Python et Java. Toutefois, toutes ses fonctions sont indispensables quand on travaille directement avec les données sans utilisation des objets, des documents ou des tables. Dans cet article je voudrais parler de différentes fonctions et commandes qui se servent à travailler avec les globales dans trois langues : ObjectScript, Python et Java (les deux derniers en utilisant Native API). Avant de commencer par les commandes et fonctions qui s’utilisent pour travailler avec les globales, à mon avis, c’est important de discourir sur le sujet des globales en général. Qu'est-ce que c'est, la globale ? En accord avec la documentation officielle, une globale (ou une variable globale) est une variable qui permet de stocker des données dans la base de données et de les rendre disponibles à tous les processus accédant à cette base de données. Pour signaler qu'une variable est globale, vous devez utiliser la syntaxe suivante : ^a où a est le nom de la variable et ^ est le symbole qui signifie que cette variable doit être stockée dans la base de données. La variable peut avoir des indices ou être sans eux. Dans l'exemple précédent, la variable n’a pas des indices. Si on veut les ajouter, on doit le faire en les écrivant dans les parenthèses comme ça: ^a(ind1, ind2, ...) ou ind1 et ind2 sont les indices de la variable a. Quand on parle des globales il est important de connaître les points suivants : Une globale consiste en un ensemble de nœuds (dans certains cas, un seul nœud), identifiés par des indices. Chaque nœud peut contenir une valeur. InterSystems IRIS fournit des fonctionnalités pour itérer à travers les nœuds d'un global et accéder rapidement aux valeurs (et on va discuter de ces fonctionnalités plus tard). Une globale est automatiquement stocké dans la base de données. Lorsque vous affectez une valeur à un nœud d'une variable globale, les données sont écrites immédiatement dans la base de données. Vous pouvez voir le contenu d'une globale par programmation ou sur le Management Portal. Vous pouvez aussi lire plus sur les globales dans la série des articles sur la Communauté des Développeurs. Pour travailler avec les globales en Python et Java on doit utiliser la Native API pour ces langages. En général, les Native APIs sont des interfaces légères qui permettent d'accéder directement aux globales. Et pour y accéder, premièrement, vous devez configurer votre environnement. Configuration pour Python : Téléchargez InterSystems IRIS Native API en accord avec votre OS (s’il y a de nécessité, renommera le fichier, par exemple pour Windows et Python 3.10 ça doit être irisnative-1.0.0-py3-none-any.whl) Installez ce wheel : Pip install irisnative-1.0.0-py3-none-any.whl Emportez le module : import irisnative Ajoutez le code pour ouvrir la connexion et créer l’objet iris args = {'hostname':'127.0.0.1', 'port':51773, 'namespace':'USER', 'username':'_SYSTEM', 'password':'SYS'} conn = irisnative.createConnection(**args) iris = irisnative.createIris(conn) Après tout est fini, n’oubliez pas à fermer la connexion et l’objet IRIS en utilisant le code ci-dessous : conn.close() iris.close() Configuration pour Java : Ajoutez intersystems-jdbc-XXX.jar dans votre projet, où XXX est la version liée à votre version de Java. Vous pouvez trouver ce fichier dans le dossier <installation>/dev/java/lib/ Emportez les modules suivants : import com.intersystems.jdbc.IRISConnection; import com.intersystems.jdbc.IRIS; import com.intersystems.jdbc.IRISIterator; Dans votre code ajoutez le code pour ouvrir la connexion et créer l’objet IRIS : String connStr = "jdbc:IRIS://<server>:<port>/<namespace>"; //par exemple "jdbc:IRIS://127.0.0.1:51773/USER" String user = "_SYSTEM"; String pwd = "SYS"; IRISConnection conn = (IRISConnection)java.sql.DriverManager.getConnection(connStr,user,pwd); IRIS iris = IRIS.createIRIS(conn); Après tout est fini, n’oubliez pas à fermer la connexion et l’objet IRIS en utilisant le code ci-dessous : iris.close(); conn.close(); Maintenant que nous sommes familiarisés avec l’info de base sur les globales et comment configurer l’environnement, allons regarder sur les fonctions et commandes pour travailler avec eux en ObjectScript (COS), Python et Java. Nom de fonction (commande) et sa description COS syntaxe COS exemples Python syntaxe Python exemples Java syntaxe Java exemples set - attribue une valeur à une variable set:pc argument, ... où pc est post condition (facultative) et argument peut être soit variable = valeur soit (liste de variables) = valeur set ^z = 648 //attribue une valeur 648 à une globale ^z set ^a = 10, ^b(“dif”) = 21, ^b(10) = 8 //attribute une valeur 10 à une globale ^a, une valeur 21 à une globale ^b(“dif”) et une valeur 8 à une globale ^b(10) set:^a=10 ^c = “string” //attribue une valeur “string” à une globale ^c si une globale ^a a une valeur 10 set (^x, ^y) = “string” //attribue une valeur “string” aux globales ^x et ^y iris.set(value, globalName, *subscripts) où value est la valeur de datatype supportée, globalName est le nom de la globale et subscripts sont la liste des indices (une ou zéro des indices) # attribue une valeur "A" à une globale ^root("foo", "SubFoo"), la même chose que set ^root("foo", "SubFoo") = "A" iris.set('A', 'root', 'foo', 'SubFoo') # attribue une valeur 123 à une globale ^root("bar", "lowbar", "UnderBar"), la même chose que set ^root("bar", "lowbar", "UnderBar") = 123 iris.set(123, 'root', 'bar', 'lowbar', 'UnderBar') # attribue une valeur 963 à une globale ^root, la même chose que set ^root = 123 iris.set(963, 'root') void jdbc.IRIS.set(value, globalname, subscripts) où value est la valeur de datatype supportée, globalname est le nom de la globale et subscripts sont la liste des indices (une ou zéro des indices) iris.set("A", "root", "foo", "SubFoo"); //attribue une valeur "A" à une globale ^root("foo", "SubFoo"), la même chose que set ^root("foo", "SubFoo") = "A" iris.set(123, "root", "bar", "lowbar", "UnderBar"); //attribue une valeur 123 à une globale ^root("bar", "lowbar", "UnderBar"), la même chose que set ^root("bar", "lowbar", "UnderBar") = 123 iris.set(963, "root"); //attribue une valeur 963 à une globale ^root, la même chose que set ^root = 963 increment – augmente la global spécifié avec la valeur passée. S'il n'y a pas de nœud à l'adresse spécifiée, un nouveau nœud est créé avec la valeur passée. $increment(variable, num) où variable est la variable la valeur de laquelle doit être changée et num est l'incrément numérique que vous souhaitez ajouter (facultatif, par défaut = 1) set ^b=$increment(^a) //^b =11 set ^c=$increment(^a, -3) //^c = 7 iris.increment(value, globalName, *subscripts) où value est la valeur numérique, globalName est le nom de globale et subscripts sont la liste des indices (une ou zéro des indices) iris.increment(-2, 'myGlobal', 'B') # diminue la valeur du nœud ^myGlobal("B") par 2 long increment(value, globalname, subscripts) où value est la valeur numérique, globalname est le nom de globale et subscripts sont la liste des indices (une ou zéro des indices) //diminue la valeur du nœud ^myGlobal("B") par 2 iris.increment(-2, "myGlobal", "B"); get - obtient une valeur de la globale $get(variable, defaut) où variable est la variable la valeur de laquelle on veut obtenir et defaut est la valeur à renvoyer si la variable n'est pas définie (facultative). set val = $get(^b(“dif”)) //val = 21 set val = $get(^b(1), 100) //val = 100 iris.get(globalName, *subscripts) iris.getBoolean(globalName, *subscripts) iris.getBytes(globalName, *subscripts) iris.getFloat(globalName, *subscripts) iris.getLong(globalName ,*subscripts) iris.getString(globalName, *subscripts) où globalName est le nom de globale et subscripts sont la liste des indices (une ou zéro des indices). # obtient la valeur string de la globale ^root("foo", "SubFoo") subfoo = iris.getString('root', 'foo', 'SubFoo') # obtient la valeur de la globale ^root("bar", "lowbar", "UnderBar") underbar = iris.get('root', 'bar', 'lowbar', 'UnderBar') Boolean jdbc.IRIS.getBoolean(globalname, subscripts) byte[] jdbc.IRIS.getBytes(globalname, subscripts) Date jdbc.IRIS.getDate(globalname, subscripts) Double jdbc.IRIS.getDouble(globalname, subscripts) Float jdbc.IRIS.getFloat(globalname, subscripts) InputStream jdbc.IRIS.getInputStream(globalname, subscripts) Integer jdbc.IRIS.getInteger(globalname, subscripts) Long jdbc.IRIS.getLong(globalname, subscripts) Object jdbc.IRIS.getObject(globalname, subscripts) Reader jdbc.IRIS.getReader(globalname, subscripts) Short jdbc.IRIS.getShort(globalname, subscripts) String jdbc.IRIS.getString(globalname, subscripts) Time jdbc.IRIS.getTime(globalname, subscripts) Timestamp jdbc.IRIS.getTimestamp(globalname, subscripts) IRISList jdbc.IRIS.getIRISList(globalname, subscripts) où globalname est le nom de globale et subscripts sont la liste des indices (une ou zéro des indices). Si le nœud n’existe pas la fonction retourne null. String subfoo = iris.getString("root", "foo", "SubFoo"); //obtient la valeur de la globale ^root("foo", "SubFoo") Integer underbar = iris.getInteger("root"); //obtient la valeur de la globale ^root Object obj = iris.getObject("root", "nothing"); //dans ce cas obj = null kill – supprime des variables avec tous leurs nœuds descendants kill:pc argument, ... où pc est post condition (facultative) et argument (facultatif) peut être soit variable, ... soit (variable, ...) kill ^a //supprime une globale ^a avec tous leurs nœuds descendants kill ^b, ^c //supprime des globales ^b et ^c avec tous leurs nœuds descendants kill:^a=10 ^z //supprime une globale ^z avec tous leurs nœuds descendants si une globale ^a a une valeur 10 iris.kill(globalName, *subscripts) où globalname est le nom de globale et subscripts sont la liste des indices (une ou zéro des indices) iris.kill('myGlobal', 'A') # supprime la variable ^myGlobal('A') et tous ses nœuds descendants jdbc.IRIS.kill(globalName, subscripts) où globalname est le nom de globale et subscripts sont la liste des indices (une ou zéro des indices) kill("root", "foo", "SubFoo") ; //supprime la variable ^root('foo', 'SubFoo') et tous ses nœuds descendants zkill:pc nœud, ... où pc est post condition (facultative) et nœud est le noud exact de la globale zkill ^b(“dif”) //supprime un nœud ^b(“dif”), laisse tous les autres nœuds data/isDefined – renvoie une valeur indiquant si le nœud spécifié existe et s'il contient une valeur. Elle peut renvoyer quatre valeurs : 0 — le nœud spécifié n'existe pas ; 1 — le nœud existe et a une valeur ; 10 — le nœud est sans valeur mais a des sous-nœuds ; 11 — le nœud a à la fois une valeur et des sous-nœuds. $data(variable, target) où variable est la variable a vérifier et target est la variable où on renvoie la valeur actuelle de la variable (facultative) set val = $data(^a) //val = 1 set val = $data(^b) //val = 10 set val = $data(^qqch) //val = 0 iris.isDefined(globalName, *subscripts) où globalName est le nom de globale et subscripts sont la liste des indices (une ou zéro des indices) if iris.isDefined('root')%10 > 0: print(iris.get('root')) # si le nœud ^root a de valeur, l’imprime int jdbc.IRIS.isDefined(globalName, subscripts) où globalname est le nom de globale et subscripts sont la liste des indices (une ou zéro des indices) //si le nœud ^root("foo") n’existe pas if isDefined("root", "foo") ==0 { //crée le nœud ^root("foo") avec la valeur 74 iris.set(74, "root", "foo"); } iterator - itère sur un ensemble de sous-nœuds. $order(variable, direction, target) où variable est la variable à partir de laquelle commence le parcours en largeur, direction est l’ordre croissant (1, par défaut) ou décroissant (-1) dans lequel on parcourt l’arbre (facultatif) et target est la variable où on renvoie la valeur actuelle de la variable trouvée (facultative) set mydata(1) = "a", mydata(-3) = "C", mydata(5) = "e", mydata(-5) = "E" set key = $order(mydata("")) while (key'="") { write key, ", " // obtient le nouvel indice set key = $order(mydata(key)) } // -5, -3, 1, 5, iris.iterator(globalName, *subscripts) - renvoie un objet iterator pour le nœud spécifié, où globalName est le nom de globale et subscripts sont la liste des indices (une ou zéro des indices) # reçoit l’iterateur subscript_iter = iris.iterator('root') for subscript, value in subscript_iter: # imprime l’indice et la valeur des sous-nœuds print("subscript = {}, value = {}".format(subscript, value)) IRISIterator getIRISIterator(globalName, subscripts) - renvoie un objet IRISIterator pour le nœud spécifié, où globalname est le nom de globale et subscripts sont la liste des indices (une ou zéro des indices) //reçoit l’iterateur IRISIterator iter = iris.getIRISIterator("myNames","people"); //vérifie si le nœud suivant existe while (iter.hasNext()) { //cherche le nœud suivant iter.next(); //imprime la valeur d’indice et de nœud System.out.print(" \"" + iter.getSubscriptValue() + "\"=" + iter.getValue()); }; iterator.items() – renvoi l’iterateur qui contient les indices et les valeurs boolean hasNext() – renvoie true si l'itérateur a un élément suivant $qsubscript(namevalue, intexpr) où namevalue est le string avec le nom de variable et intexpr est un numéro qui spécifie le nom à renvoyer : nom de l'espace de noms (-1), nom de la variable(0) ou nom de l'indice(1-..) set ^grocerylist("food","fruit",1)="apples" write $qsubscript($name(^grocerylist("food","fruit",1)), 0) //^grocerylist write $qsubscript($name(^grocerylist("food","fruit",1)), 1) //food write $qsubscript($name(^grocerylist("food","fruit",1)), 2) //fruit write $qsubscript($name(^grocerylist("food","fruit",1)), 3) //1 iterator.values() – renvoi l’iterateur qui contient juste les valeurs boolean hasPrevious() – renvoie true si l'itérateur a un élément précèdent iterator.subscripts() – renvoi l’iterateur qui contient juste les indices String next() – renvoie l’élément suivant $qlength(var) où var est le string avec le nom de variable dont la fonction compte le nombre d'indices write $qlength($name(^grocerylist("food","fruit",1))) //3 iterator.reversed() – renvoi l’iterateur dans la direction opposée de la précédente String previous() – renvoie l’élément suivant //reçoit l’iterateur IRISIterator iter = iris.getIRISIterator("myNames","people"); //vérifie si le nœud précèdent existe while (iter.hasPrevious()) { //cherche le nœud précédent iter.previous(); //imprime la valeur de l’indice et du nœud System.out.print(" \"" + iter.getSubscriptValue() + "\""); }; iterator.next() – renvoi l’indice, la valeur ou tous les deux du nœud suivant (ou précèdent) # reçoit l’iterateur iterNode= iris.iterator('root').subscripts() output = "\nSubscripts under node root: " try: while True: output += '%s ' % iterNode.next() except StopIteration: print(output + '\n') # pour finir le boucle Object getValue() – obtient la valeur du nœud à la position actuelle de l'itérateur $query(reference, direction, target) où reference est la variable à partir de laquelle commence le parcours en profondeur, direction est l’ordre croissant (1, par défaut) ou décroissant (-1) dans lequel on parcour l’arbre (facultatif) et target est la variable où on renvoie la valeur actuelle de la variable trouvée (facultative) set ^a = 10 set ^a(10) = 100 set ^a("Bonjour") = 200 set ^a(10, "Salut") = 300 set ^a(10, 150) = 80 set x = "^a" for { set x = $query(@x) quit:x="" write x, ", " } //^a(10), ^a(10,150), ^a(10,"Salut"), ^a("Bonjour"), iterator.startFrom(subscript) – renvoi l’iterateur qui commence par le nœud subscript void startFrom(subscript) – définit la position de départ de l'itérateur à l'indice spécifié où subscript est un point de départ arbitraire et n'a pas besoin de spécifier un nœud existant void remove() – supprime de la collection sous-jacente le dernier élément renvoyé par cet itérateur String getSubscriptValue() – obtient l'indice de niveau le plus bas pour le nœud à la position actuelle de l'itérateur merge – copie la source dans la destination et tous les descendants de la source dans les descendants de la destination. merge:pc arg, ... où pc est post condition (facultative) et arg est destination = source set ^a = 10 set ^a(10) = 100 set ^a("Bonjour") = 200 set ^a(10, "Salut") = 300 set ^a(10, 150) = 80 set ^b = 25 set ^b(10) = 248 set ^b(10, 48) = 963 merge ^a("Bonjour") = ^b(10) //copie les nœuds descendants de ^b(10) dans le nœud ^a("Bonjour") merge = on recoit *Tous les exemples utilisent le code présent dans les exemples précédents dans la même colonne. J'espère que ce tableau vous aidera à développer votre propre code qui travaille directement avec les globales. Vous pouvez lire plus sur l’utilisation des globales dans la Documentation Native API pour Python et Native API pour Java. Si vous avez des questions, n'hésitez pas à les poser dans les commentaires.
Article
Sylvain Guilbaud · Juil 10, 2023

Comment utiliser IRIS à partir de Django - Exemples

Découvrir Django Django est un framework web conçu pour développer des serveurs et des API, et pour traiter des bases de données de manière rapide, évolutive et sécurisée. Pour ce faire, Django fournit des outils permettant non seulement de créer le squelette du code, mais aussi de le mettre à jour sans souci. Il permet aux développeurs de voir les changements presque en direct, de corriger les erreurs avec l'outil de débogage et de traiter la sécurité avec facilité. Pour comprendre le fonctionnement de Django, examinons l'image : En résumé, le client envoie son requête avec une URL. Cette URL est adressée à une vue qui doit traiter cette requête de manière appropriée en récupérant les informations du modèle alimenté par la base de données. Une fois que le traitement est achevé, la réponse est renvoyée au client par l'intermédiaire d'un modèle. Django rend ce processus complexe moins problématique. Découvrir Django-iris Django utilise des backends personnalisables pour gérer n'importe quelle base de données dont le développeur pourrait avoir besoin. MySQL, Postgre, Oracle et d'autres ont déjà été implémentés et sont faciles à trouver sur GitHub. Django-iris est un backend permettant d'adapter le framework Django aux bases de données IRIS, de sorte que toutes les fonctionnalités des plates-formes d'InterSystems peuvent être utilisées en parallèle avec les outils Django. Pratiquement, si nous reprenons l'illustration précédente, la BASE DE DONNÉES serait IRIS au lieu de PostgreSQL. Le code source et des informations sur l'application et ses développeurs peuvent être trouvés sur django-iris par caretdev. Pour commencer J'ai appris à Innovatium que lorsqu'il s'agit d'un nouveau langage de code ou d'un nouvel outil, la meilleure façon de commencer est de découvrir comment réaliser un système CRUD (abbréviation anglaise de "Create, Read, Update, and Delete", c'est-à-dire créer, lire, mettre à jour et supprimer) simple. Après cela, il sera plus facile de comprendre comment cela fonctionne et d'évoluer vers des tâches plus complexes et plus spécifiques que vous pourriez être amené à accomplir. Compte tenu de ce qui précède, je vais d'abord vous montrer comment construire le CRUD le plus simple. Après avoir clarifié les choses, et lorsque vous commencerez à vous familiariser avec l'outil, je passerai à d'autres exemples où Django peut être utile. Enfin, si nous avons de la chance, je pourrai vous convaincre de l'utiliser. Vous pouvez suivre ce tutoriel sur l'historique des commits du référentiel GitHub, en commençant par django-admin startproject irisCrud. Conditions préalables Vous devez avoir Python, Django, un bon outil de développement comme VSCode, et un accès à une instance d'IRIS. Voici un guide rapide : Download VSCode (n'oubliez pas d'ajouter des extensions Python pour vous aider à développer) Download Python Tapez pip install django dans le terminal Tapez pip install django-iris dans le terminal Le coup d'envoi "Kick-off" Créez un dossier pour votre projet, placez le répertoire de votre terminal dans ce nouveau dossier et tapez django-admin startproject PROJECTNAME . Vous pouvez ignorer le point à la fin pour créer un dossier à l'intérieur du répertoire courant avec le nom PROJECTNAME. Cela devrait créer les fichiers suivants, dans lesquels vous devez choisir "CRUD" comme PROJECTNAME : Vous pouvez maintenant effectuer votre première migration et lancer votre serveur en tapant les commandes suivantes dans le terminal. À ce stade, vous devriez remarquer que quelques fichiers supplémentaires ont été créés. python manage.py migrate python manage.py runserver Si vous suivez le lien [http://127.0.0.1:8000/](http://127.0.0.1:8000/), vous verrez la page d'accueil par défaut des projets Django.La prochaine étape consistera à créer le superutilisateur avec python manage.py createsuperuser. Soyez prudent : lorsque vous saisissez le mot de passe, vous ne verrez aucun changement, mais rappelez-vous que le terminal lit déjà ce que vous tapez. Ajouter des exigences pour django-iris Créez un fichier dans le dossier racine, nommez-le requirements.txt, et copiez-y ce qui suit : # Django itself django>=4.0.0 # InterSystems IRIS driver for Django django-iris==0.2.2 Dans le terminal, tapez `pip install -r requirements.txt` et obtenez votre django-iris prêt à être utilisé dans votre projet.Ouvrez maintenant settings.py et cherchez la configuration DATABASES. Modifiez-la pour obtenir quelque chose de similaire à ce qui suit : DATABASES = { 'default': { 'ENGINE': 'django_iris', 'NAME': 'USER', 'USER': '_SYSTEM', 'PASSWORD': 'SYS', 'HOST': 'localhost', 'PORT': 1972, } } Où ENGINE doit pointer sur 'django_iris' ; NAME doit indiquer l'espace de noms souhaité ; USER et PASSWORD doivent se référer à un utilisateur disposant de privilèges adéquats ; HOST et PORT doivent mener à votre instance. Vous pouvez le vérifier dans le gestionnaire de serveur InterSystems IRIS. Création d'une application Retournez dans le terminal et tapez python manage.py startapp NAMEOFTHEAPP. Vous devriez obtenir un dossier similaire au dossier ci-dessous : Vous pouvez maintenant créer un modèle en modifiant models.py et en créant une classe avec le nom et les champs requis. Par exemple : class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Book</span><span class="hljs-params">(models.Model)</span>: title = models.CharField(max_length=class="hljs-number">50</span>) author = models.CharField(max_length="hljs-number">30) Chaque champ doit être spécifié en tant que propriété "models". Vous pouvez consulter tous les champs disponibles [ici](https://docs.djangoproject.com/en/4.2/ref/models/fields/). Vous pouvez également définir diverses options, telles que le traitement des valeurs nulles et vides ou la création d'alternatives possibles pour le champ.Pour s'assurer que le programme reconnaîtra cette nouvelle application, il faut retourner à settings.py et ajoutez le nom de l'application à la liste INSTALLED_APPS : INSTALLED_APPS: [ … ‘books’, ] Une fois l'étape précédente terminée, saisissez python manage.py makemigrations dans le terminal pour configurer les futures migrations. Vérifiez migrations/__pycache__/0001_initial.py Pour mieux comprendre ce le fonctionnement de cette commande. Ensuite, saisissez python manage.py migrate pour appliquer les changements. Passez dans le portail de gestion IRIS > Explorateur de système > SQL, choisissez le même espace de noms que celui que vous avez défini dans les paramètres DATABASES et recherchez SQLUser. Vous verrez les nouvelles tableaux créés dans IRIS : Si vous choisissez un schéma dans le tableau, vous pourrez voir son nom de classe qui peut être ouvert et modifié (à vos risques et périls) à partir du Studio ou de tout autre outil de développement. Un peu de front-end Si vous ouvrez le fichier urls.py à partir du dossier du projet (CRUD), vous remarquerez un chemin préconstruit qui mène à admin. Si vous suivez http://127.0.0.1:8000/admin, il vous sera demandé d'entrer un nom d'utilisateur et un mot de passe. Vous pouvez utiliser celui que vous avez sélectionné lors de l'exécution de la commande createuperuser et jeter un coup d'œil à ce que Django a à offrir sur cette page. Cependant, nous pourrions avoir plus de pages. Pour créer une URL personnalisée basée sur la nouvelle application, importez include de django.urls et ajoutez un nouveau chemin, pointant vers l'application : class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> path, include urlpatterns = [ path("hljs-string">'admin/', admin.site.urls), path("hljs-string">'books/', include("hljs-string">'books.urls')), ] Cette opération devrait vous rediriger vers le fichier books/urls.py (il vous faudra d'abord créer ce fichier), dans lequel nous allons ajouter un autre chemin nous permettant d'accéder à la vue. Le fichier devrait ressembler à ce qui suit : class="hljs-keyword">from</span> django.contrib <span class="hljs-keyword">import</span> admin <span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> path <span class="hljs-keyword">from</span> .views <span class="hljs-keyword">import</span> home urlpatterns = [ path(‘’, home) ] Il est temps de créer la vue vers laquelle nous sommes dirigés. Passez à books/views.py et ajoutez une définition avec la requête comme paramètre : class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">home</span><span class="hljs-params">(request)</span>: class="hljs-keyword">return</span> render(request, “index.html”) Finalement, créez le fichier index.html. Il doit être ajouté au dossier books/templates. Consultez l'image ci-dessous à titre de référence : Si vous utilisez VSCode, vous pouvez taper !+ENTER dans le fichier. Je vais vous donner un squelette de la page HTML, de sorte que vous soyez en mesure d'ajouter "Hello World" au corps de la page ou quelque chose de similaire. Voici un exemple : <html> <body> hello world! body> html> Saisissez python manage.py migrate pour appliquer les changements et rendez-vous sur http://127.0.0.1:8000/books. Il se peut que l'on vous demande de faire fonctionner le serveur avec python manage.py runserver une fois de plus. Après cela, le serveur devrait fonctionner sans problème. Ajouter des objets à partir de la page d'administration En revenant à la page d'administration, nous pouvons maintenant effectuer toutes les actions CRUD sur le modèle créé. Pour cela passez à books/admin.py, importez le modèle depuis .models, et enregistrez-le : class="hljs-keyword">from</span> .models <span class="hljs-keyword">import</span> Book admin.site.register(Book) Si vous passez à [http://127.0.0.1:8000/admin](http://127.0.0.1:8000/admin), vous devriez maintenant voir le tableau BOOKS avec des options CRUD. Je vous recommande d'ajouter quelques livres pour les étapes suivantes.![](/sites/default/files/inline/images/images/image-20230602110004-8.png) ![](/sites/default/files/inline/images/images/image-20230602110004-9.png) Vous pouvez éventuellement implémenter une fonction __str__ dans la classe du modèle pour obtenir une version plus lisible de l'objet. Par exemple, il pourrait s'agir de quelque chose d'aussi simple que ce qui suit : class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__str__</span><span class="hljs-params">(self)</span>: class="hljs-keyword">return</span> self.title+” “+self.author Si vous n'êtes pas familier avec cela, consultez n'importe quel article sur la POO (Programmation Orientée Objet) sur Python. Celui-ci en contient un exemple, surtout si vous voulez en savoir plus sur la méthode __str__. Les images ci-dessous montrent le portail d'administration avant et après sa mise en œuvre : Afficher les objets sur la page d'accueil - CRUD Read Revenez au fichier views.py, où nous avons défini la fonction home. Importez votre modèle et ajoutez-le à la fonction. Cela vous permettra d'y accéder depuis le HTML : class="hljs-keyword">from</span> .models <span class="hljs-keyword">import</span> Book <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">home</span><span class="hljs-params">(request)</span>: books = Book.objects.all() class="hljs-keyword">return</span> render(request, “index.html”, {“books”:books}) Maintenant, dans le fichier index.html vous pouvez accéder directement à tous les champs de votre modèle. Par exemple, j'ai décidé d'afficher une liste complète de tous les articles avec le code suivant : <body> … <ul> {% for book in books %} <li> {{book.id}} - {{book.title}} by {{book.author}} li> {% endfor %} ul> body> Encore une fois, si vous n'êtes pas familier avec ce type de code, vous pouvez consulter en ligne toute documentation fiable sur les bases du HTML, et cliquer ici pour en savoir plus sur les balises {%%}. Reprenons l'image du début de cet article. Elle nous montre que le client envoie une requête (c'est-à-dire qu'il ouvre le site web) et que l'URL l'adresse à une vue (la fonction accueil), qui répond avec les modèles contenant les informations de la base de données. Cette réponse est renvoyée au client sous la forme d'un modèle (HTML). Actuellement, il devrait ressembler à l'image ci-dessous : Ajouter de nouveaux objets - CRUD Create Tout d'abord, il faut décider de la manière dont nous prévoyons d'obtenir les informations pour le nouvel objet. J'ai choisi de construire un simple formulaire. Cependant, il faudra spécifier le token pour protéger notre serveur contre les interactions malveillantes. Pour en savoir plus sur la sécurité, cliquez [ici](https://docs.djangoproject.com/en/4.2/ref/csrf/). <body> … <form action=”{% url ‘save’ %}” method=”POST”> {% csrf_token %} <input type=”text” name=”title”> <input type “text” name=”author”> <button type=”submit”>savebutton> form> body> Une fois de plus, nous avons une requête du client (c'est-à-dire lorsque l'utilisateur remplit le formulaire), et nous avons besoin d'une URL l'adressant à une vue. L'action sur le formulaire peut spécifier cette URL, alors créons-la. Dans books/urls.py nous allons importer save depuis les vues et ajouter un nouveau chemin : class="hljs-keyword">from</span> .views <span class="hljs-keyword">import</span> home, save urlpatterns = [ … path(‘save/’, save, name=”save”), ] Finalement, nous créons la vue : class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">save</span><span class="hljs-params">(request)</span>: formsTitle = request.POST.get(“title”) formsAuthor = request.POST.get(“author”) Book.objects.create(title=formsTitle, author=formsAuthor) books = Book.objects.all() class="hljs-keyword">return</span> render(request, “index.html”, {“books”: books}) Si nous rechargeons la page, nous verrons les entrées et le bouton. Vous pouvez également ajouter quelques objets supplémentaires pour vérifier le fonctionnement. En outre, vous pouvez ouvrir le portail SQL sur le portail de gestion IRIS pour voir comment le formulaire envoie les informations directement au tableau IRIS. Modification d'objets existants - CRUD Update Tout comme pour les opérations que nous avons créées précédemment, nous devons construire quelque chose pour envoyer la demande. Par exemple, nous pouvons ajouter un lien à chaque objet qui renvoie vers un formulaire analogue à celui que nous venons de construire. Commençons par modifier le index.html: <body> … <ul> {% for book in books %} <li> {{book.id}} - {{book.title}} by {{book.author}} <a href=”{% url ‘edit’ book.id %}”>Edita> li> {% endfor %} ul> … body> Maintenant, dans le fichier urls.py nous devons ajouter la nouvelle URL, avec le paramètre ID : class="hljs-keyword">from</span> .views <span class="hljs-keyword">import</span> home, save, edit urlpatterns = [ … path(‘edit/’, edit, name=”edit”), ] Ensuite, nous pouvons créer la vue édition dans views.py: class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">edit</span><span class="hljs-params">(request, id)</span>: book = Book.objects.get(id=id) class="hljs-keyword">return</span> render(request, “update.html”, {“book”: book}) La vue édition va envoyer un nouveau modèle à l'utilisateur afin qu'il puisse saisir les informations nécessaires à la mise à jour de l'objet. Le fichier update.html doit être créé dans books/templates. Vous trouverez ci-dessous un exemple de modèle qui répond à nos besoins : <html> <body> <form action=”{% url ‘update’ book.id %}” method=”POST”> {% csrf_token %} <input type=”text” name=”title” value=”{{book.title}}”> <input type=“text” name=”author” value=”{{book.author}}”> <button type=”submit”>updatebutton> form> body> html> Puisque nous avons une nouvelle URL, nous devons la créer dans urls.py et spécifier sa vue dans views.py: class="hljs-comment"># le fichier books/urls.py</span> <span class="hljs-keyword">from</span> .views <span class="hljs-keyword">import</span> home, save, edit, update urlpatterns = [ … path(‘update/’, update, name=”update”), ] class="hljs-comment"># le fichier books/views.py</span> <span class="hljs-keyword">from</span> django.shortcuts <span class="hljs-keyword">import</span> redirect <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">update</span><span class="hljs-params">(request, id)</span>: book = Book.objects.get(id=id) book.title = request.POST.get(“title”) book.author = request.POST.get(“author”) book.save() class="hljs-keyword">return</span> redirect(home) À présent, notre retour va se faire vers le modèle renvoyé par la vue d'accueil. À ce stade, vous pouvez recharger le programme et tout tester, en vérifiant les informations correspondantes sur IRIS. Suppression d'un objet - CRUD Delete Nous devons faire en sorte que le client puisse interagir avec le serveur d'une manière qui permette à ce dernier de supprimer des informations. Il existe de nombreuses façons de rendre cela possible. Je vais simplement ajouter un autre lien qui contient une URL, qui adresse cette information à une vue, à la liste d'affichage. Modifiez le fichier index.html comme indiqué ci-dessous : <body> … <ul> {% for book in books %} <li> {{book.id}} - {{book.title}} by {{book.author}} <a href=”{% url ‘edit’ book.id %}”>Edita> <a href = “{% url ‘delete’ book.id %}”>Deletea> li> {% endfor %} ul> … body> J'espère que vous avez appris suffisamment de choses sur ce processus pour deviner qu'à ce stade, nous devons créer un chemin sur urls.py: class="hljs-keyword">from</span> .views <span class="hljs-keyword">import</span> home, save, edit, update, delete urlpatterns = [ … path(‘delete/’, delete, name=”delete”), ] Pour finir, créez la vue : class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">delete</span><span class="hljs-params">(request, id)</span>: book = book.objects.get(id=id) book.delete() class="hljs-keyword">return</span> redirect(home) Rechargez la page. Votre nouveau lien est prêt à être testé : Conclusion Dans cet article, nous avons créé la base de presque tous les serveurs. Il devrait vous être facile d'imaginer comment faire évoluer ce que nous avons fait aujourd'hui vers un système d'enregistrement ou de facturation, ou peut-être même un blog, en ajoutant simplement quelques fonctions de sécurité (et, bien sûr, un tout petit peu de CSS). Cependant, nous pouvons encore approfondir ce que nous avons appris ici pour créer des portails qui peuvent afficher toutes les informations que vous avez déjà sur votre instance IRIS, en faisant évoluer notre serveur vers un portail de gestion pour contrôler le flux de données, l'interaction avec l'utilisateur, l'affichage d'informations sur les journaux et même l'envoi de courriels. En outre, nous pouvons également utiliser Django pour créer un point final, tel qu'une page web facile à utiliser pour interagir avec les API que nous construisons à partir d'IRIS, y compris le traitement, la transformation et la surveillance des données que les plates-formes d'InterSystems fournissent.
Article
Guillaume Rongier · Jan 20, 2023

Création de questionnaires FHIR

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 Name Title Date Status Publisher Start Period End Period Description ### 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 4. Ouvrir l'application web : http://localhost:52773/fhirquestions/index.html Voici quelques illustrations : ![](/sites/default/files/inline/images/images/image(5086).png) ![](/sites/default/files/inline/images/images/image(5087).png)
Article
Guillaume Rongier · Sept 12, 2022

Rencontres autour de l'appel à projets EDS - PariSanté Campus

InterSystems participe aux Rencontres autour de l'appel à projet EDS, organisés par PariSanté Campus et le HDH (Health Data Hub) - Jeudi 12 septembre 2022 ![image](/sites/default/files/inline/images/image_0.jpg) Nous partageons ici nos notes publiques pour faire avancer le débat et invitons toutes les personnes mentionnées à les annoter, commenter ou amender. Grand merci à @Adeline.Icard pour ces lignes ! En vrac, lors des tables rondes : **Table ronde 1: Embarquer les professionnels de santé et usagers autour des EDS** Arthur DAUPHIN, Chargé de mission Numérique en santé chez France Assos Santé Il est nécessaire de la confiance entre les usagers (patients) et les professionnels de santé pour bien comprendre la finalité et l’intérêt général d’un partage des données de santé. David MALLET, Directeur de la recherche clinique et de l'innovation au CHU Rouen Comment obtenir le consentement du patient pour stocker leurs données dans l’EDS? Informer les patients et expliquer comment sont stockées les données, dans le respect des normes éthiques, est fondamental. Philippe BERTHEAU, Professeur en Anatomie et Cytologie Pathologiques à l'AP-HP et l'Université Paris Cité Comment engager les professionnels de santé ? L'organisation de data challenge est une possibilité pour engager les professionnels de santé et usagers. Ex, data challenge international sur une pathologie (ex col de l’utérus) : 3000 patientes ont été incluses dans ce data challenge Un EDS représente un investissement consequent pour les professionnels de santé, car il est nécessaire d'extraire les données, de les saisir, de les nettoyer …. Éric Vibert, Professeur de Chirurgie Digestive au Centre Hépato-Biliaire / Directeur de la Chaire Innovation BOPA à l'AP-HP Pour embarquer les professionnels de santé, il faut mettre à leur disposition des outils modernes et performants ! Il n’est plus possible de continuer à travailler avec des tableaux Excel ! Hélène GUIMIOT-BREAUD, Cheffe du service de la santé à la CNIL **Table ronde 2 : Financement pérenne des EDS** Claude GISSOT, Directeur de projet AMDAC à la DREES Un financement pérenne pour accompagner et faire évoluer les EDS. Pour pouvoir également recruter, former et accompagner des équipes multi origines (IT, clinique, datascientistes ….) dans la durée. Carole DUFOUIL, Directrice de recherches à l'INSERM Les EDS n’est pas que pour la recherche mais doit également servir au pilotage des établissements de santé et le développement de bonnes pratiques Florence FAVREL-FEUILLADE, Directrice Générale du CHRU de Brest, Présidente de la commission Recherche et Innovation de la conférence des DG de CHU, Personnalité Qualifiée de France 2030 Cet AAP était attendu depuis très longtemps. C’est une bonne chose. Il pose un cadre technique national. C’est un début …. Coût EDS moyen 800 000 a 1M de frais de exploitation annuel Il faudra aussi des AAP et financements pour développer les usages à partir des EDS Un financement spécifique devra suivre dans la logique pour financer la recherche clinique => financement complémentaire indispensable pour des usages pérenne des EDS   Alexandre VAINCHTOCK, Cofondateur de la société HEVA Comment valoriser la donnée ? Il est difficile d’avoir des grilles tarifaires de prestations communes …. Les établissements manquent de ressources administratives et juridiques. Ce travail reste à faire pour pérenniser le développement des usages de la data dans les EDS **Table ronde 3 : Identifier les données pertinentes à ingérer dans un EDS** les EDS sont d'excellents outils pour accroître la réactivité du système de santé. Jean-Yves BLAY, Président d'Unicancer Quelles données ? Toutes, celles du dpi, de l’imagerie …. Extraction massive Objectif : améliorer les soins et le suivi des patients notamment en oncologie Exemples : Quelles sont les performances et les nouvelles stratégies pour le suivi des patients ? Comment mieux prédire la vie générale du patient lorsqu’il est guéri ? **Enjeu clé de l’EDS : permettre la recherche sur ce qu’on ne connaît pas encore !** Nous avons besoin de bcp de jeunes médecins qui sont sensibles à ces nouveaux enjeux et thématiques IT autour de la donnée. Il y a tant à faire encore ! Raphaele RENARD PENNA, Radiologue et Professeur des Universités à la Faculté Sorbonne Université, Hôpital Pitié Salpétrière Il est important d’avoir un accès facilité aux données, de pouvoir les partager et les comparer …. Des données hétérogènes. Et créer des plateformes collaboratives pour développer des projets de recherches. Pour faire avancer un projet de recherche, nous manquons de moyen informatique pour saisir, extraire, nettoyer les données …. Bernard HAMELIN, Managing Partner chez Euresis Partners Les usages de la données de santé explosent depuis les dernières années. Florence ALLOUCHE, Presidente de MYRPHARM ADVISORS, Professeur Innovation & Entrepreneuriat, Faculté de Pharmacie de Paris – University Paris Cité, Membre de l’Academie Nationale de Pharmacie **Il faut déverrouiller les idéologies pour avancer ! ** Développer les partenariats entre organismes publics et industriels. Il est indispensable de faciliter l’accès aux données pour permettre aux start-ups d’innover et se développer en France, en Europe mais aussi à l’international !!!! **Let’s go please !** Jean-François THEBAUT, Vice-Président de la Fédération Française des Diabétiques Ne perdons pas de vue que la finalité des EDS est la qualité des soins aux patients. Ils doivent permettre d'accroître la sécurité des patients, la surveillance des signaux faibles, la prise en compte des inégalités au sein de la population, la prise en compte de la multiplicité des maladies chronique et des patients qui ne passent pas dans les hôpitaux … **L' EDS doit avoir un impact sur la vie réel du patient !** Conclusion Romain BONENFANT, Chef du service de l’Industrie à la DGE Un cadre se structure en France et en Europe pour faciliter le développement du numérique en santé. Il doit encourager la collaboration entre prive public, prestataires de soins, chercheurs, industriels/start-ups. Sandrine BILLET, Sous directrice Pilotage de la performance des acteurs de l’offre de soins à la DGOS **« Je félicite l’enthousiasme des différents acteurs face à ce 1er AAP. Les enjeux sont forts pour la suite, en matière de financement pérenne, développement des usages et compétitivité de la France sur la scène internationale. Je vous remercie tous pour votre engagement »** [Pour en savoir plus.](https://www.health-data-hub.fr/rencontres-aap-eds) ![image](/sites/default/files/inline/images/microsoftteams-image_3_0.png) Wow, les sujets abordés sont très captivants! Je hâte de lire vos commentaires!
Article
Robert Bira · Oct 26, 2022

L'ère de la norme FHIR : pourquoi FHIR devrait vous intéresser ?

Ces dernières années, l'émergence de la norme FHIR (Fast Healthcare Interoperability Resources) a suscité un enthousiasme croissant parmi les experts en informatique médicale du monde entier. Cette tendance n'est pas juste une autre itération des normes et de la technologie. Elle a le potentiel de transformer et révolutionner l'ensemble du secteur. Il est donc nécessaire de la rendre accessible au plus grand nombre, pour qu’elle ne se cantonne pas au domaine technique, mais soit adoptée par les décideurs et leaders d'opinion. Si SantExpo 2022 a déjà dévoilé de grands progrès à cet égard, nous devons aller encore plus loin. FHIR face au défi d’interopérabilité du système de santé actuel Beaucoup d'entre nous se sont habitués à certaines pratiques en matière de données de santé et d’interopérabilité, en raison d’années de travail avec les précédentes générations de normes et de technologies - notamment HL7 V2. Mais, que ce soit au niveau des établissements de soins ou des éditeurs de logiciels santé, il est communément admis que les intégrations entre différents systèmes d’information génèrent un bon nombre de défis. Ralentissement des déploiements dans la mesure où les éditeurs (parfois concurrents) sont souvent obligés d’échanger des informations et collaborer pour concevoir, implémenter et tester ces intégrations. Dépendance vis à vis des éditeurs, qui peut conduire à des « guerres de territoire » dans lesquelles les utilisateurs finaux n’ont que peu d’influence et qui alimente l’activité lucrative des connecteurs. Création d’une architecture n-to-n complexe à maintenir et généralement peu efficiente relative au volume de données qui transitent. Au-delà des interfaces standards (eg identité/mouvement), certaines intégrations sont très customisées et peuvent entrainer des risques, notamment pour la sécurité des patients voire des ruptures de prise en charge, et de ce fait ne dépassent souvent pas l’état d’ébauche. Au-delà des conséquences financières évidentes, ces points de tension impactent en premier lieu les utilisateurs finaux (médecins, infirmières, etc.) en complexifiant les processus de prise en charge des patients. De plus, les start-ups ou les nouveaux entrants sur le marché se trouvent confrontés à ces barrières difficilement franchissables. Les challenges de l'interopérabilité ont donc une forte responsabilité dans les difficultés rencontrées en matière de mise en œuvre, adoption et innovation numérique en santé. FHIR ou la promesse d’un meilleur partage des données Si FHIR a le potentiel de réduire considérablement les points sensibles actuels et d'introduire un nouveau paradigme en matière d'échange de données de santé, il y a toutefois une condition importante. Le FHIR doit être largement adopté ; et bien que ce soit le cas dans certaines parties du monde, la France en est encore aux premiers jours de son adaptation. En combinant les normes modernes et la technologie API contemporaine, le FHIR permettra un interfaçage beaucoup plus rapide, plus standard et plus transparent entre les différents systèmes d’information. On peut supposer que dans les années à venir, tous les logiciels santé adopteront la norme FHIR. Ainsi, les principaux DPI (Dossier Patient Informatisé) disposeront de leurs propres référentiels FHIR, les nouvelles applications cliniques destinées à traiter des secteurs niches ou des cas d'usage spécifiques seront natives FHIR, et la plupart des entrepôts de données de santé seront compatibles FHIR. Dans ce contexte, les intégrations seront plus rapides, transparentes et moins dépendantes des éditeurs car tous les systèmes parleront le même langage. L'efficacité en termes de flux de données sera également grandement améliorée, ce qui se traduira par de meilleures performances et une meilleure qualité des données. Les éditeurs se concentreront ainsi davantage sur le développement de logiciels innovants et les experts FHIR sur la gestion de l'interopérabilité. Un standard qui se décline jusqu’au stockage des données La conséquence la plus disruptive de l'adoption de FHIR pourrait bien être un nouveau paradigme concernant le stockage des données de santé. En effet, motivée par les besoins croissants d'analyse en temps réel, l'ambition de FHIR va au-delà de l'interopérabilité. Un entrepôt FHIR stocke les données cliniques, ce qui ouvre beaucoup de nouvelles possibilités d'applications. Il est vrai que l'utilisation de FHIR comme modèle de données est une décision en soi et dispose de ses avantages et inconvénients. Elle nécessite aussi les outils appropriés pour exploiter cet entrepôt au format documentaire. Le stockage FHIR des données cliniques répondra au besoin croissant de partage des données puisqu'il marie les concepts de base de données et d'échange de données. Nous constatons aujourd'hui la multiplication d’entrepôts de données de santé: à l'hôpital, entre hôpitaux (GHT, régions ou consortiums), dans les entreprises comme l'industrie pharmaceutique (recherche de nouveaux médicaments, données de vie réelle) ou encore les assurances, les institutions, les agences gouvernementales, et bien d'autres encore. Pour des raisons de recherche scientifique il faut pouvoir permettre un partage aisé des données de santé entre ces référentiels. Et c'est également une évidence en matière d’Intelligence Artificielle et de Machine Learning, où les chercheurs ont régulièrement besoin de nouvelles données pour améliorer leurs modèles. Une évolution qui doit s’inscrire dans la durée Une telle transition se fera sur plusieurs années et de nombreux des défis devront être relever en cours de route. Il faudra du temps et de l'énergie pour interpréter et adopter FHIR. Heureusement, il existe une documentation et des prestataires de services de plus en plus expérimentés. Globalement, nous continuerons à être confronté à des limitations qui nécessiteront un retour à la communauté HL7 FHIR pour des développements futurs, comme par exemple sur le stockage des données. La coexistence des anciens et des nouveaux formats peut rendre les choses complexes - heureusement, il existe des outils et des technologies pour nous accompagner. Bien que personne ne sache à quelle vitesse cette transition s’opérera, il est intéressant de noter que plus de 80 % des hôpitaux américains ont déjà adopté FHIR en 2019. Nous observons également un nombre croissant de start-ups ou de nouveaux logiciels qui choisissent de se lancer en utilisant nativement FHIR dans leurs applications. Alors en conclusion, nous devons continuer, éditeurs, chercheurs, utilisateurs finaux, à faire pression pour que les décideurs adoptent une position plus importante et plus audacieuse sur le sujet et fassent massivement le choix de ce langage commun porteur d’innovation pour le secteur de la santé. Allez à la publication initiale écrit par Nicolas Eiferman, Directeur Général France d’InterSystems
Article
Iryna Mykhailova · Jan 13, 2023

Stockage en colonne en 2022.3

Comme vous vous en souvenez peut-être du Global Summit 2022 ou du webinaire de lancement 2022.2, nous lançons une nouvelle fonctionnalité passionnante à inclure dans vos solutions d'analyse sur InterSystems IRIS. Le stockage en colonnes introduit une autre façon de stocker vos données de table SQL qui offre une accélération d'ordre de grandeur pour les requêtes analytiques. Publié pour la première fois en tant que fonctionnalité expérimentale en 2022.2, le dernier Developer Preview 2022.3 comprend un tas de mises à jour qui, selon nous, valaient la peine d'être publiées ici. Un récapitulatif rapide Si vous n'êtes pas familier avec le stockage en colonnes, veuillez regarder cette brève vidéo d'introduction ou la session GS2022 sur le sujet. En bref, nous encodons les données de la table en morceaux de 64k valeurs par colonne en utilisant un nouveau type de données $vector. $vector est un type de données interne uniquement (pour l'instant) qui exploite des schémas de codage adaptatifs pour permettre un stockage efficace des données clairsemées et denses. L'encodage est également optimisé pour un tas d'opérations $vector dédiées, telles que le calcul d'agrégats, de regroupements et de filtres de blocs entiers de valeurs de 64k à la fois, en tirant parti des instructions SIMD lorsque cela est possible. Au moment de la requête SQL, nous tirons parti de ces opérations en créant un plan de requête qui fonctionne également sur ces morceaux, ce qui, comme vous pouvez l'imaginer, entraîne une réduction massive de la quantité d'E/S et du nombre d'instructions ObjectScript pour exécuter votre requête, par rapport au classique traitement ligne par ligne. Bien sûr, les E/S individuelles sont plus grandes et les opérations $vector sont un peu plus lourdes que les homologues à valeur unique du monde orienté ligne, mais les gains sont énormes. Nous utilisons le terme plans de requête vectorisés pour les stratégies d'exécution qui traitent des données $vector, en poussant des morceaux entiers à travers une chaîne d'opérations individuelles rapides. Juste plus rapide Surtout, les choses se sont accélérées. Nous avons élargi la compréhension de l'optimiseur des index en colonnes et vous verrez désormais plus de requêtes utiliser des index en colonnes, même si certains des champs demandés ne sont pas stockés dans un index en colonnes ou une carte de données. En outre, vous verrez qu'il combine des index en colonnes et des index bitmap dans un certain nombre de cas, ce qui est idéal si vous commencez par ajouter des index en colonnes à un schéma existant. Le nouveau kit comprend également un ensemble de modifications dans la pile qui améliorent les performances, allant des optimisations à ces opérations $vector de bas niveau en passant par certaines améliorations du traitement des requêtes et un ensemble plus large de plans de requête vectorisés qui peuvent être parallélisés. Certaines méthodes de chargement des données, telles que les instructions INSERT .. SELECT, utiliseront désormais également un modèle tamponné que nous utilisions déjà pour créer des index et permettent désormais une création très performante de tables entières. JOIN vectorisés La fonctionnalité la plus intéressante que nous ayons ajoutée dans cette version est la prise en charge de JOINing de données en colonnes de manière vectorisée. En 2022.2, lorsque vous vouliez combiner les données de deux tables dans une requête, nous recourions toujours à une stratégie JOIN ligne par ligne robuste qui fonctionne aussi bien sur les données en colonnes que sur les données organisées en lignes. Désormais, lorsque les deux extrémités du JOIN sont stockées au format colonne, nous utilisons une nouvelle API du noyau pour les JOINDRE en mémoire, en conservant leur format $vector. Il s'agit d'une autre étape importante vers des plans de requête entièrement vectorisés, même pour les requêtes les plus complexes. Voici un exemple de requête qui tire parti de la nouvelle fonction, en effectuant une auto-JOINTURE de l'ensemble de données New York Taxi que nous avons utilisé dans les démonstrations précédentes : SELECT COUNT(*), MAX(r1.total_amount - r2.total_amount) FROM NYTaxi.Rides r1, NYTaxi.Rides r2 WHERE r1.DOLocationID = r2.PULocationID AND r1.tpep_dropoff_datetime = r2.tpep_pickup_datetime AND r2.DOLocationID = r1.PULocationID AND r1.passenger_count > 2 AND r2.passenger_count > 2 Cette requête recherche des paires de voyages avec plus de 2 passagers, où le deuxième voyage a commencé là où le premier s'est terminé, exactement à la même heure et où le deuxième voyage a ramené un à l'endroit où le premier a commencé. Pas une analyse super utile, mais je n'avais qu'une seule vraie table dans ce schéma et la clé composite JOIN rendait cela un peu moins trivial. Dans le plan de requête de cette instruction, vous verrez des extraits tels que Apply vector operation %VHASH (pour construire la clé composite JOIN) et Read vector-join temp-file A, qui indiquent que notre nouveau menuisier vectorisé est en train de travailler ! Cela peut sembler être une petite pépite triviale dans un plan de requête assez long, mais cela implique beaucoup d'ingénierie intelligente sur les composants internes, et il existe un certain nombre de fournisseurs de bases de données en colonnes de haut niveau qui n'autorisent tout simplement aucun de cela et mettez de sévères contraintes sur la mise en page de votre schéma, alors s'il vous plaît REJOIGNEZ-NOUS pour nous JOINDRE à cela ! :-) Lorsque le plan de requête continue à lire ce fichier temporaire, vous remarquerez peut-être qu'il y a encore du traitement ligne par ligne dans le travail post-jointure, ce qui nous amène à... Et après? Columnar Storage est toujours marqué comme "expérimental" en 2022.3, mais nous nous rapprochons de la préparation de la production et de cette vectorisation complète de bout en bout pour les requêtes multi-tables. Cela inclut le travail de post-jointure mentionné ci-dessus, une prise en charge plus large dans l'optimiseur de requête, un chargement encore plus rapide des tables en colonnes et d'autres améliorations de la jointure telles que la prise en charge de la mémoire partagée. En bref : c'est le moment idéal pour essayer tout cela pour la première fois avec l'ensemble de données New York Taxi dataset (maintenant sur IPM ou avec docker scripts) en utilisant l'édition communautaire 2022.3, il vous suffit donc d'appuyer sur "Exécuter" au moment de la sortie de 2023.1 ! Si vous êtes intéressé par des conseils plus personnalisés sur la façon d'exploiter le stockage en colonnes avec vos propres données et requêtes, veuillez me contacter ou contacter directement votre équipe de compte, et nous nous rencontrerons peut-être au Global Summit 2023 ;-).
Article
Robert Cemper · Juil 1, 2022

Travailler avec les Globales dans Embedded Python

Je m'intéresse particulièrement à l'utilisation des Globales avec Embedded Python.Alors, j'ai commencé à consulter la documentation officielle. #1 Introduction to GlobalsUne tentative de description générique de ce qu'est une Globale. Pointant ensuite vers: #2 A Closer Look at ObjectScriptMais où puis-je trouver Embedded Python ?Plus bas, se trouve: #3 Embedded Python 3.1 Embedded Python Overview3.1.1 Work with GlobalsIdéal si vous n'avez jamais vu une Globale.Sinon ce n'est qu'un exemple primitif choquant3.2 Using Embedded PythonDernier espoir: >>> Mais, absolument RIEN de visible. Une déception! Même IRIS Native API for Python est plus détaillé.Pour être clair sur ce que j'attends : SET, GET, KILL pour un node de Global Native API: Fundamental Node Operations et Navigation avec $DATA(), $ORDER(), $QUERY() Native API: Iteration with nextSubscript() and isDefined()Alors, Il me faut d' enquêter, faire du reverse engineeringet expérimenter moi-même. Voilà ce que j'ai trouvé: Tous les exemples sont présentés dans Python Shell comme fourni de IRIS for Windows (x86-64) 2022.1 (Build 209U)faisant usage intensif de la fonction implicite print(). La Globale Quoi que vous envisagiez de faire, vous devez commencer par la classe iris.gref pour créer un objet de référence pour la Globale.Le nom de la Globale est transmis directement (%String) ou par une variable similaire à l'indirection en COS/ISOS.Le caret initial (^) n'est pas nécessaire, parce qu' il est clair que nous ne traitons que des Globales ! >>> globalname='rcc' >>> nglob=iris.gref(globalname) >>> glob=iris.gref('rcc') >>> cglob=iris.gref('^rcc') Ce sont 3 references de la même Globale. C'est seulement une référence, il n'y aucune indication sur l'existance de la globale. Interactive doc: print(glob.__doc__)InterSystems IRIS global reference object.Use the iris.gref() method to obtain a reference to a global SUBSCRIPTS Tous Subscripts des Globales sont passés par Py_list [sub1,sub2]. Pas de grande difference par rapport à COS/ISOSSeul la racine, sans aucun Subscript a besoin d'un traitement spécial.Vous devez utiliser cette construction spéciale [None] pour indiquer que vous faites référence à la racine. SET Pour définir une Globale, nous pouvons le faire "directement" comme nous le ferions dans COS/ISOS. >>> glob[1,1]=11 ou utiliser la méthode gref.set() >>> glob.set([1,3],13) Interactive doc: print(glob.set.__doc__)Given the keys of a global, sets the value stored at that key of the global. Example: g.set([i,j], 10) sets the value of the node at key i,j of global g to 10 Pour accéder au contenu d'un noeud, nous pouvons le faire "directement" comme nous le ferions dans COS/ISOS. >>> glob[1,3] 13 ou utiliser la méthode gref.get() >>> glob.get([1,1]) 11 Interactive doc: print(glob.get.__doc__)Given the keys of a global, returns the value stored at that node of the global.Example: x = g.get([i,j]) sets x to the value stored at key i,j of global g. Attention: Ce n' est pas $GET() comme vous le connaissez en COS/ISOS >>> glob.get([1,99]) Traceback (most recent call last): File "<input>", line 1, in <module> KeyError: 'Global Undefined' >>> Mais en l'utilisant "directement", il agit comme $GET() en COS/ISOS >>> x=glob[1,99] >>> print(x) None >>> Ce None signale ce que SQL exprime comme NULL.Il réapparaîtra plus tard. KILL Il -y-a seulement la méthod gref.kill() pour arriver au résultat escompté. >>> glob.kill([1,3]) >>> y=glob[1,3] >>> print(y) None >>> Interactive doc: print(glob.kill.__doc__)Given the keys of a global, kills that node of the global and its subtree.Example: g.kill([i,j]) kills the node stored at key i,j of global g and any descendants. $DATA() La méthode est gref.data()Interactive doc: print(glob.data.__doc__)Given the keys of a global, returns the state of that.Example: x = g.data([i,j]) sets x to 0,1,10,11 0-if undefined, 1-defined, 10-undefined but has descendants, 11-has value and descendants Cela fonctionne comme prévu. >>> glob.data() 10 >>> glob.data([None]) 10 >>> glob[None]=9 >>> glob.data([None]) 11 >>> glob.data([1,1]) 1 >>> glob.data([1,3]) 0 >>> $ORDER() Pour cet exemple, j'ai ajouté quelques noeuds à la globale ^rcc: >zw ^rcc ^rcc=9 ^rcc(1,1)=11 ^rcc(1,2)=12 ^rcc(2,3,4)=234 ^rcc(2,3,5)=235 ^rcc(2,4,4)=244 ^rcc(7)=7 La méthode est gref.order()Interactive doc: print(glob.order.__doc__)Given the keys of a global, returns the next key of the global.Example: j = g.order([i,j]) sets j to the next second-level key of global g.Donc, nous voyons: >>> print(glob.order([])) 1 >>> print(glob.order([1])) 2 >>> print(glob.order([2])) 7 >>> print(glob.order([7])) None >>> print(glob.order([1,''])) 1 >>> print(glob.order([1,1])) 2 >>> print(glob.order([2,3,])) 4 >>> print(glob.order([2,3,""])) 4 >>> print(glob.order([2,3,4])) 5 >>> print(glob.order([2,4,4])) None >>> Ici, une référence Subscript manquante un %String vide sont équivalents. $QUERY() La méthode associée est gref.query()Interactive doc: print(glob.query.__doc__)Traverses a global starting at the specified key, returning each key and value as a tuple.Example: for (key, value) in g.query([i,j]) traverses g from key i,j, returning each key and value in turn Le comportement de cette méthode est différent de COS/ISOS ça retourne TOUS les noeuds apres le noeud de référence ça comprend les valeurs stockées ça retourne aussi les noeuds virtuelles SANS valeurs et c'est indiqué par None. Notre exemple ressemble à ceci (enveloppé pour plus de lisibilité): >>> print(list(glob.query())) [(['1'], None), (['1', '1'], 11), (['1', '2'], 12), (['2'], None), (['2', '3'], None), (['2', '3', '4'], 234), (['2', '3', '5'], 235), (['2', '4'], None), (['2', '4', '4'], 244), (['7'], 7)] >>> ou plus lisible : >>> for (key, value) in glob.query(): ... print(key,''.ljust(20-len(str(list(key))),'>'),value) ... ['1'] >>>>>>>>>>>>>>> None ['1', '1'] >>>>>>>>>> 11 ['1', '2'] >>>>>>>>>> 12 ['2'] >>>>>>>>>>>>>>> None ['2', '3'] >>>>>>>>>> None ['2', '3', '4'] >>>>> 234 ['2', '3', '5'] >>>>> 235 ['2', '4'] >>>>>>>>>> None ['2', '4', '4'] >>>>> 244 ['7'] >>>>>>>>>>>>>>> 7 >>> Ce n'est certainement pas ZWRITE ! Une autre option consiste à obtenir les Subscripts uniquement à l'aide de gref.keys()Interactive doc: print(glob.keys.__doc__)Traverses a global starting at the specified key, returning each key in the global.Example: for key in g.keys([i, j]) traverses g from key i,j, returning each key in turn. >>> >>> list(glob.keys()) [['1'], ['1', '1'], ['1', '2'], ['2'], ['2', '3'], ['2', '3', '4'], see it here ['2', '3', '5'], ['2', '4'], ['2', '4', '4'], ['7']] >>> Et ensuite, j'ai trouvé gref.orderiter() avec:Interactive doc: print(glob.orderiter.__doc__)Traverses a global starting at the specified key, returning the next key and value as a tuple.Example: for (key, value) in g.orderiter([i,j]) traverses g from key i,j, returning the next key and value. Cela agit comme $ORDER(), récupérant également les valeurs etfournis le sous-Node suivant avec sa valeur comme le $QUERY()voici: >>> list(glob.orderiter([])) [(['1'], None), (['1', '1'], 11)] >>> list(glob.orderiter([1])) [(['2'], None), (['2', '3'], None), (['2', '3', '4'], 234)] >>> list(glob.orderiter([2])) [(['7'], 7)] >>> Finallement, il-y-a une méthode gref.getAsBytes() Interactive doc: print(glob.getAsBytes.__doc__)Given the keys of a global, returns a string stored at that node of the global, as bytes.Example: x = g.getAsBytes([i,j]) sets x to the value stored at key i,j of global g, as bytes. Il échoue pour les valeurs numériques. Mais travailes pour %String: >>> glob[5]="robert" >>> glob.get([5]) 'robert' >>> glob.getAsBytes([5]) b'robert' Et si j'exécute en COS/ISOS: set ^rcc(9)=$lB(99,"robert") Je peux avoir le résultat suivant: >>> glob[9] '\x03\x04c\x08\x01robert' >>> glob.getAsBytes([9]) b'\x03\x04c\x08\x01robert' >>> Comment ai-je détecté toutes ces méthodes: >>> for meth in glob.__dir__(): ... meth ... '__len__' '__getitem__' '__setitem__' '__delitem__' '__new__' 'data' 'get' 'set' 'kill' 'getAsBytes' 'order' 'query' 'orderiter' 'keys' '__doc__' '__repr__' '__hash__' '__str__' '__getattribute__' '__setattr__' '__delattr__' '__lt__' '__le__' '__eq__' '__ne__' '__gt__' '__ge__' '__init__' '__reduce_ex__' '__reduce__' '__subclasshook__' '__init_subclass__' '__format__' '__sizeof__' '__dir__' '__class__' >>> J'espère que cela vous facilitera la vie si vous avez besoin d'un accès direct aux Globales dans Embedded PythonMon apprentissage personnel : Il y a surtout une documentation . . . . quelque part.Il suffit de creuser et d'explorer. Video Demo Merci d'avance pour votre vote au Concours d'articles techniques d'InterSystems : Édition Python Merci pour cet article @Robert.Cemper1003 Ce sera j'en suis sûr très utile! Merci d'avoir corrigé mon français rouillé Merci beaucoup!Alors, on doit être prudent!
Article
Irène Mykhailova · Oct 5, 2022

Comment profiter au maximum de vos publications sur "Dev Community"

Salut la communauté, Souhaitez-vous bénéficier de soutien, discuter d'une fonctionnalité intéressante, faire une annonce ou partager vos connaissances ? Dans cet article, nous allons vous expliquer comment faire tout cela. Pour faciliter la navigation dans ce "mode d'emploi", suivez simplement le contenu : * [Instructions générales](#GenGuide) * [Question](#Question) * [Article ou annonce](#Article) * [Discussion](#Discussion) ## **Instructions générales** Pour commencer, il faut cliquer sur le bouton "Nouveau message" dans le menu principal du site de la Communauté des développeurs : ![](/sites/default/files/inline/images/images/image(4666).png) Ensuite, vous verrez apparaître l'éditeur qui vous donnera le choix de créer une **Question**, une **Annonce**, un **Article**, ou une **Discussion**. Les différents types de messages ont leurs propres ensembles de champs obligatoires et facultatifs.  ![](/sites/default/files/inline/images/images/image(3998).png) Tout d'abord, parlons des champs communs à tous les types de messages, pour ensuite passer aux particularités. En principe, chaque message comporte un titre (Title\*), un corps (Body\*), un groupe (Group\*), des balises et quelques options supplémentaires permettant d'ajouter un sondage ou de joindre un ou plusieurs fichiers PDF. Tous les champs de texte marqués d'un astérisque (\*) sont obligatoires. Tout d'abord, vous devez choisir le type de message, par exemple, une **Question**, une **Annonce**, un **Article** ou une **Discussion**. Ensuite, veuillez formuler l'idée principale de votre question de la manière la plus précise et la plus concrète possible et la noter sous forme de titre : _Title_.   Ensuite, dans le champ Corps, vous écrivez ce que vous voulez partager avec les autres. Vous avez le choix entre deux options pour rédiger le texte de votre post. Vous pouvez utiliser l'éditeur WYSIWYG ou Markdown. Bien entendu, le résultat sera le même dans les deux cas. ![](/sites/default/files/inline/images/images/image(3999).png)   vs. ![](/sites/default/files/inline/images/images/image(4000).png) Une fois que vous avez fini de rédiger le texte, vous devez choisir le champ Groupe, qui correspond généralement à la technologie, au produit ou au service fourni par InterSystems.  ![](/sites/default/files/inline/images/images/image(4002).png) Après le champ Groupe, voici le champ Tags où vous pouvez ajouter des balises liées au contenu de votre message. Il y a pas mal de balises, donc il faut les choisir de manière responsable car d'autres personnes utilisent ces balises pour chercher ou trier les informations en cas de recherche. ![](/sites/default/files/inline/images/images/image(4003).png) Sous les balises, il y a un lien pour voir plus d'options. Il est possible d'y attacher un document pdf Attach a pdf document (par exemple, l'horaire de l'événement au format pdf) et de fournir le nom que vous souhaitez voir apparaître. ![](/sites/default/files/inline/images/images/image(4004).png) Une autre chose que vous pouvez faire par le biais de plus d'options, c'est le champ "Ajouter un questionnaire" Add a poll. Remplissez les champs avec une question, des réponses possibles, choisissez la durée, etc. Une fois que vous avez terminé, vous pouvez prévisualiser Preview votre message pour voir à quoi il ressemblera pour les autres, vous pouvez le sauvegarder Save pour le modifier plus tard, ou le publier Publish immédiatement. ![](/sites/default/files/inline/images/images/image(4005).png) En outre, vous pouvez programmer la publication de votre message. Il suffit de cliquer sur la flèche vers le bas, de choisir le champ "Programmer le message" Schedule post, puis de définir la date et l'heure.. ![](/sites/default/files/inline/images/images/image(4006).png)   Une fois que tout est prêt, il suffit de cliquer sur "Programmer le message" (Schedule post) et c'est tout!  ![](/sites/default/files/inline/images/images/image(4007).png) Il s'agit essentiellement de la fonctionnalité commune de création d'un message. ## **Question** D'après le nom qu'il porte, il est évident que vous devriez choisir ce type de message si vous avez besoin de l'aide de quelqu'un. Ici, dans la Communauté des développeurs, il y a beaucoup de spécialistes et quelqu'un a peut-être déjà été confronté à la même situation. N'hésitez donc pas à poser des questions ou à y répondre ;) Pour demander de l'aide, veuillez formuler l'idée principale de votre question et la noter sous forme de titre. Ensuite, veuillez sélectionner la version du produit Product version que vous utilisez, car les différentes versions ont des fonctionnalités et des classes différentes et certains conseils pourraient être utiles pour certaines versions et inutiles pour d'autres. ![](/sites/default/files/inline/images/images/image(4008).png) Pour être encore plus précis, vous pouvez fournir une version complète que vous utilisez actuellement dans la zone de texte _$ZV_. Pour obtenir votre version complète, vous pouvez ouvrir Terminal et exécuter la commande : write $ZV ![](/sites/default/files/inline/images/images/image(3991).png)    La même chose peut être faite dans l'EDI que vous utilisez ou vous pouvez le voir dans le portail de gestion : ![](/sites/default/files/inline/images/images/image(3992).png) Les autres champs sont les mêmes que ceux décrits précédemment. ## **Article ou annonce** Pour partager vos connaissances ou faire une annonce, vous devez choisir un type de message **Article** ou **Annonce** respectivement. Ces types de messages, en plus des champs communs, ont également des champs supplémentaires. Il s'agit du Message précédent, le Message suivant, et du _Lien de demande d'échange ouvert_. ![](/sites/default/files/inline/images/images/image(4011).png) Donc, en résumé, si cet article/cette annonce (ou une branche d'une discussion) est lié(e) à un autre, vous pouvez ajouter le lien dans le champ Article précédent (Previous post), et, par conséquent, les autres membres de la communauté verront le bloc Articles connexes suivant à la fin de l'article ![](/sites/default/files/inline/images/images/image(4010).png) Vous n'avez pas besoin d'ouvrir le message précédent et d'y ajouter le lien vers un message suivant, cette opération est effectuée automatiquement. Si votre message est lié à un projet sur Open Exchange, vous pouvez ajouter le lien vers ce projet dans un champ correspondant. ## **Discussion** Pour entamer une conversation sur une des fonctionnalités ou partager votre expérience de l'utilisation de la technologie et demander un retour d'information, vous pouvez lancer une **Discussion**. Ce type de message comporte tous les champs communs ainsi que les liens vers le Message précédent et Message suivant. Et voilà ! C'est ce que vous devez savoir pour commencer un nouveau fil de discussion sur la Communauté. Bonne chance ! Et nous avons hâte de connaître vos impressions ;)
Article
Lorenzo Scalese · Juil 15, 2022

ECP Avec Docker

Salut la communauté, Ceci est le troisième article de la série traitant sur l’initialisation d’instances IRIS avec Docker. Cette fois, on s’intéresse à *E*nterprise *C*ache *P*rotocol (ECP). De manière très simplifiée, ECP permet de configurer certaines instances IRIS avec le rôle de serveur d’applications et d’autres avec le rôle de serveur de données. Vous trouverez toutes les informations techniques détaillées dans la [documentation officielle](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GSCALE_ecp). Le but de cet article est de décrire comment nous pouvons scripter l'initialisation d’un serveur de données, d’un ou plusieurs serveurs d’applications et d’établir une connexion cryptée entre ces nœuds avec Docker. Pour cela, nous utilisons certains outils déjà rencontrés dans les articles précédents traitant de la mise en miroir la webgateway tel que OpenSSL, envsubst et Config-API. ## Prérequis ECP n’est pas disponible dans la version IRIS Community. Il est donc nécessaire d’avoir un accès au World Response Center afin de pouvoir télécharger une licence “container” ainsi que pour se connecter au registry containers.intersystems.com. ## Préparation du système Le système doit partager certains fichiers locaux avec les containers. Il est donc nécessaire de créer certains utilisateurs et groupes afin d’éviter l’erreur “access denied”. ```bash sudo useradd --uid 51773 --user-group irisowner sudo useradd --uid 52773 --user-group irisuser sudo groupmod --gid 51773 irisowner sudo groupmod --gid 52773 irisuser ```bash Si vous n’avez pas encore votre licence “iris.key”, téléchargez-là sur le WRC ensuite mettez-là dans votre répertoire home. ## Récupérer le repository d’exemple Tous les fichiers dont vous avez besoin sont disponible sur un [repository public](https://github.com/lscalese/ecp-with-docker), commencez par le cloner: ```bash git clone https://github.com/lscalese/ecp-with-docker.git cd ecp-with-docker ``` ## Certificats SSL Afin de crypter les communications entre les serveurs d’applications et le serveur de données, nous avons besoin de certificats SSL. Le script “gen-certificates.sh” prêt à l’emploi est disponible, toutefois, n’hésitez pas à le modifier pour que les paramètres des certificats soient cohérents avec votre localisation, compagnie, etc… Exécutez: ```bash sh ./gen-certificates.sh ``` Les certificats générés sont dans le répertoire “./certificates”. | Fichier | Container | Description | |--- |--- |--- | | ./certificates/CA_Server.cer | Serveur d’applications et serveur de données | Authority server certificate| | ./certificates/app_server.cer | Serveur d’applications | Certificat pour l’instance IRIS | | ./certificates/app_server.key | Serveur d’applications| Clé privée associée | | ./certificates/data_server.cer | Serveur de données | Certificat pour l’instance IRIS | | ./certificates/data_server.key | Serveur de données | Clé privée associée | ## Construire l’image Avant toute chose, loggez-vous au docker registry d’Intersystems. Lors de la construction, l’image de base sera téléchargée sur le registry: docker login -u="YourWRCLogin" -p="YourICRToken" containers.intersystems.com Si vous ne connaissez pas votre token, vous pouvez le récupérer sur cette page [https://containers.intersystems.com](https://containers.intersystems.com) en vous connectant avec votre compte WRC. Lors de cette construction, nous allons ajouter à l’image de base de IRIS quelques logiciels utilitaires: * gettext-base : qui permettra de substituer les variables d’environnements dans nos fichiers de configuration avec la commande “envsubst”. * iputils-arping : qui est requis dans le cas ou l’on souhaite mettre le serveur de données en miroir. * ZPM: gestionnaire de paquet ObjectScript. [Dockerfile](https://github.com/lscalese/ecp-with-docker/blob/master/Dockerfile) ```Dockerfile ARG IMAGE=containers.intersystems.com/intersystems/iris:2022.2.0.281.0 # Don't need to download the image from WRC. It will be pulled from ICR at build time. FROM $IMAGE USER root # Install iputils-arping to have an arping command. It's required to configure Virtual IP. # Download the latest ZPM version (ZPM is included only with community edition). RUN apt-get update && apt-get install iputils-arping gettext-base && \ rm -rf /var/lib/apt/lists/* USER ${ISC_PACKAGE_MGRUSER} WORKDIR /home/irisowner/demo RUN --mount=type=bind,src=.,dst=. \ iris start IRIS && \ iris session IRIS < iris.script && \ iris stop IRIS quietly ``` Il n’y a rien de particulier dans ce Dockerfile, excepté la dernière ligne. Celle-ci configure l’instance IRIS serveur de données pour accepter jusqu’à 3 serveurs d’applications. Attention, cette configuration nécessite un redémarrage de IRIS. Nous affectons la valeur de ce paramètre pendant la construction afin d’éviter de devoir scripter un redémarrage plus tard. Démarrez la construction: ```bash docker-compose build –no-cache ``` ## Les fichiers de configurations Pour la configuration des instances IRIS (serveurs d’applications et serveur de données) nous utilisons des fichiers format JSON config-api. Vous remarquerez que ceux-ci contiennent des variables d’environnements “${variable_name}”. Leurs valeurs sont définies dans les sections “environment” du fichier docker-compose.yml que nous verrons plus tard dans ce document. Ces variables seront substituées juste avant le chargement des fichiers à l’aide de l’utilitaire “envsubst”. ### Serveur de données Pour le serveur de données, nous allons: * Activer le service ECP et définir la liste des clients autorisés (serveurs d’applications). * Créer la configuration “SSL %ECPServer” nécessaire pour le cryptage des communications. * Créer une base de données “myappdata”. Celle-ci sera utilisée comme base de données distante depuis les applications serveurs. [data-server.json](https://github.com/lscalese/ecp-with-docker/blob/master/config-files/data-server.json) ```json { "Security.Services" : { "%Service_ECP" : { "Enabled" : true, "ClientSystems":"${CLIENT_SYSTEMS}", "AutheEnabled":"1024" } }, "Security.SSLConfigs": { "%ECPServer": { "CAFile": "${CA_ROOT}", "CertificateFile": "${CA_SERVER}", "Name": "%ECPServer", "PrivateKeyFile": "${CA_PRIVATE_KEY}", "Type": "1", "VerifyPeer": 3 } }, "Security.System": { "SSLECPServer":1 }, "SYS.Databases":{ "/usr/irissys/mgr/myappdata/" : {} }, "Databases":{ "myappdata" : { "Directory" : "/usr/irissys/mgr/myappdata/" } } } ``` Ce fichier de configuration est chargé au démarrage du container serveur de données par le script “init_datasrv.sh”. Tous les serveurs d’applications qui se connectent au serveur de données doivent être approuvés, ce script validera automatiquement toutes les connexions dans un délai de 100 secondes afin de limiter les actions manuelles dans le portail d’administration. Bien sûr, cela peut être amélioré pour renforcer la sécurité. ### Serveur d’applications Pour les serveurs d’applications, nous allons: * Activer le service ECP. * Créer la configuration SSL “%ECPClient” nécessaire pour le cryptage des communications. * Configurer les informations de connexion au serveur de données. * Créer la configuration de la base de données distante “myapp” * Créer un mapping de globale “demo.*” dans le namespace “USER” a destination la base de données “myappdata”. Ceci nous permettra de tester le fonctionnement de ECP plus tard. [app-server.json](https://github.com/lscalese/ecp-with-docker/blob/master/config-files/app-server.json) ```json { "Security.Services" : { "%Service_ECP" : { "Enabled" : true } }, "Security.SSLConfigs": { "%ECPClient": { "CAFile": "${CA_ROOT}", "CertificateFile": "${CA_CLIENT}", "Name": "%ECPClient", "PrivateKeyFile": "${CA_PRIVATE_KEY}", "Type": "0" } }, "ECPServers" : { "${DATASERVER_NAME}" : { "Name" : "${DATASERVER_NAME}", "Address" : "${DATASERVER_IP}", "Port" : "${DATASERVER_PORT}", "SSLConfig" : "1" } }, "Databases": { "myappdata" : { "Directory" : "/usr/irissys/mgr/myappdata/", "Name" : "${REMOTE_DB_NAME}", "Server" : "${DATASERVER_NAME}" } }, "MapGlobals":{ "USER": [{ "Name" : "demo.*", "Database" : "myappdata" }] } } ``` Le fichier de configuration est chargé au démarrage d’un container serveur d’applications par le script “init_appsrv.sh”. ### Démarrer les containers Nous pouvons maintenant démarrer les containers: * 2 serveurs d’applications. * 1 serveur de données. Pour cela, exécutez: ```bash docker-compose up –scale ecp-demo-app-server=2 ``` Voir le fichier docker-compose pour les détails: [Docker-compose](https://github.com/lscalese/ecp-with-docker/blob/master/docker-compose.yml) ```yaml # Les variables sont définies dans le fichier .env file # Pour afficher le fichier de configuration résolu, exécutez : # docker-compose config version: '3.7' services: ecp-demo-data-server: build: . image: ecp-demo container_name: ecp-demo-data-server hostname: data-server networks: app_net: environment: # Liste des serveurs d’applications autorisés. - CLIENT_SYSTEMS=ecp-with-docker_ecp-demo-app-server_1;ecp-with-docker_ecp-demo-app-server_2;ecp-with-docker_ecp-demo-app-server_3 # Chemin vers le certificat du serveur d’autorité. - CA_ROOT=/certificates/CA_Server.cer # Chemin vers le certificat du serveur de données - CA_SERVER=/certificates/data_server.cer # Chemin vers la clé privée du certificat du serveur de données - CA_PRIVATE_KEY=/certificates/data_server.key # Chemin vers le fichier de Configuration Config-API utilisé pour configurer cette instance - IRIS_CONFIGAPI_FILE=/home/irisowner/demo/data-server.json ports: - "81:52773" volumes: # Post start script pour initialiser l’instance du serveur de données. - ./init_datasrv.sh:/home/irisowner/demo/init_datasrv.sh # Montage des certificats - ./certificates/app_server.cer:/certificates/data_server.cer - ./certificates/app_server.key:/certificates/data_server.key - ./certificates/CA_Server.cer:/certificates/CA_Server.cer # Montage du fichier de configuration Config-API - ./config-files/data-server.json:/home/irisowner/demo/data-server.json # Montage du fichier de licence IRIS - ./iris.key:/dur/iris.key command: -a /home/irisowner/demo/init_appsrv.sh ecp-demo-app-server: image: ecp-demo networks: app_net: environment: # Nom d’hôte ou adresse ip du serveur de données. - DATASERVER_IP=data-server - DATASERVER_NAME=data-server - DATASERVER_PORT=1972 # Chemin vers le certificat du serveur d’autorité. - CA_ROOT=/certificates/CA_Server.cer # Chemin vers le certificat du serveur d’applications. - CA_CLIENT=/certificates/app_server.cer # Chemin vers la clé privée du certificat du serveur d’applications. - CA_PRIVATE_KEY=/certificates/app_server.key - IRIS_CONFIGAPI_FILE=/home/irisowner/demo/app-server.json ports: - 52773 volumes: # Post start script pour initialiser l’instance du serveur de données. - ./init_appsrv.sh:/home/irisowner/demo/init_appsrv.sh # Montage des certificats. - ./certificates/CA_Server.cer:/certificates/CA_Server.cer - ./certificates/app_server.cer:/certificates/app_server.cer - ./certificates/app_server.key:/certificates/app_server.key # Montage du fichier de configuration Config-API. - ./config-files/app-server.json:/home/irisowner/demo/app-server.json # Montage du fichier de licence IRIS. - ./iris.key:/dur/iris.key command: -a /home/irisowner/demo/init_appsrv.sh networks: app_net: ipam: driver: default config: # APP_NET_SUBNET variable is defined in .env file - subnet: "${APP_NET_SUBNET}" ``` ## Testons-le! ### Accès au portail d’administration du serveur de données Les containers sont démarrés, vérifions l’état à partir du serveur de données. Le port 52773 est mappé sur le port local 81, vous pouvez donc y accéder avec cette adresse [http://localhost:81/csp/sys/utilhome.csp](http://localhost:81/csp/sys/utilhome.csp). Connectez-vous avec le login\password par défaut ensuite allez dans Système -> Configuration -> Params ECP. Cliquez sur “Serveurs d’application ECP”. Si tout fonctionne bien, vous devriez voir 2 serveurs d’applications avec le statut “Normal”. La structure du nom du client est "data server name":"application server hostname":"IRIS instance name". Dans notre cas, nous n’avons pas paramétré les hostnames des serveurs d’applications, nous avons donc des hostnames auto-générés. ![application server list](https://raw.githubusercontent.com/lscalese/ecp-with-docker/master/img/app-server-list-fr.png) ### Accès au portail d’administration des serveurs d’applications Pour vous connecter au portail d’administration des serveurs d’applications, vous devez d’abord récupérer le numéro de port. Etant donné que nous avons utilisé l’option “--scale”, nous n’avons pas pu fixer les ports dans le fichier docker-compose. Il faut donc les retrouver à l’aide de la commande “docker ps”: ``` docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a1844f38939f ecp-demo "/tini -- /iris-main…" 25 minutes ago Up 25 minutes (unhealthy) 1972/tcp, 2188/tcp, 53773/tcp, 54773/tcp, 0.0.0.0:81->52773/tcp, :::81->52773/tcp ecp-demo-data-server 4fa9623be1f8 ecp-demo "/tini -- /iris-main…" 25 minutes ago Up 25 minutes (unhealthy) 1972/tcp, 2188/tcp, 53773/tcp, 54773/tcp, 0.0.0.0:49170->52773/tcp, :::49170->52773/tcp ecp-with-docker_ecp-demo-app-server_1 ecff03aa62b6 ecp-demo "/tini -- /iris-main…" 25 minutes ago Up 25 minutes (unhealthy) 1972/tcp, 2188/tcp, 53773/tcp, 54773/tcp, 0.0.0.0:49169->52773/tcp, :::49169->52773/tcp ecp-with-docker_ecp-demo-app-server_2 ``` Dans cet exemple, les ports sont: * 49170 pour le premier serveur d’applications http://localhost:49170/csp/sys/utilhome.csp * 49169 pour le second serveur d’applications http://localhost:49169/csp/sys/utilhome.csp ![](https://raw.githubusercontent.com/lscalese/ecp-with-docker/master/img/data-server-status-fr.png) ### Test de lecture\écriture sur la base de données distante Effectuons maintenant quelques tests de lecture\écriture en terminal. Ouvrons un terminal IRIS sur le premier serveur d’applications: ``` docker exec -it ecp-with-docker_ecp-demo-app-server_1 iris session iris Set ^demo.ecp=$zdt($h,3,1) _ “ écriture à partir du premier serveur d’applications” ``` Ouvrons maintenant un terminal sur le second serveur d’applications: ``` docker exec -it ecp-with-docker_ecp-demo-app-server_2 iris session iris Set ^demo.ecp(2)=$zdt($h,3,1) _ " écriture à partir du second serveur d’applications." zw ^demo.ecp ``` Vous devriez visualiser les écritures effectuées depuis les deux serveurs: ``` ^demo.ecp(1)="2022-07-05 23:05:10 écriture à partir du premier serveur d’applications." ^demo.ecp(2)="2022-07-05 23:07:44 écriture à partir du second serveur d’applications." ``` Pour terminer, ouvrons un terminal IRIS sur le serveur de données et effectuons une lecture de la globale demo.ecp : ``` docker exec -it ecp-demo-data-server iris session iris zw ^["^^/usr/irissys/mgr/myappdata/"]demo.ecp ^["^^/usr/irissys/mgr/myappdata/"]demo.ecp(1)="2022-07-05 23:05:10 écriture à partir du premier serveur d’applications." ^["^^/usr/irissys/mgr/myappdata/"]demo.ecp(2)="2022-07-05 23:07:44 écriture à partir du second serveur d’applications." ``` Voilà, c’est tout pour aujourd’hui, j’espère que cet article vous a plu. N’hésitez pas à laisser vos commentaires.
Article
Lorenzo Scalese · Mai 18, 2022

Personalisation des index SQL avec des fonctions Python

La recherche d'images comme celle de [Google](https://images.google.com/) est une fonctionnalité intéressante qui m'émerveille - comme presque tout ce qui est lié au traitement des images. Il y a quelques mois, InterSystems a publié un aperçu de [Python Embedded](https://learning.intersystems.com/course/view.php?id=1572&ssoPass=1). Comme Python dispose de nombreuses librairies pour le traitement d'images, j'ai décidé de lancer ma propre tentative pour jouer avec une sorte de recherche d'images - une version beaucoup plus modeste en fait :-) --- --- #### Un peu de théorie 🤓 Pour réaliser un système de recherche d'images, il faut d'abord sélectionner un ensemble de caractéristiques à extraire des images - ces caractéristiques sont également appelées descripteurs. L'étendue de chaque composante de ces descripteurs crée ce qu'on appelle un espace de caractéristiques, et chaque instance de cet espace est appelée un vecteur. Le nombre d de composantes nécessaires pour décrire les vecteurs, définit la dimension de l'espace des caractéristiques et des vecteurs, appelé d-dimensionnel. --- Figure 1 - Un espace caractéristique tridimensionnel et un vecteur descripteur dans cet espace. Credits: https://tinyurl.com/ddd76dln --- Une fois l'ensemble des descripteurs définis, tout ce que vous avez à faire pour rechercher une image dans la base de données est d'extraire les mêmes descripteurs d'une image à rechercher et de les comparer aux descripteurs des images de la base de données - qui ont été précédemment extraits. Dans ce travail, on a simplement utilisé la [couleur dominante de l'image](https://pypi.org/project/fast-colorthief/) comme descripteur (j'ai dit que c'était une version modeste...). Comme une représentation RVB des couleurs a été utilisée, l'espace caractéristique est un espace tridimensionnel - 3d en abrégé. Chaque vecteur dans un tel espace a 3 composantes - (r,g,b), dans la gamme [0, 255]. --- Figure 2 - L'espace tridimensionnel des caractéristiques RVB Credits: https://www.baslerweb.com/fp-1485687434/media/editorial/content_images/faqs/faq_RGB_1.gif --- En traitement du signal, il est très fréquent d'avoir des espaces à n dimensions avec des valeurs de n bien supérieures à 3. En fait, vous pouvez combiner un grand nombre de descripteurs dans un même vecteur afin d'obtenir une meilleure précision. C'est ce qu'on appelle la sélection de caractéristiques et c'est une étape très importante dans les tâches de classification/reconnaissance. Il est également courant de normaliser la plage de dimensions en [0, 1], mais pour des raisons de simplicité, ce travail utilise la plage par défaut [0, 255]. L'avantage de modéliser des caractéristiques sous forme de vecteurs est la possibilité de les comparer à travers des métriques de distance. Il existe de nombreuses distances, chacune ayant ses avantages et ses inconvénients, selon que l'on recherche la performance ou la précision. Dans ce travail, j'ai choisi des distances faciles à calculer - [manhattan et chebyshev](https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.DistanceMetric.html), qui sont essentiellement des différences absolues avec une précision raisonnable. --- Figure 3 - Représentation de certains paramètres de distance Credits: https://i0.wp.com/dataaspirant.com/wp-content/uploads/2015/04/cover_post_final.png --- #### Index fonctionnel Mais il ne s'agit que des outils nécessaires pour comparer les images en fonction de leur contenu. Si vous ne disposez pas d'un langage de requête comme SQL, vous vous retrouverez avec des méthodes et des paramètres de recherche fastidieux... De plus, en utilisant SQL, vous pouvez combiner cet index avec d'autres opérateurs bien connus, créant ainsi des requêtes complexes. C'est ici où [Functional Index](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GSQLOPT_indices) d'InterSystems est très utile. Un index fonctionnel est une classe qui implémente la classe abstraite [%Library.FunctionalIndex](https://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?&LIBRARY=%25SYS&PRIVATE=1&CLASSNAME=%25Library.FunctionalIndex) qui implémente certaines méthodes afin de gérer la tâche d'indexation dans une instruction SQL. Ces méthodes traitent essentiellement les insertions, les suppressions et les mises à jour. ``` /// Indexation fonctionnelle permettant d'optimiser les requêtes sur les données d'image Class dc.multimodel.ImageIndex.Index Extends %Library.FunctionalIndex [ System = 3 ] { /// Cardinalité de l'espace des caractéristiques /// Comme cette classe est destinée à indexer l'image dans l'espace RVB, sa cardinalité est de 3 Paramètre Cardinalité = 3 ; /// Cette méthode est invoquée lorsqu'une instance existante d'une classe est supprimée. ClassMethod DeleteIndex(pID As %CacheString, pArg... As %Binary) [ CodeMode = generator, ServerOnly = 1 ] { If (%mode '= "method") { $$$GENERATE("Set indexer = ##class(dc.multimodel.ImageIndex.Indexer).GetInstance("""_%class_""", """_%property_""")") $$$GENERATE("Set indexer.Cardinality = "_..#Cardinality) $$$GENERATE("Do indexer.Delete(pID, pArg...)") } Return $$$OK } ClassMethod Find(pSearch As %Binary) As %Library.Binary [ CodeMode = generator, ServerOnly = 1, SqlProc ] { If (%mode '= "method") { $$$GENERATE("Set result = """"") $$$GENERATE("Set result = ##class(dc.multimodel.ImageIndex.SQLFind).%New()") $$$GENERATE("Set indexer = ##class(dc.multimodel.ImageIndex.Indexer).GetInstance("""_%class_""", """_%property_""")") $$$GENERATE("Set indexer.Cardinality = "_..#Cardinality) $$$GENERATE("Set result.Indexer = indexer") $$$GENERATE("Do result.PrepareFind(pSearch)") $$$GENERATE("Return result") } Return $$$OK } /// Cette méthode est invoquée lorsqu'une nouvelle instance d'une classe est insérée dans la base de données. ClassMethod InsertIndex(pID As %CacheString, pArg... As %Binary) [ CodeMode = generator, ServerOnly = 1 ] { If (%mode '= "method") { $$$GENERATE("Set indexer = ##class(dc.multimodel.ImageIndex.Indexer).GetInstance("""_%class_""", """_%property_""")") $$$GENERATE("Set indexer.Cardinality = "_..#Cardinality) $$$GENERATE("Do indexer.Insert(pID, pArg...)") } Return $$$OK } ClassMethod PurgeIndex() [ CodeMode = generator, ServerOnly = 1 ] { If (%mode '= "method") { $$$GENERATE("Set indexer = ##class(dc.multimodel.ImageIndex.Indexer).GetInstance("""_%class_""", """_%property_""")") $$$GENERATE("Set indexer.Cardinality = "_..#Cardinality) $$$GENERATE("Set indexGbl = indexer.GetIndexLocation()") $$$GENERATE("Do indexer.Purge()") } Return $$$OK } /// Cette méthode est invoquée lorsqu'une instance existante d'une classe est mise à jour. ClassMethod UpdateIndex(pID As %CacheString, pArg... As %Binary) [ CodeMode = generator, ServerOnly = 1 ] { If (%mode '= "method") { $$$GENERATE("Set indexer = ##class(dc.multimodel.ImageIndex.Indexer).GetInstance("""_%class_""", """_%property_""")") $$$GENERATE("Set indexer.Cardinality = "_..#Cardinality) $$$GENERATE("Do indexer.Update(pID, pArg...)") } Return $$$OK } } ``` J'ai caché une partie du code d'implémentation pour des raisons de lisibilité ; vous pouvez consulter le code dans le lien OpenExchange. Une autre classe abstraite doit être implémentée, c'est [%SQL.AbstractFind](https://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=%25SQL.AbstractFind), afin de rendre disponible l'utilisation de l'opérateur %FIND pour demander au moteur SQL d'utiliser votre index personnalisé. Une explication beaucoup plus détaillée et conviviale des index fonctionnels est donnée par [@alexander-koblov](https://community.intersystems.com/post/creating-custom-index-type-cach%C3%A9) qui constitue également un excellent exemple d'index fonctionnel. Je vous recommande vivement de le lire. Si vous souhaitez aller plus loin, vous pouvez jouer avec le code source des index [%iFind](https://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?&LIBRARY=%25SYS&PRIVATE=1&PACKAGE=%25iFind.Index) et [%UIMA](https://docs.intersystems.com/irislatest/csp/documatic/%25CSP.Documatic.cls?&LIBRARY=%25SYS&CLASSNAME=%25UIMA.Index) index. Dans ce travail, j'ai configuré une classe de test de persistance simple, où le chemin des images est stocké, et un index personnalisé pour la recherche d'images est défini pour ce champ. ``` Class dc.multimodel.ImageIndex.Test Extends %Persistent { Property Name As %String; Property ImageFile As %String(MAXLEN = 1024); Index idxName On Name [ Type = bitmap ]; Index idxImageFile On (ImageFile) As dc.multimodel.ImageIndex.Index; ``` Notez que idxImageFile est un index personnalisé (dc.multimodel.ImageIndex.Index) pour le champ Image (qui stocke le chemin de l'image). #### Le tour de Python (et COS) ! Ainsi, les classes abstraites d'index fonctionnel vous donneront les points d'entrée où vous pourrez effectuer l'extraction de caractéristiques et la recherche lors de l'exécution des instructions SQL. Maintenant, c'est au tour de Python ! Vous pouvez importer et exécuter le code Python dans un contexte COS en utilisant Python intégré. Par exemple, pour extraire la couleur dominante d'une image : ``` Method GetDominantColorRGB(pFile As %String, ByRef pVector) As %Status { Set sc = $$$OK Try { Set json = ##class(%SYS.Python).Import("json") Set fastcolorthief = ##class(%SYS.Python).Import("fast_colorthief") Set imagepath = pFile Set dominantcolor = fastcolorthief."get_dominant_color"(imagepath, 1) Set vector = {}.%FromJSON(json.dumps(dominantcolor)) Set n = ..Cardinality - 1 For i = 0:1:n { Set pVector(i) = vector.%Get(i) } } Catch(e) { Set sc = e.AsStatus() } Return sc } ``` Dans cette méthode, deux librairies Python sont importées (json et fast_colorthief). La librairie fast_colorthief renvoie une représentation Python de type tableau 3-d avec les valeurs de RGB ; l'autre librairie - json, sérialise ce tableau dans un %DynamicArray. La couleur dominante est extraite pour chaque enregistrement qui est inséré ou mis à jour - une fois que l'index fonctionnel lève les appels aux méthodes InsertIndex et UpdateIndex en réponse aux insertions et mises à jour dans le tableau. Ces caractéristiques sont stockées dans l'index global du tableau : ``` Method Insert(pID As %CacheString, pArgs... As %Binary) { // pArgs(1) has the image path $$$ThrowOnError(..GetDominantColor(pArgs(1), .rgb)) Set idxGbl = ..GetIndexLocation() Set @idxGbl@("model", pID) = "" Merge @idxGbl@("model", pID, "rgb") = rgb Set @idxGbl@("last-modification") = $ZTIMESTAMP } Method Update(pID As %CacheString, pArg... As %Binary) { // pArgs(1) has the image path Set idxGbl = ..GetIndexLocation() Do ..GetDominantColor(pArg(1), .rgb) Kill @idxGbl@("model", pID) Set @idxGbl@("model", pID) = "" Merge @idxGbl@("model", pID, "rgb") = rgb Set @idxGbl@("last-modification") = $ZTIMESTAMP } ``` De la même manière, lorsque des enregistrements sont supprimés, l'index fonctionnel lance des appels aux méthodes DeleteIndex et PurgeIndex. À leur tour, les fonctionnalités doivent être supprimées de l'index global du tableau : ``` Method Delete(pID As %CacheString, pArg... As %Binary) { Set idxGbl = ..GetIndexLocation() Kill @idxGbl@("model", pID) Set @idxGbl@("last-modification") = $ZTIMESTAMP } Method Purge(pID As %CacheString, pArg... As %Binary) { Set idxGbl = ..GetIndexLocation() Kill @idxGbl Set @idxGbl@("last-modification") = $ZTIMESTAMP } ``` L'index global est récupéré par introspection dans la classe persistante : ``` Method GetIndexLocation() As %String { Set storage = ##class(%Dictionary.ClassDefinition).%OpenId(..ClassName).Storages.GetAt(1).IndexLocation Return $NAME(@storage@(..IndexName)) } ``` Lorsque les utilisateurs utilisent l'index dans les clauses WHERE, la méthode Find() est activée par l'index de la fonction. Les instructions de la requête sont transmises afin que vous puissiez les analyser et décider de ce qu'il faut faire. Dans ce travail, les paramètres sont sérialisés en JSON afin de faciliter leur analyse. Les paramètres de la requête ont la structure suivante : ```sql SELECT ImageFile FROM dc_multimodel_ImageIndex.Test WHERE ID %FIND search_index(idxImageFile, '{"color_similarity":{"image":"/data/img/test/161074693598711.jpg","first":5,"strategy":"knn"}}') ``` Dans cette instruction, vous pouvez voir l'utilisation de l'opérateur %FIND et de la fonction search_index. C'est ainsi que SQL accède à notre index personnalisé. Les paramètres de search_index définissent l'index à rechercher - idxImageFile, dans ce cas ; et la valeur à envoyer à l'index. Ici, l'index attend un objet JSON, avec une configuration d'objet définissant : (i) le chemin de l'image, (ii) une limite pour les résultats, et (iii) une stratégie de recherche. Une stratégie de recherche est simplement l'algorithme à utiliser pour effectuer la recherche. Actuellement, deux stratégies sont mises en œuvre : (i) fullscan et (ii) knn, qui correspond à k-proches voisins. La stratégie fullscan consiste simplement en une recherche exhaustive mesurant la distance entre l'image recherchée et chaque image stockée dans la base de données. ``` Method FullScanFindStrategy(ByRef pSearchVector, ByRef pResult) As %Status { Set sc = $$$OK Try { Set idxGbl = ..Indexer.GetIndexLocation() Set rankGbl = ..Indexer.GetRankLocation() Set id = $ORDER(@idxGbl@("model", "")) While (id '= "") { If ($ISVALIDNUM(id)) { Merge vector = @idxGbl@("model", id, "rgb") Set distance = ..Indexer.GetL1Distance(.pSearchVector, .vector) Set result(distance, id) = "" } Set id = $ORDER(@idxGbl@("model", id)) } Kill @rankGbl@(..ImagePath, ..FindStrategy) If (..First '= "") { Set c = 0 Set distance = $ORDER(result("")) While (distance '= "") && (c < ..First) { Merge resultTmp(distance) = result(distance) Set id = $ORDER(result(distance, "")) While (id '= "") { Set @rankGbl@(..ImagePath, ..FindStrategy, id) = distance Set id = $ORDER(result(distance, id)) } Set c = c + 1 Set distance = $ORDER(result(distance)) } Kill result Merge result = resultTmp } Merge pResult = result } Catch ex { Set sc = ex.AsStatus() } Return sc } ``` La stratégie KNN utilise une approche plus sophistiquée. Elle utilise une librairie Python pour créer une structure arborescente appelée [Ball Tree](https://en.wikipedia.org/wiki/Ball_tree). Une telle arborescence convient à une recherche efficace dans un espace à n dimensions. ``` Method KNNFindStrategy(ByRef pSearchVector, ByRef pResult) As %Status { Do ..Log(" ------ KNNFindStrategy ------ ") Set sc = $$$OK Try { Set idxGbl = ..Indexer.GetIndexLocation() Set rankGbl = ..Indexer.GetRankLocation() Set json = ##class(%SYS.Python).Import("json") Set knn = ##class(%SYS.Python).Import("knn") Set first = ..First Set k = $GET(first, 5) Set n = ..Indexer.Cardinality - 1 Set x = "" For i = 0:1:n { Set $LIST(x, * + 1) = pSearchVector(i) } Set x = "[["_$LISTTOSTRING(x, ",")_"]]" $$$ThrowOnError(..CreateOrUpdateKNNIndex()) Set ind = knn.query(x, k, idxGbl) Set ind = {}.%FromJSON(json.dumps(ind.tolist())) Set ind = ind.%Get(0) Kill result Kill @rankGbl@(..ImagePath, ..FindStrategy) Set n = k - 1 For i=0:1:n { Set id = ind.%Get(i) Set result(i, id) = "" Set @rankGbl@(..ImagePath, ..FindStrategy, id) = i } Merge pResult = result } Catch ex { Set sc = ex.AsStatus() } Return sc } ``` Le code Python pour générer une arborescence Ball Tree est présenté ci-dessous : ```python from sklearn.neighbors import BallTree import numpy as np import pickle import base64 import irisnative def get_iris(): ip = "127.0.0.1" port = 1972 namespace = "USER" username = "superuser" password = "SYS" connection = irisnative.createConnection(ip,port,namespace,username,password) dbnative = irisnative.createIris(connection) return (connection, dbnative) def release_iris(connection): connection.close() def normalize_filename(filename): filename = filename.encode('UTF-8') return base64.urlsafe_b64encode(filename).decode('UTF-8') def create_index(index_global, cardinality): connection, dbnative = get_iris() X = get_data(dbnative, index_global, cardinality) tree = BallTree(X, metric = "chebyshev") filename = f"/tmp/${normalize_filename(index_global)}.p" pickle.dump(tree, open(filename, "wb")) release_iris(connection) return tree def get_data(dbnative, index_global, cardinality): X = [] iter_ = dbnative.iterator(index_global, "model") for subscript, value in iter_.items(): id_ = subscript v = [] for i in range(cardinality): v.append( dbnative.get(index_global, "model", id_, "rgb", i) / 255 ) X.append(v) return X def query(x, k, index_global): filename = f"/tmp/${normalize_filename(index_global)}.p" tree = pickle.load(open(filename, "rb")) x = eval(x) x_ = [xi / 255 for xi in x[0]] dist, ind = tree.query([x_], k) return ind ``` Lorsqu'une image est recherchée, l'index personnalisé appelle la méthode de requête de l'objet Ball Tree en Python. Vous pouvez également noter l'utilisation de l'API native d'IRIS afin d'accéder aux valeurs RVB globales de l'index pour la construction de l'arborescence Ball Tree. Pour ordonner les images par similarité, il a été développé une procédure SQL qui traverse une globale stockant les distances précédemment calculées pour chaque image recherchée : ``` Method DiffRank(pSearch As %Binary, pId As %String) As %Float { Set search = {}.%FromJSON(pSearch) If (search.%IsDefined("color_similarity")) { Set config = search.%Get("color_similarity") Set imagePath = config.%Get("image") If (config.%IsDefined("strategy")) { Set findStrategy = config.%Get("strategy") } Set rankGbl = ..Indexer.GetRankLocation() Set rank = $GET(@rankGbl@(imagePath, findStrategy, pId)) Return rank } Return "" } ``` Vous pouvez donc modifier l'instruction SQL pour classer les résultats par similarité : ```sql SELECT ImageFile, dc_multimodel_ImageIndex.Test_idxImageFileDiffRank('{"color_similarity":{"image":"/data/img/test/161074693598711.jpg","first":5,"strategy":"knn"}}', id) AS DiffRank FROM dc_multimodel_ImageIndex.Test WHERE ID %FIND search_index(idxImageFile, '{"color_similarity":{"image":"/data/img/test/161074693598711.jpg","first":5,"strategy":"knn"}}') ORDER BY DiffRank ``` #### Conclusion L'objectif de ce travail était de montrer comment combiner la définition d'index fonctionnels dans COS avec des appels au code Python utilisant leurs étonnantes bibliothèques. De plus, en utilisant cette technique, vous pouvez accéder à des fonctionnalités complexes fournies par les librairies Python dans des instructions SQL, ce qui vous permet d'ajouter de nouvelles fonctionnalités à vos applications.
Article
Guillaume Rongier · Avr 13, 2022

Structure interne des blocs de la base de données Caché, partie 2

Ce texte est la suite de mon [article](https://community.intersystems.com/post/internal-structure-cach%C3%A9-database-blocks-part-1) où j'ai expliqué la structure d'une base de données Caché. Dans cet article, j'ai décrit les types de blocs, les connexions entre eux et leur relation avec les globales. L'article est purement théorique. J'ai fait un [projet](https://github.com/daimor/CacheBlocksExplorer) qui aide à visualiser l'arbre des blocs - et cet article explique comment il fonctionne en détail. [![](https://hsto.org/files/65a/263/1ca/65a2631ca90840e1b1153abeff540c12.png)](https://hsto.org/files/65a/263/1ca/65a2631ca90840e1b1153abeff540c12.png) Pour les besoins de la démonstration, j'ai créé une nouvelle base de données et l'ai débarrassée des globales que Caché initialise par défaut pour toutes les nouvelles bases de données. Créons une globale simple : set ^colors(1)="red" set ^colors(2)="blue" set ^colors(3)="green" ​ set ^colors(4)="yellow" ![](https://lh3.googleusercontent.com/uTlHS8bdRsFrQ1v0kfywcJp-6kQeq6uGas7NOLAuV24e7sSZx2EnFb0WQRPZRYE-0mVBB08Fzf-0lRBCLVbWmTGblVlMzQkydztrDJTNiqLhw4ja1PfHGbaTVFApmL_-Gl-fxb8o) Notez l'image illustrant les blocs du global créé. Celui-ci est simple, c'est pourquoi nous voyons sa description dans le bloc de type 9 (bloc catalogue des globales). Il est suivi par le bloc "pointeur supérieur et inférieur" (type 70), car l'arbre des globales n'est pas encore profond, et vous pouvez utiliser un pointeur vers un bloc de données qui tient encore dans un seul bloc de 8 Ko. Maintenant, écrivons tant de valeurs dans une autre globale qu'elles ne peuvent pas être placées dans un seul bloc - et nous verrons de nouveaux nœuds dans le bloc de pointeurs pointant vers de nouveaux blocs de données qui ne pouvaient pas être placés dans le premier. Écrivons 50 valeurs, de 1000 caractères chacune. Rappelez-vous que la taille du bloc dans notre base de données est de 8192 octets. set str="" for i=1:1:1000 { set str=str_"1" } for i=1:1:50 { set ^test(i)=str } ​ quit Regardez l'image suivante : ![](https://lh3.googleusercontent.com/tm6axITzAxWACgl5d2V53xHfdWw5MFCRqm92stdMlPjVSSnGvXZH2cRNNoStJQfZUxyCtPk-x9ClbT8xl-lMGJ3qht0zyfs5MPXLjyPt7lSR6_dltexWP1TgbRj2SL4Ot11CQuDY) Nous avons plusieurs nœuds au niveau du bloc de pointeurs pointant vers des blocs de données. Chaque bloc de données contient des pointeurs vers le bloc suivant ("lien correct"). Offset - pointe vers le nombre d'octets occupés dans ce bloc de données. Essayons de simuler une division de bloc. Ajoutons tellement de valeurs au bloc que la taille totale du bloc dépasse 8 Ko, ce qui provoquera la division du bloc en deux. Exemple de code set str="" for i=1:1:1000 { set str=str_"1" } set ^test(3,1)=str set ^test(3,2)=str ​ set ^test(3,3)=str Le résultat est présenté ci-dessous : ![](https://lh6.googleusercontent.com/bk_JK4MZaZxevS1Sd3egpWJD9kgfcP7lo1ueN_1t9uvcrI6KhJxtcr9J2mqGfAgKWHuCQ-t-QE20CWR0WcifhKxv8huKCrSYYhYxppViIA2Q80nFRSKZry_UAcUfln6vxYpzOzwY) Le bloc 50 est divisé et rempli de nouvelles données. Les valeurs remplacées se trouvent maintenant dans le bloc 58 et un pointeur vers ce bloc apparaît maintenant dans le bloc des pointeurs. Les autres blocs sont restés inchangés. **Un exemple avec de longues chaînes de caractères** Si nous utilisons des chaînes plus longues que 8 Ko (la taille du bloc de données), nous obtiendrons des blocs de "données longues". Nous pouvons simuler une telle situation en écrivant des chaînes de caractères de 10000 octets, par exemple. Exemple de code set str="" for i=1:1:10000 { set str=str_"1" } for i=1:1:50 { set ^test(i)=str ​ } Voyons le résultat : ![](https://lh3.googleusercontent.com/pPRJxZ_lRjQz6LXzmvPKBNU76XwiAO0xlenWDKR9fYrjmgovPz4dhtbbMx-qiBOZzeNQ49IoaXVf9f3ANsWf_xmZrhZzcFY4Af9VqoChDCXTHKLfcRqtYFGan2ak40dhQ-XPVzLY) En conséquence, la structure des blocs de l'image est restée la même, puisque nous n'avons pas ajouté de nouveaux nœuds globaux, mais seulement modifié les valeurs. Cependant, la valeur Offset (nombre d'octets occupés) a changé pour tous les blocs. Par exemple, la valeur Offset du bloc #51 est maintenant 172 au lieu de 7088. Il est clair que maintenant, lorsque la nouvelle valeur ne peut pas être insérée dans le bloc, le pointeur vers le dernier octet de données devrait être différent, mais où sont nos données ? Pour le moment, mon projet ne supporte pas la possibilité d'afficher des informations sur les "grands blocs". Utilisons l'outil ^REPAIR pour obtenir des informations sur le nouveau contenu du bloc #51. ![](https://lh5.googleusercontent.com/jnJfjd1eF2lwoM_Zk9N4TUL7XithZu92Z05o-lMrLBVmJfXtmZmHkDqIpdE6MxWBCNWVCXUdJQoyXgnXSQdjKZSJmQc5a3SR0ymXckZQxtGMxd9bcCfCX7EdcXPuFj4oYBYz15c0) Laissez-moi vous expliquer le fonctionnement de cet outil. Nous voyons un pointeur sur le bloc correct #52, et le même numéro est spécifié dans le bloc du pointeur parent dans le noeud suivant. Le collatéral de la globale est défini sur le type 5. Le nombre de noeuds avec des chaînes longues est de 7. Dans certains cas, le bloc peut contenir à la fois des valeurs de données pour certains noeuds et des chaînes longues pour d'autres, le tout dans un seul bloc. Nous voyons également quelle référence de pointeur suivante doit être attendue au début du bloc suivant. Concernant les blocs de longues chaînes de caractères : nous voyons que le mot clé "BIG" est spécifié comme valeur du global. Cela nous indique que les données sont en fait stockées dans des "gros blocs". La même ligne contient la longueur totale de la chaîne contenue, et la liste des blocs stockant cette valeur. Jetons un coup d'oeil au "bloc de chaînes longues", le bloc #73. ![](https://lh4.googleusercontent.com/J8ywd3WylH74L_xk0rQQ7z227FHQmvq9-MPI8L1fjN1ZUy8g2XQ8AdvHMErFisYBxHYzC-2y0ho9k_ZgrUayyxNMtl3r7uO7D4SiWpl_6uTTnf1VY7S9VJ0a093MegSSbznmwjnr) Malheureusement, ce bloc est montré encodé. Cependant, nous pouvons remarquer que les informations de service de l'en-tête du bloc (qui font toujours 28 octets) sont suivies de nos données. Connaître le type de données rend le décodage du contenu de l'en-tête assez facile : Position Value Description Comment 0-3 E4 1F 00 00 Offset pointant vers la fin des données Nous avons 8164 octets, plus 28 octets d'en-tête pour un total de 8192 octets, le bloc est plein. 4 18 Type de bloc Comme on s'en souvient, 24 est l'identifiant de type pour les longues chaînes de caractères. 5 05 Collate Collate 5 signifie "Caché standard" 8-11 4A 00 00 00 Lien correct Nous obtenons 74 ici, car nous nous souvenons que notre valeur est stockée dans les blocs 73 et 74 Je vous rappelle que les données du bloc 51 n'occupent que 172 octets. Cela s'est produit lorsque nous avons enregistré de grandes valeurs. Il semble donc que le bloc soit devenu presque vide avec seulement 172 octets de données utiles, et pourtant il occupe 8ko ! Il est clair que dans une telle situation, l'espace libre sera rempli de nouvelles valeurs, mais Caché nous permet également de compresser une telle globale. Pour cela, la classe [%Library.GlobalEdit](http://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=%25Library.GlobalEdit) dispose de la méthode CompactGlobal. Pour vérifier l'efficacité de cette méthode, utilisons notre exemple avec un grand volume de données - par exemple, en créant 500 noeuds. Voici ce que nous avons obtenu. kill ^test for l=1000,10000 { set str="" for i=1:1:l { set str=str_"1" } for i=1:1:500 { set ^test(i)=str } } quit Nous n'avons pas montré tous les blocs ci-dessous, mais le résultat devrait être clair. Nous avons beaucoup de blocs de données, mais avec un petit nombre de noeuds. ![](https://lh5.googleusercontent.com/98r7ANOIJcjjh1inIP3OB-j_MxqS1eIqIr0SoDKaEMP896Wd5VimDtVwCMEu35wIL8HDojM11hgL2xU_s5Iudjki3VN-yZpfZ-aMwnseuWknFL-dEWwfUzED0cYMfxQ81J6_61nr) Exécution de la méthode CompactGlobal : write ##class(%GlobalEdit).CompactGlobal("test","c:\intersystems\ensemble\mgr\test") Jetons un coup d'oeil au résultat. Le bloc des pointeurs ne compte plus que 2 nœuds, ce qui signifie que toutes nos valeurs sont allées à deux nœuds, alors que nous avions initialement 72 nœuds dans le bloc des pointeurs. Nous nous sommes donc débarrassés de 70 nœuds et avons ainsi réduit le temps d'accès aux données lors du passage par la globale, puisque cela nécessite moins d'opérations de lecture de bloc. ![](https://lh5.googleusercontent.com/Q44fAWxJ7NNuUIoL7Gibf-dnNf3GS34_CmspfoXNzy3uKV4Z24Ge2uwgfByFxK15AnUIUHtvJtsYBL4wRbEHUMnoqYACJzL2nEC0miX3uNadIRC2_sVeDtt8Vg_2qnmuqLX2WZV7) CompactGlobal accepte plusieurs paramètres, comme le nom du global, la base de données et la valeur de remplissage cible, 90% par défaut. Et maintenant nous voyons que Offset (le nombre d'octets occupés) est égal à 7360, ce qui est autour de ces 90%. Quelques paramètres de sortie de la fonction : le nombre de mégaoctets traités et le nombre de mégaoctets après compression. Auparavant, les globales étaient compressés à l'aide de l'outil ^GCOMPACT qui est maintenant considéré comme obsolète. Il convient de noter qu'une situation où les blocs ne sont que partiellement remplis est tout à fait normale. De plus, la compression des globales peut parfois être indésirable. Par exemple, si votre globale est principalement lue et rarement modifiée, la compression peut s'avérer utile. Mais si la globale change tout le temps, une certaine sparsité dans les blocs de données permet d'éviter de diviser les blocs trop souvent, et l'enregistrement de nouvelles données sera plus rapide.
Article
Guillaume Rongier · Juin 10, 2022

Stockage des données - Informations à connaître pour prendre de bonnes décisions lors du développement

Cette publication est le résultat direct d'une collaboration avec un client d'InterSystems qui est venu me consulter pour le problème suivant : SELECT COUNT(*) FROM MyCustomTable Cela prend 0,005 secondes, pour un total de 2300 lignes.  Cependant : SELECT * FROM MyCustomTable Prenait des minutes.  La raison en est subtile et suffisamment intéressante pour que j'écrive un article à ce sujet.  Cet article est long, mais si vous faites défiler la page jusqu'en bas, je vous donnerai un résumé rapide. Si vous êtes arrivé jusqu'ici et que vous pensez en avoir lu assez, faites défiler la page jusqu'à la fin pour connaître l'essentiel.  Vérifiez la phrase en **gras**. * * * Lors de la création de vos classes, il faut tenir compte de la question du stockage.  Comme beaucoup d'entre vous le savent, toutes les données dans Caché sont stockées dans des Globales.     Si vous ne le savez pas, je pense que cet article sera un peu trop long.  Je vous recommande de consulter un excellent tutoriel dans notre documentation : http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=TCOS Si vous n'avez jamais utilisé Caché/Ensemble/HealthShare, le tutoriel ci-dessus est très utile, et même si vous l'avez fait, il vaut la peine de le consulter !   Maintenant, comme toutes les données sont stockées dans des globales, il est important de comprendre comment les définitions de vos classes correspondent aux globales.  Construisons une application ensemble !  Nous allons examiner certains pièges courants et discuter de la façon dont le développement de vos classes affecte vos stratégies de stockage, avec un regard particulier sur les performances SQL.   Imaginons que nous soyons le Bureau du recensement des États-Unis et que nous voulions disposer d'une base de données pour stocker les informations concernant tous les habitants des États-Unis.  Nous construisons donc une classe de la manière suivante : Class USA.Person extends %Persistent { Property Name as %String; Property SSN as %String; Property Address as %String; Property DateOfBirth as %Date; } SSN est l'abréviation de "Social Security Number" (numéro de sécurité sociale) qui, bien qu'il n'ait pas été conçu à l'origine pour être un numéro d'identification des personnes, est leur numéro d'identification de facto.  Cependant, comme nous sommes traditionalistes, nous ne l'utiliserons pas pour l'identification.  Cela dit, nous tenons à ce que cet élément soit indexé, car c'est un excellent moyen de rechercher une personne.  Nous savons que nous devrons parfois rechercher des personnes par le nom, c'est pourquoi nous voulons également un index des noms.  Et parce que notre patron aime ses rapports basés sur des tranches d'âge, nous pensons qu'un index des dates de naissance pourrait également être utile.  Ajoutons-les donc à notre classe Class USA.Person extends %Persistent { Property Name as %String; Property SSN as %String; Property Address as %String; Property DateOfBirth as %Date; Index NameIDX On Name; Index SSNIDX On SSN [Unique]; Index DOBIDX on DateOfBirth; } Très bien.  Alors ajoutons une ligne et voyons à quoi ressemblent nos globales.  Notre instruction INSERT est la suivante : INSERT INTO USA.Person (Name,SSN,Address,DateOfBirth) VALUES   ('Baxter, Kyle','111-11-1111','1 Memorial Drive, Cambridge, MA 02142','1985-07-20') Et la globale: USER>zw ^USA.PersonD ^USA.PersonD=1 ^USA.PersonD(1)=$lb("","Baxter, Kyle","111-11-1111","1 Memorial Drive, Cambridge, MA 02142",52796) Le stockage par défaut d'une classe stocke vos données dans ^Package.ClassD.  Si le nom de la classe est trop long, il peut être haché, et vous pouvez le trouver dans la définition de stockage au bas de votre définition de classe.  Les index, à quoi ressemblent-ils ? USER>zw ^USA.PersonI ^USA.PersonI("DOBIDX",52796,1)="" ^USA.PersonI("NameIDX"," BAXTER, KYLE",1)="" ^USA.PersonI("SSNIDX"," 111-11-1111",1)="" Excellent, notre stockage est plutôt bon pour l'instant.  Donc on ajoute nos 320 millions de personnes et on peut trouver des gens assez rapidement.  Mais maintenant nous avons un problème, car nous voulons traiter le président et tous les ex-présidents avec une considération spéciale.  Nous ajoutons donc une classe spéciale pour le président : Class USA.President extends USA.Person { Property PresNumber as %Integer; Index PresNumberIDX on PresNumber; } Bien.  En raison de l'héritage, nous récupérons toutes les propriétés de USA.Person, et nous en ajoutons une pour nous permettre de savoir quel numéro de président il était.  Puisque je veux faire un peu de politique, je vais INSÉRER notre PROCHAIN président.  Voici l'instruction : INSERT INTO USA.President (Name,SSN,DateOfBirth,Address,PresNumber) VALUES ('McDonald,Ronald','221-18-7518','01-01-1963','1600 Pennsylvania Ave NW, Washington, DC 20006',45) Note : Son numéro de sécurité sociale s'écrit 'Burger'.  Désolé si c'est le vôtre. Alors c'est génial !  Regardons votre Globale du Président : USER>zw ^USA.PresidentD Pas de données !  Et c'est là que nous arrivons à l'essentiel de cet article.  Parce que nous avons décidé d'hériter de USA.Person FIRST, nous avons hérité non seulement de ses propriétés et index, mais aussi de son stockage !  Donc pour localiser le président McDonald, nous devons regarder dans ^USA.PersonD.  Et nous pouvons voir ce qui suit : ^USA.PersonD(2)=$lb("~USA.President~","McDonald,Ronald","221-18-7518","1600 Pennsylvania Ave NW, Washington, DC 20006",44560) ^USA.PersonD(2,"President")=$lb(45&) Deux choses à noter ici.  La première est que nous pouvons voir que le nœud (2) possède toutes les informations déjà stockées dans USA.Person.  Alors que le noeud (2, "President") ne contient que les informations spécifiques à la classe USA.President.   Qu'est-ce que cela signifie en pratique ?  Eh bien, si nous voulons faire une opération de type : SELECT * FROM USA.President, nous aurons BESOIN de parcourir l'ensemble du tableau des personnes.  Si nous pensons que le tableau des personnes contient 320 000 000 lignes et que le tableau des présidents en contient 45, alors nous devons faire plus de 320 000 045 références globales pour extraire 45 lignes !  En effet, si l'on regarde le plan de requête : * Lire la carte maîtresse USA.President.IDKEY, en bouclant sur ID. * Pour chaque ligne: *  Résultat de la ligne. Nous observons ce que nous attendons.  Cependant, nous avons déjà vu que cela signifie qu'il faut nécessairement regarder dans la globale ^USA.PersonD.  Donc, cela va être une référence globale de 320 000 000+ car nous devons tester CHAQUE ^USA.PersonD pour vérifier s'il y a des données dans ^USA.PersonD(i, "Président") puisque nous ne savons pas quelles personnes seront présidents.  Eh bien, c'est mauvais ! Ce n'est pas du tout ce que nous voulions !  Que pouvons-nous faire ?  Eh bien, nous avons deux options : Option 1 Ajouter un index d'éxtent.  Si nous faisons cela, nous obtenons une liste d'identifiants qui nous permet de savoir quelles personnes sont des présidents et nous pouvons utiliser cette information pour lire des nœuds spécifiques de la globale ^USA.Person.  Comme je dispose d'un stockage par défaut, je peux utiliser un index bitmap, ce qui rendra l'opération encore plus rapide.  Nous ajoutons l'index comme suit : Index Extent [Type=Bitmap, Extent]; Et quand nous regardons notre plan de requête pour SELECT * FROM USA.President nous pouvons voir : ![](http://localhost:57772/csp/sys/images/spacer.gif) * Lecture de l'extent du bitmap USA.President.Extent, en bouclant sur l'ID. * Pour chaque ligne : *  Lecture de la carte maîtresse USA.President.IDKEY, en utilisant la valeur idkey donnée. Résultat de la ligne. *   Ah, maintenant ça va être sympa et rapide.  Une référence globale pour lire l'Extent et ensuite 45 autres pour les présidents.  C'est plutôt efficace.   Les inconvénients ?  La connexion à ce tableau devient un peu plus compliquée et peut impliquer un plus grand nombre de tableaux temporaires que vous ne le souhaiteriez.   Option 2 Changement de la définition de la classe en :: Class USA.President extends (%Persistent, USA.Person) En faisant de %Persistent la première classe étendue, USA.President aura sa propre définition de stockage.  Ainsi, les présidents seront stockés de la manière suivante : USER>zw ^USA.PresidentD ^USA.PresidentD=1 ^USA.PresidentD(1)=$lb("","McDonald,Ronald","221-18-7518","1600 Pennsylvania Ave NW, Washington, DC 20006",44560,45) C'est donc une bonne chose, car choisir USA.President signifie simplement lire les 45 membres de cette globale.  C'est facile et agréable, et le design est clair. Les inconvénients ?  Eh bien maintenant, les présidents ne sont PAS dans le tableau des personnes Person.  Donc si vous voulez des informations sur les présidents ET les non-présidents, vous devez faire _SELECT ... FROM USA.Person UNION ALL SELECT ... FROM USA.President_ * * * **Si vous avez arrêté de lire au début, recommencez ici !** Lors de la création d'un héritage, nous avons deux options Option 1: L'héritage de la superclasse est le premier.  Cela permet de stocker les données dans la même globale que la superclasse.  Utile si vous voulez avoir toutes les informations ensemble, et vous pouvez atténuer les problèmes de performance dans la sous-classe en ayant un index extent. Option 2: Héritage de %Persistent first.  Cela permet de stocker les données dans une nouvelle globale.  C'est utile si vous interrogez beaucoup la sous-classe, mais si vous voulez voir les données de la super-classe et de la sous-classe, vous devez utiliser une requête UNION. Laquelle de ces solutions est la meilleure ?  Cela dépend de la façon dont vous allez utiliser votre application.  Si vous souhaitez effectuer un grand nombre de requêtes sur l'ensemble des données, vous opterez probablement pour la première approche.  En revanche, si vous ne pensez pas interroger les données dans leur ensemble, vous opterez probablement pour la seconde approche.  Les deux approches sont tout à fait acceptables, à condition de ne pas oublier l'index extent de l'option 1. Des questions ? Des commentaires ? De longues pensées contradictoires ?  Laissez-les ci-dessous !
Article
Lorenzo Scalese · Oct 3, 2022

Créons un profil FHIR à l'aide de SUSHI Partie 1

Bonjour, chers développeurs ! Dans cette série, je ne vous montrerai pas comment utiliser IRIS for Health, mais plutôt comment utiliser SUSHI, un outil de création de profils FHIR, en tant que technologie associée. Quand on a les outils adéquats, les informations du profil (spécifications, limitations, extensions, etc.) d'un projet FHIR peuvent être bien organisées et publiées. Avant de commencer, qu'est-ce que le SUSHI ? Je vais l'expliquer brièvement. ## Qu'est-ce que le SUSHI ? **FHIR** est un acronyme pour Fast Healthcare Interoperability Resources et est défini comme une **norme pour l'l'échange de dossiers médicaux électroniques** qui peut être mise en œuvre dans un court délai. REST est une technologie standard de communication web permettant d'échanger un ensemble de données (ressources) au format JSON/XML, très lisible et facile à manipuler. En simplifiant, l'idée est d'utiliser un format commun pour représenter les données médicales afin de faciliter le transfert et l'échange d'informations entre les systèmes et les établissements. Il existe plusieurs [ressources](http://hl7.org/fhir/resourcelist.html) définies dans FHIR. Par exemple, les informations sur les patients ont une définition appelée [Ressource Patient](http://hl7.org/fhir/patient.html), qui représente les informations sur les patients. Il existe de nombreux [modèles](http://hl7.org/fhir/patient-examples.html) sur le site officiel de FHIR, j'ai donc pensé que je pourrais en extraire quelques-uns. Par exemple, les données sont exprimées au format JSON de la manière suivante. Le numéro du patient (identifiant), son nom, son sexe, etc. sont représenté. ```JSON { "resourceType": "Patient", "id": "pat1", "texte": { "statut": "généré", "div": "\n \n Patient Donald DUCK @ Acme Healthcare, Inc. MR = 654321\n \n " }, "identifiant": [ { "utilisation": "commune", "type": { "codage": [ { "système": "http://terminology.hl7.org/CodeSystem/v2-0203", "code": "MR" } ] }, "système": "urn:oid:0.1.2.3.4.5.6.7", "valeur": "654321" } ], "active": true, "nom": [ { "utilisation": "officiel", "nom de famille": "Donald", "prénom": [ "Duck" ] } ], "sex": "masculin", "* snip *" } ``` ## Qu'est-ce que le profil FHIR ? Dans FHIR, il existe des formats de représentation JSON et XML et des règles concernant les informations à répertorier avec le nom de clé JSON, le type de code à utiliser et le type de structure à utiliser. C'est ce qu'on appelle le [Profile FHIR](http://hl7.org/fhir/profiling.html). Le profil est un terme utilisé de différentes manières. Au sens le plus large : Un ensemble de définitions pour les contraintes sur les ressources FHIR et les serveurs FHIR. Un artefact qui le représente. Au sens plus restreint : Au sens plus restreint, une ressource de conformité est une ressource à laquelle des contraintes spécifiques ont été appliquées. Dans ce cas, un profil existe pour chaque ressource (par exemple, Profil de Patient, Profil d'observation).. Pour plus de détails, veuillez regarder la vidéo du [Sommet virtuel japonais 2021] (https://youtu.be/B-B6ge_0nHg) sur les profils FHIR ici. (environ 20 minutes). Bien que le site officiel FHIR fournisse des spécifications par défaut pour chaque ressource, le degré de liberté dans l'utilisation de chaque ressource est considérable. Malheureusement, le degré de liberté dans l'utilisation de chaque ressource est très important. Il est difficile de réaliser des échanges de données interopérables avec les ressources telles qu'elles sont. Il est donc important de définir de nouvelles règles pour la description des ressources sur la base de l'"accord" préalable. Cet "accord" et ces "règles" sont équivalents au Guide de mise en œuvre et au Profil. Les directives de mise en œuvre sont pour la plupart rédigées en texte à l'aide de Word, Excel, HTML, etc. L'une des caractéristiques de FHIR est que le profil FHIR lui-même peut également être exprimé à l'aide de ressources FHIR. Par exemple, il est possible d'exprimer les spécifications au format JSON afin que des produits tels que IRIS for Health puissent intégrer les définitions et étendre les fonctions. D'autre part, le profil est écrit au format JSON, qui peut être traité à l'aide de la ressource StructureDefinition de FHIR. L'Association HL7 des États-Unis a publié IG Publisher, qui génère automatiquement des directives de mise en œuvre à partir de profils. Grâce à cet outil, vous pouvez générer des fichiers HTML de directives de mise en œuvre au format publié par l'Association HL7. La seconde partie de cet article vous montrera comment procéder. À titre d'exemple, il s'agit d'une ressource appelée "StructureDefinition" qui représente les conventions de notation pour les Ressources Patient suggérées pour une utilisation standard aux États-Unis, appelée US Core. ([Référence](https://www.hl7.org/fhir/us/core/StructureDefinition-us-core-patient.json)) ```json { "resourceType" : "StructureDefinition", "id" : "us-core-patient", "texte" : { "statut" : "extensions", "div" : "(snip)" }, "url" : "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient", "version" : "3.1.1", "nom" : "USCorePatientProfile", "titre" : "Profil du patient de base aux États-Unis", "statut" : "active", "expérimental" : false, "date" : "2020-06-27", "éditeur" : "Comité de pilotage HL7 US Realm", "contact" : [ { "telecom" : [ { "systéme" : "url", "valeur" : "http://www.healthit.gov" } ] } ], "(snip)" ``` Il existe également de nombreuses autres ressources qui décrivent le profil FHIR, comme l' [ImplementationGuide] (http://hl7.org/fhir/implementationguide.html) et le [CapabilityStatement] (http://hl7.org/fhir/capabilitystatement.html) qui résume la série de fonctions du serveur FHIR ## Qu'est-ce que FHIR Shorthand? Pour créer un profil FHIR, il suffit de compléter la structure JSON présentée ci-dessus ! Cependant, il serait difficile et fastidieux de le faire manuellement. Je ferais probablement une erreur. Il existe des applications et des outils pour faciliter ce processus, notamment plusieurs produits commerciaux et des options à code source ouvert. Voir [cette] (https://confluence.hl7.org/pages/viewpage.action?pageId=35718864#ProfileTooling-Editing&AuthoringProfiles) page. Un exemple est célèbre Forge de Firely aux Pays-Bas, bien que, plus récemment, une ** langue spécifique au domaine pour définir les artefacts FHIR** appelée **FHIR Shorthand** ([lien](https://build.fhir.org/ig/HL7/fhir-shorthand/index.html)) soit également devenue largement utilisée. FHIR Shorthand est une langue qui vous permet de créer un profil FHIR tout en créant un fichier de définition = fichier FSH(fish) (exemple) Un exemple de fichier FSH est présenté ci-dessous. Le paramètre [quote](https://build.fhir.org/ig/HL7/fhir-shorthand/overview.html#fsh-line-by-line-walkthrough) contient le nom de ce profil (Profil : Cance. ``` Alias: LNC = http://loinc.org Alias: SCT = http://snomed.info/sct Profil: CancerDiseaseStatus Parent: Observation Id: mcode-cancer-disease-status Titre: "Statut de la maladie cancéreuse" Description: ""Le jugement qualitatif d'un clinicien sur la tendance actuelle du cancer, par exemple, s'il est stable, s'il s'aggrave (progression) ou s'il s'améliore (répond)." * ^statut = #ébauche * l'extension contient un EvidenceType nommé evidenceType 0..* * extension[evidenceType].valueCodeableConcept de CancerDiseaseStatusEvidenceTypeVS (requis) * statut et code et sujet et effectif[x] et valeurCodeableConcept MS * bodySite 0..0 * modèle 0..0 * device 0..0 * referenceRange 0..0 * hasMember 0..0 * composant 0..0 * interprétation 0..1 * sujet 1..1 * basé sur la seule référence (ServiceRequest ou MedicationRequest) (snip) ``` ## Qu'est-ce que SUSHI? Après avoir expliqué le profil FHIR/FHIR/FHIR Shorthand, il est temps d'expliquer SUSHI. SUSHI (acronyme de "SUSHI Unshortens SHorthand Inputs") (4) est une implémentation de référence d'un compilateur FSH qui traduit FSH en artefacts FHIR tels que des profils, des extensions et des ensembles de valeurs. SUSHI est installé sur votre propre ordinateur et s'exécute localement à partir de la ligne de commande ([Reference](https://build.fhir.org/ig/HL7/fhir-shorthand/overview.html#sushi)) En bref, **lorsque SUSHI traite le fichier FSH(fish) décrivant le FHIR Shorthand mentionné précédemment, des fichiers tels que StructureDefinition seront générés** Voici une illustration, à la fois évidente et quelque peu obscure, montrant comment cela fonctionne ! ([Citation](https://build.fhir.org/ig/HL7/fhir-shorthand/overview.html#fsh-in-practice)) ![image](https://build.fhir.org/ig/HL7/fhir-shorthand/Workflow.png) (Dans cette image, il est décrit le processus (c'est-à-dire la compilation) de traitement du poisson pour faire du sushi, mais le compilateur SUSHI fait la compilation, et le produit fini est constitué d'artefacts FHIR tels que des profils, ce qui est un peu différent.) J'espère qu'après cette introduction, vous comprenez mieux en quoi consiste le SUSHI. ## Allons à l'école FSH Au lieu d'expliquer ici comment installer et utiliser fondamentalement SUSHI, je vous recommande de visiter le site officiel où vous trouverez une introduction très précise. Le site s'appelle [École FSH] (https://fshschool.org/). Les ressources (fichiers JSON) telles que StructureDefinition sont générées en utilisant SUSHI, et en utilisant l'outil "IG Publisher" également présenté sur ce site, vous pouvez générer la source HTML qui les combine. Vous pouvez également utiliser l'outil "IG Publisher", également présenté sur ce site, pour générer la source HTML pour l'ensemble d'entre eux. ![image](https://community.intersystems.com/sites/default/files/inline/images/sushi_part1_ss1.jpg) Tout d'abord, nous vous recommandons de suivre le contenu de ce [Tutoriel SUSHI] (https://fshschool.org/docs/tutorials/basic/) pour vérifier les fonctions de base. Si vous avez des problèmes, vous pouvez vous référer au répertoire FishExampleComplete, qui est la version complète du FSH Tank( !) inclus dans le dossier téléchargeable. Je l'ai testé dans un environnement Windows, et comme je n'avais pas installé Node.js, j'ai utilisé les informations du [site](https://blog.katsubemakito.net/nodejs/install-windows10) pour m'aider ! De plus, comme le stipule le tutoriel ci-dessous, vous aurez besoin d'un outil appelé Jekyll pour produire des fichiers HTML à l'aide d'IG Publisher. >Avertissement > >Avant de passer à la commande suivante : Si vous n'avez jamais exécuté l'IG Publisher, il vous faudra peut-être d'abord installer Jekyll. Consultez la section Installation d'IG Publisher pour plus de détails. Vous pouvez vous procurer le kit Jekyll sur le [site](http://jekyll-windows.juthilo.com/1-ruby-and-devkit/). ## Exemple d'exécution de SUSHI Voici les résultats de l'exécution de la commande SUSHI en utilisant la version complète du tutoriel dans mon environnement. Pour plus d'informations sur la commande, etc., veuillez consulter ce site ([Running SUSHI](https://fshschool.org/docs/sushi/running/)). ``` >sushi . info Running SUSHI v1.2.0 (implémentation de la spécification FHIR Shorthand v1.1.0) info Arguments: info C:\Users\kaminaka\Documents\Work\FHIR\SUSHI\fsh-tutorial-master\FishExampleComplete info Aucune route de sortie spécifiée. Sortie vers . info Utilisation du fichier de configuration : C:\Users\kaminaka\Documents\Work\FHIR\SUSHI\fsh-tutorial-master\FishExampleComplete\sushi-config.yaml info Importation du texte FSH... info Prétraitement de 2 documents avec 3 alias. info Importation de 4 définitions et 1 instance. info Vérification du cache local pour hl7.fhir.r4.core#4.0.1... info Trouver hl7.fhir.r4.core#4.0.1 dans le cache local info Paquet téléchargé hl7.fhir.r4.core#4.0.1 (node:26584) Avertissement : Accès à une propriété inexistante 'INVALID_ALT_NUMBER' des exportations du module à l'intérieur d'une dépendance circulaire (Utilisez `node --trace-warnings ...` pour montrer où l'avertissement a été créé) (node:26584) Avertissement : Accès à une propriété inexistante 'INVALID_ALT_NUMBER' des exportations du module à l'intérieur d'une dépendance circulaire info Conversion des ressources FSH en ressources FHIR... info Conversion de 3 StructureDefinitions FHIR. info Conversion de 1 ValueSets FHIR. info Conversion de 1 instance FHIR info Exportation des ressources FHIR en JSON... info Exportation de 5 ressources FHIR en JSON. info Assemblage des sources du Guide de mise en œuvre... info Génération d' ImplementationGuide-fish.json info Sources du guide de mise en œuvre assemblées ; prêtes pour l'IG Editeur. ╔════════════════════════ RÉSULTATS DE SUSHI ══════════════════════════╗ ║ ╭──────────┬────────────┬───────────┬─────────────┬───────────╮ ║ ║ │ Profils │ Extensions │ ValueSets │ CodeSystems │ Instances │ ║ ║ ├──────────┼────────────┼───────────┼─────────────┼───────────┤ ║ ║ │ 2 │ 1 │ 1 │ 0 │ 1 │ ║ ║ ╰──────────┴────────────┴───────────┴─────────────┴───────────╯ ║ ║ ║ ╠═════════════════════════════════════════════════════════════════╣ ║ Il n'y a pas mieux que ça ! 0 Erreurs 0 Avertissements ║ ╚═════════════════════════════════════════════════════════════════╝ ``` Une fois exécuté, il compilera le fichier FSH et affichera le nombre de profils et d'extensions générés au cours du processus. Si tout va bien, seul "info" sera imprimé, sinon " Avertissement et erreur " sera affiché. Les messages d'erreur sont relativement simples et permettent de déterminer facilement l'origine du problème. Après son exécution, vous pouvez confirmer que le fichier JSON de StructureDefinition a été généré avec succès dans le dossier fsh-generated du dossier du projet. Ensuite, essayons d'obtenir l'outil IG Publisher à l'aide de la commande "_updatePublisher", de lancer IG Publisher à l'aide de la commande "_genonce", et aussi de générer un groupe de fichiers HTML. Comme le journal d'exécution est long, je vais le sauter. Après l'exécution, vérifiez le dossier de sortie dans le même dossier de projet, et vous verrez que de nombreux fichiers ont été générés. Et si vous ouvrez le fichier index.html, vous verrez que la page suivante a été développée. ![image](https://community.intersystems.com/sites/default/files/inline/images/sushi_part1_ss2.jpg) Les pages de description des ressources que vous avez l'habitude de voir sur le site officiel de FHIR sont également générées automatiquement. ![image](https://community.intersystems.com/sites/default/files/inline/images/sushi_part1_ss3.jpg) ## Rédigeons un guide de mise en œuvre Bien que je ne travaille avec cet ensemble d'outils que depuis peu, je voudrais vous montrer comment rédiger un guide de mise en œuvre pour commencer. Pour plus de détails sur la façon de les utiliser, veuillez vous reporter aux informations figurant sur le site Web de l'école FSH. Les [images FHIR DevDays et autres informations] (https://fshschool.org/downloads/) peuvent également vous être très utiles. Pour commencer, utilisez la commande sushi --init pour créer un modèle pour la structure de votre projet. ``` C:\Users\kaminaka\Documents\Work\FHIR\SUSHI\TestProject>sushi --init ╭───────────────────────────────────────────────────────────╮ │ Cet outil interactif utilisera vos réponses pour créer │ │ un projet SUSHI fonctionnel configuré avec les │ │ informations de base de votre projet. │ ╰───────────────────────────────────────────────────────────╯ Nom (par défaut : ExampleIG) : MonPremierProjetSUSHIP Id (Par default: fhir.example): myfirstsushi Canoniquel (Par default: http://example.org): http://example.org/myfirstsushi Statut (Par default: ébauche): Version (Par default: 0.1.0): Initialiser le projet SUSHI en C :\Users\kaminaka\Documents\Work\FHIR\SUSHI\TestProject\MyFirstSUSHIProject? [y/n]: y Téléchargement des scripts de l'éditeur à partir de https://github.com/HL7/ig-publisher-scripts (node:13972) Avertissement : Accès à une propriété inexistante 'INVALID_ALT_NUMBER' des exportations du module à l'intérieur d'une dépendance circulaire. (Utilisez `node --trace-warnings ...` pour montrer où l'avertissement a été créé) (node:13972) Avertissement : Accès à une propriété inexistante 'INVALID_ALT_NUMBER' des exportations du module à l'intérieur d'une dépendance circulaire. ╭───────────────────────────────────────────────────────────╮ │ Le projet a été initialisé à : ./MyFirstSUSHIProject │ ├───────────────────────────────────────────────────────────┤ │ Maintenant, essayez ceci : │ │ │ │ > cd MyFirstSUSHIProject │ │ > sushi . │ │ │ │ Pour des conseils sur la structure et la configuration │ │ du projet, passez à la documentation SUSHI : │ │ https://fshschool.org/docs/sushi │ ╰───────────────────────────────────────────────────────────╯ ``` Après l'avoir exécuté, il générera le minimum de fichiers de configuration et de FSH requis. Maintenant, apportons quelques modifications. Ensuite, apportons quelques modifications. Avant cela, je voudrais présenter l'éditeur ; je recommande d'utiliser Visual Studio Code, car il y a une [extension] (https://marketplace.visualstudio.com/items?itemName=kmahalingam.vscode-language-fsh) disponible pour modifier les fichiers fsh. Puisque c'est ce que je vais faire, j'aimerais entrer quelques informations japonaises et voir comment elles sont reflétées. Tout d'abord, modifiez sushi-config.yaml. Le titre du guide d'implémentation est ajouté, l'écran de menu est changé en japonais, et la page de la liste des contenus (tuc.html) et la page personnalisée (mycustompage.html) sont ajoutées. sushi-config.yaml ``` # ╭──────────────────────────────────────Guide d'implémentation────────────────────────────────────╮ # │ Les propriétés ci-dessous sont utilisées pour créer la ressource ImplementationGuide. Pour une│ # │ liste des propriétés prises en charge, consultez : https://fshschool.org/sushi/configuration/ │ # ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ id: myfirstsushi canonique: http://example.org/myfirstsushi nom: MyFirstSUSHIProject # Ajoutez un titre qui sera affiché en haut de la page. Titre: ○○FHIR Guide d'implémentation du projet statut: ébauche publisher: InterSystems Japan/S.Kaminaka description: Il s'agit d'un exemple de guide d'implémentation pour le projet FHIR utilisant SUSHI. version: 0.1.0 fhirVersion: 4.0.1 copyrightYear: 2021+ releaseLabel: ci-build # ╭────────────────────────────────────────────menu.xml────────────────────────────────────────────────╮ # │ Pour utiliser un fichier input/includes/menu.xml fourni, supprimez la propriété "menu" ci-dessous.│ # ╰────────────────────────────────────────────────────────────────────────────────────────────────────╯ # Configurez le menu pour qu'il s'affiche en japonais. menu: Guide d'implémentation Accueil: index.html Liste de contenu: toc.html Résumé des artefacts FHIR: artifacts.html Page personnalisée: mycustompage.html ``` Les pages d'index et les pages personnalisées peuvent être écrites en markdown : index.md ``` # MyFirstSUSHIProject N'hésitez pas à modifier cette page d'index avec votre propre contenu ! ### Historique du projet pagecontent/index.md Vous pouvez modifier le fichier pour changer la description dans le fichier html. Pour décrire une page, vous pouvez utiliser la notation markdown. ### Liens vers des informations de référence (omission) ``` mycustompage.md ``` ## C'est une page personnalisée. Si vous fournissez un fichier markdown, un fichier html sera généré. Des pages spécifiques au projet peuvent être produites et incluses dans le guide d'implémentation. ``` Enfin, nous allons modifier le fichier FSH le plus essentiel. Ce modèle contient le fichier FSH pour le profil Patient, nous l'avons donc modifié légèrement. patient.fsh ``` // Voici un exemple simple d'un fichier FSH. // Ce fichier peut être renommé, et des fichiers FSH supplémentaires peuvent être ajoutés. // SUSHI recherchera les définitions dans tout fichier utilisant la terminaison .fsh. Profil: MyPatient Parent: Patient Titre: "Profil du patient pour le projet ○○" * nom 1..* MS // La partie descriptive de la liste peut être modifiée en changeant le ^short. * name ^short = "Contient le nom du patient.。" * name ^definition = "Élément permettant de stocker le nom du patient. Kanji et Katakana basés sur NeXEHRS JP CORE" ``` Exécutez maintenant la commande suivante pour voir la page du guide d'implémentation générée. >sushi. > >_updatePublisher > >_genonce Vous pouvez facilement générer une page qui contient les informations suivantes. Il génère également des fichiers JSON tels que StructureDefinition et ImplementationGuide. ![image](https://community.intersystems.com/sites/default/files/inline/images/sushi_part1_ss4.jpg) ![image](https://community.intersystems.com/sites/default/files/inline/images/sushi_part1_ss5.jpg) ## Résumé Les résultats vous ont-ils satisfait ? Cet outil génère des fichiers JSON de profils FHIR et peut créer des fichiers HTML. Ainsi, si cet outil est bien utilisé, il sera facile de créer un contenu capable de communiquer les spécifications FHIR d'une manière facile à comprendre. Bien que cet outil ne soit pas directement lié à InterSystems, nous avons pensé qu'il serait utile de le présenter à la communauté des développeurs pour échanger des informations sur le projet FHIR. Pour ceux d'entre vous qui l'ont essayé après avoir lu cet article, ou qui le connaissent déjà, je serais heureux d'entendre vos réflexions sur la façon de l'utiliser, ainsi que toute fonctionnalité ou syntaxe utile que vous souhaiteriez partager. Dans l'article suivant de cette série, je voudrais étendre les paramètres de recherche en lisant le fichier de définition du paramètre de recherche généré par SUSHI dans IRIS for Health. (2021/4/20 Nous avons corrigé la partie ambiguë de l'explication, notamment en ce qui concerne le profil. Merci de nous l'avoir signalé.)