Skip to content

Commit

Permalink
Create endpoint for resending an EhrExtract (#990)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: hospel <[email protected]>
  • Loading branch information
adrianclay and ole4ryb authored Nov 26, 2024
1 parent bf88ffd commit bde8dcc
Show file tree
Hide file tree
Showing 4 changed files with 443 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

import org.hl7.fhir.instance.model.api.IBaseResource;
import org.springframework.stereotype.Service;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.StrictErrorHandler;
import uk.nhs.adaptors.gp2gp.common.exception.FhirValidationException;

@Service
public class FhirParseService {

private final IParser jsonParser = prepareParser();

public <T extends IBaseResource> T parseResource(String body, Class<T> fhirClass) {
Expand All @@ -20,6 +20,10 @@ public <T extends IBaseResource> T parseResource(String body, Class<T> fhirClass
}
}

public String encodeToJson(IBaseResource resource) {
return jsonParser.setPrettyPrint(true).encodeResourceToString(resource);
}

private IParser prepareParser() {
FhirContext ctx = FhirContext.forDstu3();
ctx.newJsonParser();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package uk.nhs.adaptors.gp2gp.ehr;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.Meta;
import org.hl7.fhir.dstu3.model.OperationOutcome;
import org.hl7.fhir.dstu3.model.UriType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import uk.nhs.adaptors.gp2gp.common.service.FhirParseService;
import uk.nhs.adaptors.gp2gp.common.service.RandomIdGeneratorService;
import uk.nhs.adaptors.gp2gp.common.service.TimestampService;
import uk.nhs.adaptors.gp2gp.common.task.TaskDispatcher;
import uk.nhs.adaptors.gp2gp.ehr.model.EhrExtractStatus;
import uk.nhs.adaptors.gp2gp.gpc.GetGpcStructuredTaskDefinition;

import java.util.Collections;

@Slf4j
@RestController
@AllArgsConstructor(onConstructor = @__(@Autowired))
@RequestMapping(path = "/ehr-resend")
public class EhrResendController {

private static final String OPERATION_OUTCOME_URL = "https://fhir.nhs.uk/STU3/StructureDefinition/GPConnect-OperationOutcome-1";
private static final String PRECONDITION_FAILED = "PRECONDITION_FAILED";
private static final String INVALID_IDENTIFIER_VALUE = "INVALID_IDENTIFIER_VALUE";

private EhrExtractStatusRepository ehrExtractStatusRepository;
private TaskDispatcher taskDispatcher;
private RandomIdGeneratorService randomIdGeneratorService;
private final TimestampService timestampService;
private final FhirParseService fhirParseService;

@PostMapping("/{conversationId}")
public ResponseEntity<String> scheduleEhrExtractResend(@PathVariable String conversationId) {
EhrExtractStatus ehrExtractStatus = ehrExtractStatusRepository.findByConversationId(conversationId).orElseGet(() -> null);

if (ehrExtractStatus == null) {
var details = getCodeableConcept(INVALID_IDENTIFIER_VALUE);
var diagnostics = "Provide a conversationId that exists and retry the operation";

var operationOutcome = createOperationOutcome(OperationOutcome.IssueType.VALUE,
OperationOutcome.IssueSeverity.ERROR,
details,
diagnostics);
var errorBody = fhirParseService.encodeToJson(operationOutcome);

return new ResponseEntity<>(errorBody, HttpStatus.NOT_FOUND);
}

if (hasNoErrorsInEhrReceivedAcknowledgement(ehrExtractStatus) && ehrExtractStatus.getError() == null) {

var details = getCodeableConcept(PRECONDITION_FAILED);
var diagnostics = "The current resend operation is still in progress. Please wait for it to complete before retrying";
var operationOutcome = createOperationOutcome(OperationOutcome.IssueType.BUSINESSRULE,
OperationOutcome.IssueSeverity.ERROR,
details,
diagnostics);
var errorBody = fhirParseService.encodeToJson(operationOutcome);
return new ResponseEntity<>(errorBody, HttpStatus.CONFLICT);
}

var updatedEhrExtractStatus = prepareEhrExtractStatusForNewResend(ehrExtractStatus);
ehrExtractStatusRepository.save(updatedEhrExtractStatus);
createGetGpcStructuredTask(updatedEhrExtractStatus);
LOGGER.info("Scheduled GetGpcStructuredTask for resend of ConversationId: {}", conversationId);

return new ResponseEntity<>(HttpStatus.ACCEPTED);
}

private static CodeableConcept getCodeableConcept(String codeableConceptCode) {
return new CodeableConcept().addCoding(
new Coding("https://fhir.nhs.uk/STU3/ValueSet/Spine-ErrorOrWarningCode-1", codeableConceptCode, null));
}

private static boolean hasNoErrorsInEhrReceivedAcknowledgement(EhrExtractStatus ehrExtractStatus) {
var ehrReceivedAcknowledgement = ehrExtractStatus.getEhrReceivedAcknowledgement();
if (ehrReceivedAcknowledgement == null) {
return true;
}

var errors = ehrReceivedAcknowledgement.getErrors();
if (errors == null || errors.isEmpty()) {
return true;
}
return false;
}

private EhrExtractStatus prepareEhrExtractStatusForNewResend(EhrExtractStatus ehrExtractStatus) {

var now = timestampService.now();
ehrExtractStatus.setUpdatedAt(now);
ehrExtractStatus.setMessageTimestamp(now);
ehrExtractStatus.setEhrExtractCorePending(null);
ehrExtractStatus.setGpcAccessDocument(null);
ehrExtractStatus.setEhrContinue(null);
ehrExtractStatus.setEhrReceivedAcknowledgement(null);

return ehrExtractStatus;
}

private void createGetGpcStructuredTask(EhrExtractStatus ehrExtractStatus) {
var getGpcStructuredTaskDefinition = GetGpcStructuredTaskDefinition.getGetGpcStructuredTaskDefinition(randomIdGeneratorService,
ehrExtractStatus);
taskDispatcher.createTask(getGpcStructuredTaskDefinition);
}

public static OperationOutcome createOperationOutcome(
OperationOutcome.IssueType type, OperationOutcome.IssueSeverity severity, CodeableConcept details, String diagnostics) {
var operationOutcome = new OperationOutcome();
Meta meta = new Meta();
meta.setProfile(Collections.singletonList(new UriType(OPERATION_OUTCOME_URL)));
operationOutcome.setMeta(meta);
operationOutcome.addIssue()
.setCode(type)
.setSeverity(severity)
.setDetails(details)
.setDiagnostics(diagnostics);
return operationOutcome;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package uk.nhs.adaptors.gp2gp.common.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.hl7.fhir.dstu3.model.CodeableConcept;
import org.hl7.fhir.dstu3.model.Coding;
import org.hl7.fhir.dstu3.model.OperationOutcome;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import uk.nhs.adaptors.gp2gp.common.configuration.ObjectMapperBean;
import uk.nhs.adaptors.gp2gp.ehr.EhrResendController;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;


@ExtendWith(MockitoExtension.class)
class FhirParseServiceTest {

public static final String INVALID_IDENTIFIER_VALUE = "INVALID_IDENTIFIER_VALUE";
private static final String OPERATION_OUTCOME_URL = "https://fhir.nhs.uk/STU3/StructureDefinition/GPConnect-OperationOutcome-1";
private OperationOutcome operationOutcome;
private ObjectMapper objectMapper;

@BeforeEach
void setUp() {
ObjectMapperBean objectMapperBean = new ObjectMapperBean();
objectMapper = objectMapperBean.objectMapper(new Jackson2ObjectMapperBuilder());

var details = getCodeableConcept();
var diagnostics = "Provide a conversationId that exists and retry the operation";
operationOutcome = EhrResendController.createOperationOutcome(OperationOutcome.IssueType.VALUE,
OperationOutcome.IssueSeverity.ERROR,
details,
diagnostics);
}

@Test
void ableToEncodeOperationOutcomeToJson() throws JsonProcessingException {
FhirParseService fhirParseService = new FhirParseService();

String convertedToJsonOperationOutcome = fhirParseService.encodeToJson(operationOutcome);

JsonNode rootNode = objectMapper.readTree(convertedToJsonOperationOutcome);
String code =
rootNode.path("issue").get(0).path("details").path("coding").get(0).path("code").asText();
String operationOutcomeUrl = rootNode.path("meta").path("profile").get(0).asText();

assertEquals(INVALID_IDENTIFIER_VALUE, code);
assertEquals(OPERATION_OUTCOME_URL, operationOutcomeUrl);
}

private static CodeableConcept getCodeableConcept() {
var details = new CodeableConcept();
var codeableConceptCoding = new Coding();
codeableConceptCoding.setSystem("http://fhir.nhs.net/ValueSet/gpconnect-error-or-warning-code-1");
codeableConceptCoding.setCode(FhirParseServiceTest.INVALID_IDENTIFIER_VALUE);
details.setCoding(List.of(codeableConceptCoding));
return details;
}
}
Loading

0 comments on commit bde8dcc

Please sign in to comment.