Digital Patient Health Record Locker using Azure Blockchain Workbench

With Blockchain Workbench, you can define blockchain applications using configuration and writing smart contract code. In this blog I shall walk you through a set of generic steps to create and execute a blockchain application workflow in Azure Blockchain Workbench and then using those steps we shall create a Digital Patient Health Record Locker to help Clinics in the frontline of Covid19 maintain patient records securely and exchange them with authorized third parties.

Define a configuration file:

It contains configuration metadata defining the high-level workflows and interaction model of the blockchain application. 

  1. Application Metadata like name and description
  2. Application roles, defining the user roles who can act or participate within the blockchain application
  3. Workflows define one or more stages and actions of the contract.

Smart contract code file

Smart contracts represent the business logic of the blockchain application. Azure Blockchain workbench internally uses Ethereum for blockchain ledger.

Ethereum uses Solidity as its programming language. Logic is written in Solidity to enforce contract, contains state and functions to implement stages and actions of the smart contract.

Solidity code contains the following sections:

  1. Version Pragma:  Version of Solidity
  2. Contract header: Header of Smart Contract
  3. State Variables: State variables store values of the state for each contract instance.
  4. Constructor: The constructor defines input parameters for a new smart contract instance of a workflow. 
  5. Functions: Functions are the executable units of business logic within a contract. 

Deploy Azure Blockchain Workbench

  • Sign in to the Azure portal.
  • Select Create a resource in the upper left-hand corner of the Azure portal.
  • Select Blockchain > Azure Blockchain Workbench
  • Select OK to finish the basic setting configuration section.
  • In Advanced Settings, choose create a new blockchain network
  • Select Create to agree to the terms and deploy your Azure Blockchain Workbench.

After deployment is completed

  • Navigate to your resource group in Azure Portal
  • There are two resources with type App Service. Select the resource of type App Service without the “-api” suffix.
  • In the App Service Overview, copy the URL value, which represents the web URL to your deployed Blockchain Workbench.

Something like: https://CovidFrontlineblockchain-p6c7yh.azurewebsites.net

Azure AD must be configured to complete your Blockchain Workbench deployment.

  • In a browser, navigate to the https://CovidFrontlineblockchain-p6c7yh.azurewebsites.net
  • You’ll see instructions to set up Azure AD using Cloud Shell. Copy the command and launch Cloud Shell.
  • In Cloud Shell PowerShell environment, paste and run the command.
  • When prompted, enter the Azure AD tenant you want to use for Blockchain Workbench. 
  • You’ll be prompted to authenticate to the Azure AD tenant using a browser. Open the web URL in a browser, enter the code, and authenticate.
  • The script outputs several status messages. You get a SUCCESS status message if the tenant was successfully provisioned.
  • Navigate to the Blockchain Workbench URL. 
  • Select Accept to consent.
  • After consent, the Blockchain Workbench web app can be used.

Add blockchain application to Blockchain Workbench

  • In a web browser, navigate to the https://CovidFrontlineblockchain-p6c7yh.azurewebsites.net
  • Sign in as a Blockchain Workbench administrator.
  • Select Applications > New. The New application pane is displayed.
  • Select Upload the contract configuration > Browse to locate the PHRLocker.json configuration file you created.
  • Select Upload the contract code > Browse to locate the PHRLocker.sol smart contract code file.
  • Select Deploy to create the blockchain application based on the configuration and smart contract files.
  • When deployment is finished, the new application is displayed in Applications.

Add blockchain application members

  • Select Applications > PHR Locker!.
  • The number of members associated to the application is displayed in the upper right corner of the page. For a new application, the number of members will be zero.
  • Select the members link in the upper right corner of the page. A current list of members for the application is displayed.
  • In the membership list, select Add members.
  • Select or enter the member’s name you want to add.
  • Select the Role for the member.
  • Select Add to add the member with the associated role to the application.

Create new contract

  • In Blockchain Workbench application section, select the application tile that contains the contract you want to create.
  • To create a new contract, select New contract.
  • The New contract pane is displayed. Specify the initial parameters values. Select Create.
  • The newly created contract is displayed in the list with the other active contracts.

Take action on contract

  • Depending on the state the contract is in, members can take actions to transition to the next state of the contract.
  • In Blockchain Workbench application section, select the application tile that contains the contract to take the action.
  • Select the contract in the list. Details about the contract are displayed in different sections.
  • In the Action section, select Take action.
  • The details about the current state of the contract are displayed in a pane. Choose the action you want to take in the drop-down.
  • Select Take action to initiate the action.
  • If parameters are required for the action, specify the values for the action.
  • Select Take action to execute the action.

