/**
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations under
* the License.
*
* The Original Code is OpenELIS code.
*
* Copyright (C) CIRG, University of Washington, Seattle WA. All Rights Reserved.
* I-TECH, University of Washington, Seattle WA.
*/
package us.mn.state.health.lims.resultvalidation.util;
import org.apache.commons.validator.GenericValidator;
import us.mn.state.health.lims.analysis.dao.AnalysisDAO;
import us.mn.state.health.lims.analysis.daoimpl.AnalysisDAOImpl;
import us.mn.state.health.lims.analysis.valueholder.Analysis;
import us.mn.state.health.lims.analyte.dao.AnalyteDAO;
import us.mn.state.health.lims.analyte.daoimpl.AnalyteDAOImpl;
import us.mn.state.health.lims.analyte.valueholder.Analyte;
import us.mn.state.health.lims.common.exception.LIMSRuntimeException;
import us.mn.state.health.lims.common.services.*;
import us.mn.state.health.lims.common.services.NoteService.NoteType;
import us.mn.state.health.lims.common.services.StatusService.AnalysisStatus;
import us.mn.state.health.lims.common.services.StatusService.RecordStatus;
//import us.mn.state.health.lims.common.util.ConfigurationProperties;
//import us.mn.state.health.lims.common.util.ConfigurationProperties.Property;
import us.mn.state.health.lims.common.util.IdValuePair;
import us.mn.state.health.lims.common.util.StringUtil;
import us.mn.state.health.lims.dictionary.dao.DictionaryDAO;
import us.mn.state.health.lims.dictionary.daoimpl.DictionaryDAOImpl;
import us.mn.state.health.lims.dictionary.valueholder.Dictionary;
import us.mn.state.health.lims.observationhistory.dao.ObservationHistoryDAO;
import us.mn.state.health.lims.observationhistory.daoimpl.ObservationHistoryDAOImpl;
import us.mn.state.health.lims.observationhistory.valueholder.ObservationHistory;
import us.mn.state.health.lims.observationhistorytype.dao.ObservationHistoryTypeDAO;
import us.mn.state.health.lims.observationhistorytype.daoImpl.ObservationHistoryTypeDAOImpl;
import us.mn.state.health.lims.observationhistorytype.valueholder.ObservationHistoryType;
import us.mn.state.health.lims.result.dao.ResultDAO;
import us.mn.state.health.lims.result.daoimpl.ResultDAOImpl;
import us.mn.state.health.lims.result.valueholder.Result;
import us.mn.state.health.lims.resultvalidation.action.util.ResultValidationItem;
import us.mn.state.health.lims.resultvalidation.bean.AnalysisItem;
import us.mn.state.health.lims.sample.dao.SampleDAO;
import us.mn.state.health.lims.sample.daoimpl.SampleDAOImpl;
import us.mn.state.health.lims.sample.valueholder.Sample;
import us.mn.state.health.lims.statusofsample.util.StatusRules;
import us.mn.state.health.lims.test.dao.TestDAO;
import us.mn.state.health.lims.test.dao.TestSectionDAO;
import us.mn.state.health.lims.test.daoimpl.TestDAOImpl;
import us.mn.state.health.lims.test.daoimpl.TestSectionDAOImpl;
import us.mn.state.health.lims.test.valueholder.Test;
import us.mn.state.health.lims.testresult.dao.TestResultDAO;
import us.mn.state.health.lims.testresult.daoimpl.TestResultDAOImpl;
import us.mn.state.health.lims.testresult.valueholder.TestResult;
import java.util.*;
public class ResultsValidationUtility {
protected static String ANALYTE_CD4_CT_GENERATED_ID;
protected static String CONCLUSION_ID;
protected final DictionaryDAO dictionaryDAO = new DictionaryDAOImpl();
protected final TestSectionDAO testSectionDAO = new TestSectionDAOImpl();
protected final ResultDAO resultDAO = new ResultDAOImpl();
protected final TestResultDAO testResultDAO = new TestResultDAOImpl();
protected final TestDAO testDAO = new TestDAOImpl();
protected final SampleDAO sampleDAO = new SampleDAOImpl();
protected final ObservationHistoryDAO observationHistoryDAO = new ObservationHistoryDAOImpl();
protected static String SAMPLE_STATUS_OBSERVATION_HISTORY_TYPE_ID;
protected static String CD4_COUNT_SORT_NUMBER;
protected static List<Integer> notValidStatus = new ArrayList<Integer>();
protected Map<String, String> testIdToUnits = new HashMap<String, String>();
protected Map<String, Boolean> accessionToValidMap;
protected String totalTestName = "";
static {
notValidStatus.add(Integer.parseInt(StatusService.getInstance().getStatusID(AnalysisStatus.Finalized)));
notValidStatus.add(Integer.parseInt(StatusService.getInstance().getStatusID(AnalysisStatus.TechnicalRejected)));
AnalyteDAO analyteDAO = new AnalyteDAOImpl();
Analyte analyte = new Analyte();
analyte.setAnalyteName("Conclusion");
analyte = analyteDAO.getAnalyteByName(analyte, false);
CONCLUSION_ID = analyte.getId();
analyte.setAnalyteName("generated CD4 Count");
analyte = analyteDAO.getAnalyteByName(analyte, false);
ANALYTE_CD4_CT_GENERATED_ID = analyte == null ? "" : analyte.getId();
TestDAO testDAO = new TestDAOImpl();
Test test = testDAO.getTestByName("CD4 absolute count");
if( test != null){
CD4_COUNT_SORT_NUMBER = test.getSortOrder();
}
ObservationHistoryTypeDAO ohTypeDAO = new ObservationHistoryTypeDAOImpl();
ObservationHistoryType oht = ohTypeDAO.getByName("SampleRecordStatus");
if( oht != null){
SAMPLE_STATUS_OBSERVATION_HISTORY_TYPE_ID = oht.getId();
}
}
protected final AnalysisDAO analysisDAO = new AnalysisDAOImpl();
public List<AnalysisItem> getResultValidationList( List<Integer> statusList, String testSectionId) {
List<AnalysisItem> resultList = new ArrayList<AnalysisItem>();
if (!GenericValidator.isBlankOrNull(testSectionId)) {
List<ResultValidationItem> testList = getUnValidatedTestResultItemsInTestSection(testSectionId, statusList);
resultList = testResultListToAnalysisItemList(testList);
sortByAccessionNumberAndOrder(resultList);
setGroupingNumbers(resultList);
}
return resultList;
}
@SuppressWarnings("unchecked")
public final List<ResultValidationItem> getUnValidatedTestResultItemsInTestSection(String sectionId, List<Integer> statusList) {
List<Analysis> analysisList = analysisDAO.getAllAnalysisByTestSectionAndStatus(sectionId, statusList, false);
return getGroupedTestsForAnalysisList(analysisList, !StatusRules.useRecordStatusForValidation());
}
protected final void sortByAccessionNumberAndOrder(List<AnalysisItem> resultItemList) {
Collections.sort(resultItemList, new Comparator<AnalysisItem>() {
public final int compare(AnalysisItem a, AnalysisItem b) {
int accessionComp = a.getAccessionNumber().compareTo(b.getAccessionNumber());
return ((accessionComp == 0) ? Integer.parseInt(a.getTestSortNumber()) - Integer.parseInt(b.getTestSortNumber())
: accessionComp);
}
});
}
protected final void setGroupingNumbers(List<AnalysisItem> resultList) {
String currentAccessionNumber = null;
AnalysisItem headItem = null;
int groupingCount = 1;
for (AnalysisItem analysisResultItem : resultList) {
if (!analysisResultItem.getAccessionNumber().equals(currentAccessionNumber)) {
currentAccessionNumber = analysisResultItem.getAccessionNumber();
headItem = analysisResultItem;
groupingCount++;
} else {
headItem.setMultipleResultForSample(true);
analysisResultItem.setMultipleResultForSample(true);
}
analysisResultItem.setSampleGroupingNumber(groupingCount);
}
}
/*
* N.B. The ignoreRecordStatus is an abomination and should be removed. It
* is a quick and dirty fix for workplan and validation using the same code
* but having different rules
*/
public final List<ResultValidationItem> getGroupedTestsForAnalysisList(Collection<Analysis> filteredAnalysisList, boolean ignoreRecordStatus)
throws LIMSRuntimeException {
List<ResultValidationItem> selectedTestList = new ArrayList<ResultValidationItem>();
Dictionary dictionary;
for (Analysis analysis : filteredAnalysisList) {
if (ignoreRecordStatus || sampleReadyForValidation(analysis.getSampleItem().getSample())) {
List<ResultValidationItem> testResultItemList = getResultItemFromAnalysis(analysis);
//NB. The resultValue is filled in during getResultItemFromAnalysis as a side effect of setResult
for (ResultValidationItem validationItem : testResultItemList) {
if (TypeOfTestResultService.ResultType.isDictionaryVariant( validationItem.getResultType() )) {
dictionary = new Dictionary();
String resultValue = null;
try {
dictionary.setId(validationItem.getResultValue());
dictionaryDAO.getData(dictionary);
resultValue = GenericValidator.isBlankOrNull(dictionary.getLocalAbbreviation()) ? dictionary.getDictEntry()
: dictionary.getLocalAbbreviation();
} catch (Exception e) {
//no-op
}
validationItem.setResultValue(resultValue);
}
validationItem.setAnalysis( analysis );
validationItem.setNonconforming( QAService.isAnalysisParentNonConforming( analysis ) ||
StatusService.getInstance().matches( analysis.getStatusId(), AnalysisStatus.TechnicalRejected ) );
selectedTestList.add(validationItem);
}
}
}
return selectedTestList;
}
protected final boolean sampleReadyForValidation(Sample sample) {
Boolean valid = accessionToValidMap.get(sample.getAccessionNumber());
if (valid == null) {
valid = getSampleRecordStatus( sample ) != RecordStatus.NotRegistered;
accessionToValidMap.put(sample.getAccessionNumber(), valid);
}
return valid;
}
public final List<ResultValidationItem> getResultItemFromAnalysis(Analysis analysis) throws LIMSRuntimeException {
List<ResultValidationItem> testResultList = new ArrayList<ResultValidationItem>();
List<Result> resultList = resultDAO.getResultsByAnalysis(analysis);
NoteType[] noteTypes = { NoteType.EXTERNAL, NoteType.INTERNAL, NoteType.REJECTION_REASON, NoteType.NON_CONFORMITY};
String notes = new NoteService( analysis ).getNotesAsString( true, true, "<br/>", noteTypes, false );
if (resultList == null) {
return testResultList;
}
// For historical reasons we add a null member to the collection if it
// is empty
// this should be refactored.
// The result list are results associated with the analysis, if there is
// none we want
// to present the user with a blank one
if (resultList.isEmpty()) {
resultList.add(null);
}
ResultValidationItem parentItem = null;
for (Result result : resultList) {
if( parentItem != null && result.getParentResult() != null && parentItem.getResultId().equals(result.getParentResult().getId())){
parentItem.setQualifiedResultValue(result.getValue());
parentItem.setHasQualifiedResult( true );
parentItem.setQualificationResultId(result.getId());
continue;
}
ResultValidationItem resultItem = createTestResultItem(analysis, analysis.getTest(), analysis.getSampleItem().getSortOrder(),
result, analysis.getSampleItem().getSample().getAccessionNumber(), notes);
notes = null;//we only want it once
if( resultItem.getQualifiedDictionaryId() != null){
parentItem = resultItem;
}
testResultList.add(resultItem);
}
return testResultList;
}
protected final ResultValidationItem createTestResultItem(Analysis analysis, Test test, String sequenceNumber, Result result,
String accessionNumber, String notes) {
List<TestResult> testResults = getPossibleResultsForTest(test);
String displayTestName = TestService.getLocalizedTestNameWithType( test );
// displayTestName = augmentTestNameWithRange(displayTestName, result);
ResultValidationItem testItem = new ResultValidationItem();
testItem.setAccessionNumber(accessionNumber);
testItem.setAnalysis( analysis );
testItem.setSequenceNumber(sequenceNumber);
testItem.setTestName(displayTestName);
testItem.setTestId(test.getId());
testItem.setAnalysisMethod(analysis.getAnalysisType());
testItem.setResult(result);
testItem.setDictionaryResults(getAnyDictonaryValues(testResults));
testItem.setResultType(getTestResultType(testResults ));
testItem.setTestSortNumber(test.getSortOrder());
testItem.setReflexGroup(analysis.getTriggeredReflex());
testItem.setChildReflex(analysis.getTriggeredReflex() && isConclusion(result, analysis));
testItem.setQualifiedDictionaryId(getQualifiedDictionaryId(testResults));
testItem.setPastNotes( notes );
return testItem;
}
protected final String getQualifiedDictionaryId(List<TestResult> testResults){
String qualDictionaryIds = "";
for( TestResult testResult : testResults){
if( testResult.getIsQuantifiable()){
if( !"".equals(qualDictionaryIds )){
qualDictionaryIds += ",";
}
qualDictionaryIds += testResult.getValue();
}
}
return "".equals(qualDictionaryIds) ? null : "[" + qualDictionaryIds + "]";
}
protected final String augmentUOMWithRange(String uom, Result result) {
if( result == null){return uom;}
String range = new ResultService( result ).getDisplayReferenceRange( true );
uom = StringUtil.blankIfNull( uom );
return GenericValidator.isBlankOrNull( range ) ? uom : (uom + " ( " + range + " )");
}
protected final boolean isConclusion(Result testResult, Analysis analysis) {
List<Result> results = resultDAO.getResultsByAnalysis(analysis);
if (results.size() == 1) {
return false;
}
Long testResultId = Long.parseLong(testResult.getId());
// This based on the fact that the conclusion is always added
// after the shared result so if there is a result with a larger id
// then this is not a conclusion
for (Result result : results) {
if (Long.parseLong(result.getId()) > testResultId) {
return false;
}
}
return true;
}
@SuppressWarnings("unchecked")
protected final List<TestResult> getPossibleResultsForTest(Test test) {
return testResultDAO.getAllActiveTestResultsPerTest( test );
}
protected final List<IdValuePair> getAnyDictonaryValues(List<TestResult> testResults) {
List<IdValuePair> values = null;
Dictionary dictionary;
if (testResults != null && testResults.size() > 0 && TypeOfTestResultService.ResultType.isDictionaryVariant( testResults.get( 0 ).getTestResultType() )) {
values = new ArrayList<IdValuePair>();
values.add(new IdValuePair("0", ""));
for (TestResult testResult : testResults) {
// Note: result group use to be a criteria but was removed, if
// results are not as expected investigate
if ( TypeOfTestResultService.ResultType.isDictionaryVariant( testResult.getTestResultType() )) {
dictionary = dictionaryDAO.getDataForId(testResult.getValue());
String displayValue = dictionary.getLocalizedName();
if ("unknown".equals(displayValue)) {
displayValue = GenericValidator.isBlankOrNull(dictionary.getLocalAbbreviation()) ? dictionary.getDictEntry()
: dictionary.getLocalAbbreviation();
}
values.add(new IdValuePair(testResult.getValue(), displayValue));
}
}
}
return values;
}
protected final String getTestResultType(List<TestResult> testResults) {
String testResultType = TypeOfTestResultService.ResultType.NUMERIC.getCharacterValue();
if (testResults != null && testResults.size() > 0) {
testResultType = testResults.get(0).getTestResultType();
}
return testResultType;
}
public final List<AnalysisItem> testResultListToAnalysisItemList(List<ResultValidationItem> testResultList) {
List<AnalysisItem> analysisResultList = new ArrayList<AnalysisItem>();
/*
The issue with multiselect results is that each selection is one ResultValidationItem but they all need to
be condensed into one AnalysisItem. There is a many to one mapping. The first multiselect result we have gets rolled into
one AnalysisItem and the rest are skipped but we want to capture any qualified results
*/
boolean multiResultEntered = false;
String currentAccession = null;
AnalysisItem currentMultiSelectAnalysisItem = null;
for (ResultValidationItem testResultItem : testResultList) {
if( !testResultItem.getAccessionNumber().equals(currentAccession)){
currentAccession = testResultItem.getAccessionNumber();
currentMultiSelectAnalysisItem = null;
multiResultEntered = false;
}
if( !multiResultEntered){
AnalysisItem convertedItem = testResultItemToAnalysisItem(testResultItem);
analysisResultList.add(convertedItem);
if( TypeOfTestResultService.ResultType.isMultiSelectVariant( testResultItem.getResultType() )){
multiResultEntered = true;
currentMultiSelectAnalysisItem = convertedItem;
}
}
if(currentMultiSelectAnalysisItem != null && testResultItem.isHasQualifiedResult() ){
currentMultiSelectAnalysisItem.setQualifiedResultValue(testResultItem.getQualifiedResultValue());
currentMultiSelectAnalysisItem.setQualifiedDictionaryId(testResultItem.getQualifiedDictionaryId());
currentMultiSelectAnalysisItem.setHasQualifiedResult(true);
}
}
return analysisResultList;
}
protected final RecordStatus getSampleRecordStatus(Sample sample) {
List<ObservationHistory> ohList = observationHistoryDAO.getAll(null, sample, SAMPLE_STATUS_OBSERVATION_HISTORY_TYPE_ID);
if (ohList.isEmpty()) {
return null;
}
return StatusService.getInstance().getRecordStatusForID(ohList.get(0).getValue());
}
public final AnalysisItem testResultItemToAnalysisItem(ResultValidationItem testResultItem) {
AnalysisItem analysisResultItem = new AnalysisItem();
String testUnits = getUnitsByTestId(testResultItem.getTestId());
String testName = testResultItem.getTestName();
String sortOrder = testResultItem.getTestSortNumber();
Result result = testResultItem.getResult();
if (result != null && result.getAnalyte() != null
&& ANALYTE_CD4_CT_GENERATED_ID.equals(testResultItem.getResult().getAnalyte().getId())) {
testUnits = "";
testName = StringUtil.getMessageForKey("result.conclusion.cd4");
analysisResultItem.setShowAcceptReject(false);
sortOrder = CD4_COUNT_SORT_NUMBER;
} else if (testResultItem.getTestName().equals(totalTestName)) {
analysisResultItem.setShowAcceptReject(false);
analysisResultItem.setReadOnly(true);
testUnits = testResultItem.getUnitsOfMeasure();
analysisResultItem.setIsHighlighted(!"100.0".equals(testResultItem.getResult().getValue()));
}
testUnits = augmentUOMWithRange(testUnits, testResultItem.getResult());
analysisResultItem.setAccessionNumber(testResultItem.getAccessionNumber());
analysisResultItem.setTestName(testName);
analysisResultItem.setUnits(testUnits);
analysisResultItem.setAnalysisId(testResultItem.getAnalysis().getId());
analysisResultItem.setPastNotes(testResultItem.getPastNotes());
analysisResultItem.setResultId(testResultItem.getResultId());
analysisResultItem.setResultType(testResultItem.getResultType());
analysisResultItem.setTestId(testResultItem.getTestId());
analysisResultItem.setTestSortNumber(sortOrder);
analysisResultItem.setDictionaryResults(testResultItem.getDictionaryResults());
analysisResultItem.setDisplayResultAsLog(TestIdentityService.isTestNumericViralLoad(testResultItem.getTestId()));
if( result != null){
if( TypeOfTestResultService.ResultType.isMultiSelectVariant( testResultItem.getResultType() ) ){
analysisResultItem.setMultiSelectResultValues( new AnalysisService( testResultItem.getAnalysis() ).getJSONMultiSelectResults() );
}else{
analysisResultItem.setResult( getFormattedResult( testResultItem ) );
}
if( TypeOfTestResultService.ResultType.NUMERIC.matches( testResultItem.getResultType() )){
// analysisResultItem.setSignificantDigits( result.getMinNormal().equals( result.getMaxNormal())? -1 : result.getSignificantDigits());
analysisResultItem.setSignificantDigits( result.getSignificantDigits());
}
}
analysisResultItem.setReflexGroup(testResultItem.isReflexGroup());
analysisResultItem.setChildReflex(testResultItem.isChildReflex());
analysisResultItem.setNonconforming( testResultItem.isNonconforming() ||
StatusService.getInstance().matches( testResultItem.getAnalysis().getStatusId(), AnalysisStatus.TechnicalRejected ));
analysisResultItem.setQualifiedDictionaryId(testResultItem.getQualifiedDictionaryId());
analysisResultItem.setQualifiedResultValue(testResultItem.getQualifiedResultValue());
analysisResultItem.setQualifiedResultId(testResultItem.getQualificationResultId());
analysisResultItem.setHasQualifiedResult( testResultItem.isHasQualifiedResult() );
return analysisResultItem;
}
protected final String getFormattedResult(ResultValidationItem testResultItem) {
String result = testResultItem.getResult().getValue();
if( TestIdentityService.isTestNumericViralLoad(testResultItem.getTestId()) && !GenericValidator.isBlankOrNull(result)){
return result.split("\\(")[0].trim();
}else{
return new ResultService(testResultItem.getResult()).getResultValue( false );
}
}
public final String getUnitsByTestId(String testId) {
String uomName = null;
if (testId != null) {
uomName = testIdToUnits.get(testId);
if (uomName == null) {
Test test = new Test();
test.setId(testId);
test = testDAO.getTestById(test);
if (test.getUnitOfMeasure() != null) {
uomName = test.getUnitOfMeasure().getName();
testIdToUnits.put(testId, uomName);
} else {
testIdToUnits.put(testId, "");
}
}
}
return uomName;
}
protected final String getTestId(String testName) {
Test test = testDAO.getTestByName(testName);
return test.getId();
}
}