Categories
Uncategorized

Setting Values on a myAvatar Form Using ScriptLink

There are a number of reasons that you might want to set or change the value of a field in myAvatar: Setting default values dynamically, storing a computed value, or even correcting or appending a value. In my ScriptLink work, I have had each of these use cases. In the previous articles, we looked at how to read field values. Now, let’s set them.

There are a number of reasons that you might want to set or change the value of a field in myAvatar: Setting default values dynamically, storing a computed value, or even correcting or appending a value. In my ScriptLink work, I have had each of these use cases. In the previous articles, we looked at how to read field values. Now, let’s set them.

The Specification

For our example today, we will mimic an assessment form that requires scoring that cannot be computed natively in myAvatar (at this time). Specifically, we will be calculating the area of a rectangle based on the length of two adjacent sides. Units of measure may not be critical for the calculation itself, but in practice we would want to determine this for reporting and evaluation purposes. Let’s pretend we are using centimeters.

Yes, not very clinical, but this article is emphasizing how to write a value over what that value is how it may be used for treatment, payment or other healthcare operations. An organization may want to compute and write the score this way, so that they don’t have to compute the score on each run of future reports.

Please note that when considering scoring copyrighted assessments you may need to get permission from the publisher before incorporating the scoring logic into ScriptLink. As a general rule, always check with the publisher before incorporating any copyrighted material into your EHR solution and honor their response.

The Unit Tests

Ok. So let’s create our Unit Tests.

[TestMethod]
public void Execute_GetAreaOfRectangleCommand_ReturnsOptionObject()
{
    // Arrange
    OptionObject expected = new OptionObject();

    OptionObject optionObject = new OptionObject();
    var command = new GetAreaOfRectangleCommand(optionObject);

    // Act
    var actual = (OptionObject)command.Execute();

    // Assert
    Assert.AreEqual(expected.GetType(), actual.GetType());
}

[TestMethod]
public void Execute_GetAreaOfRectangleCommand_ReturnsOptionObject2()
{
    // Arrange
    OptionObject2 expected = new OptionObject2();

    OptionObject2 optionObject2 = new OptionObject2();
    var command = new GetAreaOfRectangleCommand(optionObject2);

    // Act
    var actual = (OptionObject2)command.Execute();

    // Assert
    Assert.AreEqual(expected.GetType(), actual.GetType());
}

[TestMethod]
public void Execute_GetAreaOfRectangleCommand_ReturnsOptionObject2015()
{
    // Arrange
    OptionObject2015 expected = new OptionObject2015();

    OptionObject2015 optionObject2015 = new OptionObject2015();
    var command = new GetAreaOfRectangleCommand(optionObject2015);

    // Act
    var actual = command.Execute();

    // Assert
    Assert.AreEqual(expected.GetType(), actual.GetType());
}

[TestMethod]
public void Execute_GetAreaOfRectangleCommand_ReturnsExpectedValue()
{
    // Arrange
    Double expected = 4;
    // Sides
    FieldObject fieldObject01 = new FieldObject()
    {
        Enabled = "1",
        FieldNumber = "148.01",
        FieldValue = "2",
        Lock = "0",
        Required = "1"
    };
    FieldObject fieldObject02 = new FieldObject()
    {
        Enabled = "1",
        FieldNumber = "148.02",
        FieldValue = "2",
        Lock = "0",
        Required = "1"
    };
    // Area
    FieldObject fieldObject03 = new FieldObject()
    {
        Enabled = "1",
        FieldNumber = "148.03",
        FieldValue = "",
        Lock = "0",
        Required = "1"
    };
    List<FieldObject> fields = new List<FieldObject>() { fieldObject01, fieldObject02, fieldObject03 };
    RowObject rowObject = new RowObject() { Fields = fields };
    FormObject formObject = new FormObject() { CurrentRow = rowObject };
    OptionObject2015 optionObject2015 = new OptionObject2015()
    {
        Forms = new List<FormObject>()
        {
            formObject
        }
    };
    var command = new GetAreaOfRectangleCommand(optionObject2015);

    // Act
    var response = command.Execute();
    Double actual = 0;
    foreach (var form in response.Forms)
    {
        if (form.CurrentRow != null && form.CurrentRow.Fields != null)
        {
            foreach (var field in form.CurrentRow.Fields)
            {
                if (field.FieldNumber == "148.03")
                {
                    Double.TryParse(field.FieldValue, out actual);
                }
            }
        }
    }

    // Assert
    Assert.AreEqual(expected, actual);
}

We have the usual suspects. Does the command return the expected OptionObject formats? We also add a single test for getting the correct answer. Other tests we should write would look at invalid or missing values entered. What would be the expected response? To keep the article a little more concise I will just demonstrate this test.

Notice we have to write out the same reading process we used in the previous articles. When using the AvatarScriptLink.NET library we can reduce all of that to:

// AvatarScriptLink.NET Example
OptionObject2015 response = command.Execute();
Double.TryParse(response.getFieldValue("148.03", out double actual));

This is part of the reason I was motivated to create this library. I definitely recommend you consider use of that library or using your own to simplify your code. I digress. This article is not about that library, so let’s continue.

The test should fail because they cannot compile. So let’s create our Command.

The Command

We will create the command to throw NotImplementedException when executed. This should allow our code to compile and all tests to fail.

public class GetAreaOfRectangleCommand : IRunScriptCommand
{
    private static readonly Logger logger = LogManager.GetCurrentClassLogger();

    private readonly OptionObjectBase _optionObject;

    public GetAreaOfRectangleCommand(OptionObjectBase optionObject)
    {
        _optionObject = optionObject;
    }

    public IOptionObject2015 Execute()
    {
        logger.Debug("GetAreaOfRectangleCommand executed.");

        throw new NotImplementedException();
    }
}

Ok. Let’s run our tests. Did it compile successfully? Did the test fail as expected due to a NotImplementedException? Great. Now let’s get the values and score it.

To do this we are going read the values like before and then write the computed value. There are two basic techniques used to write values back to myAvatar: edit the existing OptionObject or create a new OptionObject specifically to return. Most ScriptLink-related training use the latter so that’s what we will do here. It typically looks something like this.

public IOptionObject2015 Execute()
{
    logger.Debug("GetAreaOfRectangleCommand executed.");

    double side1 = 0;
    double side2 = 0;
    FieldObject areaField = new FieldObject();
    RowObject areaRow = new RowObject();
    FormObject areaForm = new FormObject();
    OptionObject2015 returnOptionObject = new OptionObject2015();

    if (_optionObject.Forms != null)
    {
        foreach (var form in _optionObject.Forms)
        {
            if (form.CurrentRow != null && form.CurrentRow.Fields != null)
            {
                foreach (var field in form.CurrentRow.Fields)
                {
                    switch (field.FieldNumber)
                    {
                        case "148.01":
                            _ = Double.TryParse(field.FieldValue, out side1);
                            break;
                        case "148.02":
                            _ = Double.TryParse(field.FieldValue, out side2);
                            break;
                        case "148.03":
                            // Prepare for response if area can be calculated
                            areaField = field;
                            areaRow.RowId = form.CurrentRow.RowId;
                            areaForm.FormId = form.FormId;
                            areaForm.MultipleIteration = form.MultipleIteration;
                            break;
                    }
                }
            }
        }
    }
    double area = side1 * side2;
    if (area > 0)
    {
        areaField.FieldValue = area.ToString();
        areaRow.Fields.Add(areaField);
        areaRow.RowAction = "EDIT";
        areaForm.CurrentRow = areaRow;
    }
    returnOptionObject.Forms = new List<FormObject>() { areaForm };

    returnOptionObject.EntityID = _optionObject.EntityID;
    returnOptionObject.EpisodeNumber = _optionObject.EpisodeNumber;
    returnOptionObject.Facility = _optionObject.Facility;
    returnOptionObject.NamespaceName = _optionObject.NamespaceName;
    returnOptionObject.OptionId = _optionObject.OptionId;
    returnOptionObject.OptionStaffId = _optionObject.OptionStaffId;
    returnOptionObject.OptionUserId = _optionObject.OptionUserId;
    returnOptionObject.ParentNamespace = _optionObject.ParentNamespace;
    returnOptionObject.ServerName = _optionObject.ServerName;
    returnOptionObject.SystemCode = _optionObject.SystemCode;
    returnOptionObject.SessionToken = _optionObject.SessionToken;

    return returnOptionObject;
}

What you see is that we only return the forms, rows, and fields that are being modified. Additionally, mark the specific row we wish to modify with the “EDIT” row action.

I am going to make one minor change and leverage our ToReturnOptionObject() method. This saves us from a bunch of that code at the end. Additionally, it allows us to support more than the OptionObject2015.

