Guillaume Rongier · Feb 29, 2024 go to post

thanks @sween,

I take good note of your tips for debugging, because it can become quickly a mess with too much info in ^ISCLOG

Guillaume Rongier · Feb 13, 2024 go to post

Thank you for your kind words, I'm glad you liked the article. I've read your article and it's a great use case of Embedded Python in IRIS.

I noticed that you are making an extensive use of the language tags ( [ language = python ])in your article.

In this article, I try to explain how I try to move away from the language tags and instead stick with only ObjectScript code and use the ##class(%SYS.Python).Import() method to call Python code from ObjectScript or even have a python first approach.

Since you are using the language tags, I'm curious to know if you have tried the ##class(%SYS.Python).Import() method or a python first approach and if so, what are the advantages and disadvantages of using the language tags over the ##class(%SYS.Python).Import() method and the python first approach?

Are you agree with me on all the drawbacks of using the language tags ( it's not Pythonic, it's not ObjectScript either, you don't have a debugger, you don't have a linter, you mixing two language in the same file, when you process crashes, you don't have a stack trace, ... ) ?

Guillaume Rongier · Feb 8, 2024 go to post

Ok, I see your point and I have a better understanding of your first question.

The issue here is that the Python interpreter is not able to find the module hello_world because it is not in the Python path. The Python path is a list of directories that the interpreter searches to find the modules that you import. By default, the Python path in embedded python is /<install dir>/lib/python.

To solve this issue, you can add the directory containing the hello_world module to the Python path. You can do this by adding the directory to the sys.path list in Python. Here is an example of how you can do this:

Class dc.PythonProxy Extends %RegisteredObject
{

Property PythonPath As %String;
Property PythonModule As %String;
Property PythonClassname As %String;
Property PythonClass As %SYS.Python;

ClassMethod SetPythonPath(pClasspaths)
{
    set sys = ##class(%SYS.Python).Import("sys")

    // avoid adding the same path multiple times
    for i=0:1:(sys.path."__len__"()-1) {
        Try {
            if sys.path."__getitem__"(i) = pClasspaths {
                do sys.path."__delitem__"(i)
            }
        }
        Catch ex {
            // do nothing
        }

    }
    do sys.path.insert(0, pClasspaths)
}

ClassMethod GetPythonInstance(
	pModule,
	pRemoteClassname) As %SYS.Python
{
    set importlib = ##class(%SYS.Python).Import("importlib")
    set builtins = ##class(%SYS.Python).Import("builtins")

    set module = importlib."import_module"(pModule)
    do importlib."reload"(module)

    set class = builtins.getattr(module, pRemoteClassname)
    return class."__new__"(class)
}


Method %OnNew(pPythonPath,pPythonModule,pPythonClassname) As %Status
{
    // set variables
    set ..PythonPath = pPythonPath
    set ..PythonModule = pPythonModule
    set ..PythonClassname = pPythonClassname

	// Then set the python class
	do ..SetPythonPath(..PythonPath)
	set ..PythonClass = ..GetPythonInstance(..PythonModule, ..PythonClassname)

	quit $$$OK
}

}

With this class you have a generic way to import any python module and class. You can use it like this:

Set pythonProxy = ##class(dc.PythonProxy).%New("/iris-shared/python", "hello_world", "HelloWorld")
Write pythonProxy.PythonClass.sayhello()

This will add the /iris-shared/python directory to the Python path and then import the hello_world module and create an instance of the HelloWorld class.

With the language tag you can do something like this:

Class dc.boto Extends %RegisteredObject
{

ClassMethod test1() [ Language = python ]
{
    # set path
    import sys
    
    # check if the path is already in the sys.path
    if "/iris-shared/python" not in sys.path:
        sys.path.insert(0, "/iris-shared/python")

    # import module
    import hello_world
    greeting = hello_world.HelloWorld()

    # Call the say_hello method
    ret=greeting.sayhello()
    print(ret)
}

}

I hope this helps! Let me know if you have any other questions.

Guillaume Rongier · Feb 7, 2024 go to post

Many thanks for your article, Heloisa!

I guess that we have the same goal: to make IRIS more accessible to Python developers.

Do you know that IRIS 2024.1 will have a native support of WSGI :)

I'm looking forward to your next article!

If you have time can you have a look at my article about feedback using embedded python daily for more than 2 years and tell me what do you think about it?

Guillaume Rongier · Feb 7, 2024 go to post

Thanks @Mario Sanchez Macias for your feedback. I understand your concerns and will address them in this response.

The example I provided is indeed complex, and I appreciate your patience. I will provide a simpler example to illustrate the integration of Python with ObjectScript.

