package uk.ac.ox.zoo.seeg.abraid.mp.common.service.workflow.support.extent;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import uk.ac.ox.zoo.seeg.abraid.mp.common.domain.*;
import uk.ac.ox.zoo.seeg.abraid.mp.common.service.core.*;
import java.util.Collection;
import java.util.Map;
import static ch.lambdaj.Lambda.index;
import static ch.lambdaj.Lambda.on;
/**
* Generates disease extents for all relevant diseases.
*
* Copyright (c) 2014 University of Oxford
*/
public class DiseaseExtentGenerator {
private static final String MODE_MESSAGE = "%s disease extent for disease group %d (%s)%s";
private static final String INITIAL_STRING = "Creating initial";
private static final String UPDATING_STRING = "Updating existing";
private static final String GOLD_STANDARD_STRING = " using gold standard";
private static final String DATA_MESSAGE = "Generating extent with %d disease occurrence(s) and %d review(s)";
private static final Logger LOGGER = Logger.getLogger(DiseaseExtentGenerator.class);
private ModelRunService modelRunService;
private ValidationParameterCacheService cacheService;
private DiseaseService diseaseService;
private DiseaseExtentGenerationInputDataSelector extentDataSelector;
private DiseaseExtentGeneratorHelperFactory helperFactory;
private GeometryService geometryService;
public DiseaseExtentGenerator(DiseaseExtentGenerationInputDataSelector extentDataSelector,
DiseaseExtentGeneratorHelperFactory helperFactory,
GeometryService geometryService,
DiseaseService diseaseService,
ModelRunService modelRunService,
ValidationParameterCacheService cacheService) {
this.extentDataSelector = extentDataSelector;
this.helperFactory = helperFactory;
this.geometryService = geometryService;
this.diseaseService = diseaseService;
this.modelRunService = modelRunService;
this.cacheService = cacheService;
}
/**
* Generates a disease extent for a single disease group.
* @param diseaseGroup The disease group.
* @param minimumOccurrenceDate The minimum occurrence date for the disease extent, as calculated from minimum
* occurrence date of all the occurrences that can be sent to the model.
* @param process The type of process that is being performed (auto/manual/gold).
*/
public void generateDiseaseExtent(
DiseaseGroup diseaseGroup, DateTime minimumOccurrenceDate, DiseaseProcessType process) {
// Initial extents are any before the first model run. This is so that the disease extent can be generated m
// multiple times before the initial model run, using all disease occurrences.
boolean isInitial = modelHasNeverSuccessfullyRun(diseaseGroup);
logModeMessage(diseaseGroup, process, isInitial);
Collection<? extends AdminUnitGlobalOrTropical> adminUnits = findReferenceAdminUnitSet(diseaseGroup);
// Calculate new validator extent
DiseaseExtentGenerationInputData validatorExtentInputs = extentDataSelector.selectForValidatorExtent(
diseaseGroup, adminUnits, isInitial, process, minimumOccurrenceDate);
DiseaseExtentGenerationOutputData validatorExtentResults = generateExtent(
diseaseGroup, validatorExtentInputs);
// Calculate new modelling extent (if required)
DiseaseExtentGenerationOutputData modellingExtentResults;
if (isInitial || !process.isAutomatic()) {
modellingExtentResults = validatorExtentResults;
} else {
DiseaseExtentGenerationInputData modellingExtentInputs = extentDataSelector.selectForModellingExtent(
diseaseGroup, validatorExtentInputs);
modellingExtentResults = generateExtent(diseaseGroup, modellingExtentInputs);
}
// Save everything
saveExtent(diseaseGroup, adminUnits,
validatorExtentResults, modellingExtentResults, validatorExtentInputs.getOccurrences());
}
private boolean modelHasNeverSuccessfullyRun(DiseaseGroup diseaseGroup) {
return modelRunService.getMostRecentlyRequestedModelRunWhichCompleted(diseaseGroup.getId()) == null;
}
private Collection<? extends AdminUnitGlobalOrTropical> findReferenceAdminUnitSet(DiseaseGroup diseaseGroup) {
// Find all admin units, for either global or tropical diseases depending on the disease group
// This query is necessary so that admin units with no occurrences appear in the disease extent
return geometryService.getAllAdminUnitGlobalsOrTropicalsForDiseaseGroup(diseaseGroup);
}
private DiseaseExtentGenerationOutputData generateExtent(
DiseaseGroup diseaseGroup, DiseaseExtentGenerationInputData extentInputs) {
logDataMessage(extentInputs);
DiseaseExtentGeneratorHelper helper =
helperFactory.createHelper(diseaseGroup, extentInputs);
return helper.computeDiseaseExtent();
}
private void saveExtent(DiseaseGroup diseaseGroup,
Collection<? extends AdminUnitGlobalOrTropical> adminUnits,
DiseaseExtentGenerationOutputData validatorExtentResults,
DiseaseExtentGenerationOutputData modellingExtentResults,
Collection<DiseaseOccurrence> occurrencesUsedInValidatorExtent) {
// Update admin unit disease extent classes
saveAdminUnitDiseaseExtentClasses(diseaseGroup, adminUnits, validatorExtentResults, modellingExtentResults);
// Update aggregated extent geometry
diseaseService.updateAggregatedDiseaseExtent(diseaseGroup);
// Clear distance cache
cacheService.clearDistanceToExtentCacheForDisease(diseaseGroup.getId());
// Save input occurrences
diseaseGroup.getDiseaseExtentParameters()
.setLastValidatorExtentUpdateInputOccurrences(occurrencesUsedInValidatorExtent);
// Update last extent generation date
diseaseGroup.setLastExtentGenerationDate(DateTime.now());
diseaseService.saveDiseaseGroup(diseaseGroup);
}
private void saveAdminUnitDiseaseExtentClasses(DiseaseGroup diseaseGroup,
Collection<? extends AdminUnitGlobalOrTropical> adminUnits,
DiseaseExtentGenerationOutputData validatorExtentResults,
DiseaseExtentGenerationOutputData modellingExtentResults) {
// Get data to be overwritten
Map<Integer, AdminUnitDiseaseExtentClass> currentDiseaseExtent = index(
diseaseService.getDiseaseExtentByDiseaseGroupId(diseaseGroup.getId()),
on(AdminUnitDiseaseExtentClass.class).getAdminUnitGlobalOrTropical().getGaulCode());
// Update disease extent classes
for (AdminUnitGlobalOrTropical adminUnit : adminUnits) {
Integer gaulCode = adminUnit.getGaulCode();
AdminUnitDiseaseExtentClass row;
if (currentDiseaseExtent.containsKey(gaulCode)) {
row = currentDiseaseExtent.get(gaulCode);
} else {
row = new AdminUnitDiseaseExtentClass();
row.setDiseaseGroup(diseaseGroup);
row.setAdminUnitGlobalOrTropical(adminUnit);
}
DiseaseExtentClass newModellingClass =
modellingExtentResults.getDiseaseExtentClassByGaulCode().get(gaulCode);
if (!newModellingClass.equals(row.getDiseaseExtentClass())) {
row.setClassChangedDate(DateTime.now());
}
row.setDiseaseExtentClass(newModellingClass);
row.setValidatorDiseaseExtentClass(validatorExtentResults.getDiseaseExtentClassByGaulCode().get(gaulCode));
row.setValidatorOccurrenceCount(validatorExtentResults.getOccurrenceCounts().get(gaulCode));
row.setLatestValidatorOccurrences(validatorExtentResults.getLatestOccurrencesByGaulCode().get(gaulCode));
diseaseService.saveAdminUnitDiseaseExtentClass(row);
}
}
private void logModeMessage(DiseaseGroup diseaseGroup, DiseaseProcessType process, boolean isInitial) {
LOGGER.info(String.format(MODE_MESSAGE,
isInitial ? INITIAL_STRING : UPDATING_STRING,
diseaseGroup.getId(),
diseaseGroup.getName(),
(process.isGoldStandard()) ? GOLD_STANDARD_STRING : ""));
}
private void logDataMessage(DiseaseExtentGenerationInputData extentInputs) {
LOGGER.info(String.format(DATA_MESSAGE,
extentInputs.getOccurrences().size(),
(extentInputs.getReviews() == null) ? 0 : extentInputs.getReviews().size()));
}
}