Introduzione
HL7
HL7, Health Level 7 (HL7) è un'associazione non profit internazionale che si occupa di gestire standard per la sanità. HL7 è riferito anche ad alcuni degli specifici standard creati da questa associazione (es. FHIR v2.x, v3.0, CDA, ecc.). Fondata nel 1987 e riconosciuta dall'American National Standards Institute nel 1994, riunisce organizzazioni affiliate da oltre 40 paesi.
FHIR
In riferimento al protocollo, FHIR è uno standard che descrive lo scambio in forma elettronica di dati in ambiente sanitario per favorire l'interoperabilità nell'ambiente clinico di software diversi. Descrive le interfacce tra applicazioni, la definizione dei dati, dei tempi e gli errori da scambiare fra applicazioni. Il numero 7, nel nome dello standard, indica il livello nello stack OSI (Open System Interconnection) che occupa. FHIR offre le basi per modellare il database come ontologico dove ogni ontologia è una rappresentazione formale di un certo dominio, in formato adatto alla manipolazione da parte di sistemi automatici e umani, che descrive concetti e relazioni tra questi. Ad esempio: pazienti con malattie che ricevono terapie specifiche.
FHIR è un acronimo e sta per:
F - Fast
H - Healthcare
I - Interoperable
R - Resources
Le "risorse" sono l'elemento fondamentale delle transazioni, con le loro proprietà specifiche. Ogni risorsa è accessibile mediante RESTful API ed ha un'URL per la sua identificazione, questa è l'url della risorsa "STEWARD MEDICAL GROUP, INC", che è una "Organization" in un database demo.
Le risorse sono classificare in 5 sezioni:
Foundation: relative al funzionamento e alla definizione dello stesso protocollo (CodeSystem, Bundle)
Base: entità coinvolte direttamente nel processo assistenziale di base (Patient, Location, Device)
Clinical: fanno riferimento ad un record clinico (Condition, Procedure)
Financial: di supporto a gli aspetti finanziari (Account, Coverage)
Specialized: di supporto alla definizione e specializzazione del protocollo (Measure, MedicalProduct)
(vedi : https://www.hl7.org/fhir/resourcelist.html )
Schema E/R stralcio di Patient:
da : https://wiki.hl7.org/index.php?title=File:Fhir-clinical-uml.png
HAPI FHIR
HAPI FHIR è un'implementazione, Open Source sotto licenza Apache 2.0, della specifica HL7 FHIR per Java. La implementazione è fatta ispirandosi alle API JAXB e JAX-WS che sono notoriamente delle API ben sviluppate.
Ogni tipo di risorsa definita in FHIR ha una classe corrispondente con i propri get e set.
HAPI FHIR è una applicazione Spring quindi integrabile in infrastrutture cloud facilmente.
Uso pratico
Istanza pubblica
Istanza locale
$ docker run -p 8080:8080 -e hapi.fhir.default_encoding=json hapiproject/hapi:latest
Preparazione dell'ambiente di sviluppo
Mediante Eclipse "Maven > Add Dependency", o modificando a mano il pom.xml del progetto che deve usare HL7, bisogna aggiungere le dipendenze che seguono:
<!-- https://mvnrepository.com/artifact/ca.uhn.hapi.fhir/hapi-fhir-base --><dependency> <groupId>ca.uhn.hapi.fhir</groupId> <artifactId>hapi-fhir-base</artifactId> <version>5.3.0</version></dependency>
<!-- https://mvnrepository.com/artifact/ca.uhn.hapi.fhir/hapi-fhir-structures-r5 --><dependency> <groupId>ca.uhn.hapi.fhir</groupId> <artifactId>hapi-fhir-structures-r5</artifactId> <version>5.3.0</version></dependency>
<!-- https://mvnrepository.com/artifact/ca.uhn.hapi.fhir/hapi-fhir-client --><dependency> <groupId>ca.uhn.hapi.fhir</groupId> <artifactId>hapi-fhir-client</artifactId> <version>5.3.0</version></dependency>
Operazioni di CRUD
Create (Creazione)
HAPI consente il popolamento di ogni oggetto in maniera semplificata grazie ad una serie di metodi pensati appositamente. Ad esempio la risorsa "Osservation" ha una proprietà che si chiama "issued" che di tipo "instant" per FHIR (un timestamp per intendersi). Ci sono metodi per usare il tipo di dato FHIR, ma ci sono anche metodi per usare i tipi propri di Java:
Observation obs = new Observation();
// Istruzioni tutte equivalentiobs.setIssuedElement(new InstantType(new Date()));obs.setIssuedElement(new InstantType(new Date(), TemporalPrecisionEnum.MILLI)); //FHIR data typeobs.setIssued(new Date()); //Java data type
// Il tipo InstantType, di FHIR, consente di lavorare con instant come Java Date o come una stringa FHIR .
Date date = obs.getIssuedElement().getValue(); // Oggetto DataString dateString = obs.getIssuedElement().getValueAsString(); // "2014-03-08T12:59:58.068-05:00"
Valori codificati ed enumerativi
Patient patient = new Patient();// E' possibile settare il valore di questi campi usando// una stringa liberapatient.getGenderElement().setValueAsString("male");// HAPI fornisce tipi Java enumerativi che semplificano// l'uso di queste stringhe per ottenere lo stesso risultato//di quella precedentepatient.setGender(Enumerations.AdministrativeGender.MALE);// Si può recuperare il valore allo stesso modoString genderString = patient.getGenderElement().getValueAsString();Enumerations.AdministrativeGender genderEnum = patient.getGenderElement().getValue();
Numerosità
Patient patient = new Patient();HumanName name = patient.addName();name.setFamily("Smith");StringType firstName = name.addGivenElement();firstName.setValue("Rob");StringType secondName = name.addGivenElement();secondName.setValue("Bruce");
Patient patient = new Patient();patient.addName().setFamily("Smith").addGiven("Rob").addGiven("Bruce");
Integrabilità
Un aspetto molto interessante di questo protocollo è l'apertura verso una facile integrazione in sistemi pre-esistenti. A conferma di questa tesi esiste un approccio molto duttile per la integrazione in sistemi diversi che devono comunicare fra di loro. Lo dimostro con un esempio.
Diciamo che la clinica abc.it voglia adottare una soluzione software basata su FHIR ma abbia in esercizio altri software integrati pre-esistenti che non usano FHIR. Vuole usare FHIR perché deve comunicare con altri sistemi esterni che lo adottano. Un problema sono le anagrafiche. Cioè come identifico Mario Rossi con codice fiscale RSSMRA80A01H501U che nel sistema pre-esistente è identificato da un codice paziente RSSMRA804242344 se devo comunicare, mediante FHIR, con la clinica xyz.com dove Mario Rossi con codice fiscale RSSMRA80A01H501U lo identifica in anagrafica con il codice 4534D432 ?
In FHIR la risorsa Patient ha un campo "identifier" con cardinalità 0..*. Questo identifier nel db dell'azienda abc.it sarà del tipo:
"identifier": [
{
"use": "official",
"system": "http://www.abc.it/paz",
"value": "RSSMRA804242344"
}
]
Mentre nell'azienda xyz.com:
"identifier": [
{
"use": "official",
"system": "http://www.xyz.com/patient",
"value": "4534D432"
}
]
{ "resourceType": "Patient", "id": "1977038", "meta": { "versionId": "1", "lastUpdated": "2021-03-27T17:29:42.203+00:00", "source": "#F58PhsKS87Ov3xn9" }, "text": { "status": "generated", "div": "<div xmlns=\"http://www.w3.org/1999/xhtml\"><div class=\"hapiHeaderText\">Mario <b>ROSSI </b></div><table class=\"hapiPropertyTable\"><tbody><tr><td>Identifier</td><td>RSSMRA804242344</td></tr></tbody></table></div>" }, "identifier": [ { "system": "http://abc.it/paz", "value": "RSSMRA804242344" }, { "system": "http://xyz.com/patient", "value": "4534D432" } ], "name": [ { "family": "Rossi", "given": [ "Mario" ] } ]}
Ricerche
IGenericClient client = fhirClientR5Configuration.clientFhihrR5();
Bundle results = client.search().forResource(Patient.class).where(Patient.FAMILY.matches().value(name)) // Lancia una ricerca
.returnBundle(Bundle.class).execute();
log.debug("Trovati " + results.getEntry().size() + " pazienti di cognome '" + name + "'");
// Lancia una ricerca
Identifier id = new Identifier();
id.setSystem(system).setId(code);
Bundle results = client.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode(system, code))
.returnBundle(Bundle.class).execute();
log.debug("Trovati " + results.getEntry().size() + " pazienti con system "+system + " codice '" + code + "'");
Lettura
Observation observation = new Observation();// Nessuna di queste chiamare restituisce null,// perchè costruisce i rispettivi oggetti figliList<Identifier> identifierList = observation.getIdentifier();CodeableConcept code = observation.getCode();StringType textElement = observation.getCode().getTextElement();// DateTimeDt è una primitiva FHIR tuttavia,// ciò che segue restituisce null// A meno che non ci sia stato inserito un valore.Date active = observation.addIdentifier().getPeriod().getStartElement().getValue();
Parsing (deserializzazione)
// Crea un contesto FHIRFhirContext ctx = FhirContext.forR4();// Questo è un esempio di Paziente serializzato da parsareString input = "{" +"\"resourceType\" : \"Patient\"," +" \"name\" : [{" +" \"family\": \"Simpson\"" +" }]" +"}";// Instanzia un nuovo parserIParser parser = ctx.newJsonParser();// ParsaPatient parsed = parser.parseResource(Patient.class, input);System.out.println(parsed.getName().get(0).getFamily());
// Crea un contesto FHIR
FhirContext ctx = FhirContext.forR4();
// Crea una risorsa Patient da serializzare
Patient patient = new Patient();
patient.addName().setFamily("Simpson").addGiven("James");
// Istanzia un nuovo parser JSON
IParser parser = ctx.newJsonParser();
// Lo serializza
String serialized = parser.encodeResourceToString(patient);
System.out.println(serialized);
// Uso di XML
serialized = ctx.newXmlParser().encodeResourceToString(patient);
System.out.println(serialized);
Client
FHIR fornisce due tipi di client già pronti per l'accesso ai server:
Generic client: molto flessibile e facile da usare.
Annotation client: miglior controllo in fase di compilazione con un maggior numero di funzioni esposte. Richiede maggior sforzo per essere usato.
Ricerche
Usiamo il Generic per cercare un "Patient" per cognome:
FhirContext ctx = FhirContext.forR5();
String serverBase = "http://localhost:8080/fhir";
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// Lancia una ricerca
Bundle results = client.search().forResource(Patient.class).where(Patient.FAMILY.matches().value(name))
.returnBundle(Bundle.class).execute();
System.out.println("Trovati " + results.getEntry().size() + " pazienti con nome '" + name + "'");
FhirContext ctx = FhirContext.forR5();
String serverBase = "http://localhost:8080/fhir";
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// Lancia una ricerca
Bundle results = client.search().forResource(Patient.class) .where(Patient.FAMILY.matches().value(name1, name2))
.returnBundle(Bundle.class).execute();
System.out.println("Trovati " + results.getEntry().size() + " pazienti con nome '" + name1 + "' oppure '"+name2);
FhirContext ctx = FhirContext.forR5();
String serverBase = "http://localhost:8080/fhir";
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// Lancia una ricerca
Bundle results = client.search().forResource(Patient.class) .where(Patient.ADDRESS.matches().values("Toronto"))
.and(Patient.ADDRESS.matches().values("Ontario"))
.and(Patient.ADDRESS.matches().values("Canada"))
.returnBundle(Bundle.class)
.execute();
System.out.println("Trovati " + results.getEntry().size() + " pazienti residenti nella città di 'Toronto', della provincia 'Ontario' nello stato del 'Canada'");
FhirContext ctx = FhirContext.forDstu2();String serverBase = "http://fhirtest.uhn.ca/baseDstu2";
IGenericClient client = ctx.newRestfulGenericClient(serverBase);
// Perform a search
Bundle resultBundle = client.search()
.forResource(Patient.class)
.where(Patient.NAME.matches().value("Smith"))
.returnBundle(Bundle.class)
.execute();
if (resultBundle.getLink(Bundle.LINK_NEXT) != null) {
// load next page
Bundle nextPage = client.loadPage().next(resultBundle).execute();
}
Fonti:
https://github.com/hapifhir/hapi-fhir/
https://hapifhir.io/hapi-fhir/docs/model/working_with_resources.html
Commenti
Posta un commento