First, I will give you a quick tip on how to simplify your code without the need of importing the same libraries repeatedly.

In ObjectScript, you create a class like this:

Class dc.boto3 Extends %RegisteredObject
{

XData %import [ MimeType = application/python ]
{
import boto3
}

ClassMethod moveFile() [ Language = python ]
{
    boto3.client('s3').download_file('bucket', 'key', 'filename')
    # do stuff
}

ClassMethod getFile() [ Language = python ]
{
    boto3.client('s3').upload_file('filename', 'bucket', 'key')
    # do stuff
}

}

In this example, I've created a class called dc.boto3 that extends %RegisteredObject. I've added an XData block to import the boto3 library. This way, you don't need to import the library in each method.

But in this example you are still mixing Python and ObjectScript code.

You can also try to stick to only to ObjectScript and use the ##class(%SYS.Python).Import method to import the Python library and use it in your ObjectScript code.

Here's an example of how you can use it to import the boto3 library and use it in your ObjectScript code:

Class dc.boto3 Extends %RegisteredObject
{

ClassMethod moveFile()
{
    set boto3 = ##class(%SYS.Python).Import("boto3")
    set s3 = boto3.client('s3')
    do s3."download_file"('bucket', 'key', 'filename')
    # do stuff
}

ClassMethod getFile()
{
    set boto3 = ##class(%SYS.Python).Import("boto3")
    set s3 = boto3.client('s3')
    do s3."upload_file"('filename', 'bucket', 'key')
    # do stuff
}
    
}

Now, to move to a python only approach, this will depend on your application architecture and the complexity of your Python code.

  • First, do you use the Interoperability framework of Iris ?

If yes, it will be quite easy have a look at IoP, it will allow you to create Services, Processes, Operations in Python Only and call them from a Production.

  • Your entry point is %CSP.Rest application ?

You can start to create .py files and import them in your ObjectScript code. with the ##class(%SYS.Python).Import method.

  • Create an ObjectScript abstract class that will be used to call your Python code.

This is my favorite approach, but the most complex one. This is the one I try to explain in the article.

If we take the example of the boto3 library.

First create the abstract class in ObjectScript:

Class dc.boto3 Extends %RegisteredObject [ Abstract ]
{

ClassMethod moveFile()
{
    Quit $$$ERROR($$$NOTIMPLEMENTED)
}

ClassMethod getFile()
{
    Quit $$$ERROR($$$NOTIMPLEMENTED)
}

}

The great thing about this approach is that you can create a class that extends this abstract class and implement the methods in Python, ObjectScript or any other language.

Then create the implementation of this class in ObjectScript:

Class dc.boto3Python Extends dc.boto3
{

Property PythonPath As %String;
Property PythonClassname As %String;
Property PythonModule As %String;
Property PythonClass As %SYS.Python;

Method %OnNew() As %Status
{
    // %OnNew is called when the object is created.
    set ..PythonPath = $system.Util.GetEnviron("PYTHON_BOTO_PATH")
    // Then set the python class name from the env var
    set ..PythonClassname = $system.Util.GetEnviron("PYTHON_BOTO_CLASS")
    // Then set the python module name from the env var
    set ..PythonModule = $system.Util.GetEnviron("PYTHON_BOTO_MODULE")

    if (..PythonPath = "") || (..PythonClassname = "") || (..PythonModule = "") {
        //quit ##super(pStrategy)
        set ..PythonPath = "/irisdev/app/src/python/"
        set ..PythonClassname = "CustomPythonBoto"
        set ..PythonModule = "custom"
    }


    // Then set the python class
    do ..SetPythonPath(..PythonPath)
    set ..PythonClass = ##class(FHIR.Python.Interactions).GetPythonInstance(..PythonModule, ..PythonClassname)

    quit ##super(pStrategy)
}

ClassMethod moveFile()
{
    do ..PythonClass."moveFile"()
}

ClassMethod getFile()
{
    do ..PythonClass."getFile"()
}

}

Now you can create a Python class that is used to implement the methods of the dc.boto3Python class.

import boto3

class CustomPythonBoto:
    def moveFile(self):
        s3 = boto3.client('s3')
        s3.download_file('bucket', 'key', 'filename')
        # do stuff

    def getFile(self):
        s3 = boto3.client('s3')
        s3.upload_file('filename', 'bucket', 'key')
        # do stuff

Now anone in your company can use the dc.boto3Python class as it's an ObjectScript class. But in fact, it's proxying the Python class CustomPythonBoto.

I hope this helps. Let me know if you have any further questions.

Guillaume Rongier · Feb 6, 2024 go to post

Hi Henrique and José,

