class StdDevOutlierAnalysisService implements DataAnalysisService {

    @Override
    public final List<DeflatedDataValue> analyse( Collection<OrganisationUnit> parents,
        Collection<DataElement> dataElements, Collection<Period> periods, Double stdDevFactor, Date from )
    {
        log.info( "Starting std dev analysis, no of org units: " + parents.size() + ", factor: " + stdDevFactor + ", from: " + from );

        List<DeflatedDataValue> outlierCollection = new ArrayList<>();

        List<String> parentsPaths = parents.stream().map( OrganisationUnit::getPath ).collect( Collectors.toList() );

        loop : for ( DataElement dataElement : dataElements )
        {
            // TODO filter periods with data element period type

            if ( dataElement.getValueType().isNumeric() && stdDevFactor != null )
            {
                Set<CategoryOptionCombo> categoryOptionCombos = dataElement.getCategoryOptionCombos();

                List<DataAnalysisMeasures> measuresList = dataAnalysisStore.getDataAnalysisMeasures( dataElement, categoryOptionCombos, parentsPaths, from );

                MapMap<Long, Long, Integer> lowBoundMapMap = new MapMap<>(); // catOptionComboId, orgUnitId, lowBound
                MapMap<Long, Long, Integer> highBoundMapMap = new MapMap<>(); // catOptionComboId, orgUnitId, highBound

                for ( DataAnalysisMeasures measures : measuresList )
                {
                    int lowBound = (int) Math.round( MathUtils.getLowBound( measures.getStandardDeviation(), stdDevFactor, measures.getAverage() ) );
                    int highBound = (int) Math.round( MathUtils.getHighBound( measures.getStandardDeviation(), stdDevFactor, measures.getAverage() ) );

                    lowBoundMapMap.putEntry( measures.getCategoryOptionComboId(), measures.getOrgUnitId(), lowBound );
                    highBoundMapMap.putEntry( measures.getCategoryOptionComboId(), measures.getOrgUnitId(), highBound );
                }

                for ( CategoryOptionCombo categoryOptionCombo : categoryOptionCombos )
                {
                    Map<Long, Integer> lowBoundMap = lowBoundMapMap.get( categoryOptionCombo.getId() );
                    Map<Long, Integer> highBoundMap = highBoundMapMap.get( categoryOptionCombo.getId() );

                    outlierCollection.addAll( dataAnalysisStore.getDeflatedDataValues( dataElement, categoryOptionCombo, periods,
                        lowBoundMap, highBoundMap ) );

                    if ( outlierCollection.size() > MAX_OUTLIERS )
                    {
                        break loop;
                    }
                }
            }
        }

        return outlierCollection;
    }

    public  StdDevOutlierAnalysisService( DataAnalysisStore dataAnalysisStore );

}