Advanced agent code sample
You can develop agents that perform specialized tasks, such as querying a queue table for jobs or setting field values. This advanced sample code illustrates how an agent interacts with the PatientProcessingQueue table. It uses the Relativity Services API to query for patient data. It also shows how to use the client-side proxy to connect to the Services API. To download the Patient Tracker application that uses this custom code or to view it in a Visual Studio solution, see Patient Tracker application.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 | using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Data; using System.Data.SqlClient; using System.Text.RegularExpressions; namespace PatientTracker.Agents { /// <summary> /// This agent queries the PatientProcessingQueue table in the EDDS database for patients that need to be processed in workspaces. /// After picking up a job from the queue, the agent uses the Services API to query for more metadata in the workspace. ///It also determines if the patient notes contains specific symptoms. It then sets a field indicating whether the patient requires further evaluation. /// </summary> [kCura.Agent.CustomAttributes.Name( "Patient Notes Processing Agent" )] [System.Runtime.InteropServices.Guid( "3dc132a6-ba0f-448a-bc10-1667fa04b7cd" )] public class PatientNotesProcessingAgent : kCura.Agent.AgentBase { public static readonly String[] SYMPTOMS_TO_FIND = new String[] { "bumps" , "fever" , "lethargic" , "fatigued" , "rash" , "cough" , "headache" }; public static readonly Guid PATIENT_NOTES_FIELD_GUID = new Guid( "5702F31B-48AC-4E2A-9000-A48B3E3ABDBF" ); public static readonly Guid REQUIRES_FURTHER_EVALUATION_FIELD_GUID = new Guid( "4B247F25-966D-4BFB-AD6F-2647D93B7DA4" ); public static readonly Guid PATIENT_RDO_GUID = new Guid( "EDC3AF33-7D25-4897-8285-6E36EA1D4AAE" ); private static Int32 ErrorCount { get ; set ; } public override void Execute() { PatientJobDTO patientToProcessJobDTO = null ; Int32 keySymptomsFound; //Get the current Agent artifactID. Int32 agentArtifactID = this .AgentID; //Get the EDDS database context. Relativity.API.IDBContext eddsDBContext = this .Helper.GetDBContext(-1); try { //Get the next available job from the queue. this .RaiseMessage( "Looking for the next job in the queue" , 10); patientToProcessJobDTO = GetNextPatientJob(eddsDBContext, agentArtifactID); //If a job needs processing, enter the loop and start processing work until there are no open jobs left. while (patientToProcessJobDTO != null ) { this .RaiseMessage(String.Format( "Beginning work on Patient ArtifactID: {0} in Workspace: {1}" , patientToProcessJobDTO.PatientArtifactID, patientToProcessJobDTO.WorkspaceArtifactID), 10); //Reset the key symptoms found count. keySymptomsFound = 0; String patientNotes = null ; //Construct an instance of RSAPIClient using the service account identity. using (kCura.Relativity.Client.IRSAPIClient proxy = this .Helper.GetServicesManager().CreateProxy<kCura.Relativity.Client.IRSAPIClient>(Relativity.API.ExecutionIdentity.System)) { //Set the workspace artifactID from the job. proxy.APIOptions.WorkspaceID = patientToProcessJobDTO.WorkspaceArtifactID; //Create the patientRDO to be populated with the patient artifactID. kCura.Relativity.Client.DTOs.RDO patientRDOInstance = new kCura.Relativity.Client.DTOs.RDO(patientToProcessJobDTO.PatientArtifactID); //Set the artifact type GUID of the Patient Object Type. patientRDOInstance.ArtifactTypeGuids.Add(PATIENT_RDO_GUID); //Add the fields that you want pulled back during the read operation. patientRDOInstance.Fields.Add( new kCura.Relativity.Client.DTOs.FieldValue(PATIENT_NOTES_FIELD_GUID)); //Get the patient notes for the patient to be processed. patientNotes = proxy.Repositories.RDO.Read(patientRDOInstance) //Perform the read operation .Results[0] //Get the first item in the results array .Artifact //Get the base artifact .Fields.Where(field => field.Guids.Contains(PATIENT_NOTES_FIELD_GUID)).Single() //Get the field from the Fields collection .ValueAsLongText; //Return the result as a string } this .RaiseMessage( "Beginning searching for key symptoms" , 1); //In the patient notes, look for the key symptoms in the text. //Loop through each key symptom in the array. for (Int32 i = 0; i < SYMPTOMS_TO_FIND.Length; i++) { String currentSymptom = SYMPTOMS_TO_FIND[i]; //Run a regex expression to determine if there are any matches for this symptom. Match match = Regex.Match(patientNotes, currentSymptom, System.Text.RegularExpressions.RegexOptions.IgnoreCase); if (match.Success == true ) { keySymptomsFound += 1; //If you find three of the key symptoms, break out of the loop. if (keySymptomsFound == 3) { break ; } } } Boolean requiresFurtherEvaluation = false ; //If multiple key symptoms have been found in the patient notes, set the Requires Further Evaluation field. if (keySymptomsFound >= 3) { requiresFurtherEvaluation = true ; } this .RaiseMessage(String.Format( "Updating Requires Further Evaluation field for Patient ArtifactID: {0} in Workspace: {1}" , patientToProcessJobDTO.PatientArtifactID, patientToProcessJobDTO.WorkspaceArtifactID), 10); using (kCura.Relativity.Client.IRSAPIClient client = this .Helper.GetServicesManager().CreateProxy<kCura.Relativity.Client.IRSAPIClient>(Relativity.API.ExecutionIdentity.System)) { client.APIOptions.WorkspaceID = patientToProcessJobDTO.WorkspaceArtifactID; //Create a new patientRDO to update. kCura.Relativity.Client.DTOs.RDO patientToUpdate = new kCura.Relativity.Client.DTOs.RDO(patientToProcessJobDTO.PatientArtifactID); //Set the artifact type GUID of the Patient Object Type. patientToUpdate.ArtifactTypeGuids.Add(PATIENT_RDO_GUID); //Add the fields that you want updated during the request. patientToUpdate.Fields.Add( new kCura.Relativity.Client.DTOs.FieldValue(REQUIRES_FURTHER_EVALUATION_FIELD_GUID, requiresFurtherEvaluation)); //Update the record in the workspace. client.Repositories.RDO.UpdateSingle(patientToUpdate); } this .RaiseMessage(String.Format( "Completed work on Patient ArtifactID: {0} in Workspace: {1}" , patientToProcessJobDTO.PatientArtifactID, patientToProcessJobDTO.WorkspaceArtifactID), 10); //Delete the job from the database. DeleteJob(eddsDBContext, patientToProcessJobDTO); //Get the next job from the queue if any exist. patientToProcessJobDTO = GetNextPatientJob(eddsDBContext, agentArtifactID); } } catch (System.Exception ex) { //If an exception occurred while processing a job, update the status and error message of the job. if (patientToProcessJobDTO != null ) { patientToProcessJobDTO.Status = JobStatus.Error; patientToProcessJobDTO.ErrorMessage = ex.ToString(); UpdateJobStatus(eddsDBContext, patientToProcessJobDTO); } //Increment the error count. ErrorCount += 1; //If the agent has failed 5 times, a serious issue may have occurred. Raise an error and shut it down. if (ErrorCount >= 5) { RaiseError( "Agent is shutting down because it has reached the error limit" , ex.ToString()); } } } /// <summary> /// Return the name of the agent that appears in the debugging Winform application only. /// Use description class attribute to display the description in Relativity. /// </summary> public override string Name { get { return "Patient Notes Processing Agent" ; } } /// <summary> /// Updates the job row in the PatientProcessingQueue table in the EDDS database. /// </summary> /// <param name="eddsDBContext">A properly constructed EDDS database context</param> /// <param name="patientJobDTO">The job DTO to be updated</param> private static void UpdateJobStatus(Relativity.API.IDBContext eddsDBContext, PatientJobDTO patientJobDTO) { String updateSQL = @" UPDATE [PatientProcessingQueue] SET [Status] = @status, [ErrorMessage] = @errorMessage, [AgentArtifactID] = NULL WHERE [PatientArtifactID] = @patientArtifactID AND [WorkspaceArtifactID] = @workspaceArtifactID " ; SqlParameter patientArtifactIDParam = new SqlParameter( "@patientArtifactID" , SqlDbType.Int); patientArtifactIDParam.Value = patientJobDTO.PatientArtifactID; SqlParameter workspaceArtifactIDParam = new SqlParameter( "@workspaceArtifactID" , SqlDbType.Int); workspaceArtifactIDParam.Value = patientJobDTO.WorkspaceArtifactID; SqlParameter statusParam = new SqlParameter( "@status" , SqlDbType.Int); statusParam.Value = (Int32)patientJobDTO.Status; SqlParameter errorMessageParam = new SqlParameter( "@errorMessage" , SqlDbType.NVarChar); errorMessageParam.Value = ( object )patientJobDTO.ErrorMessage ?? DBNull.Value; eddsDBContext.ExecuteNonQuerySQLStatement(updateSQL, new SqlParameter[] { patientArtifactIDParam, workspaceArtifactIDParam, statusParam, errorMessageParam }); } /// <summary> /// Queries the PatientProcessingQueue table in the EDDS database to find any jobs that are already assigned to this agent, or gets the next available job ordered by ascending ID. /// </summary> /// <param name="eddsDBContext">A properly constructed SQL database context for the EDDS database</param> /// <param name="agentArtifactID">ArtifactID for the current agent that is executing</param> /// <returns>A properly constructed PatientJobDTO or null if there are no jobs to be processed</returns> private static PatientJobDTO GetNextPatientJob(Relativity.API.IDBContext eddsDBContext, Int32 agentArtifactID) { PatientJobDTO retVal = null ; String nextJobSQL = @" DECLARE @nextJobID INT BEGIN TRAN Tran1 SET @nextJobID = (SELECT TOP 1 [ID] FROM [PatientProcessingQueue] WITH(UPDLOCK, READPAST) WHERE [AgentArtifactID] = @agentArtifactID AND [Status] = 1) IF (@nextJobID IS NULL) BEGIN SET @nextJobID = (SELECT TOP 1 [ID] FROM [PatientProcessingQueue] WITH(UPDLOCK, READPAST) WHERE [AgentArtifactID] IS NULL AND [Status] = 0 ORDER BY [ID] ASC) UPDATE [PatientProcessingQueue] SET [AgentArtifactID] = @agentArtifactID, [Status] = 1 WHERE [ID] = @nextJobID END COMMIT TRAN SELECT * FROM [PatientProcessingQueue] WHERE [ID] = @nextJobID" ; SqlParameter agentArtifactIDParam = new SqlParameter( "@agentArtifactID" , SqlDbType.Int); agentArtifactIDParam.Value = agentArtifactID; //Query the database for the next job. DataTable nextJobTable = eddsDBContext.ExecuteSqlStatementAsDataTable(nextJobSQL, new SqlParameter[] { agentArtifactIDParam }); //If the query returns any rows, create a DTO to return. if (nextJobTable.Rows.Count > 0) { DataRow nextJobRow = nextJobTable.Rows[0]; retVal = new PatientJobDTO(); retVal.PatientArtifactID = Convert.ToInt32(nextJobRow[ "PatientArtifactID" ]); retVal.WorkspaceArtifactID = Convert.ToInt32(nextJobRow[ "WorkspaceArtifactID" ]); retVal.Status = (JobStatus)Convert.ToInt32(nextJobRow[ "Status" ]); } return retVal; } /// <summary> /// Delete the patient job from the PatientProcessingQueue table in the EDDS database. /// </summary> /// <param name="eddsDBContext">A properly constructed SQL database context for the EDDS database.</param> /// <param name="patientJobDTO">The patient job DTO to be deleted.</param> private static void DeleteJob(Relativity.API.IDBContext eddsDBContext, PatientJobDTO patientJobDTO) { String deleteSQL = "DELETE FROM [PatientProcessingQueue] WHERE [PatientArtifactID] = @patientArtifactID AND [WorkspaceArtifactID] = @workspaceArtifactID" ; SqlParameter patientArtifactIDParam = new SqlParameter( "@patientArtifactID" , SqlDbType.Int); patientArtifactIDParam.Value = patientJobDTO.PatientArtifactID; SqlParameter workspaceArtifactIDParam = new SqlParameter( "@workspaceArtifactID" , SqlDbType.Int); workspaceArtifactIDParam.Value = patientJobDTO.WorkspaceArtifactID; eddsDBContext.ExecuteNonQuerySQLStatement(deleteSQL, new SqlParameter[] { patientArtifactIDParam, workspaceArtifactIDParam }); } } public class PatientJobDTO { public Int32 PatientArtifactID { get ; set ; } public Int32 WorkspaceArtifactID { get ; set ; } public JobStatus Status { get ; set ; } public String ErrorMessage { get ; set ; } } public enum JobStatus { NotStarted = 0, InProgress = 1, Error = 2 } } |