I love your project and the idea behind it.

I'm wondering if you have any plans to integrate IRIS-FHIRfy with IoP (Interoperability on Python) ?

Why ?

Because you project at the end creates a lot of Python code that can be used and wrapped in IoP.

May be it's just need to update your prompt to include some notion of IoP and we can end up with a turnkey solution with ready to use code with IoP and IRIS interoperability.

Guillaume Rongier · Dec 18, 2023 go to post

Great features, is possible to pass also an entrypoint script ?

Or is it only for vanilla versions ?

Guillaume Rongier · Nov 30, 2023 go to post

What matters is more the maturity of each resource. Maturity is rated from 0 to 5, where 0 is a draft, and 5 is normative. Further the realse of FHIR, more mature resources are. It's plan than R6 will be the first normative release of FHIR, it's even plan to be an ISO standard. This mean that all resources related to Patient will be normative, and will not change in the future.

Guillaume Rongier · Nov 24, 2023 go to post

I haven't tried it yet on 2021.2, but on 2023.1 you use case is working :

IRISAPP>w##class(Zpy.Utility).Info()
def
IRISAPP>zn"USER"
USER>w##class(Zpy.Utility).Info()
def
USER>zw$zv"IRIS for UNIX (Ubuntu Server LTS for x86-64 Containers) 2023.1 (Build 229U) Fri Apr 14 2023 17:37:52 EDT"

Can you update iris ?

If you can't try to map rPYC global to the USER namespace.

rPYC global actually contains the python code.

Guillaume Rongier · Nov 20, 2023 go to post

Hello Cyril,

I have finished this exercise, I will share my experience on this subject with you:

Have you checked that the snmpd deamon is installed and configured on your docker instance?

By default, it is not installed, so you have to install and configure it.

Example of a dockerfile to install snmpd:

ARG IMAGE=intersystemsdc/iris-community:latest
FROM $IMAGE

WORKDIR /irisdev/app

USER root
RUN apt-get update && apt-get install -y \
    nano \
    snmpd \
    snmp \
    sudo && \
    /bin/echo -e ${ISC_PACKAGE_MGRUSER}\\tALL=\(ALL\)\\tNOPASSWD: ALL >> /etc/sudoers && \
    sudo -u ${ISC_PACKAGE_MGRUSER} sudo echo enabled passwordless sudo-ing for ${ISC_PACKAGE_MGRUSER}

COPY snmpd.conf /etc/snmp/snmpd.conf
USER ${ISC_PACKAGE_MGRUSER}

Example of a snmpd.conf:

###############################################################################
#
# snmpd.conf:
#   An example configuration file for configuring the NET-SNMP agent with Cache.
#
#   This has been used successfully on Red Hat Enterprise Linux and running
#   the snmpd daemon in the foreground with the following command:
#
#   /usr/sbin/snmpd -f -L -x TCP:localhost:705 -c./snmpd.conf
#
#   You may want/need to change some of the information, especially the
#   IP address of the trap receiver of you expect to get traps. I've also seen
#   one case (on AIX) where we had to use  the "-C" option on the snmpd command
#   line, to make sure we were getting the correct snmpd.conf file. 
#
###############################################################################

###########################################################################
# SECTION: System Information Setup
#
#   This section defines some of the information reported in
#   the "system" mib group in the mibII tree.

# syslocation: The [typically physical] location of the system.
#   Note that setting this value here means that when trying to
#   perform an snmp SET operation to the sysLocation.0 variable will make
#   the agent return the "notWritable" error code.  IE, including
#   this token in the snmpd.conf file will disable write access to
#   the variable.
#   arguments:  location_string

syslocation  "System Location"

# syscontact: The contact information for the administrator
#   Note that setting this value here means that when trying to
#   perform an snmp SET operation to the sysContact.0 variable will make
#   the agent return the "notWritable" error code.  IE, including
#   this token in the snmpd.conf file will disable write access to
#   the variable.
#   arguments:  contact_string

syscontact  "Your Name"

# sysservices: The proper value for the sysServices object.
#   arguments:  sysservices_number

sysservices 76

###########################################################################
# SECTION: Agent Operating Mode
#
#   This section defines how the agent will operate when it
#   is running.

# master: Should the agent operate as a master agent or not.
#   Currently, the only supported master agent type for this token
#   is "agentx".
#   
#   arguments: (on|yes|agentx|all|off|no)

master agentx
agentXSocket tcp:localhost:705

###########################################################################
# SECTION: Trap Destinations
#
#   Here we define who the agent will send traps to.

# trapsink: A SNMPv1 trap receiver
#   arguments: host [community] [portnum]

trapsink  localhost public   