Now lets Kick the tires and light the fires !!

Using the steps defined in the sections above we shall now create a Digital Locker Application for holding patient health records.

PHR Digital Locker Application

Overview 

The PHR Digital Locker application expresses a workflow of sharing digitally locked patient record files where the Patient controls the access to his/her health record files. We illustrate Digital Locker using an example of a Patient performing access control to their document held by a Insurance Provider.  The state transition diagram below shows the interactions among the states in this workflow. 

Application Roles 

——————

NameDescription
 
PatientThe owner of the digital asset.                  
InsuranceAgentThe keeper of the digital asset.
ClinicAn Entity requesting access to the digital asset. 
CurrentAuthorizedUserAn Entity currently authorized to access the digital asset. 

States 

——————

StateDescription
 
Requested             Indicates patient’s request to make the digital asset available. 
DocumentReviewIndicates that the insurance agent has reviewed the patient’s request.                                           
AvailableToShareIndicates that the insurance agent has uploaded the digital asset and the digital asset is available for sharing
SharingWithThirdPartyIndicates that the patient is reviewing a Clinic’s request to access the digital asset.          
Terminated            Indicates termination of sharing the digital asset.                                                       

State Transition Diagram (Fig. 1)

An instance of the PHR (Patient Health Records) Digital Locker application’s workflow starts in the Requested state when a Patient requests their Insurance Agent to begin a process of sharing a document held by the Insurance Provider. 

An InsuranceAgent causes the state to transition to DocumentReview by calling the function BeginReviewProcess indicating that the process to review the request has begun. 

Once the review is complete, the InsuranceAgent then makes the document available by uploading the documents. 

The AvailableToShare state can be thought of a perpetual state, more on this in a bit. Once the document is available to share, the document can be shared either with a Clinic that the patient has identified or any random Clinic requestor. 

If the patient specifies the Clinic requestor, then the state transitions from AvailableToShare to SharingWithThirdParty.  If a random Clinic requestor needs access to the document, then that Clinic requestor first requests access to the document.  At this point, the patient can either accept the request and grant access or reject the request. 

If the patient rejects the request to the random Clinic requestor, then the state goes back to AvailableToShare.  If the patient accepts the request to allow the random Clinic request to access the document, then the state transitions to SharingWithThirdParty. 

Once the Clinic requestor is done with the document, they can release the lock to the document and the state transitions to AvailableToShare.  The patient can also cause the state to transition from SharingWithThirdParty to AvailableToShare when they revoke access from the third-party (Clinic) requestor. 

Finally, at any time during these transitions the insurance agent can decide to terminate the sharing of the document once the document becomes available to share. 

The happy path shown in the state transition diagram (Fig. 1) traces a path where the patient grants access to a random third party (Clinic). 

Some Screenshots

Workbench home

PHR Locker Smart Contract

Adding Users

Taking Action

Ledger Entries

PHRLocker.json (Configuration File)


