go to post Sylvain Guilbaud · Jul 9, 2024 Hi @Kamal Suri I've not tested it in a mirror env. The restart of the primary Linux server should not increase (neither decrease) the time for the backup member to become primary.
go to post Sylvain Guilbaud · Jul 8, 2024 Hello @Moussa SAMB the problem comes from the expired license of version IRIS 2023.1.1 published in July 2023. I invite you to use a more recent version to overcome the problem.
go to post Sylvain Guilbaud · Jul 8, 2024 Good point. I've added it back to iris.service file in the article.
go to post Sylvain Guilbaud · Jul 8, 2024 For my tests, I'm using Ubuntu 24.04 lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 24.04 LTS Release: 24.04 Codename: noble uname -a Linux ubuntu 6.8.0-36-generic #36-Ubuntu SMP PREEMPT_DYNAMIC Mon Jun 10 10:49:14 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
go to post Sylvain Guilbaud · Jul 8, 2024 Thank you @Jeffrey Drumm I've made several tests with User option and actually, even without this parameter, IRIS is started under the account provided during the installation : guilbaud 1776 1 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb -s/usr/irissys/mgr/ -w/usr/irissys/mgr/ -cc -B -C/usr/irissys/iris.cpf*IRIS guilbaud 1885 1776 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb WD guilbaud 1886 1776 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb GC guilbaud 1887 1776 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb JD guilbaud 1888 1776 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb AUX1 guilbaud 1889 1776 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb AUX3 guilbaud 1890 1776 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb AUX2 guilbaud 1891 1776 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb AUX5 guilbaud 1892 1776 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb AUX4 guilbaud 1893 1776 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb AUX6 guilbaud 1894 1776 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb AUX7 guilbaud 1895 1776 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb DBXD irisusr 2033 1 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb -s/usr/irissys/mgr -cj -p13 START^MONITOR irisusr 2041 1 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb -s/usr/irissys/mgr -cj -p13 START^CLNDMN irisusr 2106 1 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb -s/usr/irissys/mgr -cj -p13 START^LMFMON irisusr 2133 1 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb -s/usr/irissys/mgr -cj -p13 ^RECEIVE irisusr 2146 1 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb -s/usr/irissys/mgr -cj -p19 Master^%SYS.SERVER irisusr 2153 1 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb -s/usr/irissys/mgr -cj -p19 RunManager^%SYS.Task irisusr 2157 1 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb -s/usr/irissys/mgr -cj -p19 Start^%SYS.Monitor.Control irisusr 2178 1 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb -s/usr/irissys/mgr -cj -p19 RunDaemon^%SYS.WorkQueueMgr irisusr 2191 1 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb -s/usr/irissys/mgr -cj -p19 RunRemoteQueueDaemon^%SYS.WorkQueueMgr irisusr 2205 2178 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb -s/usr/irissys/mgr -cj -p24 startWork^%SYS.WorkQueueMgr irisusr 2213 2178 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb -s/usr/irissys/mgr -cj -p24 startWork^%SYS.WorkQueueMgr irisusr 2420 2178 0 13:26 ? 00:00:00 /usr/irissys/bin/irisdb -s/usr/irissys/mgr -cj -p24 startWork^%SYS.WorkQueueMgr
go to post Sylvain Guilbaud · Jul 5, 2024 + the IRIS 2024.1 release notes : Python BPL Editor WSGI Web Apps
go to post Sylvain Guilbaud · May 1, 2024 Thank you @Eduard Lebedyuk and particularly for your KafkaManager docker image 😃
go to post Sylvain Guilbaud · Apr 26, 2024 You can also use classes instead of routines. For instance : Class test.a Extends %RegisteredObject { ClassMethod run() As %Status { Set sc = $$$OK read !,"Enter the hour: ",hr read !,"Enter the minute: ",min read !,"Enter the second: ",sec do ..getInternalTime(hr,min,sec) /// Display some other internal time values : for a = 0,3600,43200,45296,86399 { w !,"Equivalent M time: ",a," (Time:",$zt(a),")",! } Return sc } ClassMethod getInternalTime(hr As %Integer, min As %Integer, sec As %Integer) As %Status { set sc=$$$OK set Mtime=$$ConvertToMTime(hr,min,sec) write !,"Equivalent M time: ",Mtime," (Time:",$zt(Mtime),")",! return sc } ClassMethod ConvertToMTime(h, m, s) As %Integer { Return (h*3600)+(m*60)+(s) } } Will give : USER>w ##class(test.a).run() Enter the hour: 12 Enter the minute: 34 Enter the second: 45 Equivalent M time: 45285 (Time:12:34:45) Equivalent M time: 0 (Time:00:00:00) Equivalent M time: 3600 (Time:01:00:00) Equivalent M time: 43200 (Time:12:00:00) Equivalent M time: 45296 (Time:12:34:56) Equivalent M time: 86399 (Time:23:59:59) 1
go to post Sylvain Guilbaud · Apr 26, 2024 Hi @James Rutledge welcome in our world ! Using this ROUTINE below : ROUTINE test.1 r !,"Enter the hour: ",hr r !,"Enter the minute: ",min r !,"Enter the second: ",sec s Mtime=$$ConvertToMTime(hr,min,sec) w !,"Equivalent M time: ",Mtime," (Time:",$zt(Mtime),")",! /// Display some other internal time values : for a = 0,3600,43200,45296,86399 { w !,"Equivalent M time: ",a," (Time:",$zt(a),")",! } q ConvertToMTime(h,m,s) q (h*3600)+(m*60)+(s) Will display the following result : USER>d ^test.1 Enter the hour: 12 Enter the minute: 34 Enter the second: 45 Equivalent M time: 45285 (Time:12:34:45) Equivalent M time: 0 (Time:00:00:00) Equivalent M time: 3600 (Time:01:00:00) Equivalent M time: 43200 (Time:12:00:00) Equivalent M time: 45296 (Time:12:34:56) Equivalent M time: 86399 (Time:23:59:59)
go to post Sylvain Guilbaud · Apr 25, 2024 Simply by using the Resource option in a CreateDatabase instruction of [Actions] section in a merge.cpf file : [Actions] CreateResource:Name=%DB_IRISAPP_DATA,Description="IRISAPP_DATA database" CreateDatabase:Name=IRISAPP_DATA,Directory=/usr/irissys/mgr/IRISAPP_DATA,Resource=%DB_IRISAPP_DATA CreateResource:Name=%DB_IRISAPP_CODE,Description="IRISAPP_CODE database" CreateDatabase:Name=IRISAPP_CODE,Directory=/usr/irissys/mgr/IRISAPP_CODE,Resource=%DB_IRISAPP_CODE CreateNamespace:Name=IRISAPP,Globals=IRISAPP_DATA,Routines=IRISAPP_CODE,Interop=1
go to post Sylvain Guilbaud · Apr 23, 2024 Hello @Alin Soare, Can you give us more details on your needs and what you are unable to do? The WRITE command is part of the basic instructions of IRIS (and even since the origin of the MUMPS language, pre-existing Caché, Ensemble, IRIS).
go to post Sylvain Guilbaud · Apr 22, 2024 By using your code in this business process, using this production, you can get the following Visual Trace :
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.