/**
* 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) ITECH, University of Washington, Seattle WA. All Rights Reserved.
*
*/
package us.mn.state.health.lims.testreflex.action.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.services.TestService;
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.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.sample.valueholder.Sample;
import us.mn.state.health.lims.scriptlet.dao.ScriptletDAO;
import us.mn.state.health.lims.scriptlet.daoimpl.ScriptletDAOImpl;
import us.mn.state.health.lims.scriptlet.valueholder.Scriptlet;
import us.mn.state.health.lims.test.dao.TestDAO;
import us.mn.state.health.lims.test.daoimpl.TestDAOImpl;
import us.mn.state.health.lims.test.valueholder.Test;
import us.mn.state.health.lims.testreflex.dao.TestReflexDAO;
import us.mn.state.health.lims.testreflex.daoimpl.TestReflexDAOImpl;
import us.mn.state.health.lims.testreflex.valueholder.TestReflex;
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 TestReflexUtil {
private static final String USER_CHOOSE_FLAG = "UC";
private static String CONCLUSION_ANAYLETE_ID = null;
private static Analyte CD4_ANAYLETE = null;
private static String CD4_SCRIPTLET_ID = null;
private static Set<String> TRIGGERING_REFLEX_TEST_IDS;
private static Set<String> TRIGGERING_UC_REFLEX_TEST_IDS;
private static Map<String, List<TestReflex>> TEST_TO_REFLEX_MAP;
private static ObservationHistoryDAO observationDAO = new ObservationHistoryDAOImpl();
private static ResultDAO resultDAO = new ResultDAOImpl();
private static TestResultDAO testResultDAO = new TestResultDAOImpl();
private static AnalysisDAO analysisDAO = new AnalysisDAOImpl();
private TestReflexDAO testReflexDAO = new TestReflexDAOImpl();
private TestReflexResolver reflexResolver = new TestReflexResolver();
private String currentUserId;
static {
AnalyteDAO analyteDAO = new AnalyteDAOImpl();
Analyte analyte = new Analyte();
analyte.setAnalyteName("Conclusion");
CONCLUSION_ANAYLETE_ID = analyteDAO.getAnalyteByName(analyte, false).getId();
analyte.setAnalyteName("generated CD4 Count");
CD4_ANAYLETE = analyteDAO.getAnalyteByName(analyte, false);
ScriptletDAO scriptletDAO = new ScriptletDAOImpl();
Scriptlet scriptlet = new Scriptlet();
scriptlet.setScriptletName("Calculate CD4");
scriptlet = scriptletDAO.getScriptletByName(scriptlet);
if (!(scriptlet == null || scriptlet.getId() == null)) {
CD4_SCRIPTLET_ID = scriptlet.getId();
}
TRIGGERING_REFLEX_TEST_IDS = new HashSet<String>();
TRIGGERING_UC_REFLEX_TEST_IDS = new HashSet<String>();
TEST_TO_REFLEX_MAP = new HashMap<String, List<TestReflex>>();
@SuppressWarnings("unchecked")
List<TestReflex> reflexes = new TestReflexDAOImpl().getAllTestReflexs();
for( TestReflex testReflex : reflexes){
String testId = testReflex.getTest().getId();
List<TestReflex> reflexValues = TEST_TO_REFLEX_MAP.get(testId);
if( reflexValues == null){
reflexValues = new ArrayList<TestReflex>();
TEST_TO_REFLEX_MAP.put(testId, reflexValues);
}
reflexValues.add(testReflex);
TRIGGERING_REFLEX_TEST_IDS.add(testId);
if( isUserChoiceReflex( testReflex )){
TRIGGERING_UC_REFLEX_TEST_IDS.add(testReflex.getTest().getId());
}
}
}
public static boolean isTriggeringReflexTestId(String testId){
return TRIGGERING_REFLEX_TEST_IDS.contains(testId);
}
public static boolean isTriggeringUserChoiceReflexTestId(String testId){
return TRIGGERING_UC_REFLEX_TEST_IDS.contains(testId);
}
public static boolean isUserChoiceReflex( TestReflex reflex){
return USER_CHOOSE_FLAG.equals(reflex.getFlags());
}
public static boolean testIsTriggeringReflexWithSibs(String testId){
if( isTriggeringReflexTestId(testId)){
List<TestReflex> reflexes = TEST_TO_REFLEX_MAP.get(testId);
for( TestReflex reflex : reflexes){
if( !GenericValidator.isBlankOrNull(reflex.getSiblingReflexId() )){
return true;
}
}
}
return false;
}
public static String makeReflexTestName(TestReflex testReflex) {
return TestService.getUserLocalizedTestName( testReflex.getAddedTest() );
}
public static String makeReflexScriptName(TestReflex testReflex) {
return testReflex.getActionScriptlet().getScriptletName();
}
public static String makeReflexTestValue(TestReflex testReflex) {
return "test_" + testReflex.getAddedTest().getId();
}
public static String makeReflexScriptValue(TestReflex testReflex) {
return "script_" + testReflex.getActionScriptletId();
}
public List<TestReflex> getSiblings(TestReflex reflex) {
List<TestReflex> siblingList = new ArrayList<TestReflex>();
if (reflex.getSiblingReflexId() != null) {
List<String> visitedReflexIdList = new ArrayList<String>();
visitedReflexIdList.add(reflex.getId());
TestReflex currentReflex = reflex;
while (true) {
String siblingId = currentReflex.getSiblingReflexId();
if (siblingId == null || visitedReflexIdList.contains(siblingId)) {
break;
}
currentReflex = new TestReflex();
currentReflex.setId(siblingId);
testReflexDAO.getData(currentReflex);
if (GenericValidator.isBlankOrNull(currentReflex.getId())) {
break;
} else {
siblingList.add(currentReflex);
visitedReflexIdList.add(siblingId);
}
}
}
return siblingList;
}
/*
* This will find all the possible reflexes for a test. The intended use is
* to mark a results row as possibly generating a reflex for which the user
* will have to select the reflex action (either a conclusion or another
* test)
*/
public List<TestReflex> getPossibleUserChoiceTestReflexsForTest(String testId) {
return testReflexDAO.getTestReflexsByTestAndFlag(testId, USER_CHOOSE_FLAG);
}
/*
* Gets the test reflex associated with this test and result.
*/
@SuppressWarnings("unchecked")
public List<TestReflex> getTestReflexsForDictioanryResultTestId(String dictionaryId, String testId, boolean userChoiceOnly) {
if (GenericValidator.isBlankOrNull(dictionaryId) || GenericValidator.isBlankOrNull(testId)) {
return new ArrayList<TestReflex>();
}
TestResult testResult = testResultDAO.getTestResultsByTestAndDictonaryResult(testId, dictionaryId);
if (testResult == null) {
return new ArrayList<TestReflex>();
}
return userChoiceOnly ? testReflexDAO.getFlaggedTestReflexesByTestResult(testResult, USER_CHOOSE_FLAG)
: testReflexDAO.getTestReflexesByTestResult(testResult);
}
public void setCurrentUserId(String currentUserId) {
this.currentUserId = currentUserId;
}
public void addNewTestsToDBForReflexTests(List<TestReflexBean> newResults) throws IllegalStateException {
if (currentUserId == null) {
throw new IllegalStateException("currentUserId not set");
}
/*
* There are several use cases
* 1. A single result triggers a single reflex
* 2. A single result triggers more than one reflex
* 3. Multiple new results triggers a single reflex
* 4. A mixture of new result and a previous results triggers a single reflex
* 5. Multiple new results trigger a more than one reflex
* 6. A mixture of new and previous results trigger more than one reflex
* 7. A single result forced the user to select reflexes
* 8. A mixture of new and previous results forced the user to select reflexes
* 9. Multiselect results forced the user to select reflexes for each result
* 10. A scriptlet determines what the next test will be
*/
/*
* We want to handle results with user selections first
*/
Collections.sort(newResults, new Comparator<TestReflexBean>(){
public int compare(TestReflexBean o1, TestReflexBean o2) {
if( o1.getTriggersToSelectedReflexesMap().isEmpty()){
return o2.getTriggersToSelectedReflexesMap().isEmpty() ? 0 : 1;
}else{
return o2.getTriggersToSelectedReflexesMap().isEmpty() ? -1 : 0;
}
}});
/*
* For each sample we want to track which testReflexes have already
* been evaluated. If we don't track them and two parents are being
* updated then we would trigger the same reflex twice
*/
Map<String, List<String>> sampleIdToHandledTestReflexIds = new HashMap<String, List<String>>();
// keep track of analysis which have triggered reflexes
List<Analysis> parentAnalysisList = new ArrayList<Analysis>();
for (TestReflexBean reflexBean : newResults) {
// list may be empty or have previous handled reflexes
List<String> handledReflexIdList = getHandledReflexesForSample(sampleIdToHandledTestReflexIds, reflexBean);
// use cases 1-6, 10
if (reflexBean.getTriggersToSelectedReflexesMap().isEmpty()) {
handleAutomaticReflexes( parentAnalysisList, reflexBean, handledReflexIdList );
} else { // use cases 7,8,9
handleUserSelectedReflexes( parentAnalysisList, reflexBean );
}
}
}
private void handleUserSelectedReflexes( List<Analysis> parentAnalysisList, TestReflexBean reflexBean ){
//The reflexes and the triggering tests have already been identified by TestReflexUserChoiceProvider, if all of the parents are not being
//picked up fix it there
for (String triggeringTests : reflexBean.getTriggersToSelectedReflexesMap().keySet()) {
List<String> addedActionIds = reflexBean.getTriggersToSelectedReflexesMap().get( triggeringTests );
// no reflexes triggered so no parents
parentAnalysisList.clear();
for( String addedActionId : addedActionIds){
TestReflex reflex = new TestReflex();
reflex.setFlags( USER_CHOOSE_FLAG );
reflex.setTestId( triggeringTests.split( "_" )[0] );
String[] splitActionId = addedActionId.split( "_" );
if("test".equals( splitActionId[0] )){
reflex.setAddedTestId( splitActionId[1] );
}else{
reflex.setActionScriptletId( splitActionId[1] );
}
addReflexTest(reflex, reflexBean.getResult(), reflexBean.getPatient().getId(), reflexBean.getSample(),
true, true, addedActionId, false);
}
if (reflexBean.getResult().getAnalysis() != null) {
parentAnalysisList.add(reflexBean.getResult().getAnalysis());
}
markSibAnalysisAsParent(parentAnalysisList);
}
}
private void handleAutomaticReflexes( List<Analysis> parentAnalysisList, TestReflexBean reflexBean, List<String> handledReflexIdList ){
// More than one reflex may be returned if more than one action
// should be taken by the result
List<TestReflex> reflexesForResult = reflexResolver.getTestReflexesForResult( reflexBean.getResult() );
for (TestReflex reflexForResult : reflexesForResult) {
// filter out handled reflexes
if ( !GenericValidator.isBlankOrNull( reflexForResult.getSiblingReflexId() ) && handledReflexIdList.contains(reflexForResult.getId())) {
continue;
}
handledReflexIdList.add(reflexForResult.getId());
List<TestReflex> siblingsOfResultReflex = getSiblings(reflexForResult);
// no reflexes triggered so no parents
parentAnalysisList.clear();
// side effect of populating parent list and
// handledRefleIdList
boolean siblingsSatisfied = checkIfSiblingsSatisfiedAndPopulateParentList(parentAnalysisList, reflexBean, handledReflexIdList,
siblingsOfResultReflex);
// All the conditions are satisfied so we can handle the
// reflexes
if (siblingsSatisfied) {
boolean allSibAnalysisCausedReflex = doAllAnalysisHaveReflex(parentAnalysisList, reflexBean);
addReflexTest(reflexForResult, reflexBean.getResult(), reflexBean.getPatient().getId(), reflexBean.getSample(),
true, true, null, allSibAnalysisCausedReflex);
// there may be multiple reflexes
for (TestReflex siblingReflex : siblingsOfResultReflex) {
// we want to make sure we don't add the same test
// or observation twice
boolean addTest = siblingReflex.getAddedTestId() != null
&& !siblingReflex.getAddedTestId().equals(reflexForResult.getAddedTestId());
boolean handleAction = siblingReflex.getActionScriptletId() != null
&& !siblingReflex.getActionScriptletId().equals(reflexForResult.getActionScriptletId());
addReflexTest(siblingReflex, reflexBean.getResult(), reflexBean.getPatient().getId(), reflexBean.getSample(),
addTest, handleAction, null, allSibAnalysisCausedReflex);
}
if (reflexBean.getResult().getAnalysis() != null) {
parentAnalysisList.add(reflexBean.getResult().getAnalysis());
}
markSibAnalysisAsParent(parentAnalysisList);
}
}
}
private boolean doAllAnalysisHaveReflex(List<Analysis> parentAnalysisList, TestReflexBean reflexBean) {
if (reflexBean.getResult().getAnalysis() == null || !reflexBean.getResult().getAnalysis().getTriggeredReflex()) {
return false;
}
for (Analysis analysis : parentAnalysisList) {
if (!analysis.getTriggeredReflex()) {
return false;
}
}
return true;
}
protected boolean checkIfSiblingsSatisfiedAndPopulateParentList(List<Analysis> parentAnalysisList, TestReflexBean resultSet,
List<String> handledReflexIdList, List<TestReflex> siblingsOfResultReflex) {
boolean siblingsSatisfied = true;
for (TestReflex siblingReflex : siblingsOfResultReflex) {
// mark the sibling as being handled, it may also be on the
// list
handledReflexIdList.add(siblingReflex.getId());
if (siblingsSatisfied) {
if (!reflexResolver.isSatisfied(siblingReflex, resultSet.getSample())) {
siblingsSatisfied = false;
parentAnalysisList.clear();
// we're not breaking out of the loop to let the rest of the
// siblings be added to the handled list
} else if (reflexResolver.getLastValidAnalysis() != null) {
parentAnalysisList.add(reflexResolver.getLastValidAnalysis());
}
}
}
return siblingsSatisfied;
}
protected List<String> getHandledReflexesForSample(Map<String, List<String>> handledReflexsBySample, TestReflexBean resultSet) {
List<String> handledReflexIdList = handledReflexsBySample.get(resultSet.getSample().getId());
if (handledReflexIdList == null) {
handledReflexIdList = new ArrayList<String>();
handledReflexsBySample.put(resultSet.getSample().getId(), handledReflexIdList);
}
return handledReflexIdList;
}
private void addReflexTest(TestReflex reflex, Result result, String patientId, Sample sample, boolean addTest, boolean handleAction,
String actionSelectionId, boolean failOnDuplicateTest) {
if (addTest || handleAction) {
ReflexAction reflexAction = reflexResolver.getReflexAction();
reflexAction.handleReflex(reflex, result, actionSelectionId);
ObservationHistory observation = reflexAction.getObservation();
if (observation != null && handleAction) {
observation.setPatientId(patientId);
observation.setSampleId(sample.getId());
observation.setSysUserId(currentUserId);
observationDAO.insertData(observation);
}
Analysis newAnalysis = reflexAction.getNewAnalysis();
Result finalResult = reflexAction.getFinalResult();
/*******
* This is allowing duplicate tests to be added for CD4 absolute. If
* the previous CD4 absolute was attached to a different analysis
* from the CD4 % then this fails. This also precludes updates
*/
if (failOnDuplicateTest && testDoneForSample(newAnalysis)) {
return;
}
if (finalResult != null) {
finalResult.setAnalysis(result.getAnalysis());
finalResult.setSysUserId(currentUserId);
if (finalResult.getId() == null) {
resultDAO.insertData(finalResult);
} else {
resultDAO.updateData(finalResult);
}
}
if (newAnalysis != null && addTest) {
Analysis currentAnalysis = result.getAnalysis();
newAnalysis.setSysUserId(currentUserId);
currentAnalysis.setSysUserId(currentUserId);
currentAnalysis.setTriggeredReflex(Boolean.TRUE);
analysisDAO.insertData(newAnalysis, false);
analysisDAO.updateData(currentAnalysis);
}
}
}
private boolean testDoneForSample(Analysis newAnalysis) {
if (newAnalysis == null) {
return false;
}
String newTestId = newAnalysis.getTest().getId();
Sample sample = newAnalysis.getSampleItem().getSample();
List<Analysis> analysisList = analysisDAO.getAnalysesBySampleId(sample.getId());
for (Analysis analysis : analysisList) {
if ( duplicateTest( newTestId, analysis, newAnalysis ) ) {
return true;
}
}
return false;
}
private boolean duplicateTest( String newTestId, Analysis existingAnalysis, Analysis newAnalysis ){
return newTestId.equals( existingAnalysis.getTest().getId() ) && existingAnalysis.getSampleTypeName().equals( newAnalysis.getSampleTypeName() );
}
private void markSibAnalysisAsParent(List<Analysis> parentAnalysisList) {
for (Analysis analysis : parentAnalysisList) {
analysis.setSysUserId(currentUserId);
analysis.setTriggeredReflex(Boolean.TRUE);
analysisDAO.updateData(analysis);
}
}
public void updateModifiedReflexes(List<TestReflexBean> reflexBeanList) throws IllegalStateException {
if (currentUserId == null) {
throw new IllegalStateException("currentUserId not set");
}
/*
* N.B. currently we are only updating calculated values and conclusions
* This means only reflexes which have scriptlets as their action.
*/
/*
* The scenarios we need to support 1. The conclusion does not yet exist
* 2. The conclusion does exist but one of the determining results has
* changed 3. The conclusion does exist and has been changed by the user
*
* If both 2 and 3 exist then 3 wins
*/
/*
* the general processing flow will be to; 1. create groups of results
* by sample 2. go through each group and ignore groups in which none of
* the results have scriptlet reflexes 3. Ignore the group if one of the
* results is a conclusion 4. find if there is an existing conclusion
* for the reflex 5. Either update or modify the conclusion if the
* reflex is satisfied
*/
Map<Sample, List<TestReflexBean>> groupedResults = groupBySample(reflexBeanList);
for (List<TestReflexBean> reflexList : groupedResults.values()) {
TestReflex scriptletReflex = getScriptletReflex(reflexList);
if (scriptletReflex != null) {
if (noConclusionInModifiedResult(reflexList)) {
List<Result> resultList = resultDAO.getResultsForSample(reflexList.get(0).getSample());
// We're Unfortunately hard coding some business rules here
if (CD4_SCRIPTLET_ID.equals(scriptletReflex.getActionScriptlet().getId())) {
RetroCIReflexActions reflexAction = new RetroCIReflexActions();
Result calculatedResults = reflexAction.getCD4CalculationResult(reflexList.get(0).getSample());
if (calculatedResults != null) {
Result cd4Result = null;
for (Result result : resultList) {
if (result.getAnalyte() != null && CD4_ANAYLETE.getId().equals(result.getAnalyte().getId())) {
cd4Result = result;
cd4Result.setValue(calculatedResults.getValue());
break;
}
}
if (cd4Result == null) {
cd4Result = calculatedResults;
}
cd4Result.setSysUserId(currentUserId);
if (cd4Result.getId() == null) {
resultDAO.insertData(cd4Result);
} else {
resultDAO.updateData(cd4Result);
}
}
} // else It is a HIV conclusion
}
}
}
}
private Map<Sample, List<TestReflexBean>> groupBySample(List<TestReflexBean> reflexBeanList) {
Map<Sample, List<TestReflexBean>> groupedBeans = new HashMap<Sample, List<TestReflexBean>>();
for (TestReflexBean bean : reflexBeanList) {
List<TestReflexBean> beanList = groupedBeans.get(bean.getSample());
if (beanList == null) {
beanList = new ArrayList<TestReflexBean>();
groupedBeans.put(bean.getSample(), beanList);
}
beanList.add(bean);
}
return groupedBeans;
}
private TestReflex getScriptletReflex(List<TestReflexBean> reflexBeanList) {
for (TestReflexBean bean : reflexBeanList) {
List<TestReflex> reflexList = reflexResolver.getTestReflexesForResult( bean.getResult() );
if (!reflexList.isEmpty()) {
for (TestReflex testReflex : reflexList) {
if (testReflex.getActionScriptlet() != null) {
return testReflex;
}
}
}
}
return null;
}
private boolean noConclusionInModifiedResult(List<TestReflexBean> reflexBeanList) {
for (TestReflexBean bean : reflexBeanList) {
if (isConclusion(bean.getResult())) {
return true;
}
}
return true;
}
private boolean isConclusion(Result result) {
Analyte analyte = result.getAnalyte();
return analyte != null && ( CONCLUSION_ANAYLETE_ID.equals( analyte.getId() ) || CD4_ANAYLETE.getId().equals( analyte.getId() ) );
}
}