Standard vs. Norme

"FHIR est un standard (et pas une norme). Un standard d’interopérabilité de modélisation de la donnée de santé.

Si vous voulez une norme, il faut prendre les profils français créés par l’ANS par exemple.

Les profils permettent de normaliser le standard FHIR pour le rendre beaucoup plus contraignant." Luc

 

FHIR pour les développeurs

"Au-delà de l'interopérabilité, FHIR apporte la modélisation de la donnée. C'est un standard orienté développeurs ; le but étant d'être un standard présenté comme un langage compréhensible par des développeurs. FHIR est censé faciliter le développement des applications de santé, en apportant la couche transport et la couche modélisation. Il ne reste plus qu'à développer une application. 

C’est vraiment sur la partie modélisation de la données de santé que FHIR est une révolution." Luc

"Clairement il n'y a pas photo entre HL7 V2 et HL7 FHIR. Avec le FHIR je me retrouve dans ma zone de confort de développeur en informatique industrielle, avec des API standardisées, au format JSON, avec des grappes d'objet ; c'est une révolution pour un développeur." Guillaume

 

Cas d'Usage

"Le partage de documents, énorme sujets sur l'IoT (cf. Mon Espace Santé), l'hospitalisation à domicile, etc.

Le plus gros cas d’usage, que l’on attend depuis des années, c’est la communication ville-hôpital. C’est de pouvoir partir avec son dossier médical quand on déménage." Luc 

 

FHIR est le Web 3.0 des données de santé

« À l’identique du Web 3.0, le FHIR tient la promesse de décentraliser les données.

Avec FHIR, moi j’ai un rêve, c’est que mon dossier patient soit stocké sur mon téléphone, pour que quand je voyage, quand je déménage, quand j’ai une consultation chez mon médecin traitant ou à l’hôpital, j’autorise le professionnel de santé à accéder à mon dossier, pour qu’il amende mon dossier et que je le récupère. » Guillaume

 

Entrepôts de Données de Santé (EDS)

« FHIR peut aussi être utilisé pour standardiser les entrepôts de données de santé (EDS) ; par exemple, le consentement, l’anonymisation/pseudonymisation, la récupération de données, les mappings sémantiques et syntaxiques pour rendre la donnée utilisable, etc. ) et  pour standardiser les données dans les EDS. » Luc

« La grande différence entre l’OMOP et le FHIR réside dans le fait qu’OMOP est à la base orienté stockage des données médicales avec un modèle de données travaillant sur les couches basses alors que FHIR est un standard à la base d’échanges et d’interopérabilité sur les couches hautes. » Guillaume

 

OMOP

« L’orientation à rechercher est de créer des serveurs FHIR avec des exports OMOP, qui est la solution la plus adaptée pour un EDS. » Luc

« FHIR stocke les données au format documentaire, ce qui ne facilite pas les recherches dynamiques (ex: en SQL), à l’inverse de l’OMOP dont le format des données est basé sur un modèle relationnel, typique des requêtes exprimées en langage SQL. InterSystems rompt cette barrière en permettant de projeter les données FHIR en relationnel, sans duplication des données, et donc de les ouvrir à de la recherche médicale. » Guillaume

 

CQL

« CQL Clinical Quality Language, un standard développé par HL7, est un langage de haut niveau, spécifique à un domaine, axé sur la qualité clinique et destiné à la modélisation de processus, pour standardiser des règles à des fins d’aide à la décision (ex: gestion populationnelle, risque de contre-indication médicamenteuse, etc.). » Luc

« Le gros bénéfice, c’est qu’avec le CQL et le FHIR, les règles deviennent portables ; on n’a plus besoin de regrouper toutes les règles et les données au même endroit ; on à juste à répliquer les règles pour les appliquer sur différents entrepôts de données, pour ensuite regrouper les résultats. » Guillaume 

FHIR SQL Builder

Le FHIR® SQL Builder, ou Builder, est un outil de projection sophistiqué utilisé pour créer des schémas SQL personnalisés à l'aide de données dans un référentiel FHIR sans déplacer ni dupliquer les données vers un référentiel SQL distinct. Le Builder est conçu spécifiquement pour fonctionner avec les référentiels FHIR et les bases de données multimodèles dans les produits InterSystems.