public IOptionObject2015 Execute()
{
    logger.Debug("GetAreaOfRectangleCommand executed.");

    double side1 = 0;
    double side2 = 0;
    FieldObject areaField = new FieldObject();
    RowObject areaRow = new RowObject();
    FormObject areaForm = new FormObject();
    OptionObjectBase returnOptionObject = _optionObject.ToReturnOptionObject();

    if (_optionObject.Forms != null)
    {
        foreach (var form in _optionObject.Forms)
        {
            if (form.CurrentRow != null && form.CurrentRow.Fields != null)
            {
                foreach (var field in form.CurrentRow.Fields)
                {
                    switch (field.FieldNumber)
                    {
                        case "148.01":
                            _ = Double.TryParse(field.FieldValue, out side1);
                            break;
                        case "148.02":
                            _ = Double.TryParse(field.FieldValue, out side2);
                            break;
                        case "148.03":
                            // Prepare for response if area can be calculated
                            areaField = field;
                            areaRow.RowId = form.CurrentRow.RowId;
                            areaForm.FormId = form.FormId;
                            areaForm.MultipleIteration = form.MultipleIteration;
                            break;
                    }
                }
            }
        }
    }
    double area = side1 * side2;
    if (area > 0)
    {
        areaField.FieldValue = area.ToString();
        areaRow.Fields = new List<FieldObject>() { areaField };
        areaRow.RowAction = "EDIT";
        areaForm.CurrentRow = areaRow;
    }
    returnOptionObject.Forms = new List<FormObject>() { areaForm };

    return returnOptionObject;
}

Now let’s run our unit tests. Do they pass?

The CommandSelector

Now we want to update our CommandSelector. First, add the Unit Test.

[TestMethod]
public void GetCommand_GetAreaOfRectangle_ReturnsHelloWorld()
{
    // Arrange
    OptionObject2015 optionObject2015 = new OptionObject2015();
    string parameter = "GetAreaOfRectangle";
    GetAreaOfRectangleCommand expected = new GetAreaOfRectangleCommand(optionObject2015);

    // Act
    IRunScriptCommand actual = CommandSelector.GetCommand(optionObject2015, parameter);

    // Assert
    Assert.AreEqual(expected.GetType(), actual.GetType());
}

Next, the CommandSelector change.

public static IRunScriptCommand GetCommand(IOptionObject2015 iOptionObject, string parameter)
{
    OptionObjectBase optionObjectBase = (OptionObjectBase)iOptionObject;

    switch (parameter)
    {
        case "GetAreaOfRectangle":
            logger.Debug("Creating a GetAreaOfRectangleCommand.");
            return new GetAreaOfRectangleCommand(optionObjectBase);
        case "HasMrPinchyPet":
            logger.Debug("Creating a HasMrPinchyPetCommand.");
            return new HasMrPinchyPetCommand(optionObjectBase);
        case "HelloWorld":
            logger.Debug("Creating a HelloWorld command.");
            return new HelloWorld(optionObjectBase);
        case "IsDraft":
            logger.Debug("Creating an IsDraftCommand.");
            return new IsDraftCommand(optionObjectBase);
        default:
            logger.Warn("No command found matching parameter '" + (parameter ?? "") + "'. Creating a DefaultCommand.");
            return new DefaultCommand(optionObjectBase, parameter);
    }
}

Let’s run our tests again. Did they all pass? Great.

Last Test: SoapUI

Ok. Now that everything is hooked up, it is time to run our app and test it with SoapUI. I have covered this in several of the previous articles, so I will let you create your test and verify the response is what you expect. Here’s a response to my sample with the values 3 and 4 in fields 148.01 and 148.02.

Notice only the area field is returned. What happens if you leave one or both of the side fields empty? Or missing?

Closing Thoughts

We have seen how to modify the value of a field with ScriptLink. With this you could replace or append just about any field value. In this example, you would typically call this on a Command button or a Pre-File, but could also be call on the side fields to automatically calculate when the user tabs out.

Warning: Don’t try to modify a field value if that field triggered the call. That creates a loop and the request will fail.

This process can benefit from a lot of refactoring. What do you see? What tests would you add? Where could better error handling exist? How could the code be more readable?

Thanks for reading. I hope you found this article helpful and it helps you and your organization better serve our communities. Next week, we will modify the value of field in a multiple iteration table.

One reply on “Setting Values on a myAvatar Form Using ScriptLink”

Comments are closed.