Created
March 26, 2022 03:55
-
-
Save brianpos/28f7b7fc15a5a1b7a8803b0c18e4a22a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Hl7.Fhir.Model; | |
using Hl7.Fhir.Rest; | |
using Hl7.FhirPath; | |
using System; | |
using System.Collections.Concurrent; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Linq; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using Task = System.Threading.Tasks.Task; | |
// using QuestionnaireSDC_Extensions; | |
namespace QForms.Helpers | |
{ | |
public class QuestionnaireResponse_Validator | |
{ | |
public QuestionnaireResponse_Validator() | |
{ | |
} | |
List<Task> AsyncValidations = new List<System.Threading.Tasks.Task>(); | |
ConcurrentQueue<OperationOutcome.IssueComponent> outcomeIssues = new ConcurrentQueue<OperationOutcome.IssueComponent>(); | |
CancellationToken _token; | |
public async Task<OperationOutcome> Validate(QuestionnaireResponse qr, Questionnaire q, CancellationToken token) | |
{ | |
_token = token; | |
// Check that the structure matches | |
ValidateItems("QuestionnaireResponse.item", qr.Item, q.Item, qr.Status); | |
// await for any background tasks to complete | |
if (AsyncValidations.Any()) | |
{ | |
Console.WriteLine("Waiting for asyc processes to complete.."); | |
await Task.WhenAll(AsyncValidations).ConfigureAwait(false); | |
Console.WriteLine("Async processing completed"); | |
} | |
// append all the outcomes into the output results | |
var outcome = new OperationOutcome(); | |
outcome.Issue.AddRange(outcomeIssues); | |
// and clear out the data | |
AsyncValidations.Clear(); | |
outcomeIssues.Clear(); | |
return outcome; | |
} | |
private void ValidateItems(string pathExpression, List<QuestionnaireResponse.ItemComponent> items, IEnumerable<Questionnaire.ItemComponent> itemDefinitions, QuestionnaireResponse.QuestionnaireResponseStatus? status) | |
{ | |
IEnumerable<QuestionnaireResponse.ItemComponent> itemsRemaining = items; | |
foreach (var itemDef in itemDefinitions) | |
{ | |
var itemsForItemDefinition = itemsRemaining.Where(i => i.LinkId == itemDef.LinkId); | |
itemsRemaining = itemsRemaining.Except(itemsForItemDefinition); | |
foreach (var item in itemsForItemDefinition) | |
{ | |
ValidateItem($"{pathExpression}[{items.IndexOf(item)}]", item, itemDef, status); | |
} | |
} | |
// Check if there are any items left that did not have a definition (as these are in error) | |
foreach (var item in itemsRemaining) | |
{ | |
ReportValidationMessage(ValidationResult.invalidLinkId, null, new[] { pathExpression }, null, item, null, null); | |
} | |
// Should we be checking the order of the items in the collection(s) too? | |
// Lloyd, yes we should be - that can help with the performance too (don't need to split/join items) | |
} | |
private void ValidateItem(string pathExpression, QuestionnaireResponse.ItemComponent item, Questionnaire.ItemComponent itemDef, QuestionnaireResponse.QuestionnaireResponseStatus? status) | |
{ | |
int answerIndex = 0; | |
foreach (var answer in item.Answer) | |
{ | |
var answerItemPathExpression = new[] { $"{pathExpression}.answer[{answerIndex}]" }; | |
// check that the datatypes for all the answers match the definition | |
ValidateItemTypeData(item, itemDef, answerIndex, answerItemPathExpression); | |
// Check for children of answers | |
if (answer.Item?.Any() == true) | |
{ | |
ValidateItems($"{pathExpression}.answer[{answerIndex}].item", answer.Item, itemDef.Item, status); | |
} | |
answerIndex++; | |
} | |
// check that all the invariants/extensions all pass | |
// ValidateInvartiants(item, itemDef); | |
// Check for children | |
if (itemDef.Item?.Any() == true) | |
{ | |
ValidateItems($"{pathExpression}.item", item.Item, itemDef.Item, status); | |
} | |
} | |
private void ValidateItemTypeData(QuestionnaireResponse.ItemComponent item, Questionnaire.ItemComponent itemDef, int answerIndex, string[] answerItemPathExpression) | |
{ | |
switch (itemDef.Type) | |
{ | |
case Questionnaire.QuestionnaireItemType.Choice: | |
if (item.Answer[answerIndex].Value is Coding coding) | |
{ | |
// validate the coding | |
ValidateCodingValue(item, itemDef, answerIndex, answerItemPathExpression, coding); | |
} | |
else | |
ReportValidationMessage(ValidationResult.invalidType, itemDef, answerItemPathExpression, null, item, answerIndex, null); | |
break; | |
case Questionnaire.QuestionnaireItemType.OpenChoice: | |
if (item.Answer[answerIndex].Value is Coding codingOpen) | |
{ | |
// validate the coding | |
ValidateCodingValue(item, itemDef, answerIndex, answerItemPathExpression, codingOpen); | |
} | |
else if (item.Answer[answerIndex].Value is FhirString strOpen) | |
{ | |
ValidateStringValue(false, item, itemDef, answerIndex, answerItemPathExpression, strOpen); | |
} | |
else | |
ReportValidationMessage(ValidationResult.invalidType, itemDef, answerItemPathExpression, null, item, answerIndex, null); | |
break; | |
} | |
} | |
private void ValidateCodingValue(QuestionnaireResponse.ItemComponent item, Questionnaire.ItemComponent itemDef, int answerIndex, string[] answerItemPathExpression, Coding coding) | |
{ | |
// Check if the value is in the actual valueset | |
// check cache, check in progress cache - check cache has a task for the lookup, so hook onto it too, include request id in the cache too | |
// call terminology server | |
// update cache | |
// return result | |
var ts = new FhirClient("https://sqlonfhir-r4.azurewebsites.net/fhir"); | |
Task validateCode = ts.ValidateCodeAsync(url: new FhirUri("http://hl7.org/fhir/ValueSet/jurisdiction"), version: new FhirString("4.0.1"), coding: coding) | |
.ContinueWith((result) => | |
{ | |
// Thread.Sleep(10000); | |
Console.WriteLine(ts.LastBodyAsText); | |
if (result.IsFaulted) | |
{ | |
Console.WriteLine(result.Exception.ToString()); | |
ReportValidationMessage(ValidationResult.unknown, itemDef, answerItemPathExpression, null, item, answerIndex, null); | |
return; | |
} | |
if (result.Result.Result.Value.Value != true) | |
{ | |
ReportValidationMessage(ValidationResult.minValue, itemDef, answerItemPathExpression, null, item, answerIndex, null); | |
} | |
}); | |
this.AsyncValidations.Add(validateCode); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment