Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

/**
* Builds the single pre-qualification {@link OperationOutcome} written to the patient NDJSON: one issue
* per <em>unacceptable</em> category (a category with {@code acceptable=false}) that has at least one
* per <em>submitted</em> category (a category with {@code submit=true}) that has at least one
* finding. See LEGLINK-425.
*/
@Component
Expand Down Expand Up @@ -55,10 +55,10 @@ public record MeasureReportRef(int index, String id) {
* @param results the patient's categorized validation results
* @param measureReport the patient's MeasureReport in the submission bundle (may be null)
* @param writeExpressions when false, {@code expression[]} is omitted from every issue
* @return the OperationOutcome, or {@link Optional#empty()} when no unacceptable-category findings exist
* @return the OperationOutcome, or {@link Optional#empty()} when no submitted-category findings exist
*/
public Optional<OperationOutcome> build(List<Result> results, MeasureReportRef measureReport, boolean writeExpressions) {
// Group findings by unacceptable category (acceptable == false); a Result may map to several
// Group findings by submitted category (submit == true); a Result may map to several
// categories. Keyed by category id, not by the Category entity: Category defines no
// equals/hashCode, so two instances of the same logical category (loaded in different
// persistence contexts, say) would otherwise land in separate groups and emit a duplicate issue
Expand All @@ -71,7 +71,7 @@ public Optional<OperationOutcome> build(List<Result> results, MeasureReportRef m
continue;
}
for (Category category : categories) {
if (!category.isAcceptable()) {
if (category.isSubmit()) {
byCategoryId.computeIfAbsent(category.getId(), id -> new ArrayList<>()).add(result);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ protected void process(ConsumerRecord<ReadyForValidation.Key, ReadyForValidation
}

/**
* When enabled, builds the pre-qualification OperationOutcome for the patient's unacceptable-category
* When enabled, builds the pre-qualification OperationOutcome for the patient's submitted-category
* findings and appends it to the same patient NDJSON blob in ABS. No-op when the flag is off, when
* there is no blob storage or payload URI (e.g. local/dev), or when there are no unacceptable findings.
* there is no blob storage or payload URI (e.g. local/dev), or when there are no submitted findings.
*/
private void appendPreQualOperationOutcome(Bundle bundle, List<Result> results, String payloadUri) {
if (!preQualificationConfig.isWritePreQualOperationOutcome()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,14 @@ void setUp() {
// -------------------------------------------------------------------------

private Category category(String id, boolean acceptable) {
return category(id, acceptable, true);
}

private Category category(String id, boolean acceptable, boolean submit) {
Category category = new Category();
category.setId(id);
category.setAcceptable(acceptable);
category.setSubmit(submit);
return category;
}

Expand All @@ -59,7 +64,7 @@ private List<String> expressionStrings(OperationOutcome.OperationOutcomeIssueCom
// -------------------------------------------------------------------------

@Test
void build_oneIssuePerUnacceptableCategory_withOoTotal() {
void build_oneIssuePerSubmittedCategory_withOoTotal() {
Result r1 = result("Code is inactive.", "expr1", category("inactive_code", false));
Result r2 = result("Unable to validate.", "expr2", category("unable_to_validate_code", false));

Expand Down Expand Up @@ -133,7 +138,7 @@ void build_distinctCategoryInstancesWithSameId_produceASingleIssue() {
}

@Test
void build_oneResultMappingToMultipleUnacceptableCategories_producesAnIssuePerCategory() {
void build_oneResultMappingToMultipleSubmittedCategories_producesAnIssuePerCategory() {
Result r = result("msg", "expr", category("cat_a", false), category("cat_b", false));

OperationOutcome oo = builder.build(List.of(r), MEASURE_REPORT, true).orElseThrow();
Expand All @@ -142,23 +147,41 @@ void build_oneResultMappingToMultipleUnacceptableCategories_producesAnIssuePerCa
}

@Test
void build_excludesAcceptableCategories() {
Result unacceptable = result("bad", "expr1", category("inactive_code", false));
Result acceptable = result("ok", "expr2", category("incorrect_display_value_for_code", true));
void build_includesSubmittedCategoriesRegardlessOfAcceptable() {
Result submitted = result("included", "expr1", category("submitted", true, true));
Result nonSubmitted = result("excluded", "expr2", category("not-submitted", false, false));

OperationOutcome oo = builder.build(List.of(unacceptable, acceptable), MEASURE_REPORT, true).orElseThrow();
OperationOutcome oo = builder.build(List.of(submitted, nonSubmitted), MEASURE_REPORT, true).orElseThrow();

assertEquals(1, oo.getIssue().size());
CodeType cat = (CodeType) oo.getIssueFirstRep()
.getExtensionByUrl(PreQualOperationOutcomeBuilder.PQ_ISSUE_CAT_URL).getValue();
assertEquals("inactive_code", cat.getValue());
assertEquals("submitted", cat.getValue());
int total = ((IntegerType) oo.getExtensionByUrl(PreQualOperationOutcomeBuilder.OO_TOTAL_URL).getValue()).getValue();
assertEquals(1, total);
}

@Test
void build_resultWithMixedSubmitCategories_emitsOnlySubmittedCategory() {
Result result = result("message", "expr",
category("submitted", false, true),
category("not-submitted", false, false));

OperationOutcome oo = builder.build(List.of(result), MEASURE_REPORT, true).orElseThrow();

assertEquals(1, oo.getIssue().size());
CodeType category = (CodeType) oo.getIssueFirstRep()
.getExtensionByUrl(PreQualOperationOutcomeBuilder.PQ_ISSUE_CAT_URL).getValue();
assertEquals("submitted", category.getValue());
int total = ((IntegerType) oo.getExtensionByUrl(PreQualOperationOutcomeBuilder.OO_TOTAL_URL).getValue()).getValue();
assertEquals(1, total);
}

@Test
void build_noUnacceptableFindings_returnsEmpty() {
Result acceptable = result("ok", "expr", category("incorrect_display_value_for_code", true));
void build_noSubmittedFindings_returnsEmpty() {
Result nonSubmitted = result("not submitted", "expr", category("not-submitted", false, false));

assertTrue(builder.build(List.of(acceptable), MEASURE_REPORT, true).isEmpty());
assertTrue(builder.build(List.of(nonSubmitted), MEASURE_REPORT, true).isEmpty());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,7 @@ private void stubBlobDownload() {
}

@Test
void process_flagOn_unacceptableFindings_appendsOperationOutcomeToSameBlob() throws Exception {
void process_flagOn_submittedFindings_appendsOperationOutcomeToSameBlob() throws Exception {
preQualificationConfig.setWritePreQualOperationOutcome(true);
stubBlobDownload();

Expand All @@ -522,7 +522,7 @@ void process_flagOn_unacceptableFindings_appendsOperationOutcomeToSameBlob() thr
when(jsonParser.encodeResourceToString(any()))
.thenReturn("{\"resourceType\":\"OperationOutcome\"}");

Result result = resultWithCategories(List.of(categoryWithAcceptable(false)));
Result result = resultWithCategories(List.of(categoryWithSubmit(true)));
result.setMessage("Code is inactive.");
when(validationService.validate(bundle)).thenReturn(List.of(result));

Expand All @@ -532,10 +532,10 @@ void process_flagOn_unacceptableFindings_appendsOperationOutcomeToSameBlob() thr
}

@Test
void process_flagOff_unacceptableFindings_doesNotAppend() throws Exception {
void process_flagOff_submittedFindings_doesNotAppend() throws Exception {
stubBlobDownload(); // flag defaults to false

Result result = resultWithCategories(List.of(categoryWithAcceptable(false)));
Result result = resultWithCategories(List.of(categoryWithSubmit(true)));
when(validationService.validate(bundle)).thenReturn(List.of(result));

consumer.process(buildRecord(PAYLOAD_URI));
Expand All @@ -544,11 +544,11 @@ void process_flagOff_unacceptableFindings_doesNotAppend() throws Exception {
}

@Test
void process_flagOn_noUnacceptableFindings_doesNotAppend() throws Exception {
void process_flagOn_noSubmittedFindings_doesNotAppend() throws Exception {
preQualificationConfig.setWritePreQualOperationOutcome(true);
stubBlobDownload();

Result result = resultWithCategories(List.of(categoryWithAcceptable(true)));
Result result = resultWithCategories(List.of(categoryWithSubmit(false)));
when(validationService.validate(bundle)).thenReturn(List.of(result));

consumer.process(buildRecord(PAYLOAD_URI));
Expand All @@ -571,7 +571,7 @@ void process_flagOn_bundleAlreadyHasPreQualOperationOutcome_doesNotAppendAgain()

stubBlobDownload();

Result result = resultWithCategories(List.of(categoryWithAcceptable(false)));
Result result = resultWithCategories(List.of(categoryWithSubmit(true)));
result.setMessage("Code is inactive.");
when(validationService.validate(bundle)).thenReturn(List.of(result));

Expand All @@ -596,7 +596,7 @@ void process_flagOn_bundleHasUnrelatedOperationOutcome_stillAppends() throws Exc
when(fhirContext.newJsonParser()).thenReturn(jsonParser);
when(jsonParser.encodeResourceToString(any())).thenReturn("{\"resourceType\":\"OperationOutcome\"}");

Result result = resultWithCategories(List.of(categoryWithAcceptable(false)));
Result result = resultWithCategories(List.of(categoryWithSubmit(true)));
result.setMessage("Code is inactive.");
when(validationService.validate(bundle)).thenReturn(List.of(result));

Expand All @@ -610,7 +610,7 @@ void process_flagOn_noBlobService_doesNotAppend() throws Exception {
preQualificationConfig.setWritePreQualOperationOutcome(true);
stubRestRetrieval(); // no blob service -> bundle comes via REST, append is skipped

Result result = resultWithCategories(List.of(categoryWithAcceptable(false)));
Result result = resultWithCategories(List.of(categoryWithSubmit(true)));
when(validationService.validate(bundle)).thenReturn(List.of(result));

consumerWithoutBlobStorage.process(buildRecord(PAYLOAD_URI));
Expand All @@ -626,7 +626,7 @@ void process_flagOn_nullPayloadUri_doesNotAppend() throws Exception {
preQualificationConfig.setWritePreQualOperationOutcome(true);
stubRestRetrieval(); // no payload URI -> bundle comes via REST

Result result = resultWithCategories(List.of(categoryWithAcceptable(false)));
Result result = resultWithCategories(List.of(categoryWithSubmit(true)));
result.setMessage("Code is inactive.");
when(validationService.validate(bundle)).thenReturn(List.of(result));

Expand Down
Loading