Skip to content

Commit

Permalink
Misc improvements to Device generation (#708)
Browse files Browse the repository at this point in the history
* Removing unnecessary arguments from Validator constructor
Keeping track of ImplementationGuide resources found in NpmPackages within the `Validator` separately from the `PrePopulatedValidationSupport` since `PrePopulatedValidationSupport` doesn't do anything with ImplementationGuide
Loading Device.notes with list of implementation guides that are loaded at the time of validation
Update Device.notes in the report when a report is re-validated
Changing interface for IPatientIdProvider to not return the patient list, since it just loads it into the report context anyways and the return value isn't used.
Change `Device` generated to use `note` for events and concept maps. Much more readable. Conferred with Karen and Dave, who agreed.

* Removing unused constant and unnecessary valueset-timing-abbreviation.json
Not logging parse errors in LenientErrorHandler. We only care if there's an exception, and we're catching and logging those anyways.
Bringing the IG status forward from the original IG.
Parsing resources from packages using a stream instead of loading a string up first.
Handling exception during parsing a little nicer for logging
  • Loading branch information
seanmcilvenna committed Mar 1, 2024
1 parent 0363ad5 commit 3b0bd38
Show file tree
Hide file tree
Showing 13 changed files with 271 additions and 299 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import com.lantanagroup.link.FhirContextProvider;
import com.lantanagroup.link.FhirHelper;
import com.lantanagroup.link.config.api.ApiConfig;
import com.lantanagroup.link.db.SharedService;
import com.lantanagroup.link.validation.Validator;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.SpringApplication;
Expand Down Expand Up @@ -74,8 +73,8 @@ public ApiInit apiInit() {
}

@Bean
public Validator validator(SharedService sharedService, ApiConfig apiConfig) {
return new Validator(sharedService, apiConfig);
public Validator validator(ApiConfig apiConfig) {
return new Validator(apiConfig);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.lantanagroup.link.time.StopwatchManager;
import com.lantanagroup.link.validation.ValidationService;
import com.lantanagroup.link.validation.Validator;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Setter;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.IOUtils;
Expand All @@ -41,7 +42,6 @@
import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;

import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URISyntaxException;
Expand Down Expand Up @@ -143,14 +143,76 @@ private void queryFhir(TenantService tenantService, ReportCriteria criteria, Rep
}
}

private List<PatientOfInterestModel> getPatientIdentifiers(TenantService tenantService, ReportCriteria criteria, ReportContext context) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
List<PatientOfInterestModel> patientOfInterestModelList;

private void loadPatientIdentifiers(TenantService tenantService, ReportCriteria criteria, ReportContext context) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<?> patientIdResolverClass = Class.forName(this.config.getPatientIdResolver());
IPatientIdProvider provider = (IPatientIdProvider) this.context.getBean(patientIdResolverClass);
patientOfInterestModelList = provider.getPatientsOfInterest(tenantService, criteria, context);
provider.loadPatientsOfInterest(tenantService, criteria, context);
}

@PostMapping("/{reportId}/$regenerate")
public Report regenerateReport(
@AuthenticationPrincipal LinkCredentials user,
HttpServletRequest request,
@PathVariable String tenantId,
@PathVariable String reportId,
@RequestBody GenerateRequest input)
throws Exception {
TenantService tenantService = TenantService.create(this.sharedService, tenantId);

if (tenantService == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Tenant not found");
}

Report report = tenantService.getReport(reportId);

if (report == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Report not found");
}

GenerateRequest generateRequest = new GenerateRequest();
generateRequest.setBundleIds(report.getMeasureIds());
generateRequest.setPeriodStart(report.getPeriodStart());
generateRequest.setPeriodEnd(report.getPeriodEnd());
generateRequest.setRegenerate(true);
generateRequest.setValidate(input.isValidate());
generateRequest.setSkipQuery(input.isSkipQuery());
generateRequest.setDebugPatients(input.getDebugPatients());

return generateReport(user, request, tenantId, generateRequest);
}

@GetMapping("/{reportId}/$regenerate")
public Report regenerateReport(
@AuthenticationPrincipal LinkCredentials user,
HttpServletRequest request,
@PathVariable String tenantId,
@PathVariable String reportId,
@RequestParam(defaultValue = "true") boolean validate,
@RequestParam(defaultValue = "false") boolean skipQuery,
@RequestParam(required = false, defaultValue = "") List<String> debugPatients)
throws Exception {
TenantService tenantService = TenantService.create(this.sharedService, tenantId);

if (tenantService == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Tenant not found");
}

Report report = tenantService.getReport(reportId);

if (report == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Report not found");
}

GenerateRequest generateRequest = new GenerateRequest();
generateRequest.setBundleIds(report.getMeasureIds());
generateRequest.setPeriodStart(report.getPeriodStart());
generateRequest.setPeriodEnd(report.getPeriodEnd());
generateRequest.setRegenerate(true);
generateRequest.setValidate(validate);
generateRequest.setSkipQuery(skipQuery);
generateRequest.setDebugPatients(debugPatients);

return patientOfInterestModelList;
return generateReport(user, request, tenantId, generateRequest);
}

@PostMapping("/$generate")
Expand Down Expand Up @@ -286,9 +348,9 @@ private Report generateResponse(TenantService tenantService, LinkCredentials use
this.eventService.triggerEvent(tenantService, EventTypes.BeforePatientOfInterestLookup, criteria, reportContext);

// Get the patient identifiers for the given date
this.getPatientIdentifiers(tenantService, criteria, reportContext);
this.loadPatientIdentifiers(tenantService, criteria, reportContext);

if (reportContext.getPatientLists() == null || reportContext.getPatientLists().size() < 1) {
if (reportContext.getPatientLists() == null || reportContext.getPatientLists().isEmpty()) {
String msg = "A census for the specified criteria was not found.";
logger.error(msg);
throw new ResponseStatusException(HttpStatus.NOT_FOUND, msg);
Expand Down Expand Up @@ -318,7 +380,7 @@ private Report generateResponse(TenantService tenantService, LinkCredentials use
report.setPeriodStart(criteria.getPeriodStart());
report.setPeriodEnd(criteria.getPeriodEnd());
report.setMeasureIds(measureIds);
report.setDeviceInfo(FhirHelper.getDevice(this.config, tenantService));
report.setDeviceInfo(FhirHelper.getDevice(this.config, tenantService, validator));
report.setQueryPlan(new YAMLMapper().writeValueAsString(queryPlan));

// Preserve the version of the already-existing report
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.lantanagroup.link.api.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.lantanagroup.link.FhirHelper;
import com.lantanagroup.link.ValidationCategorizer;
import com.lantanagroup.link.config.api.ApiConfig;
import com.lantanagroup.link.db.SharedService;
import com.lantanagroup.link.db.TenantService;
import com.lantanagroup.link.db.mappers.ValidationResultMapper;
Expand Down Expand Up @@ -41,6 +43,8 @@ public class ValidationController extends BaseController {
private SharedService sharedService;
@Autowired
private ValidationService validationService;
@Autowired
private ApiConfig apiConfig;

/**
* Validates a Bundle provided in the request body
Expand Down Expand Up @@ -324,6 +328,14 @@ public OperationOutcome validate(@PathVariable String tenantId, @PathVariable St
outcome.addContained(report.getDeviceInfo());
}

// Update the report's device with the current list of implementation guides
Device device = report.getDeviceInfo();
if (device == null) {
device = FhirHelper.getDevice(this.apiConfig);
}
FhirHelper.setImplementationGuideNotes(device, this.validator);
tenantService.saveReport(report);

return outcome;
}

Expand Down
85 changes: 85 additions & 0 deletions api/src/main/resources/swagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,91 @@ paths:
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
/api/{tenantId}/report/{reportId}/$regenerate:
get:
tags:
- Reports
summary: Regenerate a report
operationId: regenerateReport
parameters:
- name: tenantId
in: path
description: The id of the tenant
required: true
schema:
type: string
- name: reportId
in: path
description: The id of the report to regenerate
required: true
schema:
type: string
- name: validate
in: query
description: Whether to validate the report at the end of generation
schema:
type: boolean
default: true
- name: skipQuery
in: query
description: Whether to skip querying the EHR for data
schema:
type: boolean
default: false
- name: debugPatients
in: query
description: A comma separated list of patient ids to include in the report
schema:
type: string
responses:
'200':
description: successful response
content:
'application/json':
schema:
$ref: '#/components/schemas/Report'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
post:
tags:
- Reports
summary: Regenerate a report
operationId: regenerateReportPost
parameters:
- name: tenantId
in: path
description: The id of the tenant
required: true
schema:
type: string
- name: reportId
in: path
description: The id of the report to regenerate
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/GenerateReport'
responses:
'200':
description: successful response
content:
'application/json':
schema:
$ref: '#/components/schemas/Report'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
/api/{tenantId}/report/$generate:
post:
tags:
Expand Down
1 change: 0 additions & 1 deletion core/src/main/java/com/lantanagroup/link/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ public class Constants {
public static final String MeasureScoringCodeSystem = "http://terminology.hl7.org/CodeSystem/measure-scoring";
public static final String MeasureImprovementNotationCodeSystem = "http://terminology.hl7.org/CodeSystem/measure-improvement-notation";
public static final String LinkDeviceVersionCodeSystem = "http://www.cdc.gov/nhsn/fhirportal/dqm/ig/CodeSystem/codesystem-link-device-version";
public static final String LinkDevicePropertiesCodeSystem = "http://www.cdc.gov/nhsn/fhirportal/dqm/ig/CodeSystem/codesystem-link-device-properties";
public static final String ReportBundleProfileUrl = "http://www.cdc.gov/nhsn/fhirportal/dqm/ig/StructureDefinition/nhsn-measurereport-bundle";
public static final String IndividualMeasureReportProfileUrl = "http://hl7.org/fhir/us/davinci-deqm/StructureDefinition/indv-measurereport-deqm";
public static final String CensusProfileUrl = "http://www.cdc.gov/nhsn/fhirportal/dqm/ig/StructureDefinition/poi-list";
Expand Down
14 changes: 1 addition & 13 deletions core/src/main/java/com/lantanagroup/link/FhirBundler.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,6 @@ private Organization getOrg() {
return this.org;
}

private Device createDevice(Report report) {
if (report.getDeviceInfo() == null) {
return null;
}

Device device = report.getDeviceInfo().copy();
device.setId(UUID.randomUUID().toString());
device.getMeta().addProfile(Constants.SubmittingDeviceProfile);

return device;
}

/**
* Creates a Library resource that contains the query plan used for the report
*
Expand Down Expand Up @@ -102,7 +90,7 @@ private Library createQueryPlanLibrary(Report report) {
public Bundle generateBundle(Collection<Aggregate> aggregates, Report report) {
Bundle bundle = this.createBundle();
bundle.addEntry().setResource(this.getOrg());
Device device = this.createDevice(report);
Device device = report.getDeviceInfo();
if (device != null) {
bundle.addEntry().setResource(device);
}
Expand Down
Loading

0 comments on commit 3b0bd38

Please sign in to comment.