###############################################################################
# Access Control
###############################################################################

# As shipped, the snmpd demon will only respond to queries on the
# system mib group until this file is replaced or modified for
# security purposes.  Examples are shown below about how to increase the
# level of access.
#
# By far, the most common question I get about the agent is "why won't
# it work?", when really it should be "how do I configure the agent to
# allow me to access it?"
#
# By default, the agent responds to the "public" community for read
# only access, if run out of the box without any configuration file in 
# place.  The following examples show you other ways of configuring
# the agent so that you can change the community names, and give
# yourself write access to the mib tree as well.
#
# For more information, read the FAQ as well as the snmpd.conf(5)
# manual page.
#
####
# First, map the community name "public" into a "security name"

#       sec.name  source          community
com2sec notConfigUser  default       public

####
# Second, map the security name into a group name:

#       groupName      securityModel securityName
group   notConfigGroup v1           notConfigUser
group   notConfigGroup v2c           notConfigUser

####
# Third, create a view for us to let the group have rights to:

# Make at least  snmpwalk -v 1 localhost -c public system fast again.
#       name           incl/excl     subtree         mask(optional)
# access to 'internet' subtree
view    systemview    included   .1.3.6.1

# access to Cache MIBs Caché and Ensemble
view    systemview    included   .1.3.6.1.4.1.16563.1
view    systemview    included   .1.3.6.1.4.1.16563.2
####
# Finally, grant the group read-only access to the systemview view.

#       group          context sec.model sec.level prefix read   write  notif
access  notConfigGroup ""      any       noauth    exact  systemview none none

From this neat article : https://community.intersystems.com/post/intersystems-data-platforms-and-performance-part-5-monitoring-snmp

Then, you have to start the snmpd deamon:

sudo service snmpd start

On iris, you have to configure the snmp agent:

%SYS> w $$start^SNMP()

With all these steps, you should be able to retrieve information via snmp.

snmpwalk -m ALL -v 2c -c public localhost .iso.3.6.1.4.1.16563.4.1.1.1.5.4.73.82.73.83

Result :

iso.3.6.1.4.1.16563.4.1.1.1.5.4.73.82.73.83 = STRING: "IRIS for UNIX (Ubuntu Server LTS for x86-64 Containers) 2023.1 (Build 229U) Fri Apr 14 2023 17:37:52 EDT"

Now that the snmpd service is running and functional as well as the snmp agent.

I encourage you to have a look at OpenTelemetry, it's a new standard for monitoring and tracing.

https://opentelemetry.io/

And our implementation of OpenTelemetry for IRIS :

https://docs.intersystems.com/iris20233/csp/docbook/Doc.View.cls?KEY=GCM_rest

Guillaume Rongier · Oct 24, 2023 go to post

What do you mean by "avoid any security breach"? What security breach are you trying to avoid? Creating a connection to IRIS is not a security breach.

You can specify user and roles in the connection string, so you can limit the access to the database to only what is needed for the task at hand.

Guillaume Rongier · Oct 17, 2023 go to post

Hi,

Are you using docker desktop ? if so, does you file sharing implemention is "VirtioFS" ?

Then, that may be a problem, try it with gRPC FUSE, does it fix it ?

Why "VirtioFS" and durable doesn't work, i don't know, i will try to gather information about it.

Guillaume Rongier · Oct 17, 2023 go to post

Hi, FHIR Profile Validation will be support with IRIS 2023.3.

https://community.intersystems.com/post/intersystems-announces-its-seco…

Highlights

There are several exciting new features in this release, such as support to base HL7® FHIR® version R5, and the integration with the IBM FHIR® Validator. There are also runtime performance improvements for ObjectScript.  Note that these features are expected, but nor assured to appear in the final release. 

Mean while, you can use community edition of Profile Validation :

Guillaume Rongier · Oct 17, 2023 go to post

Hi, FHIR Profile Validation will be support with IRIS 2023.3.

https://community.intersystems.com/post/intersystems-announces-its-seco…

Highlights

There are several exciting new features in this release, such as support to base HL7® FHIR® version R5, and the integration with the IBM FHIR® Validator. There are also runtime performance improvements for ObjectScript.  Note that these features are expected, but nor assured to appear in the final release. 

Mean while, you can use community edition of Profile Validation :

Guillaume Rongier · Oct 11, 2023 go to post

FYI : it works great except that, it create the classes in %SYS namespace, not in our current namespace.

I will fill an issue on github.

Guillaume Rongier · Oct 10, 2023 go to post

Hi,

I can't reproduce your error. I'm missing some information.

What I have done so far is :

  • adding the missing imports
  • adding the missing class DFrameRequest
    • i suppose it is a dataclass with a field named dframe of type pd.DataFrame
    • i suppose it is a subclass of Message
  • i have added a main function to run the code
    • i'm not sure of the format of the dataframe and the data in it
from dataclasses import dataclass
import pandas as pd
from grongier.pex import BusinessOperation,Message
from sqlalchemy import create_engine, types

@dataclass
class DFrameRequest(Message):
    dframe: pd.DataFrame

class FileOperationEmbedded(BusinessOperation):
    tablename = None
    engine = None

    def on_init(self):
        if not hasattr(self, "dsnIris"):
            self.dnsIris = 'iris+emb:///'
        if not hasattr(self, "schema"):
            self.schema = 'Toto'

        self.engine = create_engine(self.dnsIris)
        return None

    def on_message(self, request:DFrameRequest):
        df = pd.DataFrame(request.dframe.col)

        for row in request.dframe.col:
            df = pd.DataFrame.from_dict(row, orient='index').T.reset_index(drop=True)
            try:
                df.to_sql(name=self.tablename, con=self.engine, if_exists='append', index=False, schema=self.schema,
                          dtype={'id': types.INTEGER, 'col_type': types.VARCHAR(50), 'col_center': types.VARCHAR(50),
                                 'col_name': types.VARCHAR(50), 'col_issue_name': types.VARCHAR(50),
                                 'col_model': types.VARCHAR(50), 'col_treatment': types.VARCHAR(50),
                                 'source': types.VARCHAR(50), 'filename': types.VARCHAR(100), 'created_at': types.TIMESTAMP})
            except Exception as e:
                self.log_info(f"Une erreur s'est produite : {e}")

        return None
    
if __name__ == '__main__':
    # create a new instance of the business operation
    bo = FileOperationEmbedded()
    # initialize the business operation
    bo.on_init()

    # create a new message
    msg = DFrameRequest(pd.DataFrame())
    msg.dframe.col = [
            {'id': 1, 'col_type': 'type1', 'col_center': 'center1', 'col_name': 'name1', 'col_issue_name': 'issue1',
             'col_model': 'model1', 'col_treatment': 'treatment1', 'source': 'source1', 'filename': 'file1',
             'created_at': '2021-10-01 00:00:00'},
            {'id': 2, 'col_type': 'type2', 'col_center': 'center2', 'col_name': 'name2', 'col_issue_name': 'issue2',
             'col_model': 'model2', 'col_treatment': 'treatment2', 'source': 'source2', 'filename': 'file2',
             'created_at': '2021-10-02 00:00:00'}
        ]

    # send the message to the business operation
    bo.on_message(msg)
    print("Done")

Then, from your code I can see the following issues :

  • you are using the same variable name for the dataframe and the list of rows
  • the variable self.tablename is not initialized
  • the name FileOperationEmbedded it's maybe not the best name for your class as it is not a file operation
  • why are you using a for loop to iterate over the rows of the dataframe ?

I have modified your code to fix these issues :

from dataclasses import dataclass
import pandas as pd
from grongier.pex import BusinessOperation,Message
from sqlalchemy import create_engine, types

@dataclass
class DFrameRequest(Message):
    dframe: pd.DataFrame

class IrisSqlAlchmyEmbedded(BusinessOperation):
    tablename = None
    engine = None

    def on_init(self):
        if not hasattr(self, "dsnIris"):
            self.dnsIris = 'iris+emb:///'
        if not hasattr(self, "schema"):
            self.schema = 'Toto'
        if not hasattr(self, "tablename") or self.tablename is None:
            self.tablename = 'mytable'

        self.engine = create_engine(self.dnsIris)
        return None

    def on_message(self, request:DFrameRequest):

        try:
            request.dframe.to_sql(name=self.tablename, con=self.engine, if_exists='append', index=False, schema=self.schema,
                      dtype={'id': types.INTEGER, 'col_type': types.VARCHAR(50), 'col_center': types.VARCHAR(50),
                             'col_name': types.VARCHAR(50), 'col_issue_name': types.VARCHAR(50),
                             'col_model': types.VARCHAR(50), 'col_treatment': types.VARCHAR(50),
                             'source': types.VARCHAR(50), 'filename': types.VARCHAR(100), 'created_at': types.TIMESTAMP})
        except Exception as e:
            print(f"Une erreur s'est produite : {e}")

        return None