L'objectif du Builder est de permettre aux analystes de données et aux développeurs de Business Intelligence de travailler avec FHIR à l'aide d'outils analytiques familiers, sans avoir à apprendre une nouvelle syntaxe de requête. Les données FHIR sont codées dans un graphe orienté complexe et ne peuvent pas être interrogées à l'aide de la syntaxe SQL standard. Un langage de requête basé sur des graphes, FHIRPath, est conçu pour interroger les données FHIR, mais il n'est pas relationnel. En permettant à un gestionnaire de données de créer une projection SQL personnalisée de son référentiel FHIR, à l'aide de tables, de colonnes et d'index, le générateur permet aux analystes de données d'interroger les données FHIR sans la complexité de l'apprentissage de FHIRPath ou de la syntaxe de recherche FHIR.

Bonjour @lilian taroua 
pour avoir une traduction de la doc en ligne, je vous conseille d'ajouter une extension (ex: Chrome Google Traduction).

Le résultat n'est pas parfait (Traduttore, traditore 🤔) mais permet quand même de répondre en partie au besoin assez rapidement et de manière efficace.

Exemple ci-dessous :

Bonjour,

vous pouvez aussi utiliser une méthode "low-code" en utilisant le langage DTL avec l'action REMOVE

 

utils.HL7.transfo.removeSegment

Class utils.HL7.transfo.removeSegment Extends Ens.DataTransformDTL [ DependsOn = EnsLib.HL7.Message ]

{
Parameter IGNOREMISSINGSOURCE = 0;
Parameter REPORTERRORS = 1;
Parameter TREATEMPTYREPEATINGFIELDASNULL = 0;
XData DTL [ XMLNamespace = "http://www.intersystems.com/dtl" ]
{
<transform sourceClass='EnsLib.HL7.Message' targetClass='EnsLib.HL7.Message' sourceDocType='2.6:ADT_A01' targetDocType='2.6:ADT_A01' create='copy' language='objectscript' >
<assign value='' property='target.{EVN}' action='remove' />
<assign value='' property='target.{DG1()}' action='remove' />
</transform>
}
}

Faire la modification depuis la classe %SYSTEM.CSP en utilisant $system.CSP.SetConfig:

CSS>d $system.CSP.DisplayConfig()
...
CSS>w $system.CSP.GetConfig("DefaultPasswordChangePage")
%CSP.PasswordChange.cls

CSS>d $system.CSP.SetConfig("DefaultPasswordChangePage","CSS.CSP.ChangePassword.cls")

CSS>w $system.CSP.GetConfig("DefaultPasswordChangePage")
CSS.CSP.ChangePassword.cls

La réponse parfaite avec l'API appropriée à utiliser a été donnée par @Vitaliy Serdtsev 

USER>zn "%sys"
%SYS>w ##class(SYS.Database).%OpenId("/is/iris/mgr/irisaudit").Size
11
%SYS>w ##class(%SYS.Audit).Erase(1)
1
%SYS>w ##class(SYS.Database).%OpenId("/is/iris/mgr/irisaudit").Size
1

Avec dans le messages.log  :

08/24/23-17:31:00:649 (23676) 0 [Database.FullExpansion] Expansion completed for database /is/iris/mgr/irisaudit/. Expanded by 10 MB.
08/24/23-17:33:38:885 (48001) 0 [Generic.Event] Dismounted database /is/iris/mgr/irisaudit/ (SFN 4)
08/24/23-17:33:39:955 (48001) 0 [Database.MountedRW] Mounted database /is/iris/mgr/irisaudit/ (SFN 4) read-write.

En fait, exclure tous les schémas '%' ne fonctionne pas très bien : avec cette option tous les noms de schémas sont masqués et quand on veut voir les données, les requêtes échouent du fait que toutes les tables sont considérées comme appartenant au schema par défaut SQLUser.

Avec embedded Python, vous pouvez avoir un code assez simple en utilisant pandas :


