Nueva publicación

查找

Anuncio
· 27 oct, 2023

[On-Demand Webinar] Analytics Capabilities using InterSystems IRIS

Hi Community,

We're excited to share with you the recording of the next webinar in the series of InterSystems UKI Tech Talk:

👉 Analytics Capabilities using InterSystems IRIS 👈

In this tech talk, we put the spotlight on analytics capabilities developers have using both InterSystems IRIS data platform and InterSystems IRIS for Health, including the following ones.

  • Adaptive Analytics allows developers to create a business-oriented virtual OLAP model layer between InterSystems IRIS and popular BI client tools like Microsoft Excel and Power BI, or Tableau. By having a centralised common data model, enterprises solve the problem of differing definitions and calculations to provide their end users with one consistent view of business metrics and data characterisation. 
  • Embedded real-time analytics that can be created directly on transactional data model and a fully automated synchronisation option avoids the need for ETL processing.
  • Columnar Storage is a new storage option for IRIS SQL tables that offers an order-of-magnitude faster analytical queries compared to traditional row storage on IRIS. $vector as a new data type to support columnar storage for SQL tables. 

To watch a recording, you need to complete the form.

We trust that you'll find this webinar to be valuable 😉

Comentarios (0)1
Inicie sesión o regístrese para continuar
Ten en cuenta que esta publicación está obsoleta.
Anuncio
· 17 oct, 2023

[Video] Clinical Alerts & Notifications on FHIR: Putting the Healthcare Action Engine into the Workflow

Hi Community,

Watch this video to learn how to combine FHIR, CDS Hooks, and the low-code Healthcare Rule Editor into an easy-to- use, powerful platform to help ensure information is delivered in the best possible way:

⏯ Clinical Alerts & Notifications on FHIR: Putting the Healthcare Action Engine into the Workflow @ Global Summit 2023

🗣 Presenter: @Frank Pandolfe, Clinical Product Manager, InterSystems

Subscribe to our YouTube channel InterSystems Developers to get the latest updates!

Comentarios (0)1
Inicie sesión o regístrese para continuar
Pregunta
· 16 oct, 2023

Problem encoding pdf content stream to base 64 using the StoreFieldStreamBase64 method

Hello,

I have a problem attempting to store a base 64 encode stream into the OBX:5 field of a HL7 message, by using the StoreFieldStreamBase64 method.

I'm using a transformer to input the contents of a pdf within a stream container (Ens.StreamContainer). The output is a HL7 message (EnsLib.HL7.Message 2.5:ORU_R01).

The transformer is being used in a business process.

This is the code used in the transformer to encode the stream to base 64:

Try {
     Set pdfStreamObj = source.StreamGet()
   } Catch {
      Quit
   }

 //Set PDF stream object into OBX:5
   Set status = target.StoreFieldStreamBase64(pdfStreamObj,"PIDgrpgrp(1).ORCgrp(1).OBXgrp(1).OBX:ObservationValue(1)")

This is the next transformer step to include a prefix to OBX:5:

set
target.{PIDgrpgrp(1).ORCgrp(1).OBXgrp(1).OBX:ObservationValue(1)}
to value
"DOC^Application^PDF^Base64^" _ target.{PIDgrpgrp(1).ORCgrp(1).OBXgrp(1).OBX:ObservationValue(1)}

Occasionally, the transformer process does not do the encoding to base 64, therefore no base 64 content is saved to OBX:5 field, even though the pdf content is present in the stream input that is passed into the transformer.

No error is generated when this situation occurs.

I'm interested to know why this problem is occurring and what could fix it?

The input service component is using EnsLib.File.PassThroughService and the output operation component is using EnsLib.HL7.Operation.TCPOperation.

I have tested an affected pdf file in our test environment. The pdf content has successfully been encoded to base 64 and passed to the HL7 message. So I don't think there is a problem with the affected pdf file.

Adrian.

2 comentarios
Comentarios (2)2
Inicie sesión o regístrese para continuar
Artículo
· 16 oct, 2023 Lectura de 10 min

Using FHIR Adapter to offer FHIR services over legacy systems - Reading a Resouce

We resume our series of articles on the FHIR Adapter tool available to HealthShare HealthConnect and InterSystems IRIS users.

In the previous articles we have presented the small application on which we set up our workshop and showed the architecture deployed in our IRIS instance after installing the FHIR Adapter. In today's article we will see an example of how we can perform one of the most common CRUD (Create - Read - Update - Delete) operations, the reading operation, and we will do it by recovering a Resource.

What is a Resource?

A Resource in FHIR corresponds to a type of clinical information that is relevant, this information can be a patient (Patient), a request to a laboratory (ServiceRequest) or a diagnosis (Condition), etc. Each resource defines the type of data that comprises it, as well as the restrictions on the data and the relationships with other types of resources. Each resource allows the extension of the information it contains, thus allowing needs to be covered that are outside the FHIR 80% (covering the needs used by more than the 80% of the users).