{
  "ApplicationName": "PHRLocker",
  "DisplayName": "PHR Locker",
  "Description": "...",
  "ApplicationRoles": [
    {
      "Name": "Patient",
      "Description": "..."
    },
    {
      "Name": "InsuranceAgent",
      "Description": "..."
    },
    {
      "Name": "Clinic",
      "Description": "..."
    },
    {
      "Name": "CurrentAuthorizedUser",
      "Description": "..."
    }
  ],
  "Workflows": [
    {
      "Name": "PHRLocker",
      "DisplayName": "PHR Locker",
      "Description": "...",
      "Initiators": [
        "Patient"
      ],
      "StartState": "Requested",
      "Properties": [
        {
          "Name": "State",
          "DisplayName": "State",
          "Description": "Holds the state of the contract",
          "Type": {
            "Name": "state"
          }
        },
        {
          "Name": "Patient",
          "DisplayName": "Patient",
          "Description": "...",
          "Type": {
            "Name": "Patient"
          }
        },
        {
          "Name": "InsuranceAgent",
          "DisplayName": "Insurance Agent",
          "Description": "...",
          "Type": {
            "Name": "InsuranceAgent"
          }
        },
        {
          "Name": "LockerFriendlyName",
          "DisplayName": "Locker Friendly Name",
          "Description": "...",
          "Type": {
            "Name": "string"
          }
        },
        {
          "Name": "LockerIdentifier",
          "DisplayName": "Locker Identifier",
          "Description": "...",
          "Type": {
            "Name": "string"
          }
        },
        {
          "Name": "CurrentAuthorizedUser",
          "DisplayName": "Current Authorized User",
          "Description": "...",
          "Type": {
            "Name": "CurrentAuthorizedUser"
          }
        },
        {
          "Name": "ExpirationDate",
          "DisplayName": "Expiration Date",
          "Description": "...",
          "Type": {
            "Name": "string"
          }
        },
        {
          "Name": "Image",
          "DisplayName": "Image",
          "Description": "...",
          "Type": {
            "Name": "string"
          }
        },
        {
          "Name": "Clinic",
          "DisplayName": "Clinic",
          "Description": "...",
          "Type": {
            "Name": "Clinic"
          }
        },
        {
          "Name": "IntendedPurpose",
          "DisplayName": "Intended Purpose",
          "Description": "...",
          "Type": {
            "Name": "string"
          }
        },
        {
          "Name": "LockerStatus",
          "DisplayName": "Locker Status",
          "Description": "...",
          "Type": {
            "Name": "string"
          }
        },
        {
          "Name": "RejectionReason",
          "DisplayName": "Rejection Reason",
          "Description": "...",
          "Type": {
            "Name": "string"
          }
        }
      ],
      "Constructor": {
        "Parameters": [
          {
            "Name": "lockerFriendlyName",
            "Description": "...",
            "DisplayName": "Locker Friendly Name",
            "Type": {
              "Name": "string"
            }
          },
          {
            "Name": "insuranceAgent",
            "Description": "...",
            "DisplayName": "Insurance Agent",
            "Type": {
              "Name": "InsuranceAgent"
            }
          }
        ]
      },
      "Functions": [
        {
          "Name": "BeginReviewProcess",
          "DisplayName": "Begin Review Process",
          "Description": "...",
          "Parameters": []
        },
        {
          "Name": "RejectApplication",
          "DisplayName": "Reject Application",
          "Description": "...",
          "Parameters": [
            {
              "Name": "rejectionReason",
              "Description": "...",
              "DisplayName": "Rejection Reason",
              "Type": {
                "Name": "string"
              }
            }
          ]
        },
        {
          "Name": "UploadDocuments",
          "DisplayName": "Documents Upload Placeholder",
          "Description": "...",
          "Parameters": [
            {
              "Name": "lockerIdentifier",
              "Description": "...",
              "DisplayName": "Locker Identifier Placeholder",
              "Type": {
                "Name": "string"
              }
            },
            {
              "Name": "image",
              "Description": "...",
              "DisplayName": "Image Upload Placeholder",
              "Type": {
                "Name": "string"
              }
            }
          ]
        },
        {
          "Name": "ShareWithThirdParty",
          "DisplayName": "Share With Third Party",
          "Description": "...",
          "Parameters": [
            {
              "Name": "clinic",
              "Description": "...",
              "DisplayName": "Clinic",
              "Type": {
                "Name": "Clinic"
              }
            },
            {
              "Name": "expirationDate",
              "Description": "...",
              "DisplayName": "Expiration Date Placeholder",
              "Type": {
                "Name": "string"
              }
            },
            {
              "Name": "intendedPurpose",
              "Description": "...",
              "DisplayName": "Intended Purpose",
              "Type": {
                "Name": "string"
              }
            }
          ]
        },
        {
          "Name": "AcceptSharingRequest",
          "DisplayName": "Accept Sharing Request",
          "Description": "...",
          "Parameters": []
        },
        {
          "Name": "RejectSharingRequest",
          "DisplayName": "Reject Sharing Request",
          "Description": "...",
          "Parameters": []
        },
        {
          "Name": "RequestLockerAccess",
          "DisplayName": "Request Locker Access",
          "Description": "...",
          "Parameters": [
            {
              "Name": "intendedPurpose",
              "Description": "...",
              "DisplayName": "Intended Purpose",
              "Type": {
                "Name": "string"
              }
            }
          ]
        },
        {
          "Name": "ReleaseLockerAccess",
          "DisplayName": "Release Locker Access",
          "Description": "...",
          "Parameters": []
        },
        {
          "Name": "RevokeAccessFromThirdParty",
          "DisplayName": "Revoke Access From Third Party",
          "Description": "...",
          "Parameters": []
        },
        {
          "Name": "Terminate",
          "DisplayName": "Terminate",
          "Description": "...",
          "Parameters": []
        }
      ],
      "States": [
        {
          "Name": "Requested",
          "DisplayName": "Requested",
          "Description": "...",
          "PercentComplete": 0,
          "Style": "Success",
          "Transitions": [
            {
              "AllowedRoles": [
                "InsuranceAgent"
              ],
              "AllowedInstanceRoles": [],
              "Description": "...",
              "Function": "BeginReviewProcess",
              "NextStates": [
                "DocumentReview"
              ],
              "DisplayName": "Begin Review Process"
            }
          ]
        },
        {
          "Name": "DocumentReview",
          "DisplayName": "DocumentReview",
          "Description": "...",
          "PercentComplete": 20,
          "Style": "Success",
          "Transitions": [
            {
              "AllowedRoles": [],
              "AllowedInstanceRoles": [ "InsuranceAgent" ],
              "Description": "...",
              "Function": "UploadDocuments",
              "NextStates": [ "AvailableToShare" ],
              "DisplayName": "Upload Documents Placeholder"
            }
          ]
        },
        {
          "Name": "AvailableToShare",
          "DisplayName": "Available To Share",
          "Description": "...",
          "PercentComplete": 30,
          "Style": "Success",
          "Transitions": [
            {
              "AllowedRoles": [],
              "AllowedInstanceRoles": [ "Patient" ],
              "Description": "...",
              "Function": "ShareWithThirdParty",
              "NextStates": ["SharingWithThirdParty" ],
              "DisplayName": "Share With Third Party"
            },
            {
              "AllowedRoles": [],
              "AllowedInstanceRoles": [ "Patient" ],
              "Description": "...",
              "Function": "Terminate",
              "NextStates": [ "Terminated" ],
              "DisplayName": "Terminate"
            },
            {
              "AllowedRoles": [ "Clinic" ],
              "AllowedInstanceRoles": [],
              "Description": "...",
              "Function": "RequestLockerAccess",
              "NextStates": [ "SharingRequestPending" ],
              "DisplayName": "Request Locker Access"
            }
          ]
        },
        {
          "Name": "SharingRequestPending",
          "DisplayName": "Sharing Request Pending",
          "Description": "...",
          "PercentComplete": 40,
          "Style": "Success",
          "Transitions": [
            {
              "AllowedRoles": [],
              "AllowedInstanceRoles": [ "Patient" ],
              "Description": "...",
              "Function": "AcceptSharingRequest",
              "NextStates": [
                "SharingWithThirdParty"
              ],
              "DisplayName": "Accept Sharing Request"
            },
            {
              "AllowedRoles": [],
              "AllowedInstanceRoles": [ "Patient" ],
              "Description": "...",
              "Function": "RejectSharingRequest",
              "NextStates": [ "AvailableToShare" ],
              "DisplayName": "Reject Sharing Request"
            },
            {
              "AllowedRoles": [],
              "AllowedInstanceRoles": [ "Patient" ],
              "Description": "...",
              "Function": "Terminate",
              "NextStates": [ "Terminated" ],
              "DisplayName": "Terminate"
            }
          ]
        },
        {
          "Name": "SharingWithThirdParty",
          "DisplayName": "Sharing With Third Party",
          "Description": "...",
          "PercentComplete": 45,
          "Style": "Success",
          "Transitions": [
            {
              "AllowedRoles": [],
              "AllowedInstanceRoles": [ "Patient" ],
              "Description": "...",
              "Function": "RevokeAccessFromThirdParty",
              "NextStates": [ "AvailableToShare" ],
              "DisplayName": "Revoke Access From Third Party"
            },
            {
              "AllowedRoles": [],
              "AllowedInstanceRoles": [ "Patient" ],
              "Description": "...",
              "Function": "Terminate",
              "NextStates": [ "Terminated" ],
              "DisplayName": "Terminate"
            },
            {
              "AllowedRoles": [],
              "AllowedInstanceRoles": [ "Clinic" ],
              "Description": "...",
              "Function": "ReleaseLockerAccess",
              "NextStates": [ "AvailableToShare" ],
              "DisplayName": "Release Locker Access"
            }
          ]
        },
        {
          "Name": "Terminated",
          "DisplayName": "Terminated",
          "Description": "...",
          "PercentComplete": 100,
          "Style": "Failure",
          "Transitions": []
        }
      ]
    }
  ]
}

PHRLocker.sol (Smartcontract code File)


pragma solidity >=0.4.25 <0.6.0;

contract PHRLocker
{
    enum StateType { Requested, DocumentReview, AvailableToShare, SharingRequestPending, SharingWithThirdParty, Terminated }
    address public Patient;
    address public InsuranceAgent;
    string public LockerFriendlyName;
    string public LockerIdentifier;
    address public CurrentAuthorizedUser;
    string public ExpirationDate;
    string public Image;
    address public Clinic;
    string public IntendedPurpose;
    string public LockerStatus;
    string public RejectionReason;
    StateType public State;

    constructor(string memory lockerFriendlyName, address insuranceAgent) public
    {
        Patient = msg.sender;
        LockerFriendlyName = lockerFriendlyName;

        State = StateType.DocumentReview; //////////////// should be StateType.Requested?

        InsuranceAgent = insuranceAgent;
    }

    function BeginReviewProcess() public
    {
        /* Need to update, likely with registry to confirm sender is agent
        Also need to add a function to re-assign the agent.
        */
        if (Patient == msg.sender)
        {
            revert();
        }
        InsuranceAgent = msg.sender;

        LockerStatus = "Pending";
        State = StateType.DocumentReview;
    }

    function RejectApplication(string memory rejectionReason) public
    {
        if (InsuranceAgent != msg.sender)
        {
            revert();
        }

        RejectionReason = rejectionReason;
        LockerStatus = "Rejected";
        State = StateType.DocumentReview;
    }

    function UploadDocuments(string memory lockerIdentifier, string memory image) public
    {
        if (InsuranceAgent != msg.sender)
        {
            revert();
        }
        LockerStatus = "Approved";
        Image = image;
        LockerIdentifier = lockerIdentifier;
        State = StateType.AvailableToShare;
    }

    function ShareWithThirdParty(address clinic, string memory expirationDate, string memory intendedPurpose) public
    {
        if (Patient != msg.sender)
        {
            revert();
        }

        Clinic = clinic;
        CurrentAuthorizedUser = Clinic;

        LockerStatus = "Shared";
        IntendedPurpose = intendedPurpose;
        ExpirationDate = expirationDate;
        State = StateType.SharingWithThirdParty;
    }

    function AcceptSharingRequest() public
    {
        if (Patient != msg.sender)
        {
            revert();
        }

        CurrentAuthorizedUser = Clinic;
        State = StateType.SharingWithThirdParty;
    }

    function RejectSharingRequest() public
    {
        if (Patient != msg.sender)
        {
            revert();
        }
        LockerStatus = "Available";
        CurrentAuthorizedUser = 0x0000000000000000000000000000000000000000;
        State = StateType.AvailableToShare;
    }

    function RequestLockerAccess(string memory intendedPurpose) public
    {
        if (Patient == msg.sender)
        {
            revert();
        }

        Clinic = msg.sender;
        IntendedPurpose = intendedPurpose;
        State = StateType.SharingRequestPending;
    }

    function ReleaseLockerAccess() public
    {

        if (CurrentAuthorizedUser != msg.sender)
        {
            revert();
        }
        LockerStatus = "Available";
        Clinic = 0x0000000000000000000000000000000000000000;
        CurrentAuthorizedUser = 0x0000000000000000000000000000000000000000;
        IntendedPurpose = "";
        State = StateType.AvailableToShare;
    }
    
    function RevokeAccessFromThirdParty() public
    {
        if (Patient != msg.sender)
        {
            revert();
        }
        LockerStatus = "Available";
        CurrentAuthorizedUser = 0x0000000000000000000000000000000000000000;
        State = StateType.AvailableToShare;
    }

    function Terminate() public
    {
        if (Patient != msg.sender)
        {
            revert();
        }
        CurrentAuthorizedUser = 0x0000000000000000000000000000000000000000;
        State = StateType.Terminated;
    }
}

References :

  1. https://docs.microsoft.com/en-us/azure/blockchain/workbench/create-app
  2. https://github.com/Azure-Samples/blockchain/tree/master/blockchain-workbench/application-and-smart-contract-samples

Disclaimer :

What is illustrated above is just the Hello World of Patient Medical record. PHRs typically contain all of the following content and much more :

  • Surgical history
  • Obstetric history
  • Medications and medical allergies
  • Family history
  • Social history
  • Habits
  • Immunization history
  • Growth chart and developmental history
  • Medical encounters
  • Chief complaint
  • History of the present illness
  • Physical examination
  • Assessment and plan
  • Orders and prescriptions
  • Progress notes
  • Test results
  • ………………

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: