← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 12589: validationRule JUnit tests, minor enhancements

 

------------------------------------------------------------
revno: 12589
committer: dhis2-c <dhis2@xxxxxxxxxxxxxx>
branch nick: trunk
timestamp: Fri 2013-10-11 08:58:30 -0400
message:
  validationRule JUnit tests, minor enhancements
modified:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationResult.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/hibernate/HibernateDataValueStore.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/DefaultValidationRuleService.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRunContext.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationWorkerThread.java
  dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/validation/ValidationRuleServiceTest.java
  dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/MathUtils.java


--
lp:dhis2
https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk

Your team DHIS 2 developers is subscribed to branch lp:dhis2.
To unsubscribe from this branch go to https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk/+edit-subscription
=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationResult.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationResult.java	2013-10-11 09:16:32 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationResult.java	2013-10-11 12:58:30 +0000
@@ -145,42 +145,77 @@
         {
             return false;
         }
+        
+        if ( leftsideValue == null )
+        {
+            if ( other.leftsideValue != null )
+            {
+                return false;
+            }
+        }
+        else if ( other.leftsideValue == null )
+        {
+            return false;
+        }
+        else if ( Math.round( 100.0 * leftsideValue ) != Math.round( 100 * other.leftsideValue ) )
+        {
+            return false;
+        }
+
+        if ( rightsideValue == null )
+        {
+            if ( other.rightsideValue != null )
+            {
+                return false;
+            }
+        }
+        else if ( other.rightsideValue == null )
+        {
+            return false;
+        }
+        else if ( Math.round( 100.0 * leftsideValue ) != Math.round( 100 * other.leftsideValue ) )
+        {
+            return false;
+        }
 
         return true;
     }
 
     public int compareTo( ValidationResult other )
     {
-        if ( source.getName().compareTo( other.source.getName() ) != 0 )
-        {
-            return source.getName().compareTo( other.source.getName() );
-        }
-        else if ( period.getStartDate().compareTo( other.period.getStartDate() ) != 0 )
-        {
-            return period.getStartDate().compareTo( other.period.getStartDate() );
-        }
-        else if ( source.getName().compareTo( other.source.getName() ) != 0 )
-        {
-            return source.getName().compareTo( other.source.getName() );
-        }
-        else if ( period.getStartDate().compareTo( other.period.getStartDate() ) != 0 )
-        {
-            return period.getStartDate().compareTo( other.period.getStartDate() );
-        }
-        else if ( period.getEndDate().compareTo( other.period.getEndDate() ) != 0 )
-        {
-            return period.getEndDate().compareTo( other.period.getEndDate() );
-        }
-        else if ( validationRule.getImportance().compareTo( other.validationRule.getImportance() ) != 0 )
-        {
-            return validationImportanceOrder( validationRule.getImportance() )
-                - validationImportanceOrder( other.validationRule.getImportance() );
-        }
-        else
-        {
-            return validationRule.getLeftSide().getDescription()
-                .compareTo( other.validationRule.getLeftSide().getDescription() );
-        }
+    	int result;
+    	
+    	result = source.getName().compareTo( other.source.getName() );
+    	if ( result != 0 ) return result;
+    	
+    	result = period.getStartDate().compareTo( other.period.getStartDate() );
+    	if ( result != 0 ) return result;
+
+    	result = period.getEndDate().compareTo( other.period.getEndDate() );
+    	if ( result != 0 ) return result;
+
+    	result = validationImportanceOrder( validationRule.getImportance() )
+				- validationImportanceOrder( other.validationRule.getImportance() );
+    	if ( result != 0 ) return result;
+
+    	result = validationRule.getLeftSide().getDescription()
+				.compareTo( other.validationRule.getLeftSide().getDescription() );
+    	if ( result != 0 ) return result;
+
+    	result = validationRule.getOperator().compareTo( other.validationRule.getOperator() );
+    	if ( result != 0 ) return result;
+
+    	result = validationRule.getRightSide().getDescription()
+				.compareTo( other.validationRule.getRightSide().getDescription() );
+    	if ( result != 0 ) return result;
+
+    	result = ( int ) Math.signum( Math.round( 100.0 * leftsideValue ) - Math.round( 100.0 * other.leftsideValue ) );
+    	if ( result != 0 ) return result;
+    	
+    	result = ( int ) Math.signum( Math.round( 100.0 * rightsideValue ) - Math.round( 100.0 * other.rightsideValue ) );
+    	if ( result != 0 ) return result;
+    	
+    	return 0;
     }
 
     private int validationImportanceOrder( String importance )

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/hibernate/HibernateDataValueStore.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/hibernate/HibernateDataValueStore.java	2013-10-08 19:10:40 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/hibernate/HibernateDataValueStore.java	2013-10-11 12:58:30 +0000
@@ -38,6 +38,8 @@
 import java.util.HashSet;
 import java.util.Map;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 import org.hibernate.Criteria;
 import org.hibernate.Query;
 import org.hibernate.Session;
@@ -70,6 +72,8 @@
 public class HibernateDataValueStore
     implements DataValueStore
 {
+    private static final Log log = LogFactory.getLog( HibernateDataValueStore.class );
+    
     // -------------------------------------------------------------------------
     // Dependencies
     // -------------------------------------------------------------------------
@@ -489,6 +493,8 @@
             "and p.enddate >= '" + DateUtils.getMediumDateString( date ) + "' " +
         	"and p.periodtypeid in (" + TextUtils.getCommaDelimitedString( ConversionUtils.getIdentifiers( PeriodType.class, periodTypes ) ) + ") ";
 
+        log.trace( "getDataValueMap sql = " + sql );
+        
         SqlRowSet rowSet = jdbcTemplate.queryForRowSet( sql );
         
         Map<DataElementOperand, Long> checkForDuplicates = new HashMap<DataElementOperand, Long>();
@@ -503,6 +509,8 @@
             Date periodEndDate = rowSet.getDate( 6 );
             long periodInterval = periodEndDate.getTime() - periodStartDate.getTime();
 
+            log.trace( "row: " + dataElement + " = " + value + " [" + periodStartDate + " : " + periodEndDate + "]");
+
             if ( value != null )
             {
                 DataElementOperand dataElementOperand = new DataElementOperand( dataElement, optionCombo );

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/DefaultValidationRuleService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/DefaultValidationRuleService.java	2013-10-11 09:16:32 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/DefaultValidationRuleService.java	2013-10-11 12:58:30 +0000
@@ -175,7 +175,7 @@
 
     public Collection<ValidationResult> validate( Date startDate, Date endDate, Collection<OrganisationUnit> sources )
     {
-        log.info( "validate( startDate=" + startDate + " endDate=" + endDate + " sources[" + sources.size() + "] )" );
+        log.info( "Validate startDate=" + startDate + " endDate=" + endDate + " sources[" + sources.size() + "]" );
         Collection<Period> periods = periodService.getPeriodsBetweenDates( startDate, endDate );
         Collection<ValidationRule> rules = getAllValidationRules();
         return validateInternal( sources, periods, rules, ValidationRunType.INTERACTIVE, null );
@@ -184,8 +184,8 @@
     public Collection<ValidationResult> validate( Date startDate, Date endDate, Collection<OrganisationUnit> sources,
         ValidationRuleGroup group )
     {
-        log.info( "validate( startDate=" + startDate + " endDate=" + endDate + " sources[" + sources.size()
-            + "] group=" + group.getName() + " )" );
+    	log.info( "Validate startDate=" + startDate + " endDate=" + endDate + " sources[" + sources.size()
+            + "] group=" + group.getName() );
         Collection<Period> periods = periodService.getPeriodsBetweenDates( startDate, endDate );
         Collection<ValidationRule> rules = group.getMembers();
         return validateInternal( sources, periods, rules, ValidationRunType.INTERACTIVE, null );
@@ -193,7 +193,7 @@
 
     public Collection<ValidationResult> validate( Date startDate, Date endDate, OrganisationUnit source )
     {
-        log.info( "validate( startDate=" + startDate + " endDate=" + endDate + " source=" + source.getName() + " )" );
+    	log.info( "Validate startDate=" + startDate + " endDate=" + endDate + " source=" + source.getName() );
         Collection<Period> periods = periodService.getPeriodsBetweenDates( startDate, endDate );
         Collection<ValidationRule> rules = getAllValidationRules();
         return validateInternal( source, periods, rules, ValidationRunType.INTERACTIVE, null );
@@ -201,8 +201,8 @@
 
     public Collection<ValidationResult> validate( DataSet dataSet, Period period, OrganisationUnit source )
     {
-        log.info( "validate( dataSet=" + dataSet.getName() + " period=[" + period.getPeriodType().getName() + " "
-            + period.getStartDate() + " " + period.getEndDate() + "]" + " source=" + source.getName() + " )" );
+    	log.info( "Validate dataSet=" + dataSet.getName() + " period=[" + period.getPeriodType().getName() + " "
+            + period.getStartDate() + " " + period.getEndDate() + "]" + " source=" + source.getName() );
         Collection<Period> periods = new ArrayList<Period>();
         periods.add( period );
 
@@ -241,11 +241,13 @@
         
         Date thisAlertRun = new Date();
         
-        log.info( "alertRun() sources[" + sources.size() + "] periods[" + periods.size() + "] rules[" + rules.size()
+        log.info( "alertRun sources[" + sources.size() + "] periods[" + periods.size() + "] rules[" + rules.size()
             + "] last run " + lastAlertRun == null ? "(none)" : lastAlertRun );
+        
         Collection<ValidationResult> results = validateInternal( sources, periods, rules, ValidationRunType.ALERT,
             lastAlertRun );
-        log.info( "alertRun() results[" + results.size() + "]" );
+        
+        log.trace( "alertRun() results[" + results.size() + "]" );
         
         if ( !results.isEmpty() )
         {

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRunContext.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRunContext.java	2013-10-11 09:16:32 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationRunContext.java	2013-10-11 12:58:30 +0000
@@ -39,6 +39,8 @@
 
 import org.apache.commons.lang.builder.ToStringBuilder;
 import org.apache.commons.lang.builder.ToStringStyle;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
 import org.hisp.dhis.dataelement.DataElement;
 import org.hisp.dhis.dataset.DataSet;
 import org.hisp.dhis.datavalue.DataValueService;
@@ -66,6 +68,8 @@
  */
 public class ValidationRunContext
 {
+    private static final Log log = LogFactory.getLog( ValidationRunContext.class );
+
     private Map<PeriodType, PeriodTypeExtended> periodTypeExtendedMap;
 
     private ValidationRunType runType;
@@ -141,6 +145,8 @@
     private void initialize( Collection<OrganisationUnit> sources, Collection<Period> periods,
         Collection<ValidationRule> rules )
     {
+    	boolean monitoringRulesPresent = false;
+
         // Group the periods by period type.
         for ( Period period : periods )
         {
@@ -150,6 +156,17 @@
 
         for ( ValidationRule rule : rules )
         {
+        	if ( ValidationRule.RULE_TYPE_MONITORING.equals( rule.getRuleType() ) )
+        	{
+        		if ( rule.getOrganisationUnitLevel() == null )
+        		{
+        			log.error( "monitoring-type validationRule '" + ( rule.getName() == null ? "" : rule.getName() )
+        					+ "' has no organisationUnitLevel." );
+        			continue; // Ignore rule, avoid null reference later.
+        		}
+        		monitoringRulesPresent = true;
+        	}
+
             // Find the period type extended for this rule
             PeriodTypeExtended periodTypeX = getOrCreatePeriodTypeExtended( rule.getPeriodType() );
             periodTypeX.getRules().add( rule ); // Add this rule to the period
@@ -183,30 +200,60 @@
             }
         }
 
+        // If we have some monitoring rules, then make sure that we add all
+        // the descendants of each source to our collection of sources.
+        // This is so we can recurse if needed to find monitoring rule values.
+        if ( monitoringRulesPresent )
+        {
+	        for ( OrganisationUnit source : new HashSet<OrganisationUnit>( sources ) )
+	        {
+	        	addSourceRecursive ( sources, source );
+	        }
+        }
+        
+        // Get the information we need for each source.
         for ( OrganisationUnit source : sources )
         {
             OrganisationUnitExtended sourceX = new OrganisationUnitExtended( source );
             sourceXs.add( sourceX );
 
-            Map<PeriodType, Set<DataElement>> sourceDataElementsByPeriodType = source
+            Map<PeriodType, Set<DataElement>> sourceElementsMap = source
                 .getDataElementsInDataSetsByPeriodType();
             for ( PeriodTypeExtended periodTypeX : periodTypeExtendedMap.values() )
             {
-                Collection<DataElement> sourceDataElements = sourceDataElementsByPeriodType.get( periodTypeX
-                    .getPeriodType() );
-                if ( sourceDataElements != null )
-                {
-                    periodTypeX.getSourceDataElements().put( source, sourceDataElements );
-                }
-                else
-                {
-                    periodTypeX.getSourceDataElements().put( source, new HashSet<DataElement>() );
-                }
+            	periodTypeX.getSourceDataElements().put( source, new HashSet<DataElement>() );
+            	for ( PeriodType allowedType : periodTypeX.getAllowedPeriodTypes() )
+            	{
+	                Collection<DataElement> sourceDataElements = sourceElementsMap.get( allowedType );
+	                if ( sourceDataElements != null )
+	                {
+	                    periodTypeX.getSourceDataElements().get( source ).addAll( sourceDataElements );
+	                }
+            	}
             }
         }
     }
 
     /**
+     * If the children of this organisation unit are not in the collection,
+     * then add them and all their descendants if needed.
+     * 
+     * @param sources organisation units to test and add to
+     * @param source organisation unit whose children to check
+     */
+    private void addSourceRecursive( Collection<OrganisationUnit> sources, OrganisationUnit source )
+    {
+    	for ( OrganisationUnit child : source.getChildren() )
+    	{
+    		if ( !sources.contains( child ) )
+			{
+				sources.add( child );
+				addSourceRecursive( sources, child );
+			}
+    	}
+    }
+    
+    /**
      * Gets the PeriodTypeExtended from the context object. If not found,
      * creates a new PeriodTypeExtended object, puts it into the context object,
      * and returns it.

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationWorkerThread.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationWorkerThread.java	2013-10-09 05:16:53 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/ValidationWorkerThread.java	2013-10-11 12:58:30 +0000
@@ -29,7 +29,7 @@
  */
 
 import static org.hisp.dhis.system.util.MathUtils.expressionIsTrue;
-import static org.hisp.dhis.system.util.MathUtils.getRounded;
+import static org.hisp.dhis.system.util.MathUtils.roundSignificant;
 import static org.hisp.dhis.system.util.MathUtils.zeroIfNull;
 
 import java.util.ArrayList;
@@ -66,12 +66,6 @@
 {
     private static final Log log = LogFactory.getLog( ValidationWorkerThread.class );
 
-    /**
-     * Defines how many decimal places for rounding the left and right side
-     * evaluation values in the report of results.
-     */
-    private static final int DECIMALS = 1;
-
     private OrganisationUnitExtended sourceX;
 
     private ValidationRunContext context;
@@ -117,12 +111,12 @@
                         Map<DataElementOperand, Double> currentValueMap = getDataValueMapRecursive( periodTypeX,
                             periodTypeX.getDataElements(), sourceDataElements, recursiveCurrentDataElements,
                             periodTypeX.getAllowedPeriodTypes(), period, sourceX.getSource(), lastUpdatedMap, incompleteValues );
-                        log.trace( "currentValueMap[" + currentValueMap.size() + "]" );
+                        log.trace( "\nsource " + sourceX.getSource().getName()
+                        		+ " [" + period.getStartDate() + " - " + period.getEndDate() + "]"
+                        		+ " valueMap[" + currentValueMap.size() + "]" );
 
                         for ( ValidationRule rule : rules )
                         {
-                            CalendarPeriodType calendarPeriodType = ( CalendarPeriodType ) rule.getPeriodType(); //TODO: remove
-
                             if ( evaluateCheck( lastUpdatedMap, rule, context ) )
                             {
                                 Double leftSide = context.getExpressionService().getExpressionValue( rule.getLeftSide(),
@@ -149,9 +143,10 @@
 
                                         if ( violation )
                                         {
-                                            context.getValidationResults().add( new ValidationResult( period,
-                                                sourceX.getSource(), rule, getRounded( zeroIfNull( leftSide ), DECIMALS ),
-                                                getRounded( zeroIfNull( rightSide ), DECIMALS ) ) );
+                                            context.getValidationResults().add( new ValidationResult(
+                                            		period, sourceX.getSource(), rule,
+                                            		roundSignificant( zeroIfNull( leftSide ) ),
+                                            		roundSignificant( zeroIfNull( rightSide ) ) ) );
                                         }
 
                                         log.trace( "-->Evaluated " + rule.getName() + ": "
@@ -388,9 +383,9 @@
     }
 
     /**
-     * Evaluates the right side of a monitoring-type validation rule for a given
-     * organisation unit and period, and adds the value to a list of sample
-     * values.
+     * Evaluates the right side of a monitoring-type validation rule for
+     * a given organisation unit and period, and adds the value to a list
+     * of sample values.
      * 
      * Note that for a monitoring-type rule, evaluating the right side
      * expression can result in sampling multiple periods and/or child
@@ -426,6 +421,13 @@
             {
                 sampleValues.add( value );
             }
+
+            log.trace("ValidationRightSide[" + dataValueMap.size()+ "] - sample "
+            		+ (value == null ? "(null)" : value) + " [" + period.getStartDate() + " - " + period.getEndDate() + "]" );
+        }
+        else
+        {
+        	log.trace("ValidationRightSide - no period [" + period.getStartDate() + " - " + period.getEndDate() + "]" );
         }
     }
 
@@ -507,8 +509,12 @@
     {
         Set<DataElement> dataElementsToGet = new HashSet<DataElement>( ruleDataElements );
         dataElementsToGet.retainAll( sourceDataElements );
-        log.trace( "getDataValueMapRecursive: source:" + source.getName() + " elementsToGet["
-            + dataElementsToGet.size() + "] allowedPeriodTypes[" + allowedPeriodTypes.size() + "]" );
+        log.trace( "getDataValueMapRecursive: source:" + source.getName()
+        		+ " ruleDataElements[" + ruleDataElements.size() // + Arrays.toString( ruleDataElements.toArray() )
+        		+ "] sourceDataElements[" + sourceDataElements.size() // + Arrays.toString( sourceDataElements.toArray() )
+        		+ "] elementsToGet[" + dataElementsToGet.size() // + Arrays.toString( sourceDataElements.toArray() )
+        		+ "] recursiveDataElements[" + recursiveDataElements.size()
+        		+ "] allowedPeriodTypes[" + allowedPeriodTypes.size() + "]" );
 
         Map<DataElementOperand, Double> dataValueMap;
         

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/validation/ValidationRuleServiceTest.java'
--- dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/validation/ValidationRuleServiceTest.java	2013-10-08 17:20:57 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/validation/ValidationRuleServiceTest.java	2013-10-11 12:58:30 +0000
@@ -39,8 +39,11 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 import org.hisp.dhis.DhisTest;
@@ -67,6 +70,7 @@
 
 /**
  * @author Lars Helge Overland
+ * @author Jim Grace
  * @version $Id$
  */
 public class ValidationRuleServiceTest
@@ -134,10 +138,26 @@
 
     private Period periodI;
 
+    private Period periodJ;
+
+    private Period periodK;
+
+    private Period periodL;
+
+    private Period periodM;
+
+    private Period periodN;
+
+    private Period periodO;
+
+    private Period periodW;
+
     private Period periodX;
 
     private Period periodY;
 
+    private Period periodZ;
+
     private OrganisationUnit sourceA;
 
     private OrganisationUnit sourceB;
@@ -176,6 +196,8 @@
 
     private ValidationRule monitoringRuleK;
 
+    private ValidationRule monitoringRuleL;
+
     private ValidationRuleGroup group;
 
     private PeriodType periodTypeWeekly;
@@ -188,6 +210,12 @@
     // Fixture
     // -------------------------------------------------------------------------
 
+    private void joinDataSetToSource ( DataSet dataSet, OrganisationUnit source )
+    {
+    	source.getDataSets().add( dataSet );
+    	dataSet.getSources().add( source );
+    }
+    
     @Override
     public void setUpTest()
         throws Exception
@@ -248,10 +276,10 @@
             "descriptionB", dataElementsB , optionCombos);
         expressionC = new Expression( "#{" + dataElementB.getUid() + suffix + "} * 2", "descriptionC", dataElementsC, optionCombos );
         expressionD = new Expression( "#{" + dataElementB.getUid() + suffix + "}", "descriptionD", dataElementsC, optionCombos );
-        expressionE = new Expression( "#{" + dataElementB.getUid() + suffix + "} * 1.25", "descriptionE", dataElementsC, optionCombos );
+        expressionE = new Expression( "#{" + dataElementB.getUid() + suffix + "} * 1.5", "descriptionE", dataElementsC, optionCombos );
         expressionF = new Expression( "#{" + dataElementB.getUid() + suffix + "} / #{" + dataElementE.getUid() + suffix + "}",
         		"descriptionF", dataElementsD, optionCombos );
-        expressionG = new Expression( "#{" + dataElementB.getUid() + suffix + "} * 1.25 / #{" + dataElementE.getUid() + suffix + "}",
+        expressionG = new Expression( "#{" + dataElementB.getUid() + suffix + "} * 1.5 / #{" + dataElementE.getUid() + suffix + "}",
         		"descriptionG", dataElementsD, optionCombos );
 
         expressionService.addExpression( expressionA );
@@ -265,14 +293,26 @@
         periodA = createPeriod( periodTypeMonthly, getDate( 2000, 3, 1 ), getDate( 2000, 3, 31 ) );
         periodB = createPeriod( periodTypeMonthly, getDate( 2000, 4, 1 ), getDate( 2000, 4, 30 ) );
         periodC = createPeriod( periodTypeMonthly, getDate( 2000, 5, 1 ), getDate( 2000, 5, 31 ) );
-        periodD = createPeriod( periodTypeMonthly, getDate( 2000, 6, 1 ), getDate( 2000, 6, 31 ) );
-        periodE = createPeriod( periodTypeMonthly, getDate( 2001, 2, 1 ), getDate( 2001, 2, 28 ) );
+        periodD = createPeriod( periodTypeMonthly, getDate( 2000, 6, 1 ), getDate( 2000, 6, 30 ) );
+        periodE = createPeriod( periodTypeMonthly, getDate( 2000, 7, 1 ), getDate( 2000, 7, 31 ) );
+        
         periodF = createPeriod( periodTypeMonthly, getDate( 2001, 3, 1 ), getDate( 2001, 3, 31 ) );
         periodG = createPeriod( periodTypeMonthly, getDate( 2001, 4, 1 ), getDate( 2001, 4, 30 ) );
         periodH = createPeriod( periodTypeMonthly, getDate( 2001, 5, 1 ), getDate( 2001, 5, 31 ) );
-        periodI = createPeriod( periodTypeWeekly, getDate( 2000, 4, 1 ), getDate( 2000, 4, 30 ) );
+        periodI = createPeriod( periodTypeMonthly, getDate( 2001, 6, 1 ), getDate( 2001, 6, 30 ) );
+        periodJ = createPeriod( periodTypeMonthly, getDate( 2001, 7, 1 ), getDate( 2001, 7, 31 ) );
+        
+        periodK = createPeriod( periodTypeMonthly, getDate( 2002, 3, 1 ), getDate( 2002, 3, 31 ) );
+        periodL = createPeriod( periodTypeMonthly, getDate( 2002, 4, 1 ), getDate( 2002, 4, 30 ) );
+        periodM = createPeriod( periodTypeMonthly, getDate( 2002, 5, 1 ), getDate( 2002, 5, 31 ) );
+        periodN = createPeriod( periodTypeMonthly, getDate( 2002, 6, 1 ), getDate( 2002, 6, 30 ) );
+        periodO = createPeriod( periodTypeMonthly, getDate( 2002, 7, 1 ), getDate( 2002, 7, 31 ) );
+        
+        periodW = createPeriod( periodTypeWeekly, getDate( 2002, 4, 1 ), getDate( 2000, 4, 7 ) );
+        
         periodX = createPeriod( periodTypeYearly, getDate( 2000, 1, 1 ), getDate( 2000, 12, 31 ) );
         periodY = createPeriod( periodTypeYearly, getDate( 2001, 1, 1 ), getDate( 2001, 12, 31 ) );
+        periodZ = createPeriod( periodTypeYearly, getDate( 2002, 1, 1 ), getDate( 2002, 12, 31 ) );
 
         dataSetWeekly = createDataSet( 'W', periodTypeWeekly );
         dataSetMonthly = createDataSet( 'M', periodTypeMonthly );
@@ -286,17 +326,28 @@
         sourceF = createOrganisationUnit( 'F', sourceD );
         sourceG = createOrganisationUnit( 'G' );
 
-        sourceA.getDataSets().add( dataSetMonthly );
-        sourceB.getDataSets().add( dataSetMonthly );
-        sourceC.getDataSets().add( dataSetWeekly );
-        sourceC.getDataSets().add( dataSetMonthly );
-        sourceC.getDataSets().add( dataSetYearly );
-        sourceD.getDataSets().add( dataSetMonthly );
-        sourceD.getDataSets().add( dataSetYearly );
-        sourceE.getDataSets().add( dataSetMonthly );
-        sourceE.getDataSets().add( dataSetYearly );
-        sourceF.getDataSets().add( dataSetMonthly );
-        sourceF.getDataSets().add( dataSetYearly );
+        sourcesA.add( sourceA );
+        sourcesA.add( sourceB );
+
+        joinDataSetToSource( dataSetMonthly, sourceA );
+        joinDataSetToSource( dataSetMonthly, sourceB );
+        joinDataSetToSource( dataSetMonthly, sourceC );
+        joinDataSetToSource( dataSetMonthly, sourceD );
+        joinDataSetToSource( dataSetMonthly, sourceE );
+        joinDataSetToSource( dataSetMonthly, sourceF );
+        
+        joinDataSetToSource( dataSetWeekly, sourceB );
+        joinDataSetToSource( dataSetWeekly, sourceC );
+        joinDataSetToSource( dataSetWeekly, sourceD );
+        joinDataSetToSource( dataSetWeekly, sourceE );
+        joinDataSetToSource( dataSetWeekly, sourceF );
+        joinDataSetToSource( dataSetWeekly, sourceG );
+
+        joinDataSetToSource( dataSetYearly, sourceB );
+        joinDataSetToSource( dataSetYearly, sourceC );
+        joinDataSetToSource( dataSetYearly, sourceD );
+        joinDataSetToSource( dataSetYearly, sourceE );
+        joinDataSetToSource( dataSetYearly, sourceF );
 
         organisationUnitService.addOrganisationUnit( sourceA );
         organisationUnitService.addOrganisationUnit( sourceB );
@@ -304,70 +355,64 @@
         organisationUnitService.addOrganisationUnit( sourceD );
         organisationUnitService.addOrganisationUnit( sourceE );
         organisationUnitService.addOrganisationUnit( sourceF );
+        organisationUnitService.addOrganisationUnit( sourceG );
         
-        sourcesA.add( sourceA );
-        sourcesA.add( sourceB );
-
         dataSetMonthly.getDataElements().add( dataElementA );
         dataSetMonthly.getDataElements().add( dataElementB );
         dataSetMonthly.getDataElements().add( dataElementC );
         dataSetMonthly.getDataElements().add( dataElementD );
 
-        dataSetMonthly.getSources().add( sourceA );
-        dataSetMonthly.getSources().add( sourceB );
-        dataSetMonthly.getSources().add( sourceC );
-        dataSetMonthly.getSources().add( sourceD );
-        dataSetMonthly.getSources().add( sourceE );
-        dataSetMonthly.getSources().add( sourceF );
-        dataSetWeekly.getSources().add( sourceB );
-        dataSetWeekly.getSources().add( sourceC );
-        dataSetWeekly.getSources().add( sourceD );
-        dataSetWeekly.getSources().add( sourceE );
-        dataSetWeekly.getSources().add( sourceF );
-        dataSetWeekly.getSources().add( sourceG );
-        dataSetYearly.getSources().add( sourceB );
-        dataSetYearly.getSources().add( sourceC );
-        dataSetYearly.getSources().add( sourceD );
-        dataSetYearly.getSources().add( sourceE );
-        dataSetYearly.getSources().add( sourceF );
+        dataSetWeekly.getDataElements().add( dataElementE );
+
+        dataSetYearly.getDataElements().add( dataElementE );
 
         dataElementA.getDataSets().add( dataSetMonthly );
         dataElementB.getDataSets().add( dataSetMonthly );
         dataElementC.getDataSets().add( dataSetMonthly );
         dataElementD.getDataSets().add( dataSetMonthly );
-
+        
+        dataElementE.getDataSets().add( dataSetWeekly );
+
+        dataElementE.getDataSets().add( dataSetYearly );
+
+        dataSetService.addDataSet( dataSetWeekly );
         dataSetService.addDataSet( dataSetMonthly );
+        dataSetService.addDataSet( dataSetYearly );
 
         dataElementService.updateDataElement( dataElementA );
         dataElementService.updateDataElement( dataElementB );
         dataElementService.updateDataElement( dataElementC );
         dataElementService.updateDataElement( dataElementD );
+        dataElementService.updateDataElement( dataElementE );
 
         validationRuleA = createValidationRule( 'A', equal_to, expressionA, expressionB, periodTypeMonthly );
         validationRuleB = createValidationRule( 'B', greater_than, expressionB, expressionC, periodTypeMonthly );
         validationRuleC = createValidationRule( 'C', less_than_or_equal_to, expressionB, expressionA, periodTypeMonthly );
         validationRuleD = createValidationRule( 'D', less_than, expressionA, expressionC, periodTypeMonthly );
         
-        // Compare dataElementB with 1.25 times itself for one previous sequential period.
-        monitoringRuleE = createMonitoringRule( 'E', less_than, expressionD, expressionE, periodTypeMonthly, 1, 1, 0, 0, 0 );
+        // Compare dataElementB with 1.5 times itself for one sequential previous period.
+        monitoringRuleE = createMonitoringRule( 'E', less_than_or_equal_to, expressionD, expressionE, periodTypeMonthly, 1, 1, 0, 0, 0 );
 
-        // Compare dataElementB with 1.25 times itself for one previous annual period.
-        monitoringRuleF = createMonitoringRule( 'F', less_than, expressionD, expressionE, periodTypeMonthly, 1, 0, 1, 0, 0 );
-        
-        // Compare dataElementB with 1.25 times itself for two previous and two annual sequential periods.
-        monitoringRuleG = createMonitoringRule( 'G', less_than, expressionD, expressionE, periodTypeMonthly, 1, 2, 2, 0, 0 );
-        
-        // Compare dataElementB with 1.25 times itself for two previous and two annual sequential periods, discarding 2 high outliers.
-        monitoringRuleH = createMonitoringRule( 'H', less_than, expressionD, expressionE, periodTypeMonthly, 1, 2, 2, 2, 0 );
-        
-        // Compare dataElementB with 1.25 times itself for two previous and two annual sequential periods, discarding 2 low outliers.
-        monitoringRuleI = createMonitoringRule( 'I', less_than, expressionD, expressionE, periodTypeMonthly, 1, 2, 2, 2, 0 );
-        
-        // Compare dataElementB with 1.25 times itself for two previous and two annual sequential periods, discarding 2 high & 2 low outliers.
-        monitoringRuleJ = createMonitoringRule( 'J', less_than, expressionD, expressionE, periodTypeMonthly, 1, 2, 2, 2, 2 );
-        
-        // Compare dataElements B/E with 1.25 * B/E for two previous and two annual sequential periods, no outlier discarding
-        monitoringRuleK = createMonitoringRule( 'K', less_than, expressionF, expressionG, periodTypeMonthly, 1, 2, 2, 0, 0 );
+        // Compare dataElementB with 1.5 times itself for one annual previous period.
+        monitoringRuleF = createMonitoringRule( 'F', less_than_or_equal_to, expressionD, expressionE, periodTypeMonthly, 1, 0, 1, 0, 0 );
+        
+        // Compare dataElementB with 1.5 times itself for one sequential and two annual previous periods.
+        monitoringRuleG = createMonitoringRule( 'G', less_than_or_equal_to, expressionD, expressionE, periodTypeMonthly, 1, 1, 2, 0, 0 );
+        
+        // Compare dataElementB with 1.5 times itself for two sequential and two annual previous periods.
+        monitoringRuleH = createMonitoringRule( 'H', less_than_or_equal_to, expressionD, expressionE, periodTypeMonthly, 1, 2, 2, 0, 0 );
+        
+        // Compare dataElementB with 1.5 times itself for two sequential and two annual previous periods, discarding 2 high outliers.
+        monitoringRuleI = createMonitoringRule( 'I', less_than_or_equal_to, expressionD, expressionE, periodTypeMonthly, 1, 2, 2, 2, 0 );
+        
+        // Compare dataElementB with 1.5 times itself for two sequential and two annual previous periods, discarding 2 low outliers.
+        monitoringRuleJ = createMonitoringRule( 'J', less_than_or_equal_to, expressionD, expressionE, periodTypeMonthly, 1, 2, 2, 0, 2 );
+        
+        // Compare dataElementB with 1.5 times itself for two sequential and two annual previous periods, discarding 2 high & 2 low outliers.
+        monitoringRuleK = createMonitoringRule( 'K', less_than_or_equal_to, expressionD, expressionE, periodTypeMonthly, 1, 2, 2, 2, 2 );
+        
+        // Compare dataElements B/E with 1.5 * B/E for one annual period, no outlier discarding.
+        monitoringRuleL = createMonitoringRule( 'L', less_than_or_equal_to, expressionF, expressionG, periodTypeMonthly, 1, 0, 1, 0, 0 );
 
         group = createValidationRuleGroup( 'A' );
     }
@@ -379,6 +424,32 @@
     }
 
     // -------------------------------------------------------------------------
+    // Local convenience routines
+    // -------------------------------------------------------------------------
+
+    /**
+     * Returns a naturally ordered list of ValidationResults.
+     * 
+     * When comparing two collections, this assures that all the items
+     * are in the same order for comparison. It also means that when there
+     * are different values for the same period/rule/source, etc., the
+     * results are more likely to be in the same order to make it easier
+     * to see the difference.
+     * 
+     * By making this a List instead of, say a TreeSet, duplicate values
+     * (if any should exist by mistake!) are preserved.
+     * 
+     * @param results collection of ValidationResult to order
+     * @return ValidationResults in their natural order
+     */
+    private List<ValidationResult> orderedList( Collection<ValidationResult> results )
+    {
+    	List<ValidationResult> resultList = new ArrayList<ValidationResult>( results );
+    	Collections.sort( resultList );
+    	return resultList;
+    }
+    
+    // -------------------------------------------------------------------------
     // Business logic tests
     // -------------------------------------------------------------------------
 
@@ -413,8 +484,8 @@
         // Note: in this and subsequent tests we insert the validation results collection into a new HashSet. This
         // insures that if they are the same as the reference results, they will appear in the same order.
         
-        Collection<ValidationResult> results = new HashSet<ValidationResult>( validationRuleService.validate( getDate( 2000, 2, 1 ),
-        		getDate( 2000, 6, 1 ), sourcesA ) );
+        Collection<ValidationResult> results = validationRuleService.validate(
+        		getDate( 2000, 2, 1 ), getDate( 2000, 6, 1 ), sourcesA );
 
         Collection<ValidationResult> reference = new HashSet<ValidationResult>();
 
@@ -435,7 +506,7 @@
         }
 
         assertEquals( 8, results.size() );
-        assertEquals( reference, results );
+        assertEquals( orderedList( reference ), orderedList( results ) );
     }
 
     @Test
@@ -471,8 +542,8 @@
 
         validationRuleService.addValidationRuleGroup( group );
 
-        Collection<ValidationResult> results = new HashSet<ValidationResult>( validationRuleService.validate( getDate( 2000, 2, 1 ),
-        		getDate( 2000, 6, 1 ), sourcesA, group ) );
+        Collection<ValidationResult> results = validationRuleService.validate(
+        		getDate( 2000, 2, 1 ), getDate( 2000, 6, 1 ), sourcesA, group );
 
         Collection<ValidationResult> reference = new HashSet<ValidationResult>();
 
@@ -488,7 +559,7 @@
         }
 
         assertEquals( 4, results.size() );
-        assertEquals( reference, results );
+        assertEquals( orderedList( reference ), orderedList( results ) );
     }
 
     @Test
@@ -509,8 +580,8 @@
         validationRuleService.saveValidationRule( validationRuleC );
         validationRuleService.saveValidationRule( validationRuleD );
 
-        Collection<ValidationResult> results = new HashSet<ValidationResult>( validationRuleService.validate( getDate( 2000, 2, 1 ),
-        		getDate( 2000, 6, 1 ), sourceA ) );
+        Collection<ValidationResult> results = validationRuleService.validate(
+        		getDate( 2000, 2, 1 ), getDate( 2000, 6, 1 ), sourceA );
 
         Collection<ValidationResult> reference = new HashSet<ValidationResult>();
 
@@ -526,7 +597,7 @@
         }
 
         assertEquals( 4, results.size() );
-        assertEquals( reference, results );
+        assertEquals( orderedList( reference ), orderedList( results ) );
     }
 
     @Test
@@ -542,7 +613,8 @@
         validationRuleService.saveValidationRule( validationRuleC );
         validationRuleService.saveValidationRule( validationRuleD );
 
-        Collection<ValidationResult> results = new HashSet<ValidationResult>( validationRuleService.validate( dataSetMonthly, periodA, sourceA ) );
+        Collection<ValidationResult> results = validationRuleService.validate(
+        		dataSetMonthly, periodA, sourceA );
 
         Collection<ValidationResult> reference = new HashSet<ValidationResult>();
 
@@ -556,64 +628,389 @@
         }
 
         assertEquals( 2, results.size() );
-        assertEquals( reference, results );
+        assertEquals( orderedList( reference ), orderedList( results ) );
     }
 
     @Test
     public void testValidateMonitoringSequential()
     {
-    	
+    	// System.out.println("\ntestValidateMonitoring1Sequential");
+    	// Note: for some monitoring tests, we enter more data than needed, to be sure the extra data *isn't* used.
+    	
+    	dataValueService.addDataValue( createDataValue( dataElementB, periodA, sourceA, "11", categoryOptionCombo ) ); // Mar 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodB, sourceA, "12", categoryOptionCombo ) ); // Apr 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodC, sourceA, "13", categoryOptionCombo ) ); // May 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodD, sourceA, "14", categoryOptionCombo ) ); // Jun 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodE, sourceA, "15", categoryOptionCombo ) ); // Jul 2000
+
+        dataValueService.addDataValue( createDataValue( dataElementB, periodF, sourceA, "30", categoryOptionCombo ) ); // Mar 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodG, sourceA, "35", categoryOptionCombo ) ); // Apr 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodH, sourceA, "40", categoryOptionCombo ) ); // May 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodI, sourceA, "45", categoryOptionCombo ) ); // Jun 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodJ, sourceA, "50", categoryOptionCombo ) ); // Jul 2001
+
+        dataValueService.addDataValue( createDataValue( dataElementB, periodK, sourceA, "100", categoryOptionCombo ) ); // Mar 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodL, sourceA, "200", categoryOptionCombo ) ); // Apr 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodM, sourceA, "400", categoryOptionCombo ) ); // May 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodN, sourceA, "700", categoryOptionCombo ) ); // Jun 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodO, sourceA, "800", categoryOptionCombo ) ); // Jul 2002
+    	
+        validationRuleService.saveValidationRule( monitoringRuleE );
+        
+        Collection<ValidationResult> results = validationRuleService.validate(
+        		getDate( 2002, 1, 15 ), getDate( 2002, 8, 15 ), sourceA );
+        
+        Collection<ValidationResult> reference = new HashSet<ValidationResult>();
+
+        reference.add( new ValidationResult( periodL, sourceA, monitoringRuleE, 200.0, 150.0 /* 1.5 * 100 */ ) );
+        reference.add( new ValidationResult( periodM, sourceA, monitoringRuleE, 400.0, 300.0 /* 1.5 * 200 */ ) );
+        reference.add( new ValidationResult( periodN, sourceA, monitoringRuleE, 700.0, 600.0 /* 1.5 * 400 */ ) );
+
+        for ( ValidationResult result : results )
+        {
+            assertFalse( MathUtils.expressionIsTrue( result.getLeftsideValue(), result.getValidationRule()
+                .getOperator(), result.getRightsideValue() ) );
+        }
+
+        assertEquals( 3, results.size() );
+        assertEquals( orderedList( reference ), orderedList( results ) );
     }
 
     @Test
     public void testValidateMonitoringAnnual()
     {
-    	
-    }
-
-    @Test
-    public void testValidateMonitoringSequentialAndAnnual()
-    {
-    	
-    }
-
-    @Test
-    public void testValidateMonitoringTwoSequentialAndAnnual()
-    {
-    	
-    }
-
-    @Test
-    public void testValidateMonitoringHighOutliers()
-    {
-    	
-    }
-
-    @Test
-    public void testValidateMonitoringLowOutliers()
-    {
-    	
-    }
-
-    @Test
-    public void testValidateMonitoringHighAndLowOutliers()
-    {
-    	
+    	// System.out.println("\ntestValidateMonitoring1Annual");
+
+    	dataValueService.addDataValue( createDataValue( dataElementB, periodA, sourceA, "11", categoryOptionCombo ) ); // Mar 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodB, sourceA, "12", categoryOptionCombo ) ); // Apr 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodC, sourceA, "13", categoryOptionCombo ) ); // May 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodD, sourceA, "14", categoryOptionCombo ) ); // Jun 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodE, sourceA, "15", categoryOptionCombo ) ); // Jul 2000
+
+        dataValueService.addDataValue( createDataValue( dataElementB, periodF, sourceA, "50", categoryOptionCombo ) ); // Mar 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodG, sourceA, "150", categoryOptionCombo ) ); // Apr 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodH, sourceA, "200", categoryOptionCombo ) ); // May 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodI, sourceA, "600", categoryOptionCombo ) ); // Jun 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodJ, sourceA, "400", categoryOptionCombo ) ); // Jul 2001
+
+        dataValueService.addDataValue( createDataValue( dataElementB, periodK, sourceA, "100", categoryOptionCombo ) ); // Mar 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodL, sourceA, "200", categoryOptionCombo ) ); // Apr 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodM, sourceA, "400", categoryOptionCombo ) ); // May 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodN, sourceA, "700", categoryOptionCombo ) ); // Jun 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodO, sourceA, "800", categoryOptionCombo ) ); // Jul 2002
+    	
+        validationRuleService.saveValidationRule( monitoringRuleF );
+        
+        Collection<ValidationResult> results = validationRuleService.validate(
+        		getDate( 2002, 1, 15 ), getDate( 2002, 8, 15 ), sourceA );
+        
+        Collection<ValidationResult> reference = new HashSet<ValidationResult>();
+
+        reference.add( new ValidationResult( periodK, sourceA, monitoringRuleF, 100.0, 75.0 /* 1.5 * 50 */ ) );
+        reference.add( new ValidationResult( periodM, sourceA, monitoringRuleF, 400.0, 300.0 /* 1.5 * 200 */ ) );
+        reference.add( new ValidationResult( periodO, sourceA, monitoringRuleF, 800.0, 600.0 /* 1.5 * 400 */ ) );
+
+        for ( ValidationResult result : results )
+        {
+            assertFalse( MathUtils.expressionIsTrue( result.getLeftsideValue(), result.getValidationRule()
+                .getOperator(), result.getRightsideValue() ) );
+        }
+
+        assertEquals( 3, results.size() );
+        assertEquals( orderedList( reference ), orderedList( results ) );
+    }
+
+    @Test
+    public void testValidateMonitoring1Sequential2Annual()
+    {
+    	// System.out.println("\ntestValidateMonitoring1Sequential2Annual");
+
+    	dataValueService.addDataValue( createDataValue( dataElementB, periodA, sourceA, "11", categoryOptionCombo ) ); // Mar 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodB, sourceA, "12", categoryOptionCombo ) ); // Apr 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodC, sourceA, "13", categoryOptionCombo ) ); // May 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodD, sourceA, "14", categoryOptionCombo ) ); // Jun 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodE, sourceA, "15", categoryOptionCombo ) ); // Jul 2000
+
+        dataValueService.addDataValue( createDataValue( dataElementB, periodF, sourceA, "50", categoryOptionCombo ) ); // Mar 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodG, sourceA, "150", categoryOptionCombo ) ); // Apr 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodH, sourceA, "200", categoryOptionCombo ) ); // May 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodI, sourceA, "600", categoryOptionCombo ) ); // Jun 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodJ, sourceA, "400", categoryOptionCombo ) ); // Jul 2001
+
+        dataValueService.addDataValue( createDataValue( dataElementB, periodK, sourceA, "100", categoryOptionCombo ) ); // Mar 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodL, sourceA, "200", categoryOptionCombo ) ); // Apr 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodM, sourceA, "400", categoryOptionCombo ) ); // May 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodN, sourceA, "700", categoryOptionCombo ) ); // Jun 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodO, sourceA, "800", categoryOptionCombo ) ); // Jul 2002
+    	
+        validationRuleService.saveValidationRule( monitoringRuleG ); // 1 sequential and 2 annual periods
+        
+        Collection<ValidationResult> results = validationRuleService.validate(
+        		getDate( 2002, 1, 15 ), getDate( 2002, 8, 15 ), sourceA );
+        
+        Collection<ValidationResult> reference = new HashSet<ValidationResult>();
+
+        reference.add( new ValidationResult( periodK, sourceA, monitoringRuleG, 100.0, 83.6 /* 1.5 * ( 11 + 12 + 50 + 150 ) / 4 */  ) );
+        reference.add( new ValidationResult( periodL, sourceA, monitoringRuleG, 200.0, 114.9 /* 1.5 * ( 11 + 12 + 13 + 50 + 150 + 200 + 100 ) / 7 */  ) );
+        reference.add( new ValidationResult( periodM, sourceA, monitoringRuleG, 400.0, 254.8 /* 1.5 * ( 12 + 13 + 14 + 150 + 200 + 600 + 200 ) / 7 */  ) );
+        reference.add( new ValidationResult( periodN, sourceA, monitoringRuleG, 700.0, 351.9 /* 1.5 * ( 13 + 14 + 15 + 200 + 600 + 400 + 400 ) / 7 */  ) );
+        reference.add( new ValidationResult( periodO, sourceA, monitoringRuleG, 800.0, 518.7 /* 1.5 * ( 14 + 15 + 600 + 400 + 700 ) / 5 */  ) );
+
+        for ( ValidationResult result : results )
+        {
+            assertFalse( MathUtils.expressionIsTrue( result.getLeftsideValue(), result.getValidationRule()
+                .getOperator(), result.getRightsideValue() ) );
+        }
+
+        assertEquals( 5, results.size() );
+        assertEquals( orderedList( reference ), orderedList( results ) );
+    }
+    
+    @Test
+    public void testValidateMonitoring2Sequential2Annual()
+    {
+    	// System.out.println("\ntestValidateMonitoring2Sequential2Annual");
+
+    	dataValueService.addDataValue( createDataValue( dataElementB, periodA, sourceA, "11", categoryOptionCombo ) ); // Mar 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodB, sourceA, "12", categoryOptionCombo ) ); // Apr 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodC, sourceA, "13", categoryOptionCombo ) ); // May 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodD, sourceA, "14", categoryOptionCombo ) ); // Jun 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodE, sourceA, "15", categoryOptionCombo ) ); // Jul 2000
+
+        dataValueService.addDataValue( createDataValue( dataElementB, periodF, sourceA, "50", categoryOptionCombo ) ); // Mar 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodG, sourceA, "150", categoryOptionCombo ) ); // Apr 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodH, sourceA, "200", categoryOptionCombo ) ); // May 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodI, sourceA, "600", categoryOptionCombo ) ); // Jun 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodJ, sourceA, "400", categoryOptionCombo ) ); // Jul 2001
+
+        dataValueService.addDataValue( createDataValue( dataElementB, periodK, sourceA, "100", categoryOptionCombo ) ); // Mar 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodL, sourceA, "200", categoryOptionCombo ) ); // Apr 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodM, sourceA, "400", categoryOptionCombo ) ); // May 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodN, sourceA, "700", categoryOptionCombo ) ); // Jun 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodO, sourceA, "800", categoryOptionCombo ) ); // Jul 2002
+    	
+        validationRuleService.saveValidationRule( monitoringRuleH ); // 2 sequential and 2 annual periods
+        
+        Collection<ValidationResult> results = validationRuleService.validate(
+        		getDate( 2002, 1, 15 ), getDate( 2002, 8, 15 ), sourceA );
+        
+        Collection<ValidationResult> reference = new HashSet<ValidationResult>();
+
+        // Not in results: reference.add( new ValidationResult( periodK, sourceA, monitoringRuleH, 100.0, 109.0 /* 1.5 * ( 11 + 12 + 13 + 50 + 150 + 200 ) / 6 */  ) );
+        reference.add( new ValidationResult( periodL, sourceA, monitoringRuleH, 200.0, 191.7 /* 1.5 * ( 11 + 12 + 13 + 14 + 50 + 150 + 200 + 600 + 100 ) / 9 */  ) );
+        reference.add( new ValidationResult( periodM, sourceA, monitoringRuleH, 400.0, 220.6 /* 1.5 * ( 11 + 12 + 13 + 14 + 15 + 50 + 150 + 200 + 600 + 400 + 100 + 200 ) / 12 */  ) );
+        reference.add( new ValidationResult( periodN, sourceA, monitoringRuleH, 700.0, 300.6 /* 1.5 * ( 12 + 13 + 14 + 15 + 150 + 200 + 600 + 400 + 200 + 400 ) / 10 */  ) );
+        reference.add( new ValidationResult( periodO, sourceA, monitoringRuleH, 800.0, 439.1 /* 1.5 * ( 13 + 14 + 15 + 200 + 600 + 400 + 400 + 700 ) / 8 */  ) );
+        
+        for ( ValidationResult result : results )
+        {
+            assertFalse( MathUtils.expressionIsTrue( result.getLeftsideValue(), result.getValidationRule()
+                .getOperator(), result.getRightsideValue() ) );
+        }
+
+        assertEquals( 4, results.size() );
+        assertEquals( orderedList( reference ), orderedList( results ) );
+    }
+
+    @Test
+    public void testValidateMonitoring2HighOutliers()
+    {
+    	// System.out.println("\ntestValidateMonitoring2HighOutliers");
+
+    	dataValueService.addDataValue( createDataValue( dataElementB, periodA, sourceA, "11", categoryOptionCombo ) ); // Mar 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodB, sourceA, "12", categoryOptionCombo ) ); // Apr 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodC, sourceA, "13", categoryOptionCombo ) ); // May 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodD, sourceA, "14", categoryOptionCombo ) ); // Jun 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodE, sourceA, "15", categoryOptionCombo ) ); // Jul 2000
+
+        dataValueService.addDataValue( createDataValue( dataElementB, periodF, sourceA, "50", categoryOptionCombo ) ); // Mar 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodG, sourceA, "150", categoryOptionCombo ) ); // Apr 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodH, sourceA, "200", categoryOptionCombo ) ); // May 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodI, sourceA, "600", categoryOptionCombo ) ); // Jun 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodJ, sourceA, "400", categoryOptionCombo ) ); // Jul 2001
+
+        dataValueService.addDataValue( createDataValue( dataElementB, periodK, sourceA, "100", categoryOptionCombo ) ); // Mar 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodL, sourceA, "200", categoryOptionCombo ) ); // Apr 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodM, sourceA, "400", categoryOptionCombo ) ); // May 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodN, sourceA, "700", categoryOptionCombo ) ); // Jun 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodO, sourceA, "800", categoryOptionCombo ) ); // Jul 2002
+    	
+        validationRuleService.saveValidationRule( monitoringRuleI ); // discard 2 highest outliers
+        
+        Collection<ValidationResult> results = validationRuleService.validate(
+        		getDate( 2002, 1, 15 ), getDate( 2002, 8, 15 ), sourceA );
+        
+        Collection<ValidationResult> reference = new HashSet<ValidationResult>();
+
+        reference.add( new ValidationResult( periodK, sourceA, monitoringRuleI, 100.0, 32.3 /* 1.5 * ( 11 + 12 + 13 + 50 ) / 4 */  ) );
+        reference.add( new ValidationResult( periodL, sourceA, monitoringRuleI, 200.0, 75.0 /* 1.5 * ( 11 + 12 + 13 + 14 + 50 + 150 + 100 ) / 7 */  ) );
+        reference.add( new ValidationResult( periodM, sourceA, monitoringRuleI, 400.0, 114.8 /* 1.5 * ( 11 + 12 + 13 + 14 + 15 + 50 + 150 + 200 + 100 + 200 ) / 10 */  ) );
+        reference.add( new ValidationResult( periodN, sourceA, monitoringRuleI, 700.0, 188.3 /* 1.5 * ( 12 + 13 + 14 + 15 + 150 + 200 + 200 + 400 ) / 8 */  ) );
+        reference.add( new ValidationResult( periodO, sourceA, monitoringRuleI, 800.0, 260.5 /* 1.5 * ( 13 + 14 + 15 + 200 + 400 + 400 ) / 6 */  ) );
+        
+        for ( ValidationResult result : results )
+        {
+            assertFalse( MathUtils.expressionIsTrue( result.getLeftsideValue(), result.getValidationRule()
+                .getOperator(), result.getRightsideValue() ) );
+        }
+
+        assertEquals( 5, results.size() );
+        assertEquals( orderedList( reference ), orderedList( results ) );
+    }
+
+    @Test
+    public void testValidateMonitoring2LowOutliers()
+    {
+    	// System.out.println("\ntestValidateMonitoring2LowOutliers");
+
+    	dataValueService.addDataValue( createDataValue( dataElementB, periodA, sourceA, "11", categoryOptionCombo ) ); // Mar 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodB, sourceA, "12", categoryOptionCombo ) ); // Apr 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodC, sourceA, "13", categoryOptionCombo ) ); // May 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodD, sourceA, "14", categoryOptionCombo ) ); // Jun 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodE, sourceA, "15", categoryOptionCombo ) ); // Jul 2000
+
+        dataValueService.addDataValue( createDataValue( dataElementB, periodF, sourceA, "50", categoryOptionCombo ) ); // Mar 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodG, sourceA, "150", categoryOptionCombo ) ); // Apr 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodH, sourceA, "200", categoryOptionCombo ) ); // May 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodI, sourceA, "600", categoryOptionCombo ) ); // Jun 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodJ, sourceA, "400", categoryOptionCombo ) ); // Jul 2001
+
+        dataValueService.addDataValue( createDataValue( dataElementB, periodK, sourceA, "100", categoryOptionCombo ) ); // Mar 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodL, sourceA, "200", categoryOptionCombo ) ); // Apr 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodM, sourceA, "400", categoryOptionCombo ) ); // May 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodN, sourceA, "700", categoryOptionCombo ) ); // Jun 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodO, sourceA, "800", categoryOptionCombo ) ); // Jul 2002
+    	
+        validationRuleService.saveValidationRule( monitoringRuleJ ); // 2 sequential and 2 annual periods
+        
+        Collection<ValidationResult> results = validationRuleService.validate(
+        		getDate( 2002, 1, 15 ), getDate( 2002, 8, 15 ), sourceA );
+        
+        Collection<ValidationResult> reference = new HashSet<ValidationResult>();
+
+        // Not in results: reference.add( new ValidationResult( periodK, sourceA, monitoringRuleH, 100.0, 154.9 /* 1.5 * ( 13 + 50 + 150 + 200 ) / 4 */  ) );
+        // Not in results: reference.add( new ValidationResult( periodL, sourceA, monitoringRuleJ, 200.0, 241.5 /* 1.5 * ( 13 + 14 + 50 + 150 + 200 + 600 + 100 ) / 7 */  ) );
+        reference.add( new ValidationResult( periodM, sourceA, monitoringRuleJ, 400.0, 261.3 /* 1.5 * ( 13 + 14 + 15 + 50 + 150 + 200 + 600 + 400 + 100 + 200 ) / 10 */  ) );
+        reference.add( new ValidationResult( periodN, sourceA, monitoringRuleJ, 700.0, 371.1 /* 1.5 * ( 14 + 15 + 150 + 200 + 600 + 400 + 200 + 400 ) / 8 */  ) );
+        reference.add( new ValidationResult( periodO, sourceA, monitoringRuleJ, 800.0, 578.8 /* 1.5 * ( 15 + 200 + 600 + 400 + 400 + 700 ) / 6 */  ) );
+        
+        for ( ValidationResult result : results )
+        {
+            assertFalse( MathUtils.expressionIsTrue( result.getLeftsideValue(), result.getValidationRule()
+                .getOperator(), result.getRightsideValue() ) );
+        }
+
+        assertEquals( 3, results.size() );
+        assertEquals( orderedList( reference ), orderedList( results ) );
+    }
+
+    @Test
+    public void testValidateMonitoring2High2LowOutliers()
+    {
+    	// System.out.println("\ntestValidateMonitoring2High2LowOutliers");
+
+    	dataValueService.addDataValue( createDataValue( dataElementB, periodA, sourceA, "11", categoryOptionCombo ) ); // Mar 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodB, sourceA, "12", categoryOptionCombo ) ); // Apr 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodC, sourceA, "13", categoryOptionCombo ) ); // May 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodD, sourceA, "14", categoryOptionCombo ) ); // Jun 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodE, sourceA, "15", categoryOptionCombo ) ); // Jul 2000
+
+        dataValueService.addDataValue( createDataValue( dataElementB, periodF, sourceA, "50", categoryOptionCombo ) ); // Mar 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodG, sourceA, "150", categoryOptionCombo ) ); // Apr 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodH, sourceA, "200", categoryOptionCombo ) ); // May 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodI, sourceA, "600", categoryOptionCombo ) ); // Jun 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodJ, sourceA, "400", categoryOptionCombo ) ); // Jul 2001
+
+        dataValueService.addDataValue( createDataValue( dataElementB, periodK, sourceA, "100", categoryOptionCombo ) ); // Mar 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodL, sourceA, "200", categoryOptionCombo ) ); // Apr 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodM, sourceA, "400", categoryOptionCombo ) ); // May 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodN, sourceA, "700", categoryOptionCombo ) ); // Jun 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodO, sourceA, "800", categoryOptionCombo ) ); // Jul 2002
+    	
+        validationRuleService.saveValidationRule( monitoringRuleK ); // discard 2 highest outliers
+        
+        Collection<ValidationResult> results = validationRuleService.validate(
+        		getDate( 2002, 1, 15 ), getDate( 2002, 8, 15 ), sourceA );
+        
+        Collection<ValidationResult> reference = new HashSet<ValidationResult>();
+
+        reference.add( new ValidationResult( periodK, sourceA, monitoringRuleK, 100.0, 47.3 /* 1.5 * ( 13 + 50 ) / 2 */  ) );
+        reference.add( new ValidationResult( periodL, sourceA, monitoringRuleK, 200.0, 98.1 /* 1.5 * ( 13 + 14 + 50 + 150 + 100 ) / 5 */  ) );
+        reference.add( new ValidationResult( periodM, sourceA, monitoringRuleK, 400.0, 139.1 /* 1.5 * ( 13 + 14 + 15 + 50 + 150 + 200 + 100 + 200 ) / 8 */  ) );
+        reference.add( new ValidationResult( periodN, sourceA, monitoringRuleK, 700.0, 244.8 /* 1.5 * ( 14 + 15 + 150 + 200 + 200 + 400 ) / 6 */  ) );
+        reference.add( new ValidationResult( periodO, sourceA, monitoringRuleK, 800.0, 380.6 /* 1.5 * ( 15 + 200 + 400 + 400 ) / 4 */  ) );
+        
+        for ( ValidationResult result : results )
+        {
+            assertFalse( MathUtils.expressionIsTrue( result.getLeftsideValue(), result.getValidationRule()
+                .getOperator(), result.getRightsideValue() ) );
+        }
+
+        assertEquals( 5, results.size() );
+        assertEquals( orderedList( reference ), orderedList( results ) );
     }
 
     @Test
     public void testValidateMonitoringWithBaseline()
     {
-    	
+    	// System.out.println("\ntestValidateMonitoringWithBaseline");
+
+    	dataValueService.addDataValue( createDataValue( dataElementB, periodA, sourceB, "11", categoryOptionCombo ) ); // Mar 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodB, sourceB, "12", categoryOptionCombo ) ); // Apr 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodC, sourceB, "13", categoryOptionCombo ) ); // May 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodD, sourceB, "14", categoryOptionCombo ) ); // Jun 2000
+        dataValueService.addDataValue( createDataValue( dataElementB, periodE, sourceB, "15", categoryOptionCombo ) ); // Jul 2000
+
+        dataValueService.addDataValue( createDataValue( dataElementB, periodF, sourceB, "50", categoryOptionCombo ) ); // Mar 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodG, sourceB, "150", categoryOptionCombo ) ); // Apr 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodH, sourceB, "200", categoryOptionCombo ) ); // May 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodI, sourceB, "600", categoryOptionCombo ) ); // Jun 2001
+        dataValueService.addDataValue( createDataValue( dataElementB, periodJ, sourceB, "400", categoryOptionCombo ) ); // Jul 2001
+
+        dataValueService.addDataValue( createDataValue( dataElementB, periodK, sourceB, "100", categoryOptionCombo ) ); // Mar 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodL, sourceB, "200", categoryOptionCombo ) ); // Apr 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodM, sourceB, "400", categoryOptionCombo ) ); // May 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodN, sourceB, "700", categoryOptionCombo ) ); // Jun 2002
+        dataValueService.addDataValue( createDataValue( dataElementB, periodO, sourceB, "800", categoryOptionCombo ) ); // Jul 2002
+        
+        // This weekly baseline data should be ignored because the period length is less than monthly:
+        dataValueService.addDataValue( createDataValue( dataElementE, periodW, sourceB, "1000", categoryOptionCombo ) ); // Week: 1-7 Apr 2002
+
+        dataValueService.addDataValue( createDataValue( dataElementE, periodX, sourceB, "40", categoryOptionCombo ) ); // Year: 2000
+        dataValueService.addDataValue( createDataValue( dataElementE, periodY, sourceB, "50", categoryOptionCombo ) ); // Year: 2001
+        dataValueService.addDataValue( createDataValue( dataElementE, periodZ, sourceB, "10", categoryOptionCombo ) ); // Year: 2002
+        
+        validationRuleService.saveValidationRule( monitoringRuleL );
+        
+        Collection<ValidationResult> results = validationRuleService.validate(
+        		getDate( 2002, 1, 15 ), getDate( 2002, 8, 15 ), sourceB );
+        
+        Collection<ValidationResult> reference = new HashSet<ValidationResult>();
+
+        reference.add( new ValidationResult( periodK, sourceB, monitoringRuleL, 10.0 /* 100 / 10 */, 1.5 /* 1.5 * 50 / 50 */ ) );
+        reference.add( new ValidationResult( periodL, sourceB, monitoringRuleL, 20.0 /* 200 / 10 */, 4.5 /* 1.5 * 150 / 50 */ ) );
+        reference.add( new ValidationResult( periodM, sourceB, monitoringRuleL, 40.0 /* 400 / 10 */, 6.0 /* 1.5 * 200 / 50 */ ) );
+        reference.add( new ValidationResult( periodN, sourceB, monitoringRuleL, 70.0 /* 700 / 10 */, 18.0 /* 1.5 * 600 / 50 */ ) );
+        reference.add( new ValidationResult( periodO, sourceB, monitoringRuleL, 80.0 /* 800 / 10 */, 12.0 /* 1.5 * 400 / 50 */ ) );
+
+        for ( ValidationResult result : results )
+        {
+            assertFalse( MathUtils.expressionIsTrue( result.getLeftsideValue(), result.getValidationRule()
+                .getOperator(), result.getRightsideValue() ) );
+        }
+
+        assertEquals( 5, results.size() );
+        assertEquals( orderedList( reference ), orderedList( results ) );
     }
 
     // -------------------------------------------------------------------------
     // CURD functionality tests
     // -------------------------------------------------------------------------
 
-    //@Test
+    @Test
     public void testSaveValidationRule()
     {
+    	// System.out.println("\ntestSaveValidationRule");
         int id = validationRuleService.saveValidationRule( validationRuleA );
 
         validationRuleA = validationRuleService.getValidationRule( id );
@@ -677,7 +1074,7 @@
         assertNull( validationRuleService.getValidationRule( idB ) );
     }
 
-    //@Test
+    @Test
     public void testGetAllValidationRules()
     {
         validationRuleService.saveValidationRule( validationRuleA );

=== modified file 'dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/MathUtils.java'
--- dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/MathUtils.java	2013-10-07 17:58:57 +0000
+++ dhis-2/dhis-support/dhis-support-system/src/main/java/org/hisp/dhis/system/util/MathUtils.java	2013-10-11 12:58:30 +0000
@@ -181,6 +181,54 @@
     }
 
     /**
+     * Rounds a number, keeping at least 3 significant digits.
+     * 
+     * <ul>
+     * <li>If value is >= 10 or <= -10 it will have 1 decimal.</li>
+     * <li>If value is between -10 and 10 it will have three significant digits.</li>
+     * </ul>
+     * 
+     * @param value the value to round off.
+     * @return a rounded off number.
+     */
+    public static double roundSignificant( double value )
+    {
+        if ( value >= 10.0 || value <= -10.0 )
+        {
+            return getRounded( value, 1 );
+        }
+        else
+        {
+            return roundToSignificantDigits( value, 3 );
+        }
+    }
+
+    /**
+     * Rounds a number to a given number of significant decimal digits.
+     * Note that the number will be left with *only* this number of
+     * significant digits regardless of magnitude, e.g. 12345 to 3 digits
+     * will be 12300, whereas 0.12345 will be 0.123.
+     * 
+     * @param value the value to round off.
+     * @param n the number of significant decimal digits desired.
+     * @return a rounded off number.
+     */
+    public static double roundToSignificantDigits( double value, int n )
+    {
+        if( value == 0.0 )
+        {
+            return 0.0;
+        }
+
+        final double d = Math.ceil( Math.log10( value < 0.0 ? - value: value ) );
+        final int power = n - (int) d;
+
+        final double magnitude = Math.pow( 10.0, power );
+        final long shifted = Math.round( value * magnitude );
+        return shifted / magnitude;
+    }
+    
+    /**
      * Returns a string representation of number rounded to given number of
      * significant figures
      *