For the example in this article we are going to use the most common resource, the Patient. Let's look at its definition:

{
  "resourceType" : "Patient",
  // from Resource: id, meta, implicitRules, and language
  // from DomainResource: text, contained, extension, and modifierExtension
  "identifier" : [{ Identifier }], // An identifier for this patient
  "active" : <boolean>, // Whether this patient's record is in active use
  "name" : [{ HumanName }], // A name associated with the patient
  "telecom" : [{ ContactPoint }], // A contact detail for the individual
  "gender" : "<code>", // male | female | other | unknown
  "birthDate" : "<date>", // The date of birth for the individual
  // deceased[x]: Indicates if the individual is deceased or not. One of these 2:
  "deceasedBoolean" : <boolean>,
  "deceasedDateTime" : "<dateTime>",
  "address" : [{ Address }], // An address for the individual
  "maritalStatus" : { CodeableConcept }, // Marital (civil) status of a patient
  // multipleBirth[x]: Whether patient is part of a multiple birth. One of these 2:
  "multipleBirthBoolean" : <boolean>,
  "multipleBirthInteger" : <integer>,
  "photo" : [{ Attachment }], // Image of the patient
  "contact" : [{ // A contact party (e.g. guardian, partner, friend) for the patient
    "relationship" : [{ CodeableConcept }], // The kind of relationship
    "name" : { HumanName }, // I A name associated with the contact person
    "telecom" : [{ ContactPoint }], // I A contact detail for the person
    "address" : { Address }, // I Address for the contact person
    "gender" : "<code>", // male | female | other | unknown
    "organization" : { Reference(Organization) }, // I Organization that is associated with the contact
    "period" : { Period } // The period during which this contact person or organization is valid to be contacted relating to this patient
  }],
  "communication" : [{ // A language which may be used to communicate with the patient about his or her health
    "language" : { CodeableConcept }, // R!  The language which can be used to communicate with the patient about his or her health
    "preferred" : <boolean> // Language preference indicator
  }],
  "generalPractitioner" : [{ Reference(Organization|Practitioner|
   PractitionerRole) }], // Patient's nominated primary care provider
  "managingOrganization" : { Reference(Organization) }, // Organization that is the custodian of the patient record
  "link" : [{ // Link to a Patient or RelatedPerson resource that concerns the same actual individual
    "other" : { Reference(Patient|RelatedPerson) }, // R!  The other patient or related person resource that the link refers to
    "type" : "<code>" // R!  replaced-by | replaces | refer | seealso
  }]
}

As you can see, it covers practically all the administrative information needs of a patient.

Recovering a patient from our HIS

If you remember from previous articles we have deployed a PostgreSQL database that simulates the database of an HIS system, let's take a look at the example tables that we have in our particular HIS.

There are not many, but they will be enough for our example. Let's look at our patient table in a little more detail.

Here we have our 3 example patients, as you can see each one has a unique identifier (ID) as well as a series of administrative data relevant to the health organization. Our first objective will be to obtain the FHIR resource for one of our patients.

Patient consultation

How can we request patient data from our server? According to the implementation specification made by FHIR, we must perform a GET via REST to a URL with the address of our server, the name of the resource and the identifier. We must invoke:

http://SERVER_PATH/Patient/{id}

For our example we will perform a search for Juan López Hurtado, with his id = 1, so we must invoke the following URL:

http://localhost:52774/Adapter/r4/Patient/1

For testing we will use Postman as a client. Let's see what the server's response is:

{
    "resourceType": "Patient",
    "address": [
        {
            "city": "TERUEL",
            "line": [
                "CALLE SUSPIROS 39 2ºA"
            ],
            "postalCode": "98345"
        }
    ],
    "birthDate": "1966-11-23",
    "gender": "M",
    "id": "1",
    "identifier": [
        {
            "type": {
                "text": "ID"
            },
            "value": "1"
        },
        {
            "type": {
                "text": "NHC"
            },
            "value": "588392"
        },
        {
            "type": {
                "text": "DNI"
            },
            "value": "12345678X"
        }
    ],
    "name": [
        {
            "family": "LÓPEZ HURTADO",
            "given": [
                "JUAN"
            ]
        }
    ],
    "telecom": [
        {
            "system": "phone",
            "value": "844324239"
        },
        {
            "system": "email",
            "value": "juanitomaravilla@terra.es"
        }
    ]
}

Now let's analyze the path that our request has taken within our production:

Here we have the path:

  1. Arrival of the request to our BS InteropService.
  2. Forwarding the request to the BP that we have configured as the destination of our BS where the patient identifier of the call received will be recovered.
  3. Query from our BO FromAdapterToHIS to our HIS database.
  4. Forwarding the patient data to our BP and transforming it to an FHIR Patient resource.
  5. Forwarding the response to the BS.

Let's take a look at the type of message we receive in our BP ProcessFHIRBP:

Let's look at three attributes that will be key to identifying what type of operation has been requested from the client:

  • Request.RequestMethod: which tells us what type of operation we are going to perform. In this example, the search for a patient will be a GET.
  • Request.RequestPath: this attribute contains the path of the request that arrived at our server, this attribute will indicate the resource on which we will work and in this case it will include the specific identifier for its recovery.
  • Quick.StreamId: FHIR Adapter will transform every FHIR message received into a Stream and assign it an identifier that will be saved in this attribute. For this example we will not need it since we are performing a GET and we are not sending any FHIR object.

Let's continue with the journey of our message by analyzing in depth the GLP responsible for the processing.

ProcessFHIRBP:

We have implemented a BPL in our production that will manage the FHIR messaging that we receive from our Business Service. Let's see how it is implemented:

Let's see the operations that we will perform in each step:

Manage FHIR object:

We will invoke the BO FromAdapterToHIS responsible for the connection to the HIS database and which will be in charge of the database query.

Method ManageFHIR(requestData As HS.FHIRServer.Interop.Request, response As Adapter.Message.FHIRResponse) As %Status
{
  set sc = $$$OK
  set response = ##class(Adapter.Message.FHIRResponse).%New()

  if (requestData.Request.RequestPath = "Bundle")
  {
    If requestData.QuickStreamId '= "" {
        Set quickStreamIn = ##class(HS.SDA3.QuickStream).%OpenId(requestData.QuickStreamId,, .tSC)
        
        set dynamicBundle = ##class(%DynamicAbstractObject).%FromJSON(quickStreamIn)

        set sc = ..GetBundle(dynamicBundle, .response)
      }
    
  }
  elseif (requestData.Request.RequestPath [ "Patient")
  {
    if (requestData.Request.RequestMethod = "POST")
    {
      If requestData.QuickStreamId '= "" {
        Set quickStreamIn = ##class(HS.SDA3.QuickStream).%OpenId(requestData.QuickStreamId,, .tSC)
        
        set dynamicPatient = ##class(%DynamicAbstractObject).%FromJSON(quickStreamIn)

        set sc = ..InsertPatient(dynamicPatient, .response)
      }      
    }
    elseif (requestData.Request.RequestMethod = "GET")
    {
      set patientId = $Piece(requestData.Request.RequestPath,"/",2)
      set sc = ..GetPatient(patientId, .response)
    }

  }
  Return sc
}

Our BO will check the message received of type HS.FHIRServer.Interop.Request, in this case by setting a GET and indicating in the path that corresponds to the Patient resource the GetPatient Method will be invoked, which we will see below:

Method GetPatient(patientId As %String, Output patient As Adapter.Message.FHIRResponse) As %Status
{
  Set tSC = $$$OK
  set sql="SELECT id, name, lastname, phone, address, city, email, nhc, postal_code, birth_date, dni, gender FROM his.patient WHERE id = ?"
  //perform the Select
  set tSC = ..Adapter.ExecuteQuery(.resultSet, sql, patientId)
  
  If resultSet.Next() {
     set personResult = {"id":(resultSet.GetData(1)), "name": (resultSet.GetData(2)), 
        "lastname": (resultSet.GetData(3)), "phone": (resultSet.GetData(4)), 
        "address": (resultSet.GetData(5)), "city": (resultSet.GetData(6)),
        "email": (resultSet.GetData(7)), "nhc": (resultSet.GetData(8)),
        "postalCode": (resultSet.GetData(9)), "birthDate": (resultSet.GetData(10)),
        "dni": (resultSet.GetData(11)), "gender": (resultSet.GetData(12)), "type": ("Patient")}

   } else {
     set personResult = {}
   }
  
  //create the response message
  do patient.Resource.Insert(personResult.%ToJSON())
 
	Return tSC
}

As you can see, this method only launches a query on the database of our HIS and recovers all the patient information, then generates a DynamicObject that is subsequently transformed into a String and stored in a variable of the type Adapter.Message.FHIRResponse. We have defined the Resource property as a String list to be able to display the response later in the trace. You could define it directly as DynamicObjects, saving subsequent transformations.

Check if Bundle:

With the response from our BO we check if it is a Bundle type (we will explain it in a future article) or if it is just a Resource.

Create Dynamic Object:

We transform the BO response into a DynamicObject and assign it to a temporary context variable (context.temporalDO). The function used for the transformation is the following:

##class(%DynamicAbstractObject).%FromJSON(context.FHIRObject.Resource.GetAt(1))

FHIR transform:

With our temporary variable of type DynamicObject we launch a transformation of it to an object of class HS.FHIR.DTL.vR4.Model.Resource.Patient. If we want to look for other types of resources we would have to define particular transformations for each type. Let's see our transformation:

This transformation allows us to have an object that our BS InteropService can interpret. We will store the result in the variable context.PatientResponse.

Assign resource to Stream:

We convert the variable context.PatientResponse obtained in FHIR transform to Stream.

Transform to QuickStream:

We assign to the response variable all the data that we must return to our client:

 set qs=##class(HS.SDA3.QuickStream).%New()
 set response.QuickStreamId = qs.%Id()
 set copyStatus = qs.CopyFrom(context.JSONPayloadStream)
 set response.Response.ResponseFormatCode="JSON"
 set response.Response.Status=200
 set response.ContentType="application/fhir+json"
 set response.CharSet = "utf8"
  

In this case we are always returning a 200 response. In a production environment we should check if we have correctly recovered the searched resource, and if not, modify the status of the response from 200 to a 404 corresponding to a "Not found". As you can see in this code fragment, the object HS.FHIR.DTL.vR4.Model.Resource.Patient
is transformed into a Stream and stored as a HS.SDA3.QuickStream, adding the identifier of said object to the QuickStreamID attribute, subsequently our InteropService service will return the result correctly as a JSON.

Conclusion:

Let's summarize what we have done:

  1. We have sent a GET type request to search for a Patient resource with a defined ID.
  2. The BS InteropService has forwarded the request to the configured BP.
  3. The BP has invoked the BO responsible for interacting with the HIS database.
  4. The configured BO has retrieved the patient data from the HIS database.
  5. The BP has transformed the result into an object understandable by the BS created by default InteropService.
  6. The BS has received the response and has forwarded it to the client.

As you can see, the operation is relatively simple, if we want to add more types of resources to our server we will only have to add in our BO the query to the tables of our database that correspond to the new resource to be recovered and include in our BP the transformation of the result of our BO to an object of type HS.FHIR.DTL.vR4.Model.Resource.* that corresponds.

In our next article we will review how we can add new FHIR resources of the Patient type to our HIS database.

Thank you all for your attention!

3 comentarios
Comentarios (3)1
Inicie sesión o regístrese para continuar
Pregunta
· 13 oct, 2023

Python Integration Issues on Remote IRIS Server

Hi everyone, 

I'm attempting to compile a basic Python code on a remote server, but it appears that the compiler doesn't recognize the language.

The remote server is running a virtual machine with Oracle Linux Server 7.9 (64-bit), and it has IRIS for UNIX (Red Hat Enterprise Linux for x86-64) 2021.1 (Build 215U) [HealthConnect:3.3.0] installed.

When I try to compile a script that includes a Python ClassMethod, such as this "testpy.cls":

ClassMethod python() As %Status [ Language = python ]
{
    # prova python
    print("hello world")
}

This error returns (Error #5486: Invalid method language / Error #5030 An error occurred while compiling class '<className>'): 

Compilation started on 10/13/2023 17:01:41 with qualifiers 'cuk'
Compiling class <className>
ERROR #5486: Invalid method language: <className>:MethodName:python
  > ERROR #5030: An error occurred while compiling class '<className>'
Detected 1 errors during compilation in 0.090s.

I've attempted to follow the instructions provided on this page: Python Prerequisites, but I couldn't resolve this problem. The problem persisted, even after executing "sudo su -" command to obtain root privileges before trying to install python3 or a python package, like numpy, through the command "yum install python3".

I also attempted to write some ObjectScript code, recalling ##class(%SYS.Python).Builtins() or ##calls(%SYS.Python).Shell(), such as: 

ClassMethod HelloWorld() As %Status
{
    set pythonBuiltins = ##class(%SYS.Python).Builtins()
    do pythonBuiltins.print("hello world")
}

However, I encountered an error indicating that these methods do not exist.

The same Python codes work fine on my local instance of IRIS and I can't figure out why they doesn't work on the remote server instance.

I've identified the path of the Python packages folder on my computer (C:\InterSystems\IRISHealth\mgr\python) and noticed the only big difference between the local and remote instances. While the local folder includes several subfolders (such as numpy, pandas, etc.), the corresponding folder on the remote server (u01/<instanceName>/mgr/python) is empty, despite multiple attempts to install python3 and packages like numpy or pandas, even if executed directly from the mgr/python folder. 

It is like Python and its packages are installed but invisible. In fact, if I try to run the command yum install python3 again, it returns this message, even if the folder is still empty:

Loaded plugins: langpacks, ulninfo
Package python3-3.6.8-19.0.1.el7_9.x86_64 already installed and latest version
Nothing to do

I've tried to clean some space on the remote server and re-tried the installation with 2.5 Gb of free space available, but nothing changed. 

Does anybody know how to handle this?

4 comentarios
Comentarios (4)1
Inicie sesión o regístrese para continuar