go to post Sylvain Guilbaud · Apr 22, 2024 Hi @Otto Medin very good point. In order to get links visible on the production configuration, you can add this method : /// Return an array of connections for drawing lines on the config diagram ClassMethod OnGetConnections(Output pArray As %String, pItem As Ens.Config.Item) { Do ##super(.pArray,pItem) If pItem.GetModifiedSetting("TargetConfigNames",.tValue) { For i=1:1:$L(tValue,",") { Set tOne=$ZStrip($P(tValue,",",i),"<>W") Continue:""=tOne Set pArray(tOne)="" } } }
go to post Sylvain Guilbaud · Apr 19, 2024 With the following results : python account.py Deposit succeeded: Balance after deposit: 100 Deposit of negative amount failed as expected: Deposit amount must be non-negative Withdrawal succeeded: Balance after withdrawal: 50 Withdrawal of more than available balance failed as expected: Insufficient funds Balance invariant holds true Balance invariant violated as expected: Balance invariant violated: Balance is negative Account operations completed successfully
go to post Sylvain Guilbaud · Apr 19, 2024 In Python : class Account: def __init__(self): self.balance = 0 def deposit(self, amount): if amount < 0: raise ValueError("Deposit amount must be non-negative") old_balance = self.balance self.balance += amount # Postconditions if self.balance != old_balance + amount: raise ValueError("Postcondition failed: Balance calculation error") def withdraw(self, amount): if amount < 0: raise ValueError("Withdrawal amount must be non-negative") if self.balance < amount: raise ValueError("Insufficient funds") old_balance = self.balance self.balance -= amount # Postconditions if self.balance != old_balance - amount: raise ValueError("Postcondition failed: Balance calculation error") def check_balance_invariant(self): if self.balance < 0: raise ValueError("Balance invariant violated: Balance is negative") @classmethod def test_account(cls): account = cls() try: # Test depositing a positive amount account.deposit(100) print("Deposit succeeded: Balance after deposit:", account.balance) # Test depositing a negative amount (should fail) account.deposit(-50) except ValueError as e: print("Deposit of negative amount failed as expected:", e) else: raise ValueError("Deposit of negative amount unexpectedly succeeded") try: # Test withdrawing a valid amount account.withdraw(50) print("Withdrawal succeeded: Balance after withdrawal:", account.balance) # Test withdrawing more than the available balance (should fail) account.withdraw(200) except ValueError as e: print("Withdrawal of more than available balance failed as expected:", e) else: raise ValueError("Withdrawal of more than available balance unexpectedly succeeded") try: # Check balance invariant (should succeed) account.check_balance_invariant() print("Balance invariant holds true") except ValueError as e: print("Balance invariant violated:", e) # Intentionally set balance to negative value to trigger balance invariant failure account.balance = -10 try: # Check balance invariant (should fail) account.check_balance_invariant() except ValueError as e: print("Balance invariant violated as expected:", e) else: raise ValueError("Balance invariant unexpectedly held true") print("Account operations completed successfully") # Run the test Account.test_account()
go to post Sylvain Guilbaud · Apr 19, 2024 With the following results : write ##class(MyApp.Account).TestAccount() Deposit succeeded: Balance after deposit: 100 Deposit of negative amount failed as expected: ERROR #5001: Deposit amount must be non-negative Withdrawal succeeded: Balance after withdrawal: 50 Withdrawal of more than available balance failed as expected: ERROR #5001: Insufficient funds Balance invariant holds true Balance invariant violated as expected: ERROR #5001: Balance invariant violated: Balance is negative Account operations completed successfully 1
go to post Sylvain Guilbaud · Apr 19, 2024 Very good point @Herman Slagman To illustrate your point, in ObjectScript : Include %occErrors Class MyApp.Account Extends %Persistent { Property Balance As %Numeric; /// Deposit money into the account Method Deposit(amount As %Numeric) As %Status { // Preconditions If amount < 0 { Return $$$ERROR($$$GeneralError, "Deposit amount must be non-negative") } // Store the old balance Set oldBalance = ..Balance // Update balance Set ..Balance = oldBalance + amount // Postconditions If (..Balance '= $$$NULLOREF) && (..Balance '= (oldBalance + amount)) { Return $$$ERROR($$$GeneralError, "Postcondition failed: Balance calculation error") } Quit $$$OK } /// Withdraw money from the account Method Withdraw(amount As %Numeric) As %Status { // Preconditions If amount < 0 { Return $$$ERROR($$$GeneralError, "Withdrawal amount must be non-negative") } If (..Balance = $$$NULLOREF) || (..Balance < amount) { Return $$$ERROR($$$GeneralError, "Insufficient funds") } // Store the old balance Set oldBalance = ..Balance // Update balance Set ..Balance = oldBalance - amount // Postconditions If (..Balance '= $$$NULLOREF) && (..Balance '= (oldBalance - amount)) { Return $$$ERROR($$$GeneralError, "Postcondition failed: Balance calculation error") } Quit $$$OK } /// Invariant: Balance should always be non-negative Method CheckBalanceInvariant() As %Status { Set tSC = $$$OK If ..Balance < 0 { Set tSC = $$$ERROR($$$GeneralError, "Balance invariant violated: Balance is negative") } Quit tSC } /// Class method to test the Account class ClassMethod TestAccount() As %Status { // Create a new instance of Account Set account = ##class(MyApp.Account).%New() // Initialize the balance Set account.Balance = 0 // Test depositing a positive amount Set tSC = account.Deposit(100) If $$$ISERR(tSC) { Write "Deposit failed: ", $system.Status.GetErrorText(tSC), ! Quit tSC } Write "Deposit succeeded: Balance after deposit: ", account.Balance, ! // Test depositing a negative amount (should fail) Set tSC = account.Deposit(-50) If $$$ISERR(tSC) { Write "Deposit of negative amount failed as expected: ", $system.Status.GetErrorText(tSC), ! } Else { Write "Deposit of negative amount unexpectedly succeeded", ! Quit $$$ERROR($$$GeneralError, "Deposit of negative amount unexpectedly succeeded") } // Test withdrawing a valid amount Set tSC = account.Withdraw(50) If $$$ISERR(tSC) { Write "Withdrawal failed: ", $system.Status.GetErrorText(tSC), ! Quit tSC } Write "Withdrawal succeeded: Balance after withdrawal: ", account.Balance, ! // Test withdrawing more than the available balance (should fail) Set tSC = account.Withdraw(200) If $$$ISERR(tSC) { Write "Withdrawal of more than available balance failed as expected: ", $system.Status.GetErrorText(tSC), ! } Else { Write "Withdrawal of more than available balance unexpectedly succeeded", ! Quit $$$ERROR($$$GeneralError, "Withdrawal of more than available balance unexpectedly succeeded") } // Check balance invariant (should succeed) Set tSC = account.CheckBalanceInvariant() If $$$ISERR(tSC) { Write "Balance invariant violated: ", $system.Status.GetErrorText(tSC), ! Quit tSC } Write "Balance invariant holds true", ! // Intentionally set balance to negative value to trigger balance invariant failure Set account.Balance = -10 // Check balance invariant (should fail) Set tSC = account.CheckBalanceInvariant() If $$$ISERR(tSC) { Write "Balance invariant violated as expected: ", $system.Status.GetErrorText(tSC), ! } Else { Write "Balance invariant unexpectedly held true", ! Quit $$$ERROR($$$GeneralError, "Balance invariant unexpectedly held true") } Write "Account operations completed successfully", ! Quit $$$OK } Storage Default { <Data name="AccountDefaultData"> <Value name="1"> <Value>%%CLASSNAME</Value> </Value> <Value name="2"> <Value>Balance</Value> </Value> </Data> <DataLocation>^MyApp.AccountD</DataLocation> <DefaultData>AccountDefaultData</DefaultData> <IdLocation>^MyApp.AccountD</IdLocation> <IndexLocation>^MyApp.AccountI</IndexLocation> <StreamLocation>^MyApp.AccountS</StreamLocation> <Type>%Storage.Persistent</Type> } }
go to post Sylvain Guilbaud · Apr 17, 2024 Many thanks @Alex Woodhead for your article. For those who want to start with Alex's example, you can use the code below or online (process + production) /// Class python.process.demo Extends Ens.BusinessProcessBPL { /// BPL Definition XData BPL [ XMLNamespace = "http://www.intersystems.com/bpl" ] { <process language='python' request='Ens.StringRequest' response='Ens.StringResponse' height='2000' width='2000' > <pyFromImport> import requests </pyFromImport> <sequence xend='200' yend='1050' > <switch name='What Drink' xpos='200' ypos='250' xend='200' yend='500' > <annotation><![CDATA[looking for Drink keyword in request]]></annotation> <case condition='request.StringValue.lower().find("coffee")>0' name='is coffee' languageOverride='python' > <assign name="Coffe" property="response.StringValue" value=""Coffee"" action="set" languageOverride="objectscript" xpos='200' ypos='400' /> </case> <case condition='request.StringValue.lower().find("tea")>0' name='is tea' languageOverride='python' > <assign name="Tea" property="response.StringValue" value=""Tea"" action="set" languageOverride="objectscript" xpos='470' ypos='400' /> </case> <default> <assign name="Chocolate" property="response.StringValue" value=""Chocolate"" action="set" languageOverride="objectscript" xpos='740' ypos='400' /> </default> </switch> <if name='Is White' condition='request.StringValue.lower().find("black")==-1' languageOverride="python" xpos='200' ypos='600' xend='200' yend='850' > <annotation><![CDATA[looking for absence of keyword "black" in request]]></annotation> <true> <assign name="Add Milk" property="response.StringValue" value="response.StringValue + " MILK"" action="set" languageOverride="python" xpos='335' ypos='750' /> </true> </if> <assign name="Add Hot Water" property="response.StringValue" value="response.StringValue + " HOT WATER"" action="set" languageOverride="python" xpos='200' ypos='950' /> </sequence> </process> } Storage Default { <Type>%Storage.Persistent</Type> } }
go to post Sylvain Guilbaud · Apr 11, 2024 I got an interesting answer from chat.fhir.org Lloyd McKenzie says : R5 has a bunch of fixes and enhancements and is 'closer' to what the normative/locked down versions of each of the resources is likely to be. So, all things being equal, R5 is a better bet. However, the amount of implementation support for R5 is considerably less than for R4 and some jurisdictions may opt to not support R5 at all. (E.g. the U.S.) Therefore consider who you're going to need to share with. The migration effort really depends on what you're implementing. Some resources (e.g. Patient, Observation, etc.) have changed very little - and the changes that did occur may not impact your application because you don't use those elements. Other resources have changed more radically. The amount of effort involved also depends on how your software is architected. You can see lists of changes and maps between R4 and R5 in the R5 spec on each resource page. Finally, 'repository' is not the same as 'interface'. It's perfectly possible to expose both an R4 and an R5 API on the same internal data store. Typically you'll have to map between your internal storage representation and your FHIR API regardless of what version you support, so two interfaces just means two sets of maps.
go to post Sylvain Guilbaud · Mar 21, 2024 Hi Zhang, you'll find a JDBC example below : import java.sql.Blob; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; public class RetrieveBlob { public static void main(String[] args) throws Exception { // Replace with your connection details String url = "jdbc:mysql://localhost:3306/your_database"; String username = "your_username"; String password = "your_password"; Connection conn = DriverManager.getConnection(url, username, password); String sql = "SELECT image FROM your_table WHERE id = ?"; PreparedStatement stmt = conn.prepareStatement(sql); stmt.setInt(1, 1); // Replace with the ID you want to query ResultSet rs = stmt.executeQuery(); if (rs.next()) { Blob blob = rs.getBlob("image"); // Replace "image" with your column name // Option 1: Get Bytes byte[] imageBytes = blob.getBytes(1, (int) blob.length()); // Get all bytes // Process the imageBytes byte array // Option 2: Stream Processing // InputStream in = blob.getBinaryStream(); // ... process the stream } rs.close(); stmt.close(); conn.close(); } }
go to post Sylvain Guilbaud · Mar 20, 2024 Congrats @Robert Cemper 👏Your tireless contribution to the community is a model that no one will be able to match.
go to post Sylvain Guilbaud · Mar 11, 2024 Thanks @Alberto Fuentes I suggest to keep information regarding the classname of the message and its ID : Method %ShowContents(pZenOutput As %Boolean = 0) { do ..%JSONExportToString(.jsonExport) set formatter = ##class(%JSON.Formatter).%New() do formatter.FormatToString(jsonExport, .json) &html< <i style="color:green;">type: #($classname())# id: #(..%Id())# </i> <pre>#(json)#</pre> > } in order to get :
go to post Sylvain Guilbaud · Feb 7, 2024 Thanks to @Vitaliy Serdtsev : it is also possible to directly retrieve JSON from an SQL query, with the functionsJSON_ARRAYAGG and JSON_OBJECT : SELECT JSON_ARRAYAGG(json_obj) FROM (SELECT TOP 5 JSON_OBJECT( 'Name':name ,'Age':age ,'DOB':to_char(dob,'Day DD Month YYYY') ) json_obj FROM sample.person ) SELECT JSON_ARRAYAGG(json_obj) FROM (SELECT JSON_OBJECT( 'Name':name ,'Age':age ,'DOB':to_char(dob,'Day DD Month YYYY') ) json_obj FROM sample.person ) WHERE %VID BETWEEN 1 AND 5 Result :
go to post Sylvain Guilbaud · Feb 7, 2024 Many thanks @Vitaliy Serdtsev ; this is exactly what I needed.
go to post Sylvain Guilbaud · Feb 5, 2024 Congrats @José Roberto Pereira, @Henry Pereira, @Henrique Dias , @Muhammad Waseem, @Flavio Naves, Denis Kiselev, Maria Ogienko, Anastasia Samoilova, Kseniya Hoar and all the participants to this FHIR contest !!
go to post Sylvain Guilbaud · Feb 5, 2024 Hello @Ashok Kumar this SQL query is very powerful, but I'm not able to make it work neither with simple TOP, nor with %VID in order to get a pagination. SELECT top 5 JSON_ARRAYAGG( JSON_OBJECT( 'Name':name ,'Age':age ,'DOB':to_char(dob,'Day DD Month YYYY') ) ) FROM sample.person SELECT * FROM (SELECT JSON_ARRAYAGG( JSON_OBJECT( 'Name':name ,'Age':age ,'DOB':to_char(dob,'Day DD Month YYYY') ) ) FROM sample.person) WHERE %VID BETWEEN 1 AND 5 in both cases, I get the full table with all its records.
go to post Sylvain Guilbaud · Feb 1, 2024 Hello, with IRIS you can avoid the now deprecated %ZEN.Auxiliary and rather use %DynamicObject / %DynamicArray : ObjectScript : ClassMethod getJSON() As %String { set query = "SELECT top 3 name,age,to_char(dob,'Day DD Month YYYY') as dob FROM sample.person" set rs=##class(%SQL.Statement).%ExecDirect(,query) set list = [] while rs.%Next() { set row = {"name":(rs.%Get("name")),"age":(rs.%Get("age")),"dob":(rs.%Get("dob"))} do list.%Push(row) } set result = {"results":(list.%Size()),"items":(list)} return result.%ToJSON() } Result : write ##class(resultset).getJSON() {"results":3,"items":[{"name":"Pantaleo,Mary Q.","age":36,"dob":"Monday 07 December 1987"},{"name":"Underman,Laura I.","age":71,"dob":"Friday 16 May 1952"},{"name":"Huff,Lydia J.","age":26,"dob":"Sunday 09 November 1997"}]} Embedded Python : ClassMethod get() As %String [ Language = python ] { import iris import json query = "SELECT top 3 name,age,to_char(dob,'Day DD Month YYYY') FROM sample.person" rs=iris.sql.exec(query) list = [] for idx, x in enumerate(rs): row = {"name":x[0],"age":x[1],"dob":x[2]} list.append(row) result = {"results":len(list),"items":list} return json.dumps(result) } Result : write ##class(resultset).get() {"results": 3, "items": [{"name": "Pantaleo,Mary Q.", "age": 36, "dob": "Monday 07 December 1987"}, {"name": "Underman,Laura I.", "age": 71, "dob": "Friday 16 May 1952"}, {"name": "Huff,Lydia J.", "age": 26, "dob": "Sunday 09 November 1997"}]}
go to post Sylvain Guilbaud · Feb 1, 2024 With embedded python you can get your JSON with few lines of code. resultsetJSONembedded.py : import os import getpass if not 'IRISUSERNAME' in os.environ: os.environ['IRISUSERNAME'] = input('set IRISUSERNAME [_SYSTEM] :') or "_SYSTEM" if not 'IRISPASSWORD' in os.environ: os.environ['IRISPASSWORD'] = getpass.getpass('set IRISPASSWORD [SYS]:') or 'SYS' if not 'IRISNAMESPACE' in os.environ: os.environ['IRISNAMESPACE'] = input('set IRISNAMESPACE [IRISAPP] :') or "IRISAPP" import iris import json query = "SELECT top 5 name,age,to_char(dob,'Day DD Month YYYY') FROM sample.person" rs=iris.sql.exec(query) list = [] for idx, x in enumerate(rs): row = {"name":x[0],"age":x[1],"dob":x[2]} list.append(row) result = {"results":len(list),"items":list} print(json.dumps(result)) Result : irispython resultsetJSONembedded.py set IRISUSERNAME [_SYSTEM] : set IRISPASSWORD [SYS]: set IRISNAMESPACE [IRISAPP] : {"results": 3, "items": [{"name": "Xander,Dan T.", "age": 35, "dob": "Tuesday 10 January 1989"}, {"name": "Kratzmann,Jane O.", "age": 34, "dob": "Thursday 14 September 1989"}, {"name": "Ingrahm,Bill S.", "age": 82, "dob": "Saturday 01 November 1941"}]} or if you prefer export environment variables (without exposing the IRISPASSWORD) : export IRISUSERNAME=_SYSTEM export IRISNAMESPACE=IRISAPP irispython resultsetJSONembedded.py set IRISPASSWORD [SYS]: {"results": 3, "items": [{"name": "Xander,Dan T.", "age": 35, "dob": "Tuesday 10 January 1989"}, {"name": "Kratzmann,Jane O.", "age": 34, "dob": "Thursday 14 September 1989"}, {"name": "Ingrahm,Bill S.", "age": 82, "dob": "Saturday 01 November 1941"}]} {"results": 3, "items": [{"name": "Xander,Dan T.", "age": 35, "dob": "Tuesday 10 January 1989"}, {"name": "Kratzmann,Jane O.", "age": 34, "dob": "Thursday 14 September 1989"}, {"name": "Ingrahm,Bill S.", "age": 82, "dob": "Saturday 01 November 1941"}]}
go to post Sylvain Guilbaud · Feb 1, 2024 Hi, I know your question was about ObjectScript, but if Python is allowed in your environment, I suggest to use it to get JSON : resultsetJSON.py : import iris import json import getpass import os if 'IRISHOSTNAME' in os.environ: hostname = os.environ['IRISHOSTNAME'] else: hostname = input('hostname [localhost] :') or "localhost" if 'IRISPORT' in os.environ: port = os.environ['IRISPORT'] else: port = input('port [1972] :') or "1972" if 'IRISUSERNAME' in os.environ: username = os.environ['IRISUSERNAME'] else: username = input('login [_SYSTEM] :') or "_SYSTEM" if 'IRISPASSWORD' in os.environ: password = os.environ['IRISPASSWORD'] else: password = getpass.getpass('password [SYS]:') or 'SYS' if 'IRISNAMESPACE' in os.environ: namespace = os.environ['IRISNAMESPACE'] else: namespace = input('namespace [IRISAPP] :') or "IRISAPP" connection_string = hostname + ":" + port + "/" + namespace connectionIRIS = iris.connect(connection_string, username, password) cursorIRIS = connectionIRIS.cursor() print("Connected to",connection_string) query = "SELECT top 3 name,age,to_char(dob,'Day DD Month YYYY') FROM sample.person" cursorIRIS.execute(query) resultSet = cursorIRIS.fetchall() list = [] result = {"results":len(resultSet),"items":list} for x in resultSet: row = {"name":x[0],"age":x[1],"dob":x[2]} list.append(row) print(json.dumps(result)) connectionIRIS.close() Result : python resultsetJSON.py hostname [localhost] : port [1972] :51779 login [_SYSTEM] : password [SYS]: namespace [IRISAPP] : Connected to localhost:51779/IRISAPP {"results": 3, "items": [{"name": "Pantaleo,Mary Q.", "age": 36, "dob": "Monday 07 December 1987"}, {"name": "Underman,Laura I.", "age": 71, "dob": "Friday 16 May 1952"}, {"name": "Huff,Lydia J.", "age": 26, "dob": "Sunday 09 November 1997"}]} { "results":3, "items":[ { "name":"Pantaleo,Mary Q.", "age":36, "dob":"Monday 07 December 1987" }, { "name":"Underman,Laura I.", "age":71, "dob":"Friday 16 May 1952" }, { "name":"Huff,Lydia J.", "age":26, "dob":"Sunday 09 November 1997" } ] }
go to post Sylvain Guilbaud · Jan 30, 2024 Very good article ; thank you @Luis Angel Pérez Ramos For those who want to extend the discovery of FHIR, you can access the online documentation on the official web site.