/// Convert an Excel file to a CSV file
ClassMethod XLStoCSV(source As %String = "/data/sample.xlsx") As %Status [ Language = python ]
{
import pandas as pd
read_file = pd.read_excel(source)
read_file.to_csv(source+'.csv', index = None, header=True)
}
 

NB : pour le stockage orienté colonne indiqué au niveau de la table via l'instruction WITH STORAGETYPE = COLUMNAR , il est à noter qu'IRIS se laisse la liberté de choisir pour vous le stockage le plus communément optimal (en ligne ou en colonne), en fonction des types de données.

Exemple : 

l'instruction suivante :


CREATE TABLE a.addressV1 (
        city varchar(50),
        zip varchar(15),
        country varchar(15)
    )
    WITH STORAGETYPE = COLUMNAR

Ne créera aucun stockage orienté colonne, lié au risque de données trop disparates, du fait du nombre de caractères autorisés dans chaque colonne (15 ou 50) : 


Class a.addressV1 Extends %Persistent [ ClassType = persistent, DdlAllowed, Final, Owner = {_SYSTEM}, ProcedureBlock, SqlRowIdPrivate, SqlTableName = addressV1 ]

{

Property city As %Library.String(COLLATION = "EXACT", MAXLEN = 50, STORAGEDEFAULT = "ROW") [ SqlColumnNumber = 2 ];

Property zip As %Library.String(COLLATION = "EXACT", MAXLEN = 15, STORAGEDEFAULT = "ROW") [ SqlColumnNumber = 3 ];

Property country As %Library.String(COLLATION = "EXACT", MAXLEN = 15, STORAGEDEFAULT = "ROW") [ SqlColumnNumber = 4 ];

Parameter STORAGEDEFAULT = "columnar";

Parameter USEEXTENTSET = 1;

alors que l'exemple donné dans l'article, retient bien une colonne (et une seule) en stockage orienté colonne, puisqu'ayant seulement 5 caractères autorisés pour la colonne zip.




 CREATE TABLE a.addressV2 (
        city varchar(50),
        zip varchar(5),
        country varchar(15)
    )
    WITH STORAGETYPE = COLUMNAR

Class a.addressV2 Extends %Persistent [ ClassType = persistent, DdlAllowed, Final, Owner = {_SYSTEM}, ProcedureBlock, SqlRowIdPrivate, SqlTableName = addressV2 ]

{

Property city As %Library.String(COLLATION = "EXACT", MAXLEN = 50, STORAGEDEFAULT = "ROW") [ SqlColumnNumber = 2 ];

Property zip As %Library.String(COLLATION = "EXACT", MAXLEN = 5) [ SqlColumnNumber = 3 ];
Property country As %Library.String(COLLATION = "EXACT", MAXLEN = 15, STORAGEDEFAULT = "ROW") [ SqlColumnNumber = 4 ];

Parameter STORAGEDEFAULT = "columnar";
Parameter USEEXTENTSET = 1;

Pour activer l'ensemble des événements d'AUDIT système, il suffit d'exécuter la requête SQL suivante depuis l'espace de noms %SYS :


update security.events set enabled=1 where flags = 1

exemple :


set tRes = ##class(%SQL.Statement).%ExecDirect(,"update security.events set enabled=1 where flags = 1")

if tRes.%SQLCODE=0 {

set ^["USER"]TRACE("%SYS Security.Events")=tRes.%ROWCOUNT_" successfully enabled"

} else {

set ^["USER"]TRACE("%SYS Security.Events")=tRes.%Message_" SQLCODE:"_tRes.%SQLCODE

}

Le moyen le plus simple de toujours rester dans le siècle en cours est :

$ZDATEH("26/05/23",4,,6)

Il suffit d'utiliser yearopt = 6 pour obtenir toutes les dates qui n'ont que 2 chiffres dans le siècle courant.

w $zdt($ZDATEH("26/05/23",4,,6),3)
2023-05-26

w $zdt($ZDATEH("26/05/1923",4,,6),3)
1923-05-26

Bonjour @yurimarx Marx,

et aujourd'hui, vous pouvez ajouter à cette liste :

  1. Embedded Python
  2. Adaptive Analytics (AtScale)
  3. System Alerting and Monitoring (SAM)
  4. InterSystems Kubernetes Operator (IKO)
  5. Améliorations SQL sur les performances et les capacités (Global Iterator,  Columnar Storage, Adaptive Parallel Execution, LOAD DATA, etc.)
  6. Améliorations du noyau (Mirroring, etc.)
  7. Améliorations sur la sécurité 
  8. etc. (cf. release notes)

Bonjour Franck,
une autre façon consiste à définir un mapping utilisant le SQLStorage (comme nous avions commence à l'explorer ensemble).

Même si Lorenzo t'a déjà donné la solution avec $Query, à toute fin utile pour d'autres cas, je t'invite à consulter les articles de @Brendan Bannon  

avec tous les exemples ici

Bonjour @Franck Hanotin 
en utilisant un paramètre, tu peux utiliser le même code sur plusieurs globales :
 




Class dc.axiell
{
Query data(globalName As %String) As %Query(ROWSPEC = "key1:%String,key2:%String,key3:%String,key4:%String,key5:%String,key6:%String,datavalue:%String") [ SqlProc ]
{
}
ClassMethod dataExecute(ByRef qHandle As %Binary, globalName As %String) As %Status
{
Set qHandle("node") = globalName
Quit $$$OK
}
ClassMethod dataFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Boolean) As %Status [ PlaceAfter = dataExecute ]
{
Set sc = $$$OK
Set qHandle("node") = $Query(@qHandle("node"), 1, data)
If qHandle("node") = "" Set Row = "", AtEnd = $$$YES Quit $$$OK
; feeds the key x fields based on the subscripts of the global
For i=1:1:$QLength(qHandle("node")) Set $List(Row, i) = $QSubscript(qHandle("node"), i)
If i < 6 { ; if we do not have 6 subscripts, we feed the rest with an empty string
For j = i+1:1:6 Set $List(Row, j) = ""
}

Set $List(Row, 7) = data, AtEnd = $$$NO
Quit sc

}

ClassMethod dataClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = dataExecute ]
{
Kill qHandle Quit $$$OK
}

/// just for some test data
ClassMethod set() As %Status
{
set sc = $$$OK
kill ^AFO
s ^AFO("Site","Ville")="66722,3743"
s ^AFO("Site","Ville","111BB","OBT")=",MMM,XXX,"
s ^AFO("Site","Ville","111OW","OBT")=",XXX,MMM,"
s ^AFO("Site","Ville","AANVRBIBS","zzz") = "1^^1"
s ^AFO("Site","Ville","AANVRBIBS","zzz","*","dut") = "*afhalen waar gevonden"
s ^AFO("Site","Ville","AANVRBIBS","zzz","*","eng") = "*Pickup where found"
s ^AFO("Site","Ville","AANVRBIBS","zzz","*","fre") = "*Lieu où trouvé"

kill ^AAA
s ^AAA(1,2)="66722,3743"
s ^AAA(1,2,"3","4") =",MMM,XXX,"
s ^AAA(1,2,"4","5") =",XXX,MMM,"
s ^AAA(1,2,3,4) = "1^^1"
s ^AAA(1,2,3,4,"*","dut") = "*afhalen waar gevonden"
s ^AAA(1,2,3,4,"*","eng") = "*Pickup where found"
s ^AAA(1,2,3,4,"*","fre") = "*Lieu où trouvé"

kill ^BBB
s ^BBB("en") ="Hello"
s ^BBB("en","sub") ="World"
s ^BBB(1,2,3) ="BBB"
s ^BBB(1,2,3,4) = "BBB^^BBB"

kill ^CCC
s ^CCC("fr") ="Bonjour"
s ^CCC("fr","sub") ="la Communauté"
s ^CCC(1,2,3,"QUATRE","CINQ","SIX") ="6"
s ^CCC(1,2,3,4,"5","6") ="6"
s ^CCC("UN","DEUX","TROIS") ="3"

return sc
}
}



IRISAPP>:sql
SQL Command Line Shell
----------------------------------------------------

The command prefix is currently set to: <<nothing>>.
Enter <command>, 'q' to quit, '?' for help.
[SQL]IRISAPP>>select * from dc.axiell_data('^AFO')
34.     select * from dc.axiell_data('^AFO')

key1    key2    key3    key4    key5    key6    datavalue
Site    Ville                                   66722,3743
Site    Ville   111BB   OBT                     ,MMM,XXX,
Site    Ville   111OW   OBT                     ,XXX,MMM,
Site    Ville   AANVRBIBS       zzz                     1^^1
Site    Ville   AANVRBIBS       zzz     *       dut     *afhalen waar gevonden
Site    Ville   AANVRBIBS       zzz     *       eng     *Pickup where found
Site    Ville   AANVRBIBS       zzz     *       fre     *Lieu où trouvé

7 Rows(s) Affected
statement prepare time(s)/globals/cmds/disk: 0.0003s/4/139/0ms
          execute time(s)/globals/cmds/disk: 0.0023s/11/3,294/0ms
                                query class: %sqlcq.IRISAPP.cls59
---------------------------------------------------------------------------
[SQL]IRISAPP>>select * from dc.axiell_data('^AAA')
35.     select * from dc.axiell_data('^AAA')

key1    key2    key3    key4    key5    key6    datavalue
1       2                                       66722,3743
1       2       3       4                       1^^1
1       2       3       4       *       dut     *afhalen waar gevonden
1       2       3       4       *       eng     *Pickup where found
1       2       3       4       *       fre     *Lieu où trouvé
1       2       4       5                       ,XXX,MMM,

6 Rows(s) Affected
statement prepare time(s)/globals/cmds/disk: 0.0002s/4/139/0ms
          execute time(s)/globals/cmds/disk: 0.0014s/10/3,099/0ms
                                query class: %sqlcq.IRISAPP.cls59
---------------------------------------------------------------------------
[SQL]IRISAPP>>select * from dc.axiell_data('^BBB')
36.     select * from dc.axiell_data('^BBB')

key1    key2    key3    key4    key5    key6    datavalue
1       2       3                               BBB
1       2       3       4                       BBB^^BBB
en                                              Hello
en      sub                                     World

4 Rows(s) Affected
statement prepare time(s)/globals/cmds/disk: 0.0003s/4/139/0ms
          execute time(s)/globals/cmds/disk: 0.0045s/8/2,702/0ms
                                query class: %sqlcq.IRISAPP.cls59
---------------------------------------------------------------------------
[SQL]IRISAPP>>select * from dc.axiell_data('^CCC')
37.     select * from dc.axiell_data('^CCC')

key1    key2    key3    key4    key5    key6    datavalue
1       2       3       4       5       6       6
1       2       3       QUATRE  CINQ    SIX     6
UN      DEUX    TROIS                           3
fr                                              Bonjour
fr      sub                                     la Communauté

5 Rows(s) Affected
statement prepare time(s)/globals/cmds/disk: 0.0002s/4/139/0ms
          execute time(s)/globals/cmds/disk: 0.0018s/9/2,899/0ms
                                query class: %sqlcq.IRISAPP.cls59
---------------------------------------------------------------------------
[SQL]IRISAPP>>q

IRISAPP>

@Alexander Koblov vient de me donner la solution.
La commande GRANT ... ON SCHEMA ... répond parfaitement au besoin.


GRANT SELECT ON SCHEMA data TO ROLE|USER

Et ce qui est cool, c'est que dès que votre utilisateur a reçu un GRANT sur un schéma, lorsque vous modifiez ensuite l'utilisateur dans le portail de gestion de la sécurité, vous pouvez voir dans l'onglet Tables SQL toutes les tables apparaître au fur et à mesure de leur création.
 

@Ben Spead a donné une première réponse en proposant d'utiliser Embedded Python :

Cela ressemble à un exemple classique d'exploitation de Python intégré dans IRIS pour utiliser le très grand nombre de bibliothèques Python qui font à peu près n'importe quoi :) Voici un article que j'ai trouvé lors d'une recherche rapide sur Google sur la façon de procéder en Python :

https://www.geeksforgeeks.org/convert-pdf-to-image-using-python/

Vous pouvez simplement exploiter les capacités Python d'InterSystems IRIS pour utiliser la façon dont ce problème a été résolu en Python !