if __name__ == '__main__':
    # create a new instance of the business operation
    bo = IrisSqlAlchmyEmbedded()
    # initialize the business operation
    bo.on_init()

    # create a new message
    msg = DFrameRequest(pd.DataFrame([
            {'id': 1, 'col_type': 'type1', 'col_center': 'center1', 'col_name': 'name1', 'col_issue_name': 'issue1',
             'col_model': 'model1', 'col_treatment': 'treatment1', 'source': 'source1', 'filename': 'file1',
             'created_at': '2021-10-01 00:00:00'},
            {'id': 2, 'col_type': 'type2', 'col_center': 'center2', 'col_name': 'name2', 'col_issue_name': 'issue2',
             'col_model': 'model2', 'col_treatment': 'treatment2', 'source': 'source2', 'filename': 'file2',
             'created_at': '2021-10-02 00:00:00'}
        ]))

    # send the message to the business operation
    bo.on_message(msg)
    print("Done")
Guillaume Rongier · Oct 9, 2023 go to post

I don't think so.

FHIR is an API rest. FHIR has not been designed to apply deduplication and data merge business rules. This is the role of HealthShare not the role of an FHIR endpoint.

Guillaume Rongier · Oct 9, 2023 go to post

It seems that 2022.x have the same capability that 2023.x, I mean by that 2022.x support conditional Update and Create.

Then if you want to update only that is needed to be updated you have to go with JSON Patch :

PATCH https://localhost:4443/fhir/r4/Organization?identifier=HEAD
Accept: application/fhir+json
Content-Type: application/json-patch+json

[
 { 
   "op": "add", 
   "path": "/telecom/0", 
   "value": {
                "system": "phone",
                "value": "some-other-phone"
                }
 }
]

Result :

GET https://localhost:4443/fhir/r4/Organization?identifier=HEAD&_elements=telecom
Accept: application/fhir+json
{
  "resourceType": "Bundle",
  "id": "9962a9aa-668b-11ee-8f79-0242ac160003",
  "type": "searchset",
  "timestamp": "2023-10-09T10:05:38Z",
  "total": 1,
  "link": [
    {
      "relation": "self",
      "url": "https://localhost:4443/fhir/r4/Organization?_elements=telecom&identifier=HEAD"
    }
  ],
  "entry": [
    {
      "fullUrl": "https://localhost:4443/fhir/r4/Organization/1",
      "resource": {
        "resourceType": "Organization",
        "telecom": [
          {
            "system": "phone",
            "value": "some-other-phone"
          },
          {
            "system": "phone",
            "value": "some-other-phone"
          }
        ],
        "meta": {
          "tag": [
            {
              "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationValue",
              "code": "SUBSETTED",
              "display": "Resource content reduced because _elements search parameter"
            }
          ]
        }
      },
      "search": {
        "mode": "match"
      }
    }
  ]
}

Attention JSON patch only work with direct access to the resource, if you need json path in a bundle you have to pass it as a base64 :

{
  "resourceType": "Bundle",
  "type": "transaction",
  "entry": [
    {
      "fullUrl": "Patient/1",
      "resource": {
        "resourceType": "Binary",
        "contentType": "application/json-patch+json",
        "data": "WyB7ICJvcCI6InJlcGxhY2UiLCAicGF0aCI6Ii9hY3RpdmUiLCAidmFsdWUiOmZhbHNlIH0gXQ=="
      },
      "request": {
        "method": "PATCH",
        "url": "Patient/1"
      }
    }
  ]
}

Example of a json path in a bundle for the patient 1 with this patch :

[ { "op":"replace", "path":"/active", "value":false } ]
Guillaume Rongier · Oct 6, 2023 go to post
{
"entry":
    [
        {
        "request":
            {
            "method": "PUT",
            "url": "Organization?identifier=EFG"
            },
        "resource":
            {
            "active": true,
            "identifier":
                [
                    {
                    "system": "https://fhir.domain.de/identifiers/domain-orgid",
                    "value": "EFG"
                    }
                ],
            "name": "Allgemeine Einrichtung  EFG.",
            "partOf":
                {
                "display": "HEAD",
                "reference": "urn:uuid:87a8f88a-5223-11ee-8c2c-005056b163cd"
                },
            "resourceType": "Organization",
            "telecom":
                [
                    {
                    "system": "phone",
                    "value": "some-phonenr"
                    },
                    {
                    "system": "fax",
                    "value": "some-faxnr"
                    }
                ],
            "type":
                [
                    {
                    "coding":
                        [
                            {
                            "code": "2",
                            "system": "https://fhir.domain.de/CodeSystem/domain-orglevel"
                            },
                            {
                            "code": "KL",
                            "display": "Klinik m. Poliklinik",
                            "system": "https://fhir.domain.de/CodeSystem/domain-orgtype"
                            }
                        ]
                    }
                ]
            }
        },
        {
        "request":
            {
            "method": "PUT",
            "url": "Organization?identifier=AEAP"
            },
        "resource":
            {
            "active": true,
            "identifier":
                [
                    {
                    "system": "https://fhir.domain.de/identifiers/domain-orgid",
                    "value": "AEAP"
                    }
                ],
            "name": "Allgemeine Einrichtung AEAP",
            "partOf":
                {
                "display": "HEAD",
                "reference": "urn:uuid:87a8f88a-5223-11ee-8c2c-005056b163cd"
                },
            "resourceType": "Organization",
            "telecom":
                [
                    {
                    "system": "phone",
                    "value": "some-phonenr"
                    },
                    {
                    "system": "fax",
                    "value": "some-faxnr"
                    }
                ],
            "type":
                [
                    {
                    "coding":
                        [
                            {
                            "code": "2",
                            "system": "https://fhir.domain.de/CodeSystem/domain-orglevel"
                            },
                            {
                            "code": "KL",
                            "display": "Klinik m. Poliklinik",
                            "system": "https://fhir.domain.de/CodeSystem/domain-orgtype"
                            }
                        ]
                    }
                ]
            }
        },
        {
        "fullUrl": "urn:uuid:87a8f88a-5223-11ee-8c2c-005056b163cd",
        "request":
            {
            "method": "PUT",
            "url": "Organization?identifier=HEAD"
            },
        "resource":
            {
            "active": true,
            "address":
                [
                    {
                    "line":
                        [
                        "Some street 2"
                        ],
                    "type": "postal",
                    "use": "work"
                    }
                ],
            "identifier":
                [
                    {
                    "system": "https://fhir.domain.de/identifiers/domain-orgid",
                    "value": "HEAD"
                    }
                ],
            "name": "Some Hospital",
            "partOf":
                {
                "display": "NULL"
                },
            "resourceType": "Organization",
            "telecom":
                [
                    {
                    "system": "phone",
                    "value": "some-phonenr"
                    }
                ],
            "type":
                [
                    {
                    "coding":
                        [
                            {
                            "code": "0",
                            "system": "https://fhir.domain.de/CodeSystem/domain-orglevel"
                            },
                            {
                            "code": "SHOS",
                            "display": "Some Hospital",
                            "system": "https://fhir.domain.de/CodeSystem/domain-orgtype"
                            }
                        ]
                    }
                ]
            }
        }
    ],
"id": "214a4092-eeb9-440d-b645-0ec3b5b30bbe",
"resourceType": "Bundle",
"type": "transaction"
}

Here is your bundle with conditional update instead of conditional create.

Result on IRIS 2023.2 :

First call :

{
  "resourceType": "Bundle",
  "id": "409996a4-644b-11ee-8e3a-0242ac160003",
  "type": "transaction-response",
  "timestamp": "2023-10-06T13:07:35Z",
  "entry": [
    {
      "response": {
        "status": "201",
        "location": "https://localhost:4443/fhir/r4/Organization/2",
        "etag": "W/\"1\"",
        "lastModified": "2023-10-06T13:07:35Z"
      }
    },
    {
      "response": {
        "status": "201",
        "location": "https://localhost:4443/fhir/r4/Organization/3",
        "etag": "W/\"1\"",
        "lastModified": "2023-10-06T13:07:35Z"
      }
    },
    {
      "fullUrl": "urn:uuid:87a8f88a-5223-11ee-8c2c-005056b163cd",
      "response": {
        "status": "201",
        "location": "https://localhost:4443/fhir/r4/Organization/1",
        "etag": "W/\"1\"",
        "lastModified": "2023-10-06T13:07:35Z"
      }
    }
  ]
}

as you can see we have 201 (mean created)

Second call :

{
  "resourceType": "Bundle",
  "id": "ccc5c35a-644b-11ee-8f77-0242ac160003",
  "type": "transaction-response",
  "timestamp": "2023-10-06T13:08:47Z",
  "entry": [
    {
      "response": {
        "status": "200",
        "location": "https://localhost:4443/fhir/r4/Organization/2",
        "etag": "W/\"2\"",
        "lastModified": "2023-10-06T13:08:48Z"
      }
    },
    {
      "response": {
        "status": "200",
        "location": "https://localhost:4443/fhir/r4/Organization/3",
        "etag": "W/\"2\"",
        "lastModified": "2023-10-06T13:08:48Z"
      }
    },
    {
      "fullUrl": "urn:uuid:87a8f88a-5223-11ee-8c2c-005056b163cd",
      "response": {
        "status": "200",
        "location": "https://localhost:4443/fhir/r4/Organization/1",
        "etag": "W/\"2\"",
        "lastModified": "2023-10-06T13:08:47Z"
      }
    }
  ]
}

we can see 200 mean ok/updated.

Get all organization :

{
  "resourceType": "Bundle",
  "id": "56ccfcb4-644a-11ee-8f78-0242ac160003",
  "type": "searchset",
  "timestamp": "2023-10-06T13:09:50Z",
  "total": 3,
  "link": [
    {
      "relation": "self",
      "url": "https://localhost:4443/fhir/r4/Organization"
    }
  ],
  "entry": [
    {
      "fullUrl": "https://localhost:4443/fhir/r4/Organization/1",
      "resource": {
        "active": true,
        "address": [
          {
            "line": [
              "Some street 2"
            ],
            "type": "postal",
            "use": "work"
          }
        ],
        "identifier": [
          {
            "system": "https://fhir.domain.de/identifiers/domain-orgid",
            "value": "HEAD"
          }
        ],
        "name": "Some Hospital",
        "partOf": {
          "display": "NULL"
        },
        "resourceType": "Organization",
        "telecom": [
          {
            "system": "phone",
            "value": "some-phonenr"
          }
        ],
        "type": [
          {
            "coding": [
              {
                "code": "0",
                "system": "https://fhir.domain.de/CodeSystem/domain-orglevel"
              },
              {
                "code": "SHOS",
                "display": "Some Hospital",
                "system": "https://fhir.domain.de/CodeSystem/domain-orgtype"
              }
            ]
          }
        ],
        "id": "1",
        "meta": {
          "lastUpdated": "2023-10-06T13:08:47Z",
          "versionId": "2"
        }
      },
      "search": {
        "mode": "match"
      }
    },
    {
      "fullUrl": "https://localhost:4443/fhir/r4/Organization/2",
      "resource": {
        "active": true,
        "identifier": [
          {
            "system": "https://fhir.domain.de/identifiers/domain-orgid",
            "value": "EFG"
          }
        ],
        "name": "Allgemeine Einrichtung  EFG.",
        "partOf": {
          "display": "HEAD",
          "reference": "Organization/1"
        },
        "resourceType": "Organization",
        "telecom": [
          {
            "system": "phone",
            "value": "some-phonenr"
          },
          {
            "system": "fax",
            "value": "some-faxnr"
          }
        ],
        "type": [
          {
            "coding": [
              {
                "code": "2",
                "system": "https://fhir.domain.de/CodeSystem/domain-orglevel"
              },
              {
                "code": "KL",
                "display": "Klinik m. Poliklinik",
                "system": "https://fhir.domain.de/CodeSystem/domain-orgtype"
              }
            ]
          }
        ],
        "id": "2",
        "meta": {
          "lastUpdated": "2023-10-06T13:08:48Z",
          "versionId": "2"
        }
      },
      "search": {
        "mode": "match"
      }
    },
    {
      "fullUrl": "https://localhost:4443/fhir/r4/Organization/3",
      "resource": {
        "active": true,
        "identifier": [
          {
            "system": "https://fhir.domain.de/identifiers/domain-orgid",
            "value": "AEAP"
          }
        ],
        "name": "Allgemeine Einrichtung AEAP",
        "partOf": {
          "display": "HEAD",
          "reference": "Organization/1"
        },
        "resourceType": "Organization",
        "telecom": [
          {
            "system": "phone",
            "value": "some-phonenr"
          },
          {
            "system": "fax",
            "value": "some-faxnr"
          }
        ],
        "type": [
          {
            "coding": [
              {
                "code": "2",
                "system": "https://fhir.domain.de/CodeSystem/domain-orglevel"
              },
              {
                "code": "KL",
                "display": "Klinik m. Poliklinik",
                "system": "https://fhir.domain.de/CodeSystem/domain-orgtype"
              }
            ]
          }
        ],
        "id": "3",
        "meta": {
          "lastUpdated": "2023-10-06T13:08:48Z",
          "versionId": "2"
        }
      },
      "search": {
        "mode": "match"
      }
    }
  ]
}

As you can see "partOf" are referencing internal id of HEAD.

TIP :

To quickly test on a fresh server, you can wipe data like this (only do it on dev server, your laptop, never in production !!! )

Set appKey = "/fhir/r4"Set strategy = ##class(HS.FHIRServer.API.InteractionsStrategy).GetStrategyForEndpoint(appKey)
Set options("deleteDataOnly") = 1Do strategy.Delete(.options)
Guillaume Rongier · Oct 2, 2023 go to post

Try to follow the article, the last part is about the ingress, this will help you to expose the webgateway and have access to the portal.

Right now, I can't help you on specific features of EKS.