← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 12515: Merge from Jim Grace for branch lp:~dhis2-devs-core/dhis2/alerts. Implements blueprint 'alerts' /...

 

Merge authors:
  Jim Grace (dhis2-c)
------------------------------------------------------------
revno: 12515 [merge]
committer: Lars Helge Øverland <larshelge@xxxxxxxxx>
branch nick: dhis2
timestamp: Tue 2013-10-08 21:10:40 +0200
message:
  Merge from Jim Grace for branch lp:~dhis2-devs-core/dhis2/alerts. Implements blueprint 'alerts' / surveillance and monitoring feature. Well done.
added:
  dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/validationRule.js
modified:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueService.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueStore.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/ExpressionService.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnit.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/setting/SystemSettingManager.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationResult.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRule.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleGroup.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleService.java
  dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueService.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/expression/DefaultExpressionService.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/resources/META-INF/dhis/beans.xml
  dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRule.hbm.xml
  dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRuleGroup.hbm.xml
  dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/validation/ValidationRuleServiceTest.java
  dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.java
  dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/DhisConvenienceTest.java
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/commons.js
  dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/validationRules.js
  dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/AddValidationRuleAction.java
  dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/ExportValidationResultAction.java
  dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/GetPeriodTypesAction.java
  dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/RunValidationAction.java
  dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/ShowUpdateValidationRuleFormAction.java
  dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/UpdateValidationRuleAction.java
  dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/AddValidationRuleGroupAction.java
  dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/ShowUpdateValidationRuleGroupFormAction.java
  dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/UpdateValidationRuleGroupAction.java
  dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/META-INF/dhis/beans.xml
  dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/org/hisp/dhis/validationrule/i18n_module.properties
  dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/struts.xml
  dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/addValidationRuleForm.vm
  dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/addValidationRuleGroupForm.vm
  dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/expressionBuilderForm.vm
  dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/general.js
  dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/validationRuleGroup.js
  dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/jsonValidationRule.vm
  dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/runValidationForm.vm
  dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/searchResult.vm
  dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/updateValidationRuleForm.vm
  dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/updateValidationRuleGroupForm.vm
  dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/validationRule.vm
  dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/viewValidationResultDetailsForm.vm
  dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/viewValidationResultForm.vm


--
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/datavalue/DataValueService.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueService.java	2013-08-23 15:56:19 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueService.java	2013-10-08 17:20:57 +0000
@@ -29,6 +29,7 @@
  */
 
 import java.util.Collection;
+import java.util.Date;
 import java.util.Map;
 
 import org.hisp.dhis.dataelement.DataElement;
@@ -257,7 +258,32 @@
      */
     int getDataValueCount( int days );
     
-    Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Period period, OrganisationUnit unit );
+    /**
+     * Returns a map of values indexed by DataElementOperand.
+     * 
+     * @param dataElements collection of DataElements to fetch for
+     * @param period period for which to fetch the values
+     * @param unit OrganisationUnit for which to fetch the values
+     * @return
+     */
+    Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Period period, OrganisationUnit source );
+
+    /**
+     * Returns a map of values indexed by DataElementOperand.
+     * 
+     * In the (unlikely) event that the same dataElement/optionCombo is found in
+     * more than one period for the same organisationUnit and date, the value
+     * is returned from the period with the shortest duration.
+     * 
+     * @param dataElements collection of DataElements to fetch for
+     * @param date date which must be present in the period
+     * @param unit OrganisationUnit for which to fetch the values
+     * @param periodTypes allowable period types in which to find the data
+     * @param lastUpdatedMap map in which to return the lastUpdated date for each value
+     * @return
+     */
+    Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Date date, OrganisationUnit source,
+    		Collection<PeriodType> periodTypes, Map<DataElementOperand, Date> lastUpdatedMap );
 
     /**
      * Gets a Collection of DeflatedDataValues.

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueStore.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueStore.java	2013-08-23 15:56:19 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/datavalue/DataValueStore.java	2013-10-08 17:20:57 +0000
@@ -250,9 +250,35 @@
      */
     int getDataValueCount( Date date );
     
-    Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Period period, OrganisationUnit unit );
+    /**
+     * Returns a map of values indexed by DataElementOperand.
+     * 
+     * @param dataElements collection of DataElements to fetch for
+     * @param period period for which to fetch the values
+     * @param unit OrganisationUnit for which to fetch the values
+     * @param lastUpdatedMap optional map in which to return the lastUpdated date for each value
+     * @return
+     */
+    Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Period period, OrganisationUnit source );
 
     /**
+     * Returns a map of values indexed by DataElementOperand.
+     * 
+     * In the (unlikely) event that the same dataElement/optionCombo is found in
+     * more than one period for the same organisationUnit and date, the value
+     * is returned from the period with the shortest duration.
+     * 
+     * @param dataElements collection of DataElements to fetch for
+     * @param date date which must be present in the period
+     * @param unit OrganisationUnit for which to fetch the values
+     * @param periodTypes allowable period types in which to find the data
+     * @param lastUpdatedMap map in which to return the lastUpdated date for each value
+     * @return
+     */
+    Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Date date, OrganisationUnit source,
+    		Collection<PeriodType> periodTypes, Map<DataElementOperand, Date> lastUpdatedMap );
+    
+    /**
      * Gets a Collection of DeflatedDataValues.
      * 
      * @param dataElementId the DataElement identifier.

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/ExpressionService.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/ExpressionService.java	2013-10-03 10:21:15 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/ExpressionService.java	2013-10-08 17:20:57 +0000
@@ -137,6 +137,24 @@
         Map<String, Double> constantMap, Integer days );
     
     /**
+     * Generates the calculated value for the given expression base on the values
+     * supplied in the value map, constant map and days.
+     * 
+     * @param expression the expression which holds the formula for the calculation.
+     * @param valueMap the mapping between data element operands and values to
+     *        use in the calculation.
+     * @param constantMap the mapping between the constant uid and value to use
+     *        in the calculation.
+     * @param days the number of days to use in the calculation.
+     * @param set of data element operands that have values but they are incomplete
+     *        (for example due to aggregation from organisationUnit children where
+     *        not all children had a value.)
+     * @return the calculated value as a double.
+     */
+    Double getExpressionValue( Expression expression, Map<DataElementOperand, Double> valueMap, 
+        Map<String, Double> constantMap, Integer days, Set<DataElementOperand> incompleteValues );
+    
+    /**
      * Returns the uids of the data element totals in the given expression.
      * 
      * @param expression the expression.

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnit.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnit.java	2013-10-08 13:23:38 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/organisationunit/OrganisationUnit.java	2013-10-08 19:10:40 +0000
@@ -34,6 +34,7 @@
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+
 import org.apache.commons.lang.StringUtils;
 import org.hisp.dhis.attribute.AttributeValue;
 import org.hisp.dhis.common.BaseIdentifiableObject;
@@ -46,6 +47,7 @@
 import org.hisp.dhis.common.view.UuidView;
 import org.hisp.dhis.dataelement.DataElement;
 import org.hisp.dhis.dataset.DataSet;
+import org.hisp.dhis.period.PeriodType;
 import org.hisp.dhis.user.User;
 
 import java.util.ArrayList;
@@ -536,6 +538,26 @@
         return dataElements;
     }
 
+    public Map<PeriodType, Set<DataElement>> getDataElementsInDataSetsByPeriodType()
+    {
+    	Map<PeriodType,Set<DataElement>> map = new HashMap<PeriodType,Set<DataElement>>();
+    	
+        for ( DataSet dataSet : dataSets )
+        {
+            Set<DataElement> dataElements = map.get( dataSet.getPeriodType() );
+            
+            if ( dataElements == null )
+            {
+                dataElements = new HashSet<DataElement>();
+                map.put( dataSet.getPeriodType(), dataElements );
+            }
+            
+            dataElements.addAll( dataSet.getDataElements() );
+        }
+        
+        return map;
+    }
+
     public void updateParent( OrganisationUnit newParent )
     {
         if ( this.parent != null && this.parent.getChildren() != null )

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/setting/SystemSettingManager.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/setting/SystemSettingManager.java	2013-10-01 16:44:42 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/setting/SystemSettingManager.java	2013-10-08 17:20:57 +0000
@@ -79,6 +79,7 @@
     final String KEY_SCHEDULE_AGGREGATE_QUERY_BUILDER_TASK_STRATEGY = "scheduleAggregateQueryBuilderTackStrategy";
     final String KEY_CONFIGURATION = "keyConfig";
     final String KEY_ACCOUNT_RECOVERY = "keyAccountRecovery";
+    final String KEY_LAST_ALERT_RUN = "keyLastAlertRun";
 
     final String DEFAULT_SCHEDULE_AGGREGATE_QUERY_BUILDER_TASK_STRATEGY = "lastMonth";
     final String DEFAULT_FLAG = "dhis2";

=== 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-08-23 15:56:19 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationResult.java	2013-10-08 19:10:40 +0000
@@ -38,7 +38,7 @@
  * @version $Id: ValidationResult.java 5277 2008-05-27 15:48:42Z larshelg $
  */
 public class ValidationResult
-    implements Serializable
+    implements Serializable, Comparable<ValidationResult>
 {
     /**
      * Determines if a de-serialized file is compatible with this class.
@@ -74,7 +74,7 @@
     }
 
     // -------------------------------------------------------------------------
-    // Equals, hashCode and toString
+    // Equals, compareTo, hashCode and toString
     // -------------------------------------------------------------------------     
 
     @Override
@@ -150,6 +150,39 @@
         return true;
     }
 
+    public int compareTo( ValidationResult other )
+    {
+        int result = source.getName().compareTo( other.source.getName() );
+
+        if ( result == 0 )
+        {
+            result = period.getStartDate().compareTo( other.period.getStartDate() );
+
+            if ( result == 0 )
+            {
+                result = period.getEndDate().compareTo( other.period.getEndDate() );
+
+                if ( result == 0 )
+                {
+                    result = validationImportanceOrder( validationRule.getImportance() )
+                        - validationImportanceOrder( other.validationRule.getImportance() );
+
+                    if ( result == 0 )
+                    {
+                        result = validationRule.getLeftSide().getDescription()
+                            .compareTo( other.validationRule.getLeftSide().getDescription() );
+                    }
+                }
+            }
+        }
+        return result;
+    }
+    
+    private int validationImportanceOrder ( String importance )
+    {
+    	return ( importance.equals("high") ? 0 : importance.equals("medium") ? 1 : 2 );
+    }
+    
     @Override
     public String toString()
     {

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRule.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRule.java	2013-09-11 15:26:20 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRule.java	2013-10-08 19:10:40 +0000
@@ -28,13 +28,9 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.JsonView;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
-import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
-import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+import java.util.HashSet;
+import java.util.Set;
+
 import org.hisp.dhis.common.BaseIdentifiableObject;
 import org.hisp.dhis.common.DxfNamespaces;
 import org.hisp.dhis.common.IdentifiableObject;
@@ -42,12 +38,18 @@
 import org.hisp.dhis.common.adapter.JacksonPeriodTypeSerializer;
 import org.hisp.dhis.common.view.DetailedView;
 import org.hisp.dhis.common.view.ExportView;
+import org.hisp.dhis.dataelement.DataElement;
 import org.hisp.dhis.expression.Expression;
 import org.hisp.dhis.expression.Operator;
 import org.hisp.dhis.period.PeriodType;
 
-import java.util.HashSet;
-import java.util.Set;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonView;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
 
 /**
  * @author Kristian Nordal
@@ -61,22 +63,90 @@
      */
     private static final long serialVersionUID = -9058559806538024350L;
 
+    public static final String IMPORTANCE_HIGH = "high";
+    public static final String IMPORTANCE_MEDIUM = "medium";
+    public static final String IMPORTANCE_LOW = "low";
+
+    public static final String RULE_TYPE_VALIDATION = "validation";
+    public static final String RULE_TYPE_MONITORING = "monitoring";
+    
     public static final String TYPE_STATISTICAL = "statistical";
     public static final String TYPE_ABSOLUTE = "absolute";
 
+    /**
+     * A description of the ValidationRule.
+     */
     private String description;
 
+    /**
+     * The user-assigned importance of this rule (e.g. high, medium or low).
+     */
+    private String importance;
+    
+    /**
+     * Whether this is a VALIDATION or MONITORING type rule.
+     */
+    private String ruleType;
+
+    /**
+     * Whether this is a STATISTICAL or ABSOLUTE rule (only ABSOLUTE rules are currently implemented!)
+     */
     private String type;
 
+    /**
+     * The comparison operator to compare left and right expressions in the rule.
+     */
     private Operator operator;
 
+    /**
+     * The left-side expression to be compared against the right side.
+     */
     private Expression leftSide;
 
+    /**
+     * The right-side expression to be compared against the left side.
+     */
     private Expression rightSide;
 
+    /**
+     * The set of ValidationRuleGroups to which this ValidationRule belongs.
+     */
     private Set<ValidationRuleGroup> groups = new HashSet<ValidationRuleGroup>();
 
+    /**
+     * The organisation unit level at which this rule is evaluated (Monitoring-type rules only).
+     */
+    private Integer organisationUnitLevel;
+
+    /**
+     * The type of period in which this rule is evaluated.
+     */
     private PeriodType periodType;
+    
+    /**
+     * The number of sequential right-side periods from which to collect samples
+     * to average (Monitoring-type rules only). Sequential periods are those
+     * immediately preceding (or immediately following in previous years) the selected period.
+     */
+    private Integer sequentialSampleCount; // Number of sequential right-side samples to average.
+
+    /**
+     * The number of annual right-side periods from which to collect samples
+     * to average (Monitoring-type rules only). Annual periods are from previous
+     * years. Samples collected from previous years can also include sequential
+     * periods adjacent to the equivalent period in previous years.
+     */
+    private Integer annualSampleCount; // Number of (previous) annual right-side samples to average.
+
+    /**
+     * The number of high values sampled from previous periods that are discarded before averaging.
+     */
+    private Integer highOutliers;
+    
+    /**
+     * The number of low values sampled from previous periods that are discarded before averaging.
+     */
+    private Integer lowOutliers;
 
     // -------------------------------------------------------------------------
     // Constructors
@@ -96,34 +166,87 @@
         this.leftSide = leftSide;
         this.rightSide = rightSide;
     }
-
+    
     // -------------------------------------------------------------------------
     // Logic
     // ------------------------------------------------------------------------- 
 
+    /**
+     * Clears the left-side and right-side expressions. This can be useful, for example,
+     * before changing the validation rule period type, because the data elements
+     * allowed in the expressions depend on the period type.
+     */
     public void clearExpressions()
     {
         this.leftSide = null;
         this.rightSide = null;
     }
 
+    /**
+     * Joins a validation rule group.
+     * 
+     * @param validationRuleGroup the group to join.
+     */
     public void addValidationRuleGroup( ValidationRuleGroup validationRuleGroup )
     {
         groups.add( validationRuleGroup );
         validationRuleGroup.getMembers().add( this );
     }
 
+    /**
+     * Leaves a validation rule group.
+     * 
+     * @param validationRuleGroup the group to leave.
+     */
     public void removeValidationRuleGroup( ValidationRuleGroup validationRuleGroup )
     {
         groups.remove( validationRuleGroup );
         validationRuleGroup.getMembers().remove( this );
     }
-    
+
+    /**
+     * Gets the validation rule description, but returns the validation rule name
+     * if there is no description.
+     * 
+     * @return the description (or name).
+     */
     public String getDescriptionNameFallback()
     {
         return description != null && !description.trim().isEmpty() ? description : name;
     }
 
+    /**
+     * Gets the data elements to evaluate for the current period. For validation-type
+     * rules this means all data elements. For monitoring-type rules this means just
+     * the left side elements.
+     * 
+     * @return the data elements to evaluate for the current period.
+     */
+    public Set<DataElement> getCurrentDataElements()
+    {
+    	Set<DataElement> currentDataElements = leftSide.getDataElementsInExpression();
+    	
+    	if ( RULE_TYPE_VALIDATION.equals( ruleType ) )
+    	{
+    	    currentDataElements = new HashSet<DataElement>( currentDataElements );
+    	    currentDataElements.addAll( rightSide.getDataElementsInExpression() );
+    	}
+    	
+    	return currentDataElements;
+    }
+
+    /**
+     * Gets the data elements to compare against for past periods. For validation-type
+     * rules this returns null. For monitoring-type rules this is just the
+     * right side elements.
+     * 
+     * @return the data elements to evaluate for past periods.
+     */
+    public Set<DataElement> getPastDataElements()
+    {
+        return RULE_TYPE_VALIDATION.equals( ruleType ) ? null : rightSide.getDataElementsInExpression();
+    }
+
     // -------------------------------------------------------------------------
     // Set and get methods
     // -------------------------------------------------------------------------  
@@ -142,6 +265,45 @@
     }
 
     @JsonProperty
+    @JsonView( {DetailedView.class, ExportView.class} )
+    @JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0)
+    public String getImportance()
+    {
+        return importance != null && !importance.isEmpty() ? importance : IMPORTANCE_MEDIUM;
+    }
+
+    public void setImportance( String importance )
+    {
+        this.importance = importance;
+    }
+
+    @JsonProperty
+    @JsonView( {DetailedView.class, ExportView.class} )
+    @JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0)
+    public Integer getOrganisationUnitLevel()
+    {
+        return organisationUnitLevel;
+    }
+
+    public void setOrganisationUnitLevel( Integer organisationUnitLevel )
+    {
+        this.organisationUnitLevel = organisationUnitLevel;
+    }
+
+    @JsonProperty
+    @JsonView( {DetailedView.class, ExportView.class} )
+    @JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0)
+    public String getRuleType()
+    {
+        return ruleType != null && !ruleType.isEmpty() ? ruleType : RULE_TYPE_VALIDATION;
+    }
+
+    public void setRuleType( String ruleType )
+    {
+        this.ruleType = ruleType;
+    }
+
+    @JsonProperty
     @JsonSerialize( using = JacksonPeriodTypeSerializer.class )
     @JsonDeserialize( using = JacksonPeriodTypeDeserializer.class )
     @JsonView( {DetailedView.class, ExportView.class} )
@@ -159,6 +321,58 @@
     @JsonProperty
     @JsonView( {DetailedView.class, ExportView.class} )
     @JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0)
+    public Integer getSequentialSampleCount()
+    {
+        return sequentialSampleCount;
+    }
+
+    public void setSequentialSampleCount( Integer sequentialSampleCount )
+    {
+        this.sequentialSampleCount = sequentialSampleCount;
+    }
+
+    @JsonProperty
+    @JsonView( {DetailedView.class, ExportView.class} )
+    @JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0)
+    public Integer getAnnualSampleCount()
+    {
+        return annualSampleCount;
+    }
+
+    public void setAnnualSampleCount( Integer annualSampleCount )
+    {
+        this.annualSampleCount = annualSampleCount;
+    }
+
+    @JsonProperty
+    @JsonView( {DetailedView.class, ExportView.class} )
+    @JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0)
+    public Integer getHighOutliers()
+    {
+        return highOutliers;
+    }
+
+    public void setHighOutliers( Integer highOutliers )
+    {
+        this.highOutliers = highOutliers;
+    }
+
+    @JsonProperty
+    @JsonView( {DetailedView.class, ExportView.class} )
+    @JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0)
+    public Integer getLowOutliers()
+    {
+        return lowOutliers;
+    }
+
+    public void setLowOutliers( Integer lowOutliers )
+    {
+        this.lowOutliers = lowOutliers;
+    }
+
+    @JsonProperty
+    @JsonView( {DetailedView.class, ExportView.class} )
+    @JacksonXmlProperty( namespace = DxfNamespaces.DXF_2_0)
     public Operator getOperator()
     {
         return operator;

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleGroup.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleGroup.java	2013-09-11 15:26:20 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleGroup.java	2013-09-27 17:05:36 +0000
@@ -34,12 +34,14 @@
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+
 import org.hisp.dhis.common.BaseIdentifiableObject;
 import org.hisp.dhis.common.DxfNamespaces;
 import org.hisp.dhis.common.IdentifiableObject;
 import org.hisp.dhis.common.annotation.Scanned;
 import org.hisp.dhis.common.view.DetailedView;
 import org.hisp.dhis.common.view.ExportView;
+import org.hisp.dhis.user.UserAuthorityGroup;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -60,6 +62,10 @@
 
     @Scanned
     private Set<ValidationRule> members = new HashSet<ValidationRule>();
+    
+    private Set<UserAuthorityGroup> userAuthorityGroupsToAlert = new HashSet<UserAuthorityGroup>();
+    
+    
 
     // -------------------------------------------------------------------------
     // Constructors
@@ -129,6 +135,21 @@
         this.members = members;
     }
 
+    @JsonProperty
+    @JsonSerialize( contentAs = BaseIdentifiableObject.class )
+    @JsonView( { DetailedView.class } )
+    @JacksonXmlElementWrapper( localName = "userRolesToAlert", namespace = DxfNamespaces.DXF_2_0)
+    @JacksonXmlProperty( localName = "userRoleToAlert", namespace = DxfNamespaces.DXF_2_0)
+    public Set<UserAuthorityGroup> getUserAuthorityGroupsToAlert()
+    {
+        return userAuthorityGroupsToAlert;
+    }
+
+    public void setUserAuthorityGroupsToAlert( Set<UserAuthorityGroup> userAuthorityGroupsToAlert )
+    {
+        this.userAuthorityGroupsToAlert = userAuthorityGroupsToAlert;
+    }
+
     @Override
     public void mergeWith( IdentifiableObject other )
     {

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleService.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleService.java	2013-08-23 15:56:19 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/validation/ValidationRuleService.java	2013-10-08 17:20:57 +0000
@@ -44,7 +44,8 @@
 {
     String ID = ValidationRuleService.class.getName();
 
-    int MAX_VIOLATIONS = 500;
+    int MAX_INTERACTIVE_VIOLATIONS = 500;
+    int MAX_ALERT_VIOLATIONS = 100000;
 
     // -------------------------------------------------------------------------
     // ValidationRule business logic
@@ -91,6 +92,11 @@
      */
     Collection<ValidationResult> validate( Date startDate, Date endDate, OrganisationUnit source );
 
+    /**
+     * For a nightly alert run, run validation tests and notify users as requested of any validations.
+     */
+    void alertRun();
+    
     // -------------------------------------------------------------------------
     // ValidationRule
     // -------------------------------------------------------------------------
@@ -172,13 +178,6 @@
      */
     Collection<ValidationRule> getValidationRulesByDataElements( Collection<DataElement> dataElements );
 
-    /**
-     * Get all data elements associated with any validation rule.
-     *
-     * @return a collection of data elements.
-     */
-    Collection<DataElement> getDataElementsInValidationRules();
-
     // -------------------------------------------------------------------------
     // ValidationRuleGroup
     // -------------------------------------------------------------------------

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueService.java	2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/DefaultDataValueService.java	2013-10-08 17:20:57 +0000
@@ -32,6 +32,7 @@
 
 import java.util.Calendar;
 import java.util.Collection;
+import java.util.Date;
 import java.util.Map;
 
 import org.hisp.dhis.dataelement.DataElement;
@@ -190,9 +191,15 @@
         return dataValueStore.getDataValueCount( cal.getTime() );
     }
     
-    public Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Period period, OrganisationUnit unit )
-    {
-        return dataValueStore.getDataValueMap( dataElements, period, unit );
+    public Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Period period, OrganisationUnit source )
+    {
+        return dataValueStore.getDataValueMap( dataElements, period, source );
+    }
+    
+    public Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Date date, OrganisationUnit source,
+    		Collection<PeriodType> periodTypes, Map<DataElementOperand, Date> lastUpdatedMap )
+    {
+    	return dataValueStore.getDataValueMap( dataElements, date, source, periodTypes, lastUpdatedMap );
     }
     
     public Collection<DeflatedDataValue> getDeflatedDataValues( int dataElementId, int periodId, Collection<Integer> sourceIds )

=== 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-08-23 16:05:01 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/datavalue/hibernate/HibernateDataValueStore.java	2013-10-08 19:10:40 +0000
@@ -57,6 +57,7 @@
 import org.hisp.dhis.system.objectmapper.DataValueRowMapper;
 import org.hisp.dhis.system.objectmapper.DeflatedDataValueRowMapper;
 import org.hisp.dhis.system.util.ConversionUtils;
+import org.hisp.dhis.system.util.DateUtils;
 import org.hisp.dhis.system.util.MathUtils;
 import org.hisp.dhis.system.util.TextUtils;
 import org.springframework.dao.EmptyResultDataAccessException;
@@ -431,7 +432,7 @@
         return rs != null ? rs.intValue() : 0;
     }
     
-    public Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Period period, OrganisationUnit unit )
+    public Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Period period, OrganisationUnit source )
     {
         Map<DataElementOperand, Double> map = new HashMap<DataElementOperand, Double>();
         
@@ -441,13 +442,13 @@
         }
         
         final String sql = 
-            "select de.uid, coc.uid, value " +
+            "select de.uid, coc.uid, dv.value " +
             "from datavalue dv " +
             "join dataelement de on dv.dataelementid = de.dataelementid " +
             "join categoryoptioncombo coc on dv.categoryoptioncomboid = coc.categoryoptioncomboid " +
             "where dv.dataelementid in (" + TextUtils.getCommaDelimitedString( ConversionUtils.getIdentifiers( DataElement.class, dataElements ) ) + ") " +
             "and dv.periodid = " + period.getId() + " " +
-            "and dv.sourceid = " + unit.getId();
+            "and dv.sourceid = " + source.getId();
         
         SqlRowSet rowSet = jdbcTemplate.queryForRowSet( sql );
         
@@ -466,6 +467,68 @@
         return map; 
     }
 
+    public Map<DataElementOperand, Double> getDataValueMap( Collection<DataElement> dataElements, Date date, OrganisationUnit source,
+    		Collection<PeriodType> periodTypes, Map<DataElementOperand, Date> lastUpdatedMap )
+    {
+        Map<DataElementOperand, Double> map = new HashMap<DataElementOperand, Double>();
+        
+        if ( dataElements.isEmpty() || periodTypes.isEmpty() )
+        {
+            return map;
+        }
+        
+        final String sql = 
+            "select de.uid, coc.uid, dv.value, dv.lastupdated, p.startdate, p.enddate " +
+            "from datavalue dv " +
+            "join dataelement de on dv.dataelementid = de.dataelementid " +
+            "join categoryoptioncombo coc on dv.categoryoptioncomboid = coc.categoryoptioncomboid " +
+            "join period p on p.periodid = dv.periodid " +
+            "where dv.dataelementid in (" + TextUtils.getCommaDelimitedString( ConversionUtils.getIdentifiers( DataElement.class, dataElements ) ) + ") " +
+            "and dv.sourceid = " + source.getId() + " " +
+            "and p.startdate <= '" + DateUtils.getMediumDateString( date ) + "' " +
+            "and p.enddate >= '" + DateUtils.getMediumDateString( date ) + "' " +
+        	"and p.periodtypeid in (" + TextUtils.getCommaDelimitedString( ConversionUtils.getIdentifiers( PeriodType.class, periodTypes ) ) + ") ";
+
+        SqlRowSet rowSet = jdbcTemplate.queryForRowSet( sql );
+        
+        Map<DataElementOperand, Long> checkForDuplicates = new HashMap<DataElementOperand, Long>();
+
+        while ( rowSet.next() )
+        {
+            String dataElement = rowSet.getString( 1 );
+            String optionCombo = rowSet.getString( 2 );
+            Double value = MathUtils.parseDouble( rowSet.getString( 3 ) );
+            Date lastUpdated = rowSet.getDate( 4 );
+            Date periodStartDate = rowSet.getDate( 5 );
+            Date periodEndDate = rowSet.getDate( 6 );
+            long periodInterval = periodEndDate.getTime() - periodStartDate.getTime();
+
+            if ( value != null )
+            {
+                DataElementOperand dataElementOperand = new DataElementOperand( dataElement, optionCombo );
+                Long existingPeriodInterval = checkForDuplicates.get( dataElementOperand );
+                
+                if ( existingPeriodInterval != null && existingPeriodInterval < periodInterval )
+                {
+                    // Don't overwrite the previously-stored value if it was 
+                    // for a shorter interval.
+                    continue; 
+                }
+                
+                map.put( dataElementOperand, value );
+                
+                if ( lastUpdatedMap != null )
+                {
+                    lastUpdatedMap.put( dataElementOperand, lastUpdated );
+                }
+                
+                checkForDuplicates.put( dataElementOperand, periodInterval );
+            }
+        }
+
+        return map;
+    }
+
     public Collection<DeflatedDataValue> getDeflatedDataValues( int dataElementId, int periodId, Collection<Integer> sourceIds )
     {
         final String sql =

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/expression/DefaultExpressionService.java'
--- dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/expression/DefaultExpressionService.java	2013-10-03 10:21:15 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/expression/DefaultExpressionService.java	2013-10-08 17:20:57 +0000
@@ -184,12 +184,20 @@
     }
     
     public Double getExpressionValue( Expression expression, Map<DataElementOperand, Double> valueMap, 
-        Map<String, Double> constantMap, Integer days )
-    {
-        final String expressionString = generateExpression( expression.getExpression(), valueMap, constantMap, days, expression.isNullIfBlank() );
-
-        return expressionString != null ? calculateExpression( expressionString ) : null;
-    }
+            Map<String, Double> constantMap, Integer days )
+        {
+            final String expressionString = generateExpression( expression.getExpression(), valueMap, constantMap, days, expression.isNullIfBlank() );
+
+            return expressionString != null ? calculateExpression( expressionString ) : null;
+        }
+
+    public Double getExpressionValue( Expression expression, Map<DataElementOperand, Double> valueMap, 
+            Map<String, Double> constantMap, Integer days, Set<DataElementOperand> incompleteValues )
+        {
+            final String expressionString = generateExpression( expression.getExpression(), valueMap, constantMap, days, expression.isNullIfBlank(), incompleteValues );
+
+            return expressionString != null ? calculateExpression( expressionString ) : null;
+        }
 
     @Transactional
     public Set<DataElement> getDataElementsInExpression( String expression )
@@ -636,6 +644,12 @@
     @Transactional
     public String generateExpression( String expression, Map<DataElementOperand, Double> valueMap, Map<String, Double> constantMap, Integer days, boolean nullIfNoValues )
     {
+    	return generateExpression( expression, valueMap, constantMap, days, nullIfNoValues, null );
+    }
+
+    private String generateExpression( String expression, Map<DataElementOperand, Double> valueMap, Map<String, Double> constantMap, Integer days, boolean nullIfNoValues,
+    		Set<DataElementOperand> incompleteValues )
+    {
         if ( expression == null || expression.isEmpty() )
         {
             return null;
@@ -654,7 +668,7 @@
 
             final Double value = valueMap.get( operand );
             
-            if ( value == null && nullIfNoValues )
+            if ( nullIfNoValues && ( value == null || ( incompleteValues != null && incompleteValues.contains( operand ) ) ) )
             {
                 return null;
             }

=== 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-08-23 16:05:01 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/validation/DefaultValidationRuleService.java	2013-10-08 19:10:40 +0000
@@ -37,12 +37,29 @@
 import static org.hisp.dhis.system.util.MathUtils.getRounded;
 import static org.hisp.dhis.system.util.MathUtils.zeroIfNull;
 
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 
+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.common.GenericIdentifiableObjectStore;
 import org.hisp.dhis.constant.ConstantService;
 import org.hisp.dhis.dataelement.DataElement;
@@ -53,24 +70,177 @@
 import org.hisp.dhis.expression.ExpressionService;
 import org.hisp.dhis.expression.Operator;
 import org.hisp.dhis.i18n.I18nService;
+import org.hisp.dhis.message.MessageService;
 import org.hisp.dhis.organisationunit.OrganisationUnit;
+import org.hisp.dhis.organisationunit.OrganisationUnitService;
+import org.hisp.dhis.period.CalendarPeriodType;
 import org.hisp.dhis.period.Period;
 import org.hisp.dhis.period.PeriodService;
+import org.hisp.dhis.period.PeriodType;
+import org.hisp.dhis.setting.SystemSettingManager;
 import org.hisp.dhis.system.util.Filter;
 import org.hisp.dhis.system.util.FilterUtils;
+import org.hisp.dhis.system.util.SystemUtils;
+import org.hisp.dhis.user.User;
+import org.hisp.dhis.user.UserAuthorityGroup;
+import org.hisp.dhis.user.UserCredentials;
 import org.springframework.transaction.annotation.Transactional;
 
 /**
  * @author Margrethe Store
  * @author Lars Helge Overland
- * @version $Id
+ * @author Jim Grace
  */
 @Transactional
 public class DefaultValidationRuleService
     implements ValidationRuleService
 {
+    private static final Log log = LogFactory.getLog( DefaultValidationRuleService.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;
 
+    /**
+     * Defines the types of alert run.
+     */
+    private enum ValidationRunType
+    {
+        INTERACTIVE, ALERT
+    }
+
+    /**
+     * This private subclass holds information for each organisation unit that
+     * is needed during a validation run (either interactive or an alert run).
+     * 
+     * It is important that they should be copied from Hibernate lazy
+     * collections before the multithreaded part of the run starts, otherwise
+     * the threads may not be able to access these values.
+     */
+    private class OrganisationUnitExtended
+    {
+        OrganisationUnit source;
+
+        Collection<OrganisationUnit> children;
+
+        int level;
+
+        public String toString()
+        {
+            return new ToStringBuilder( this, ToStringStyle.SHORT_PREFIX_STYLE )
+                .append( "\n     name", source.getName() ).append( "\n     children[", children.size() + "]" )
+                .append( "\n     level", level ).toString();
+        }
+    }
+
+    /**
+     * This private subclass holds information for each period type that is
+     * needed during a validation run (either interactive or an alert run).
+     * 
+     * By computing these values once at the start of a validation run, we avoid
+     * the overhead of having to compute them during the processing of every
+     * organisation unit. For some of these properties this is also important
+     * because they should be copied from Hibernate lazy collections before the
+     * multithreaded part of the run starts, otherwise the threads may not be
+     * able to access these values.
+     */
+    private class PeriodTypeExtended
+    {
+        PeriodType periodType;
+
+        Collection<Period> periods;
+
+        Collection<ValidationRule> rules;
+
+        Collection<DataElement> dataElements;
+
+        Collection<PeriodType> allowedPeriodTypes;
+
+        Map<OrganisationUnit, Collection<DataElement>> sourceDataElements;
+
+        public String toString()
+        {
+            return new ToStringBuilder( this, ToStringStyle.SHORT_PREFIX_STYLE )
+                .append( "\n periodType", periodType )
+                .append( "\n periods", (Arrays.toString( periods.toArray() )) )
+                .append( "\n rules", (Arrays.toString( rules.toArray() )) )
+                .append( "\n dataElements", (Arrays.toString( dataElements.toArray() )) )
+                .append( "\n allowedPeriodTypes", (Arrays.toString( allowedPeriodTypes.toArray() )) )
+                .append( "\n sourceDataElements", "[" + sourceDataElements.size() + "]" ).toString();
+        }
+    }
+
+    /**
+     * This private subclass holds common values that are used during a
+     * validation run (either interactive or an alert run.) These values don't
+     * change during the multi-threaded tasks (except that results entries are
+     * added in a threadsafe way.)
+     * 
+     * Some of the values are precalculated collections, to save CPU time during
+     * the run. All of these values are stored in this single "context" object
+     * to allow a single object reference for each of the scheduled tasks. (This
+     * also reduces the amount of memory needed to queue all the multi-threaded
+     * tasks.)
+     * 
+     * For some of these properties this is also important because they should
+     * be copied from Hibernate lazy collections before the multithreaded part
+     * of the run starts, otherwise the threads may not be able to access these
+     * values.
+     */
+    private class ValidationRunContext
+    {
+        private Map<PeriodType, PeriodTypeExtended> PeriodTypeExtendedMap;
+
+        private ValidationRunType runType;
+
+        private Date lastAlertRun;
+
+        private Map<String, Double> constantMap;
+
+        private Collection<OrganisationUnitExtended> sourceXs;
+
+        private Collection<ValidationResult> validationResults;
+
+        public String toString()
+        {
+            return new ToStringBuilder( this, ToStringStyle.SHORT_PREFIX_STYLE )
+                .append( "\n  PeriodTypeExtendedMap", (Arrays.toString( PeriodTypeExtendedMap.entrySet().toArray() )) )
+                .append( "\n  runType", runType ).append( "\n  lastAlertRun", lastAlertRun )
+                .append( "\n  constantMap", "[" + constantMap.size() + "]" )
+                .append( "\n  sourceXs", Arrays.toString( sourceXs.toArray() ) )
+                .append( "\n  validationResults", Arrays.toString( validationResults.toArray() ) ).toString();
+        }
+    }
+
+    /**
+     * Runs a validation task on a thread within a multi-threaded validation
+     * run.
+     * 
+     * Each thread looks for validation results in a different organisation
+     * unit.
+     */
+    private class ValidationWorkerThread
+        implements Runnable
+    {
+        private OrganisationUnitExtended sourceX;
+
+        private ValidationRunContext context;
+
+        private ValidationWorkerThread( OrganisationUnitExtended sourceX, ValidationRunContext context )
+        {
+            this.sourceX = sourceX;
+            this.context = context;
+        }
+
+        @Override
+        public void run()
+        {
+            validateSource( sourceX, context );
+        }
+    }
+
     // -------------------------------------------------------------------------
     // Dependencies
     // -------------------------------------------------------------------------
@@ -84,7 +254,8 @@
 
     private GenericIdentifiableObjectStore<ValidationRuleGroup> validationRuleGroupStore;
 
-    public void setValidationRuleGroupStore( GenericIdentifiableObjectStore<ValidationRuleGroup> validationRuleGroupStore )
+    public void setValidationRuleGroupStore(
+        GenericIdentifiableObjectStore<ValidationRuleGroup> validationRuleGroupStore )
     {
         this.validationRuleGroupStore = validationRuleGroupStore;
     }
@@ -95,7 +266,7 @@
     {
         this.expressionService = expressionService;
     }
-    
+
     private DataEntryFormService dataEntryFormService;
 
     public void setDataEntryFormService( DataEntryFormService dataEntryFormService )
@@ -110,13 +281,20 @@
         this.periodService = periodService;
     }
 
+    private OrganisationUnitService organisationUnitService;
+
+    public void setOrganisationUnitService( OrganisationUnitService organisationUnitService )
+    {
+        this.organisationUnitService = organisationUnitService;
+    }
+
     private DataValueService dataValueService;
 
     public void setDataValueService( DataValueService dataValueService )
     {
         this.dataValueService = dataValueService;
     }
-    
+
     private ConstantService constantService;
 
     public void setConstantService( ConstantService constantService )
@@ -124,6 +302,20 @@
         this.constantService = constantService;
     }
 
+    private SystemSettingManager systemSettingManager;
+
+    public void setSystemSettingManager( SystemSettingManager systemSettingManager )
+    {
+        this.systemSettingManager = systemSettingManager;
+    }
+
+    private MessageService messageService;
+
+    public void setMessageService( MessageService messageService )
+    {
+        this.messageService = messageService;
+    }
+
     private I18nService i18nService;
 
     public void setI18nService( I18nService service )
@@ -137,253 +329,1096 @@
 
     public Collection<ValidationResult> validate( Date startDate, Date endDate, Collection<OrganisationUnit> sources )
     {
-        Map<String, Double> constantMap = constantService.getConstantMap();
-        
-        Collection<ValidationResult> validationViolations = new HashSet<ValidationResult>();
-
-        Collection<Period> relevantPeriods = periodService.getPeriodsBetweenDates( startDate, endDate );
-
-        for ( OrganisationUnit source : sources )
-        {
-            Collection<ValidationRule> relevantRules = getRelevantValidationRules( source.getDataElementsInDataSets() );
-            
-            Set<DataElement> dataElements = getDataElementsInValidationRules( relevantRules ); //TODO move outside loop?
-            
-            if ( relevantRules != null && relevantRules.size() > 0 )
-            {
-                for ( Period period : relevantPeriods )
-                {
-                    validationViolations.addAll( validateInternal( period, source, relevantRules, dataElements, constantMap, 
-                        validationViolations.size() ) );
-                }
-            }
-        }
-
-        return validationViolations;
+        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 );
     }
 
     public Collection<ValidationResult> validate( Date startDate, Date endDate, Collection<OrganisationUnit> sources,
         ValidationRuleGroup group )
     {
-        Map<String, Double> constantMap = constantService.getConstantMap();
-        
-        Collection<ValidationResult> validationViolations = new HashSet<ValidationResult>();
-
-        Collection<Period> relevantPeriods = periodService.getPeriodsBetweenDates( startDate, endDate );
-
-        for ( OrganisationUnit source : sources )
-        {
-            Collection<ValidationRule> relevantRules = getRelevantValidationRules( source.getDataElementsInDataSets() );
-            relevantRules.retainAll( group.getMembers() );
-
-            Set<DataElement> dataElements = getDataElementsInValidationRules( relevantRules );
-            
-            if ( !relevantRules.isEmpty() )
-            {
-                for ( Period period : relevantPeriods )
-                {
-                    validationViolations.addAll( validateInternal( period, source, relevantRules, dataElements, constantMap, 
-                        validationViolations.size() ) );
-                }
-            }
-        }
-
-        return validationViolations;
+        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 );
     }
 
     public Collection<ValidationResult> validate( Date startDate, Date endDate, OrganisationUnit source )
     {
-        Map<String, Double> constantMap = constantService.getConstantMap();
-        
-        Collection<ValidationResult> validationViolations = new HashSet<ValidationResult>();
-
-        Collection<ValidationRule> relevantRules = getRelevantValidationRules( source.getDataElementsInDataSets() );
-
-        Set<DataElement> dataElements = getDataElementsInValidationRules( relevantRules );
-        
-        Collection<Period> relevantPeriods = periodService.getPeriodsBetweenDates( startDate, endDate );
-
-        for ( Period period : relevantPeriods )
-        {
-            validationViolations.addAll( validateInternal( period, source, relevantRules, dataElements, constantMap, validationViolations
-                .size() ) );
-        }
-
-        return validationViolations;
+        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 );
     }
 
     public Collection<ValidationResult> validate( DataSet dataSet, Period period, OrganisationUnit source )
     {
-        Map<String, Double> constantMap = constantService.getConstantMap();
-        
-        Collection<ValidationRule> relevantRules = null;
-        
+        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 );
+
+        Collection<ValidationRule> rules = null;
         if ( DataSet.TYPE_CUSTOM.equals( dataSet.getDataSetType() ) )
         {
-            relevantRules = getRelevantValidationRules( dataSet );
-        }
-        else
-        {
-            relevantRules = getRelevantValidationRules( dataSet.getDataElements() );
-        }
-        
-        Set<DataElement> dataElements = getDataElementsInValidationRules( relevantRules );
-        
-        return validateInternal( period, source, relevantRules, dataElements, constantMap, 0 );
-    }
-
-    public Collection<DataElement> getDataElementsInValidationRules()
-    {
-        Set<DataElement> dataElements = new HashSet<DataElement>();
-
-        for ( ValidationRule rule : getAllValidationRules() )
-        {
-            dataElements.addAll( rule.getLeftSide().getDataElementsInExpression() );
-            dataElements.addAll( rule.getRightSide().getDataElementsInExpression() );
-        }
-
-        return dataElements;
-    }
-
-    // -------------------------------------------------------------------------
-    // Supportive methods
-    // -------------------------------------------------------------------------
-
-    /**
-     * Validates a collection of validation rules.
-     * 
-     * @param period the period to validate for.
-     * @param source the source to validate for.
-     * @param validationRules the rules to validate.
-     * @param dataElementsInRules the data elements which are part of the rules expressions.
-     * @param constantMap the constants which are part of the rule expressions.
-     * @param currentSize the current number of validation violations.
-     * @returns a collection of rules that did not pass validation.
-     */
-    private Collection<ValidationResult> validateInternal( Period period, OrganisationUnit unit,
-        Collection<ValidationRule> validationRules, Set<DataElement> dataElementsInRules, Map<String, Double> constantMap, int currentSize )
-    {
-        Map<DataElementOperand, Double> valueMap = dataValueService.getDataValueMap( dataElementsInRules, period, unit );
-
-        final Collection<ValidationResult> validationViolations = new HashSet<ValidationResult>();
-
-        if ( currentSize < MAX_VIOLATIONS )
-        {
-            Double leftSide = null;
-            Double rightSide = null;
-
-            boolean violation = false;
-
-            for ( final ValidationRule validationRule : validationRules )
-            {
-                if ( validationRule.getPeriodType() != null
-                    && validationRule.getPeriodType().equals( period.getPeriodType() ) )
-                {
-                    Operator operator = validationRule.getOperator();
+            rules = getRulesForDataSet( dataSet );
+        }
+        else
+        {
+            rules = getValidationTypeRulesForDataElements( dataSet.getDataElements() );
+        }
+
+        return validateInternal( source, periods, rules, ValidationRunType.INTERACTIVE, null );
+    }
+
+    // TODO: Schedule this to run every night.
+    public void alertRun()
+    {
+        // Find all the rules belonging to groups that will send alerts to user roles.
+        Set<ValidationRule> rules = new HashSet<ValidationRule>();
+        for ( ValidationRuleGroup validationRuleGroup : getAllValidationRuleGroups() )
+        {
+            Collection<UserAuthorityGroup> userRolesToAlert = validationRuleGroup.getUserAuthorityGroupsToAlert();
+            if ( userRolesToAlert != null && !userRolesToAlert.isEmpty() )
+            {
+                rules.addAll( validationRuleGroup.getMembers() );
+            }
+        }
+
+        Collection<OrganisationUnit> sources = organisationUnitService.getAllOrganisationUnits();
+        Collection<Period> periods = getAlertPeriodsFromRules( rules );
+        Date lastAlertRun = (Date) systemSettingManager.getSystemSetting( SystemSettingManager.KEY_LAST_ALERT_RUN );
+        
+        // Any database changes after this moment will contribute to the next run.
+        
+        Date thisAlertRun = new Date();
+        
+        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() + "]" );
+        
+        if ( !results.isEmpty() )
+        {
+            postAlerts( results, thisAlertRun ); // Alert the users.
+        }
+        
+        systemSettingManager.saveSystemSetting( SystemSettingManager.KEY_LAST_ALERT_RUN, thisAlertRun );
+    }
+
+    // -------------------------------------------------------------------------
+    // Support methods
+    // -------------------------------------------------------------------------
+
+    /**
+     * Evaluates validation rules for a single organisation unit.
+     * 
+     * @param source the organisation unit in which to run the validation rules
+     * @param periods the periods of data to check
+     * @param rules the ValidationRules to evaluate
+     * @param runType whether this is an interactive or alert run
+     * @param lastAlertRun date/time of the most recent successful alerts run
+     *        (needed only for alert run)
+     * @return a collection of any validations that were found
+     */
+    private Collection<ValidationResult> validateInternal( OrganisationUnit source, Collection<Period> periods,
+        Collection<ValidationRule> rules, ValidationRunType runType, Date lastAlertRun )
+    {
+        Collection<OrganisationUnit> sources = new HashSet<OrganisationUnit>();
+        sources.add( source );
+        return validateInternal( sources, periods, rules, ValidationRunType.INTERACTIVE, null );
+    }
+
+    /**
+     * Evaluates validation rules for a collection of organisation units. This
+     * method breaks the job down by organisation unit. It assigns the
+     * evaluation for each organisation unit to a task that can be evaluated
+     * independently in a multithreaded environment.
+     * 
+     * @param sources the organisation units in which to run the validation
+     *        rules
+     * @param periods the periods of data to check
+     * @param rules the ValidationRules to evaluate
+     * @param runType whether this is an INTERACTIVE or ALERT run
+     * @param lastAlertRun date/time of the most recent successful alerts run
+     *        (needed only for alert run)
+     * @return a collection of any validations that were found
+     */
+    private Collection<ValidationResult> validateInternal( Collection<OrganisationUnit> sources,
+        Collection<Period> periods, Collection<ValidationRule> rules, ValidationRunType runType, Date lastAlertRun )
+    {
+        ValidationRunContext context = buildNewContext( sources, periods, rules, ValidationRunType.ALERT, lastAlertRun );
+        boolean singleThreadedOption = false;
+
+        if ( singleThreadedOption )
+        {
+            for ( OrganisationUnitExtended sourceX : context.sourceXs )
+            {
+                validateSource( sourceX, context );
+            }
+        }
+        else
+        {
+            int threadPoolSize = SystemUtils.getCpuCores();
+            if ( threadPoolSize > 2 )
+            {
+                threadPoolSize--;
+            }
+            if ( threadPoolSize > sources.size() )
+            {
+                threadPoolSize = sources.size();
+            }
+
+            ExecutorService executor = Executors.newFixedThreadPool( threadPoolSize );
+
+            for ( OrganisationUnitExtended sourceX : context.sourceXs )
+            {
+                Runnable worker = new ValidationWorkerThread( sourceX, context );
+                executor.execute( worker );
+            }
+
+            executor.shutdown();
+            try
+            {
+                executor.awaitTermination( 23, TimeUnit.HOURS );
+            }
+            catch ( InterruptedException e )
+            {
+                executor.shutdownNow();
+            }
+        }
+        return context.validationResults;
+    }
+
+    /**
+     * Creates and fills a new context object for a validation run.
+     * 
+     * @param sources organisation units for validation
+     * @param periods periods for validation
+     * @param rules validation rules for validation
+     * @param runType whether this is an INTERACTIVE or ALERT run
+     * @param lastAlertRun (for ALERT runs) date of previous alert run
+     * @return context object for this run
+     */
+    private ValidationRunContext buildNewContext( Collection<OrganisationUnit> sources, Collection<Period> periods,
+        Collection<ValidationRule> rules, ValidationRunType runType, Date lastAlertRun )
+    {
+        ValidationRunContext context = new ValidationRunContext();
+        context.runType = runType;
+        context.lastAlertRun = lastAlertRun;
+        context.validationResults = new ConcurrentLinkedQueue<ValidationResult>(); // thread-safe
+        context.PeriodTypeExtendedMap = new HashMap<PeriodType, PeriodTypeExtended>();
+        context.sourceXs = new HashSet<OrganisationUnitExtended>();
+
+        context.constantMap = new HashMap<String, Double>();
+        context.constantMap.putAll( constantService.getConstantMap() );
+
+        // Group the periods by period type.
+        for ( Period period : periods )
+        {
+            PeriodTypeExtended periodTypeX = getOrCreatePeriodTypeExtended( context, period.getPeriodType() );
+            periodTypeX.periods.add( period );
+        }
+
+        for ( ValidationRule rule : rules )
+        {
+            // Find the period type extended for this rule
+            PeriodTypeExtended periodTypeX = getOrCreatePeriodTypeExtended( context, rule.getPeriodType() ); 
+            periodTypeX.rules.add( rule ); // Add this rule to the period type ext.
+            
+            if ( rule.getCurrentDataElements() != null )
+            {
+                // Add this rule's data elements to the period extended.
+                periodTypeX.dataElements.addAll( rule.getCurrentDataElements() );
+            }
+            // Add the allowed period types for this rule's data elements:
+            periodTypeX.allowedPeriodTypes.addAll( getAllowedPeriodTypesForDataElements( rule.getCurrentDataElements(),
+                rule.getPeriodType() ) );
+        }
+
+        // We only need to keep period types that are selected and also used by
+        // rules that are selected.
+        // Start by making a defensive copy so we can delete while iterating.
+        Set<PeriodTypeExtended> periodTypeXs = new HashSet<PeriodTypeExtended>( context.PeriodTypeExtendedMap.values() );
+        for ( PeriodTypeExtended periodTypeX : periodTypeXs )
+        {
+            if ( periodTypeX.periods.isEmpty() || periodTypeX.rules.isEmpty() )
+            {
+                context.PeriodTypeExtendedMap.remove( periodTypeX.periodType );
+            }
+        }
+
+        for ( OrganisationUnit source : sources )
+        {
+            OrganisationUnitExtended sourceX = new OrganisationUnitExtended();
+            sourceX.source = source;
+            sourceX.children = new HashSet<OrganisationUnit>( source.getChildren() );
+            sourceX.level = source.getOrganisationUnitLevel();
+            context.sourceXs.add( sourceX );
+
+            Map<PeriodType, Set<DataElement>> sourceDataElementsByPeriodType = source
+                .getDataElementsInDataSetsByPeriodType();
+            for ( PeriodTypeExtended periodTypeX : context.PeriodTypeExtendedMap.values() )
+            {
+                Collection<DataElement> sourceDataElements = sourceDataElementsByPeriodType
+                    .get( periodTypeX.periodType );
+                if ( sourceDataElements != null )
+                {
+                    periodTypeX.sourceDataElements.put( source, sourceDataElements );
+                }
+                else
+                {
+                    periodTypeX.sourceDataElements.put( source, new HashSet<DataElement>() );
+                }
+            }
+        }
+
+        return context;
+    }
+
+    /**
+     * Gets the PeriodTypeExtended from the context object. If not found,
+     * creates a new PeriodTypeExtended object, puts it into the context object,
+     * and returns it.
+     * 
+     * @param context validation run context
+     * @param periodType period type to search for
+     * @return period type extended from the context object
+     */
+    PeriodTypeExtended getOrCreatePeriodTypeExtended( ValidationRunContext context, PeriodType periodType )
+    {
+        PeriodTypeExtended periodTypeX = context.PeriodTypeExtendedMap.get( periodType );
+        if ( periodTypeX == null )
+        {
+            periodTypeX = new PeriodTypeExtended();
+            periodTypeX.periodType = periodType;
+            periodTypeX.periods = new HashSet<Period>();
+            periodTypeX.rules = new HashSet<ValidationRule>();
+            periodTypeX.dataElements = new HashSet<DataElement>();
+            periodTypeX.allowedPeriodTypes = new HashSet<PeriodType>();
+            periodTypeX.sourceDataElements = new HashMap<OrganisationUnit, Collection<DataElement>>();
+            context.PeriodTypeExtendedMap.put( periodType, periodTypeX );
+        }
+        return periodTypeX;
+    }
+
+    /**
+     * For an alert run, gets the current and most periods to search, based on
+     * the period types from the rules to run.
+     * 
+     * @param rules the ValidationRules to be evaluated on this alert run
+     * @return periods to search for new alerts
+     */
+    private Collection<Period> getAlertPeriodsFromRules( Collection<ValidationRule> rules )
+    {
+        Set<Period> periods = new HashSet<Period>();
+
+        // Construct a set of all period types found in validation rules.
+        Set<PeriodType> rulePeriodTypes = new HashSet<PeriodType>();
+        for ( ValidationRule rule : rules )
+        {
+            // (Note that we have to get periodType from periodService,
+            // otherwise the ID will not be present.)
+            rulePeriodTypes.add( periodService.getPeriodTypeByName( rule.getPeriodType().getName() ) );
+        }
+
+        // For each period type, find the period containing the current date (if
+        // any), and the most recent previous period. Add whichever one(s) of these 
+        // are present in the database.
+        for ( PeriodType periodType : rulePeriodTypes )
+        {
+            // This is a bit awkward. The current periodType object is of type
+            // periodType, but not of type CalendarPeriodType. In other words, 
+            // ( periodType instanceof CalendarPeriodType ) returns false!
+            // In order to do periodType calendar math, we want a real
+            // "CalendarPeriodType" instance.
+            // TODO just cast to calendar period type
+            CalendarPeriodType calendarPeriodType = getCalendarPeriodType( periodType );
+            if ( calendarPeriodType != null )
+            {
+                Period currentPeriod = calendarPeriodType.createPeriod();
+                Period previousPeriod = calendarPeriodType.getPreviousPeriod( currentPeriod );
+                periods.addAll( periodService.getIntersectingPeriodsByPeriodType( periodType,
+                    previousPeriod.getStartDate(), currentPeriod.getEndDate() ) );
+                // Note: If the last successful daily run was more than one day
+                // ago, we might consider adding some additional periods of type 
+                // DailyPeriodType so we don't miss any alerts.
+            }
+        }
+
+        return periods;
+    }
+
+    /**
+     * Evaluates validation rules for a single organisation unit. This is the
+     * central method in validation rule evaluation.
+     * 
+     * @param sourceX extended object of the organisation unit in which to run
+     *        the validation rules
+     * @param context the validation run context
+     */
+    private void validateSource( OrganisationUnitExtended sourceX, ValidationRunContext context )
+    {
+        if ( context.validationResults.size() < (ValidationRunType.INTERACTIVE == context.runType ? MAX_INTERACTIVE_VIOLATIONS
+            : MAX_ALERT_VIOLATIONS) )
+        {
+            for ( PeriodTypeExtended periodTypeX : context.PeriodTypeExtendedMap.values() )
+            {
+                Collection<DataElement> sourceDataElements = periodTypeX.sourceDataElements.get( sourceX.source );
+                Set<ValidationRule> rules = getRulesBySourceAndPeriodType( sourceX, periodTypeX, context,
+                    sourceDataElements );
+
+                if ( !rules.isEmpty() )
+                {
+                    Set<DataElement> recursiveCurrentDataElements = getRecursiveCurrentDataElements( rules );
+                    for ( Period period : periodTypeX.periods )
+                    {
+                        Map<DataElementOperand, Date> lastUpdatedMap = new HashMap<DataElementOperand, Date>();
+                        Set<DataElementOperand> incompleteValues = new HashSet<DataElementOperand>();
+                        Map<DataElementOperand, Double> currentValueMap = getDataValueMapRecursive( periodTypeX,
+                            periodTypeX.dataElements, sourceDataElements, recursiveCurrentDataElements,
+                            periodTypeX.allowedPeriodTypes, period, sourceX.source, lastUpdatedMap, incompleteValues );
+                        log.trace( "currentValueMap[" + currentValueMap.size() + "]" );
+
+                        for ( ValidationRule rule : rules )
+                        {
+                            if ( evaluateCheck( lastUpdatedMap, rule, context ) )
+                            {
+                                Double leftSide = expressionService.getExpressionValue( rule.getLeftSide(),
+                                    currentValueMap, context.constantMap, null, incompleteValues );
+
+                                if ( leftSide != null || Operator.compulsory_pair.equals( rule.getOperator() ) )
+                                {
+                                    Double rightSide = getRightSideValue( sourceX.source, periodTypeX, period, rule,
+                                        currentValueMap, sourceDataElements, context );
+
+                                    if ( rightSide != null || Operator.compulsory_pair.equals( rule.getOperator() ) )
+                                    {
+                                        boolean violation = false;
+
+                                        if ( Operator.compulsory_pair.equals( rule.getOperator() ) )
+                                        {
+                                            violation = (leftSide != null && rightSide == null)
+                                                || (leftSide == null && rightSide != null);
+                                        }
+                                        else if ( leftSide != null && rightSide != null )
+                                        {
+                                            violation = !expressionIsTrue( leftSide, rule.getOperator(), rightSide );
+                                        }
+
+                                        if ( violation )
+                                        {
+                                            context.validationResults.add( new ValidationResult( period,
+                                                sourceX.source, rule, getRounded( zeroIfNull( leftSide ), DECIMALS ),
+                                                getRounded( zeroIfNull( rightSide ), DECIMALS ) ) );
+                                        }
+
+                                        log.trace( "-->Evaluated " + rule.getName() + ": "
+                                            + (violation ? "violation" : "OK") + " " + leftSide.toString() + " "
+                                            + rule.getOperator() + " " + rightSide.toString() + " ("
+                                            + context.validationResults.size() + " results)" );
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Checks to see if the evaluation should go further for this
+     * evaluationRule, after the "current" data to evaluate has been fetched.
+     * For INTERACTIVE runs, we always go further (always return true.) For
+     * ALERT runs, we go further only if something has changed since the last
+     * successful alert run -- either the rule definition or one of the
+     * "current" data element / option values.
+     * 
+     * @param lastUpdatedMap when each data value was last updated
+     * @param rule the rule that may be evaluated
+     * @param context the evaluation run context
+     * @return true if the rule should be evaluated with this data, false if not
+     */
+    private boolean evaluateCheck( Map<DataElementOperand, Date> lastUpdatedMap, ValidationRule rule,
+        ValidationRunContext context )
+    {
+        boolean evaluate = true; // Assume true for now.
+
+        if ( ValidationRunType.ALERT == context.runType )
+        {
+            if ( context.lastAlertRun != null ) // True if no previous alert run
+            {
+                if ( rule.getLastUpdated().before( context.lastAlertRun ) )
+                {
+                    // Get the "current" DataElementOperands from this rule:
+                    // Left+Right sides for VALIDATION, Left side only for
+                    // MONITORING
+                    Collection<DataElementOperand> deos = expressionService.getOperandsInExpression( rule.getLeftSide()
+                        .getExpression() );
+                    if ( ValidationRule.RULE_TYPE_VALIDATION == rule.getRuleType() )
+                    {
+                        // Make a copy so we can add to it.
+                        deos = new HashSet<DataElementOperand>( deos );                        
+                        
+                        deos.addAll( expressionService.getOperandsInExpression( rule.getRightSide().getExpression() ) );
+                    }
+
+                    // Return true if any data is more recent than the last
+                    // ALERT run, otherwise return false.
+                    evaluate = false;
+                    for ( DataElementOperand deo : deos )
+                    {
+                        Date lastUpdated = lastUpdatedMap.get( deo );
+                        if ( lastUpdated != null && lastUpdated.after( context.lastAlertRun ) )
+                        {
+                            evaluate = true; // True if new/updated data.
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        return evaluate;
+    }
+
+    /**
+     * Gets the rules that should be evaluated for a given organisation unit and
+     * period type.
+     * 
+     * @param sourceX the organisation unit extended information
+     * @param periodTypeX the period type extended information
+     * @param context the alert run context
+     * @param sourceDataElements all data elements collected for this
+     *        organisation unit
+     * @return
+     */
+    private Set<ValidationRule> getRulesBySourceAndPeriodType( OrganisationUnitExtended sourceX,
+        PeriodTypeExtended periodTypeX, ValidationRunContext context, Collection<DataElement> sourceDataElements )
+    {
+        Set<ValidationRule> periodTypeRules = new HashSet<ValidationRule>();
+
+        for ( ValidationRule rule : periodTypeX.rules )
+        {
+            if ( (ValidationRule.RULE_TYPE_VALIDATION.equals( rule.getRuleType() )) )
+            {
+                // For validation-type rules, include only rules where the
+                // organisation collects all the data elements in the rule.
+                // But if this is some funny kind of rule with no elements (like
+                // for testing), include it also.
+                Collection<DataElement> elements = rule.getCurrentDataElements();
+                if ( elements == null || elements.size() == 0 || sourceDataElements.containsAll( elements ) )
+                {
+                    periodTypeRules.add( rule );
+                }
+            }
+            else
+            {
+                // For monitoring-type rules, include only rules for this
+                // organisation's unit level.
+                // The organisation may not be configured for the data elements
+                // because they could be aggregated from a lower level.
+                if ( rule.getOrganisationUnitLevel() == sourceX.level )
+                {
+                    periodTypeRules.add( rule );
+                }
+            }
+        }
+        
+        return periodTypeRules;
+    }
+
+    /**
+     * Gets the data elements for which values should be fetched recursively if
+     * they are not collected for an organisation unit.
+     * 
+     * @param rules ValidationRules to be evaluated
+     * @return the data elements to fetch recursively
+     */
+    private Set<DataElement> getRecursiveCurrentDataElements( Set<ValidationRule> rules )
+    {
+        Set<DataElement> recursiveCurrentDataElements = new HashSet<DataElement>();
+
+        for ( ValidationRule rule : rules )
+        {
+            if ( ValidationRule.RULE_TYPE_MONITORING.equals( rule.getRuleType() )
+                && rule.getCurrentDataElements() != null )
+            {
+                recursiveCurrentDataElements.addAll( rule.getCurrentDataElements() );
+            }
+        }
+        
+        return recursiveCurrentDataElements;
+    }
+
+    /**
+     * Returns the right-side evaluated value of the validation rule.
+     * 
+     * @param source organisation unit being evaluated
+     * @param periodTypeX period type being evaluated
+     * @param period period being evaluated
+     * @param rule ValidationRule being evaluated
+     * @param currentValueMap current values already fetched
+     * @param sourceDataElements the data elements collected by the organisation
+     *        unit
+     * @param context the validation run context
+     * @return the right-side value
+     */
+    private Double getRightSideValue( OrganisationUnit source, PeriodTypeExtended periodTypeX, Period period,
+        ValidationRule rule, Map<DataElementOperand, Double> currentValueMap,
+        Collection<DataElement> sourceDataElements, ValidationRunContext context )
+    {
+        Double rightSideValue = null;
+
+        // If ruleType is VALIDATION, the right side is evaluated using the same
+        // (current) data values.
+        // If ruleType is MONITORING but there are no data elements in the right
+        // side, then it doesn't matter
+        // what data values we use, so just supply the current data values in
+        // order to evaluate the (constant) expression.
+
+        if ( ValidationRule.RULE_TYPE_VALIDATION.equals( rule.getRuleType() )
+            || rule.getRightSide().getDataElementsInExpression().isEmpty() )
+        {
+            rightSideValue = expressionService.getExpressionValue( rule.getRightSide(), currentValueMap,
+                context.constantMap, null );
+        }
+        else
+        // ruleType equals MONITORING, and there are some data elements in the
+        // right side expression
+        {
+            CalendarPeriodType calendarPeriodType = getCalendarPeriodType( period.getPeriodType() );
+
+            if ( calendarPeriodType != null )
+            {
+                Collection<PeriodType> rightSidePeriodTypes = getAllowedPeriodTypesForDataElements(
+                    rule.getPastDataElements(), rule.getPeriodType() );
+                List<Double> sampleValues = new ArrayList<Double>();
+                Calendar yearlyCalendar = PeriodType.createCalendarInstance( period.getStartDate() );
+                int annualSampleCount = rule.getAnnualSampleCount() == null ? 0 : rule.getAnnualSampleCount();
+                int sequentialSampleCount = rule.getSequentialSampleCount() == null ? 0 : rule
+                    .getSequentialSampleCount();
+
+                for ( int annualCount = 0; annualCount <= annualSampleCount; annualCount++ )
+                {
+
+                    // Defensive copy because createPeriod mutates Calendar.
+                    Calendar calCopy = PeriodType.createCalendarInstance( yearlyCalendar.getTime() );
                     
-                    leftSide = expressionService.getExpressionValue( validationRule.getLeftSide(), valueMap, constantMap, null );
-
-                    if ( leftSide != null || Operator.compulsory_pair.equals( operator ) )
+                    // To track the period at the same time in preceding years.
+                    Period yearlyPeriod = calendarPeriodType.createPeriod( calCopy );
+
+                    // For past years, fetch the period at the same time of year
+                    // as this period,
+                    // and any periods after this period within the
+                    // sequentialPeriod limit.
+                    // For the year of the stating period, we will only fetch
+                    // previous sequential periods.
+
+                    if ( annualCount > 0 )
                     {
-                        rightSide = expressionService.getExpressionValue( validationRule.getRightSide(), valueMap, constantMap, null );
+                        // Fetch the period at the same time of year as the
+                        // starting period.
+                        evaluateRightSidePeriod( periodTypeX, sampleValues, source, rightSidePeriodTypes, yearlyPeriod,
+                            rule, sourceDataElements, context );
 
-                        if ( rightSide != null || Operator.compulsory_pair.equals( operator )  )
+                        // Fetch the sequential periods after this prior-year
+                        // period.
+                        Period sequentialPeriod = new Period( yearlyPeriod );
+                        for ( int sequentialCount = 0; sequentialCount < sequentialSampleCount; sequentialCount++ )
                         {
-                            if ( Operator.compulsory_pair.equals( operator ) )
-                            {
-                                violation = ( leftSide != null && rightSide == null ) || ( leftSide == null && rightSide != null );
-                            }
-                            else if ( leftSide != null && rightSide != null )
-                            {
-                                violation = !expressionIsTrue( leftSide, operator, rightSide );
-                            }
-                            
-                            if ( violation )
-                            {
-                                validationViolations.add( new ValidationResult( period, unit, validationRule,
-                                    getRounded( zeroIfNull( leftSide ), DECIMALS ), getRounded( zeroIfNull( rightSide ), DECIMALS ) ) );
-                            }
+                            sequentialPeriod = calendarPeriodType.getNextPeriod( sequentialPeriod );
+                            evaluateRightSidePeriod( periodTypeX, sampleValues, source, rightSidePeriodTypes,
+                                sequentialPeriod, rule, sourceDataElements, context );
                         }
                     }
-                }
-            }
-        }
-
-        return validationViolations;
-    }
-
-    /**
-     * Returns all validation rules which have data elements assigned to it
+
+                    // Fetch the seqential periods before this period (both this
+                    // year and past years):
+                    Period sequentialPeriod = new Period( yearlyPeriod );
+                    for ( int sequentialCount = 0; sequentialCount < sequentialSampleCount; sequentialCount++ )
+                    {
+                        sequentialPeriod = calendarPeriodType.getPreviousPeriod( sequentialPeriod );
+                        evaluateRightSidePeriod( periodTypeX, sampleValues, source, rightSidePeriodTypes,
+                            sequentialPeriod, rule, sourceDataElements, context );
+                    }
+
+                    // Move to the previous year:
+                    yearlyCalendar.set( Calendar.YEAR, yearlyCalendar.get( Calendar.YEAR ) - 1 );
+                }
+                
+                rightSideValue = rightSideAverage( rule, sampleValues, annualSampleCount, sequentialSampleCount );
+            }
+        }
+        return rightSideValue;
+    }
+
+    /**
+     * 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
+     * organisation units.
+     * 
+     * @param periodTypeX the period type extended information
+     * @param sampleValues the list of sample values to add to
+     * @param source the organisation unit
+     * @param allowedPeriodTypes the period types in which the data may exist
+     * @param period the main period for the validation rule evaluation
+     * @param rule the monitoring-type rule being evaluated
+     * @param sourceDataElements the data elements configured for this
+     *        organisation unit
+     * @param context the evaluation run context
+     */
+    private void evaluateRightSidePeriod( PeriodTypeExtended periodTypeX, List<Double> sampleValues,
+        OrganisationUnit source, Collection<PeriodType> allowedPeriodTypes, Period period, ValidationRule rule,
+        Collection<DataElement> sourceDataElements, ValidationRunContext context )
+    {
+        Period periodInstance = periodService.getPeriod( period.getStartDate(), period.getEndDate(),
+            period.getPeriodType() );
+        
+        if ( periodInstance != null )
+        {
+            Set<DataElement> dataElements = rule.getRightSide().getDataElementsInExpression();
+            Set<DataElementOperand> incompleteValues = new HashSet<DataElementOperand>();
+            Map<DataElementOperand, Double> dataValueMap = getDataValueMapRecursive( periodTypeX, dataElements,
+                sourceDataElements, dataElements, allowedPeriodTypes, period, source, null, incompleteValues );
+            Double value = expressionService.getExpressionValue( rule.getRightSide(), dataValueMap,
+                context.constantMap, null, incompleteValues );
+            
+            if ( value != null )
+            {
+                sampleValues.add( value );
+            }
+        }
+    }
+
+    /**
+     * Finds the average right-side sample value. This is used as the right-side
+     * expression value to evaluate a monitoring-type rule.
+     * 
+     * @param rule monitoring-type rule being evaluated
+     * @param sampleValues sample values actually collected
+     * @param annualSampleCount number of annual samples tried for
+     * @param sequentialSampleCount number of sequential samples tried for
+     * @return
+     */
+    Double rightSideAverage( ValidationRule rule, List<Double> sampleValues, int annualSampleCount,
+        int sequentialSampleCount )
+    {
+        // Find the expected sample count for the last period of its type in the
+        // database: sequentialSampleCount for the immediately preceding periods 
+        // in this year and for every past year: one sample for the same period 
+        // in that year, plus sequentialSampleCounts before and after.
+        Double average = null;
+        if ( !sampleValues.isEmpty() )
+        {
+            int expectedSampleCount = sequentialSampleCount + annualSampleCount * (1 + 2 * sequentialSampleCount);
+            int highOutliers = rule.getHighOutliers() == null ? 0 : rule.getHighOutliers();
+            int lowOutliers = rule.getLowOutliers() == null ? 0 : rule.getLowOutliers();
+
+            // If we had fewer than the expected number of samples, then scale
+            // back
+            if ( highOutliers + lowOutliers > sampleValues.size() )
+            {
+                highOutliers = (highOutliers * sampleValues.size()) / expectedSampleCount;
+                lowOutliers = (lowOutliers * sampleValues.size()) / expectedSampleCount;
+            }
+
+            // If we (still) have any high and/or low outliers to remove, then
+            // sort the sample values and remove the high and/or low outliers.
+            if ( highOutliers + lowOutliers > 0 )
+            {
+                Collections.sort( sampleValues );
+                log.trace( "Removing " + highOutliers + " high and " + lowOutliers + " low outliers from "
+                    + Arrays.toString( sampleValues.toArray() ) );
+                sampleValues = sampleValues.subList( lowOutliers, sampleValues.size() - highOutliers );
+                log.trace( "Result: " + Arrays.toString( sampleValues.toArray() ) );
+            }
+            Double sum = 0.0;
+            for ( Double sample : sampleValues )
+            {
+                sum += sample;
+            }
+            average = sum / sampleValues.size();
+        }
+        return average;
+    }
+
+    /**
+     * Gets data values for a given organisation unit and period, recursing if
+     * necessary to sum the values from child organisation units.
+     * 
+     * @param periodTypeX period type which we are evaluating
+     * @param ruleDataElements data elements configured for the rule
+     * @param sourceDataElements data elements configured for the organisation
+     *        unit
+     * @param recursiveDataElements data elements for which we will recurse if
+     *        necessary
+     * @param allowedPeriodTypes all the periods in which we might find the data
+     *        values
+     * @param period period in which we are looking for values
+     * @param source organisation unit for which we are looking for values
+     * @param lastUpdatedMap map showing when each data values was last updated
+     * @param incompleteValues ongoing list showing which values were found but
+     *        not from all children
+     * @return the map of values found
+     */
+    private Map<DataElementOperand, Double> getDataValueMapRecursive( PeriodTypeExtended periodTypeX,
+        Collection<DataElement> ruleDataElements, Collection<DataElement> sourceDataElements,
+        Set<DataElement> recursiveDataElements, Collection<PeriodType> allowedPeriodTypes, Period period,
+        OrganisationUnit source, Map<DataElementOperand, Date> lastUpdatedMap, Set<DataElementOperand> incompleteValues )
+    {
+        Set<DataElement> dataElementsToGet = new HashSet<DataElement>( ruleDataElements );
+        dataElementsToGet.retainAll( sourceDataElements );
+        log.trace( "getDataValueMapRecursive: source:" + source.getName() + " elementsToGet["
+            + dataElementsToGet.size() + "] allowedPeriodTypes[" + allowedPeriodTypes.size() + "]" );
+
+        Map<DataElementOperand, Double> dataValueMap;
+        
+        if ( dataElementsToGet.isEmpty() )
+        {
+            // We still might get something recursively
+            dataValueMap = new HashMap<DataElementOperand, Double>();
+        }
+        else
+        {
+            dataValueMap = dataValueService.getDataValueMap( dataElementsToGet, period.getStartDate(), source,
+                allowedPeriodTypes, lastUpdatedMap );
+        }
+
+        // See if there are any data elements we need to get recursively:
+        Set<DataElement> recursiveDataElementsNeeded = new HashSet<DataElement>( recursiveDataElements );
+        recursiveDataElementsNeeded.removeAll( dataElementsToGet );
+        if ( !recursiveDataElementsNeeded.isEmpty() )
+        {
+            int childCount = 0;
+            Map<DataElementOperand, Integer> childValueCounts = new HashMap<DataElementOperand, Integer>();
+            
+            for ( OrganisationUnit child : source.getChildren() )
+            {
+                Collection<DataElement> childDataElements = periodTypeX.sourceDataElements.get( child );
+                Map<DataElementOperand, Double> childMap = getDataValueMapRecursive( periodTypeX,
+                    recursiveDataElementsNeeded, childDataElements, recursiveDataElementsNeeded, allowedPeriodTypes,
+                    period, child, lastUpdatedMap, incompleteValues );
+
+                for ( DataElementOperand deo : childMap.keySet() )
+                {
+                    Double baseValue = dataValueMap.get( deo );
+                    dataValueMap.put( deo, baseValue == null ? childMap.get( deo ) : baseValue + childMap.get( deo ) );
+
+                    Integer childValueCount = childValueCounts.get( deo );
+                    childValueCounts.put( deo, childValueCount == null ? 1 : childValueCount + 1 );
+                }
+
+                childCount++;
+            }
+            
+            for ( Map.Entry<DataElementOperand, Integer> entry : childValueCounts.entrySet() )
+            {
+                if ( childCount != entry.getValue() )
+                {
+                    // Found this DataElementOperand value in some but not all children
+                    incompleteValues.add( entry.getKey() );
+                }
+            }
+        }
+
+        return dataValueMap;
+    }
+
+    /**
+     * Returns all validation-type rules which have specified data elements
+     * assigned to them.
+     * 
+     * @param dataElements the data elements to look for
+     * @return all validation rules which have the data elements assigned to
+     *         them
+     */
+    private Collection<ValidationRule> getValidationTypeRulesForDataElements( Set<DataElement> dataElements )
+    {
+        Set<ValidationRule> rulesForDataElements = new HashSet<ValidationRule>();
+
+        Set<DataElement> validationRuleElements = new HashSet<DataElement>();
+
+        for ( ValidationRule validationRule : getAllValidationRules() )
+        {
+            if ( validationRule.getRuleType().equals( ValidationRule.RULE_TYPE_VALIDATION ) )
+            {
+                validationRuleElements.clear();
+                validationRuleElements.addAll( validationRule.getLeftSide().getDataElementsInExpression() );
+                validationRuleElements.addAll( validationRule.getRightSide().getDataElementsInExpression() );
+
+                if ( dataElements.containsAll( validationRuleElements ) )
+                {
+                    rulesForDataElements.add( validationRule );
+                }
+            }
+        }
+
+        return rulesForDataElements;
+    }
+
+    /**
+     * Returns all validation rules which have data elements assigned to them
      * which are members of the given data set.
      * 
-     * @param dataSet the data set.
-     * @return all validation rules which have data elements assigned to it
-     *         which are members of the given data set.
+     * @param dataSet the data set
+     * @return all validation rules which have data elements assigned to them
+     *         which are members of the given data set
      */
-    private Collection<ValidationRule> getRelevantValidationRules( Set<DataElement> dataElements )
+    private Collection<ValidationRule> getRulesForDataSet( DataSet dataSet )
     {
-        Set<ValidationRule> relevantValidationRules = new HashSet<ValidationRule>();
-        
-        Set<DataElement> validationRuleElements = new HashSet<DataElement>();
-        
-        for ( ValidationRule validationRule : getAllValidationRules() )
-        {
-            validationRuleElements.clear();
-            validationRuleElements.addAll( validationRule.getLeftSide().getDataElementsInExpression() );
-            validationRuleElements.addAll( validationRule.getRightSide().getDataElementsInExpression() );
-            
-            if ( dataElements.containsAll( validationRuleElements ) )
-            {
-                relevantValidationRules.add( validationRule );
-            }
-        }
+        Set<ValidationRule> rulesForDataSet = new HashSet<ValidationRule>();
 
-        return relevantValidationRules;
-    }
-    
-    public Collection<ValidationRule> getRelevantValidationRules( DataSet dataSet )
-    {
-        Set<ValidationRule> relevantValidationRules = new HashSet<ValidationRule>();
-        
         Set<DataElementOperand> operands = dataEntryFormService.getOperandsInDataEntryForm( dataSet );
-        
+
         Set<DataElementOperand> validationRuleOperands = new HashSet<DataElementOperand>();
-        
-        for ( ValidationRule validationRule : getAllValidationRules() )
-        {
-            validationRuleOperands.clear();
-            validationRuleOperands.addAll( expressionService.getOperandsInExpression( validationRule.getLeftSide().getExpression() ) );
-            validationRuleOperands.addAll( expressionService.getOperandsInExpression( validationRule.getRightSide().getExpression() ) );
-            
-            if ( operands.containsAll( validationRuleOperands ) )
-            {
-                relevantValidationRules.add( validationRule );
-            }
-        }
-        
-        return relevantValidationRules;
-    }
-
-    /**
-     * Returns all validation rules referred to in the left and right side expressions
-     * of the given validation rules.
-     * 
-     * @param validationRules the validation rules.
-     * @return a collection of data elements.
-     */
-    private Set<DataElement> getDataElementsInValidationRules( Collection<ValidationRule> validationRules )
-    {
-        Set<DataElement> dataElements = new HashSet<DataElement>();
-
-        for ( ValidationRule rule : validationRules )
-        {
-            dataElements.addAll( rule.getLeftSide().getDataElementsInExpression() );
-            dataElements.addAll( rule.getRightSide().getDataElementsInExpression() );
-        }
-
-        return dataElements;
-    }
-    
+
+        for ( ValidationRule rule : getAllValidationRules() )
+        {
+            if ( rule.getRuleType().equals( ValidationRule.RULE_TYPE_VALIDATION ) )
+            {
+                validationRuleOperands.clear();
+                validationRuleOperands.addAll( expressionService.getOperandsInExpression( rule.getLeftSide()
+                    .getExpression() ) );
+                validationRuleOperands.addAll( expressionService.getOperandsInExpression( rule.getRightSide()
+                    .getExpression() ) );
+
+                if ( operands.containsAll( validationRuleOperands ) )
+                {
+                    rulesForDataSet.add( rule );
+                }
+            }
+        }
+
+        return rulesForDataSet;
+    }
+
+    /**
+     * Finds all period types that may contain given data elements, whose period
+     * type interval is at least as long as the given period type.
+     * 
+     * @param dataElements data elements to look for
+     * @param periodType the minimum-length period type
+     * @return all period types that are allowed for these data elements
+     */
+    private Collection<PeriodType> getAllowedPeriodTypesForDataElements( Collection<DataElement> dataElements,
+        PeriodType periodType )
+    {
+        Collection<PeriodType> allowedPeriodTypes = new HashSet<PeriodType>();
+        for ( DataElement dataElement : dataElements )
+        {
+            for ( DataSet dataSet : dataElement.getDataSets() )
+            {
+                if ( dataSet.getPeriodType().getFrequencyOrder() >= periodType.getFrequencyOrder() )
+                {
+                    allowedPeriodTypes.add( dataSet.getPeriodType() );
+                }
+            }
+        }
+        return allowedPeriodTypes;
+    }
+
+    /**
+     * Returns an instance of type CalendarPeriodType that matches the specified
+     * periodType. This can be needed in order to access the calendar-computing
+     * methods that are available in a CalendarPeriodType but not an ordinary
+     * PeriodType.
+     * 
+     * Note: Perhaps this should be moved to PeriodService. Or perhaps some
+     * refactoring can be done in the relationship between PeriodType and
+     * CalendarPeriodType.
+     * 
+     * @param periodType the period type of interest
+     * @return the corresponding CalendarPeriodType
+     */
+    private CalendarPeriodType getCalendarPeriodType( PeriodType periodType )
+    {
+        for ( PeriodType p : PeriodType.PERIOD_TYPES )
+        {
+            if ( periodType.getName().equals( p.getName() ) )
+            {
+                if ( p instanceof CalendarPeriodType )
+                {
+                    return (CalendarPeriodType) p;
+                }
+                else
+                {
+                    log.error( "DefaultValidationRuleService.getCalendarPeriodType() - PeriodType.PERIOD_TYPES ["
+                        + p.getName() + "] is not a CalendarPeriodType!" );
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * At the end of an ALERT run, post messages to the users who want to see
+     * the results.
+     * 
+     * @param validationResults the set of validation error results
+     * @param alertRunStart the date/time when the alert run started
+     */
+    private void postAlerts( Collection<ValidationResult> validationResults, Date alertRunStart )
+    {
+        SortedSet<ValidationResult> results = new TreeSet<ValidationResult>( validationResults );
+
+        // Find out which users receive alerts, and which ValidationRules they
+        // receive alerts for.
+        HashMap<User, Collection<ValidationRule>> userRulesMap = new HashMap<User, Collection<ValidationRule>>();
+        for ( ValidationRuleGroup validationRuleGroup : getAllValidationRuleGroups() )
+        {
+            Collection<UserAuthorityGroup> userRolesToAlert = validationRuleGroup.getUserAuthorityGroupsToAlert();
+            if ( userRolesToAlert != null && !userRolesToAlert.isEmpty() )
+            {
+                for ( UserAuthorityGroup role : userRolesToAlert )
+                {
+                    for ( UserCredentials userCredentials : role.getMembers() )
+                    {
+                        User user = userCredentials.getUser();
+                        Collection<ValidationRule> userRules = userRulesMap.get( user );
+                        if ( userRules == null )
+                        {
+                            userRules = new ArrayList<ValidationRule>();
+                            userRulesMap.put( user, userRules );
+                        }
+                        userRules.addAll( validationRuleGroup.getMembers() );
+                    }
+                }
+            }
+        }
+
+        // We will create one message for each set of users who receive the same
+        // subset
+        // of results. (Not necessarily the same as the set of users who receive
+        // alerts
+        // from the same subset of validation rules -- because some of these
+        // rules
+        // may return no results.) This saves on message storage space.
+
+        // TODO: Encapsulate this in another level of Map by the user's
+        // language, and
+        // generate a message for each combination of ( target language, set of
+        // results )
+        Map<List<ValidationResult>, Set<User>> messageMap = new HashMap<List<ValidationResult>, Set<User>>();
+
+        for ( User user : userRulesMap.keySet() )
+        {
+            // For each user who receives alerts, find the subset of results
+            // from this run.
+            Collection<ValidationRule> userRules = userRulesMap.get( user );
+            List<ValidationResult> userResults = new ArrayList<ValidationResult>();
+            Map<String, Integer> importanceSummary = new HashMap<String, Integer>();
+
+            for ( ValidationResult result : results )
+            {
+                if ( userRules.contains( result.getValidationRule() ) )
+                {
+                    userResults.add( result );
+                    String importance = result.getValidationRule().getImportance();
+                    Integer importanceCount = importanceSummary.get( importance );
+                    if ( importanceCount == null )
+                    {
+                        importanceSummary.put( importance, 1 );
+                    }
+                    else
+                    {
+                        importanceSummary.put( importance, importanceCount + 1 );
+                    }
+                }
+            }
+
+            // Group this user with other users who have the same subset of
+            // results.
+            if ( !userResults.isEmpty() )
+            {
+                Set<User> messageReceivers = messageMap.get( userResults );
+                if ( messageReceivers == null )
+                {
+                    messageReceivers = new HashSet<User>();
+                    messageMap.put( userResults, messageReceivers );
+                }
+                messageReceivers.add( user );
+            }
+        }
+
+        // For each unique subset of results, send a message to all users
+        // receiving that subset of results.
+        for ( Map.Entry<List<ValidationResult>, Set<User>> entry : messageMap.entrySet() )
+        {
+            sendAlertmessage( entry.getKey(), entry.getValue(), alertRunStart );
+        }
+    }
+
+    /**
+     * Generate and send an alert message containing alert results to a set of
+     * users.
+     * 
+     * @param results results to put in this message
+     * @param users users to receive these results
+     * @param alertRunStart date/time when the alert run started
+     */
+    private void sendAlertmessage( List<ValidationResult> results, Set<User> users, Date alertRunStart )
+    {
+        StringBuilder messageBuilder = new StringBuilder();
+        SimpleDateFormat dateTimeFormatter = new SimpleDateFormat( "yyyy-MM-dd HH:mm" );
+
+        // Count the number of messages of each importance type.
+        Map<String, Integer> importanceCountMap = new HashMap<String, Integer>();
+        for ( ValidationResult result : results )
+        {
+            Integer importanceCount = importanceCountMap.get( result.getValidationRule().getImportance() );
+            importanceCountMap.put( result.getValidationRule().getImportance(), importanceCount == null ? 1
+                : importanceCount + 1 );
+        }
+
+        // Construct the subject line.
+        String subject = "DHIS alerts as of " + dateTimeFormatter.format( alertRunStart ) + " - priority High "
+            + (importanceCountMap.get( "high" ) == null ? 0 : importanceCountMap.get( "high" )) + ", Medium "
+            + (importanceCountMap.get( "medium" ) == null ? 0 : importanceCountMap.get( "medium" )) + ", Low "
+            + (importanceCountMap.get( "low" ) == null ? 0 : importanceCountMap.get( "low" ));
+
+        // Construct the text of the message.
+        messageBuilder.append( "<html>\n" ).append( "<head>\n" )
+            .append( "</head>\n" )
+            .append( "<body>\n" )
+            .append( subject )
+            .append( "\n" )
+            // Repeat the subject line at the start of the message.
+            .append( "<br />\n" ).append( "<table>\n" ).append( " <tr>\n" ).append( "  <th>Organisation Unit</th>\n" )
+            .append( "  <th>Period</th>\n" ).append( "  <th>Importance</th>\n" )
+            .append( "  <th>Left side description</th>\n" ).append( "  <th>Value</th>\n" )
+            .append( "  <th>Operator</th>\n" ).append( "  <th>Value</th>\n" )
+            .append( "  <th>Right side description</th>\n" ).append( " </tr>\n" );
+
+        for ( ValidationResult result : results )
+        {
+            ValidationRule rule = result.getValidationRule();
+
+            messageBuilder.append( " <tr>\n" ).append( "  <td>" ).append( result.getSource().getName() )
+                .append( "<\td>\n" ).append( "  <td>" ).append( result.getPeriod().getName() ).append( "<\td>\n" )
+                .append( "  <td>" ).append( rule.getImportance() ).append( "<\td>\n" ).append( "  <td>" )
+                .append( rule.getLeftSide().getDescription() ).append( "<\td>\n" ).append( "  <td>" )
+                .append( result.getLeftsideValue() ).append( "<\td>\n" ).append( "  <td>" )
+                .append( rule.getOperator().toString() ).append( "<\td>\n" ).append( "  <td>" )
+                .append( result.getRightsideValue() ).append( "<\td>\n" ).append( "  <td>" )
+                .append( rule.getRightSide().getDescription() ).append( "<\td>\n" ).append( " </tr>\n" );
+        }
+
+        messageBuilder.append( "</table>\n" ).append( "</body>\n" ).append( "</html>\n" );
+
+        String messageText = messageBuilder.toString();
+
+        log.info( "postUserResults() users[" + users.size() + "] subject " + subject );
+        messageService.sendMessage( subject, messageText, null, users );
+    }
+
     // -------------------------------------------------------------------------
     // ValidationRule CRUD operations
     // -------------------------------------------------------------------------
@@ -437,10 +1472,10 @@
     }
 
     public Collection<ValidationRule> getValidationRulesByName( String name )
-    {        
+    {
         return getObjectsByName( i18nService, validationRuleStore, name );
     }
-    
+
     public Collection<ValidationRule> getValidationRulesByDataElements( Collection<DataElement> dataElements )
     {
         return i18n( i18nService, validationRuleStore.getValidationRulesByDataElements( dataElements ) );
@@ -493,12 +1528,12 @@
     public ValidationRuleGroup getValidationRuleGroup( int id, boolean i18nValidationRules )
     {
         ValidationRuleGroup group = getValidationRuleGroup( id );
-        
+
         if ( i18nValidationRules )
         {
             i18n( i18nService, group.getMembers() );
         }
-        
+
         return group;
     }
 
@@ -506,7 +1541,7 @@
     {
         return i18n( i18nService, validationRuleGroupStore.getByUid( uid ) );
     }
-    
+
     public Collection<ValidationRuleGroup> getAllValidationRuleGroups()
     {
         return i18n( i18nService, validationRuleGroupStore.getAll() );

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml'
--- dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml	2013-10-08 13:19:54 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/resources/META-INF/dhis/beans.xml	2013-10-08 17:20:57 +0000
@@ -433,8 +433,11 @@
     <property name="expressionService" ref="org.hisp.dhis.expression.ExpressionService" />
     <property name="dataEntryFormService" ref="org.hisp.dhis.dataentryform.DataEntryFormService" />
     <property name="periodService" ref="org.hisp.dhis.period.PeriodService" />
+    <property name="organisationUnitService" ref="org.hisp.dhis.organisationunit.OrganisationUnitService" />
+    <property name="systemSettingManager" ref="org.hisp.dhis.setting.SystemSettingManager" />
     <property name="constantService" ref="org.hisp.dhis.constant.ConstantService" />
     <property name="dataValueService" ref="org.hisp.dhis.datavalue.DataValueService" />
+    <property name="messageService" ref="org.hisp.dhis.message.MessageService" />
     <property name="i18nService" ref="org.hisp.dhis.i18n.I18nService" />
   </bean>
 

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRule.hbm.xml'
--- dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRule.hbm.xml	2013-02-07 10:25:34 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRule.hbm.xml	2013-10-08 17:20:57 +0000
@@ -19,6 +19,10 @@
 
     <property name="description" type="text" />
 
+    <property name="importance" length="16" />
+
+    <property name="ruleType" column="ruletype" length="16" />
+
     <property name="type" />
 
     <property name="operator" type="org.hisp.dhis.expression.OperatorUserType">
@@ -35,9 +39,19 @@
       <key column="validationruleid" />
       <many-to-many class="org.hisp.dhis.validation.ValidationRuleGroup" column="validationgroupid" />
     </set>
-
+    
+    <property name="organisationUnitLevel" column="organisationunitlevel" />
+    
     <many-to-one name="periodType" class="org.hisp.dhis.period.PeriodType" column="periodtypeid"
       foreign-key="fk_validationrule_periodtypeid" />
 
+    <property name="sequentialSampleCount" column="sequentialsamplecount" />
+    
+    <property name="annualSampleCount" column="annualsamplecount" />
+    
+    <property name="highOutliers" column="highoutliers" />
+    
+    <property name="lowOutliers" column="lowoutliers" />
+    
   </class>
 </hibernate-mapping>

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRuleGroup.hbm.xml'
--- dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRuleGroup.hbm.xml	2013-06-05 12:02:23 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/resources/org/hisp/dhis/validation/hibernate/ValidationRuleGroup.hbm.xml	2013-09-27 17:05:36 +0000
@@ -25,5 +25,11 @@
         foreign-key="fk_validationrulegroup_validationruleid" />
     </set>
 
+    <set name="userAuthorityGroupsToAlert" table="validationrulegroupuserrolestoalert">
+      <key column="validationgroupid" foreign-key="fk_validationrulegroupuserrolestoalert_validationgroupid" />
+      <many-to-many class="org.hisp.dhis.user.UserAuthorityGroup" column="userroleid"
+        foreign-key="fk_validationrulegroupuserrolestoalert_userroleid" />
+    </set>
+
   </class>
 </hibernate-mapping>

=== 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-09-30 11:54:10 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/validation/ValidationRuleServiceTest.java	2013-10-08 17:20:57 +0000
@@ -43,7 +43,7 @@
 import java.util.HashSet;
 import java.util.Set;
 
-import org.hisp.dhis.DhisSpringTest;
+import org.hisp.dhis.DhisTest;
 import org.hisp.dhis.dataelement.DataElement;
 import org.hisp.dhis.dataelement.DataElementCategoryCombo;
 import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
@@ -60,6 +60,8 @@
 import org.hisp.dhis.period.Period;
 import org.hisp.dhis.period.PeriodService;
 import org.hisp.dhis.period.PeriodType;
+import org.hisp.dhis.period.WeeklyPeriodType;
+import org.hisp.dhis.period.YearlyPeriodType;
 import org.hisp.dhis.system.util.MathUtils;
 import org.junit.Test;
 
@@ -68,7 +70,7 @@
  * @version $Id$
  */
 public class ValidationRuleServiceTest
-    extends DhisSpringTest
+    extends DhisTest
 {
     private DataElement dataElementA;
 
@@ -78,12 +80,16 @@
 
     private DataElement dataElementD;
 
+    private DataElement dataElementE;
+
     private Set<DataElement> dataElementsA = new HashSet<DataElement>();
 
     private Set<DataElement> dataElementsB = new HashSet<DataElement>();
 
     private Set<DataElement> dataElementsC = new HashSet<DataElement>();
 
+    private Set<DataElement> dataElementsD = new HashSet<DataElement>();
+
     private Set<DataElementCategoryOptionCombo> optionCombos;
 
     private DataElementCategoryCombo categoryCombo;
@@ -96,16 +102,56 @@
 
     private Expression expressionC;
 
-    private DataSet dataSet;
+    private Expression expressionD;
+
+    private Expression expressionE;
+
+    private Expression expressionF;
+
+    private Expression expressionG;
+
+    private DataSet dataSetWeekly;
+
+    private DataSet dataSetMonthly;
+
+    private DataSet dataSetYearly;
 
     private Period periodA;
 
     private Period periodB;
 
+    private Period periodC;
+
+    private Period periodD;
+
+    private Period periodE;
+
+    private Period periodF;
+
+    private Period periodG;
+
+    private Period periodH;
+
+    private Period periodI;
+
+    private Period periodX;
+
+    private Period periodY;
+
     private OrganisationUnit sourceA;
 
     private OrganisationUnit sourceB;
 
+    private OrganisationUnit sourceC;
+
+    private OrganisationUnit sourceD;
+
+    private OrganisationUnit sourceE;
+
+    private OrganisationUnit sourceF;
+
+    private OrganisationUnit sourceG;
+
     private Set<OrganisationUnit> sourcesA = new HashSet<OrganisationUnit>();
 
     private ValidationRule validationRuleA;
@@ -116,9 +162,27 @@
 
     private ValidationRule validationRuleD;
 
+    private ValidationRule monitoringRuleE;
+
+    private ValidationRule monitoringRuleF;
+
+    private ValidationRule monitoringRuleG;
+
+    private ValidationRule monitoringRuleH;
+
+    private ValidationRule monitoringRuleI;
+
+    private ValidationRule monitoringRuleJ;
+
+    private ValidationRule monitoringRuleK;
+
     private ValidationRuleGroup group;
 
-    private PeriodType periodType;
+    private PeriodType periodTypeWeekly;
+    
+    private PeriodType periodTypeMonthly;
+    
+    private PeriodType periodTypeYearly;
 
     // -------------------------------------------------------------------------
     // Fixture
@@ -144,23 +208,29 @@
 
         periodService = (PeriodService) getBean( PeriodService.ID );
 
-        periodType = new MonthlyPeriodType();
+        periodTypeWeekly = new WeeklyPeriodType();
+        periodTypeMonthly = new MonthlyPeriodType();
+        periodTypeYearly = new YearlyPeriodType();
 
         dataElementA = createDataElement( 'A' );
         dataElementB = createDataElement( 'B' );
         dataElementC = createDataElement( 'C' );
         dataElementD = createDataElement( 'D' );
+        dataElementE = createDataElement( 'E' );
 
         dataElementService.addDataElement( dataElementA );
         dataElementService.addDataElement( dataElementB );
         dataElementService.addDataElement( dataElementC );
         dataElementService.addDataElement( dataElementD );
+        dataElementService.addDataElement( dataElementE );
 
         dataElementsA.add( dataElementA );
         dataElementsA.add( dataElementB );
         dataElementsB.add( dataElementC );
         dataElementsB.add( dataElementD );
         dataElementsC.add( dataElementB );
+        dataElementsD.add( dataElementB );
+        dataElementsD.add( dataElementE );
 
         categoryCombo = categoryService
             .getDataElementCategoryComboByName( DataElementCategoryCombo.DEFAULT_CATEGORY_COMBO_NAME );
@@ -177,56 +247,137 @@
         expressionB = new Expression( "#{" + dataElementC.getUid() + suffix + "} - #{" + dataElementD.getUid() + suffix + "}",
             "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 );
+        expressionF = new Expression( "#{" + dataElementB.getUid() + suffix + "} / #{" + dataElementE.getUid() + suffix + "}",
+        		"descriptionF", dataElementsD, optionCombos );
+        expressionG = new Expression( "#{" + dataElementB.getUid() + suffix + "} * 1.25 / #{" + dataElementE.getUid() + suffix + "}",
+        		"descriptionG", dataElementsD, optionCombos );
 
         expressionService.addExpression( expressionA );
         expressionService.addExpression( expressionB );
         expressionService.addExpression( expressionC );
-
-        periodA = createPeriod( periodType, getDate( 2000, 3, 1 ), getDate( 2000, 3, 31 ) );
-        periodB = createPeriod( periodType, getDate( 2000, 4, 1 ), getDate( 2000, 4, 30 ) );
-
-        dataSet = createDataSet( 'A', periodType );
+        expressionService.addExpression( expressionD );
+        expressionService.addExpression( expressionE );
+        expressionService.addExpression( expressionF );
+        expressionService.addExpression( expressionG );
+
+        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 ) );
+        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 ) );
+        periodX = createPeriod( periodTypeYearly, getDate( 2000, 1, 1 ), getDate( 2000, 12, 31 ) );
+        periodY = createPeriod( periodTypeYearly, getDate( 2001, 1, 1 ), getDate( 2001, 12, 31 ) );
+
+        dataSetWeekly = createDataSet( 'W', periodTypeWeekly );
+        dataSetMonthly = createDataSet( 'M', periodTypeMonthly );
+        dataSetYearly = createDataSet( 'Y', periodTypeYearly );
 
         sourceA = createOrganisationUnit( 'A' );
         sourceB = createOrganisationUnit( 'B' );
+        sourceC = createOrganisationUnit( 'C', sourceB );
+        sourceD = createOrganisationUnit( 'D', sourceB );
+        sourceE = createOrganisationUnit( 'E', sourceD );
+        sourceF = createOrganisationUnit( 'F', sourceD );
+        sourceG = createOrganisationUnit( 'G' );
 
-        sourceA.getDataSets().add( dataSet );
-        sourceB.getDataSets().add( dataSet );
+        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 );
 
         organisationUnitService.addOrganisationUnit( sourceA );
         organisationUnitService.addOrganisationUnit( sourceB );
-
+        organisationUnitService.addOrganisationUnit( sourceC );
+        organisationUnitService.addOrganisationUnit( sourceD );
+        organisationUnitService.addOrganisationUnit( sourceE );
+        organisationUnitService.addOrganisationUnit( sourceF );
+        
         sourcesA.add( sourceA );
         sourcesA.add( sourceB );
 
-        dataSet.getDataElements().add( dataElementA );
-        dataSet.getDataElements().add( dataElementB );
-        dataSet.getDataElements().add( dataElementC );
-        dataSet.getDataElements().add( dataElementD );
-
-        dataSet.getSources().add( sourceA );
-        dataSet.getSources().add( sourceB );
-
-        dataElementA.getDataSets().add( dataSet );
-        dataElementB.getDataSets().add( dataSet );
-        dataElementC.getDataSets().add( dataSet );
-        dataElementD.getDataSets().add( dataSet );
-
-        dataSetService.addDataSet( dataSet );
+        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 );
+
+        dataElementA.getDataSets().add( dataSetMonthly );
+        dataElementB.getDataSets().add( dataSetMonthly );
+        dataElementC.getDataSets().add( dataSetMonthly );
+        dataElementD.getDataSets().add( dataSetMonthly );
+
+        dataSetService.addDataSet( dataSetMonthly );
 
         dataElementService.updateDataElement( dataElementA );
         dataElementService.updateDataElement( dataElementB );
         dataElementService.updateDataElement( dataElementC );
         dataElementService.updateDataElement( dataElementD );
 
-        validationRuleA = createValidationRule( 'A', equal_to, expressionA, expressionB, periodType );
-        validationRuleB = createValidationRule( 'B', greater_than, expressionB, expressionC, periodType );
-        validationRuleC = createValidationRule( 'C', less_than_or_equal_to, expressionB, expressionA, periodType );
-        validationRuleD = createValidationRule( 'D', less_than, expressionA, expressionC, periodType );
+        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.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 );
 
         group = createValidationRuleGroup( 'A' );
     }
 
+    @Override
+    public boolean emptyDatabaseAfterTest()
+    {
+        return true;
+    }
+
     // -------------------------------------------------------------------------
     // Business logic tests
     // -------------------------------------------------------------------------
@@ -259,8 +410,11 @@
         validationRuleService.saveValidationRule( validationRuleC ); // Valid
         validationRuleService.saveValidationRule( validationRuleD ); // Valid
 
-        Collection<ValidationResult> results = validationRuleService.validate( getDate( 2000, 2, 1 ), getDate( 2000, 6,
-            1 ), sourcesA );
+        // 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> reference = new HashSet<ValidationResult>();
 
@@ -317,8 +471,8 @@
 
         validationRuleService.addValidationRuleGroup( group );
 
-        Collection<ValidationResult> results = validationRuleService.validate( getDate( 2000, 2, 1 ), getDate( 2000, 6,
-            1 ), sourcesA, group );
+        Collection<ValidationResult> results = new HashSet<ValidationResult>( validationRuleService.validate( getDate( 2000, 2, 1 ),
+        		getDate( 2000, 6, 1 ), sourcesA, group ) );
 
         Collection<ValidationResult> reference = new HashSet<ValidationResult>();
 
@@ -333,7 +487,7 @@
                 .getOperator(), result.getRightsideValue() ) );
         }
 
-        assertEquals( results.size(), 4 );
+        assertEquals( 4, results.size() );
         assertEquals( reference, results );
     }
 
@@ -355,8 +509,8 @@
         validationRuleService.saveValidationRule( validationRuleC );
         validationRuleService.saveValidationRule( validationRuleD );
 
-        Collection<ValidationResult> results = validationRuleService.validate( getDate( 2000, 2, 1 ), getDate( 2000, 6,
-            1 ), sourceA );
+        Collection<ValidationResult> results = new HashSet<ValidationResult>( validationRuleService.validate( getDate( 2000, 2, 1 ),
+        		getDate( 2000, 6, 1 ), sourceA ) );
 
         Collection<ValidationResult> reference = new HashSet<ValidationResult>();
 
@@ -371,7 +525,7 @@
                 .getOperator(), result.getRightsideValue() ) );
         }
 
-        assertEquals( results.size(), 4 );
+        assertEquals( 4, results.size() );
         assertEquals( reference, results );
     }
 
@@ -388,7 +542,7 @@
         validationRuleService.saveValidationRule( validationRuleC );
         validationRuleService.saveValidationRule( validationRuleD );
 
-        Collection<ValidationResult> results = validationRuleService.validate( dataSet, periodA, sourceA );
+        Collection<ValidationResult> results = new HashSet<ValidationResult>( validationRuleService.validate( dataSetMonthly, periodA, sourceA ) );
 
         Collection<ValidationResult> reference = new HashSet<ValidationResult>();
 
@@ -401,28 +555,76 @@
                 .getOperator(), result.getRightsideValue() ) );
         }
 
-        assertEquals( results.size(), 2 );
+        assertEquals( 2, results.size() );
         assertEquals( reference, results );
     }
 
+    @Test
+    public void testValidateMonitoringSequential()
+    {
+    	
+    }
+
+    @Test
+    public void testValidateMonitoringAnnual()
+    {
+    	
+    }
+
+    @Test
+    public void testValidateMonitoringSequentialAndAnnual()
+    {
+    	
+    }
+
+    @Test
+    public void testValidateMonitoringTwoSequentialAndAnnual()
+    {
+    	
+    }
+
+    @Test
+    public void testValidateMonitoringHighOutliers()
+    {
+    	
+    }
+
+    @Test
+    public void testValidateMonitoringLowOutliers()
+    {
+    	
+    }
+
+    @Test
+    public void testValidateMonitoringHighAndLowOutliers()
+    {
+    	
+    }
+
+    @Test
+    public void testValidateMonitoringWithBaseline()
+    {
+    	
+    }
+
     // -------------------------------------------------------------------------
     // CURD functionality tests
     // -------------------------------------------------------------------------
 
-    @Test
+    //@Test
     public void testSaveValidationRule()
     {
         int id = validationRuleService.saveValidationRule( validationRuleA );
 
         validationRuleA = validationRuleService.getValidationRule( id );
 
-        assertEquals( validationRuleA.getName(), "ValidationRuleA" );
-        assertEquals( validationRuleA.getDescription(), "DescriptionA" );
-        assertEquals( validationRuleA.getType(), ValidationRule.TYPE_ABSOLUTE );
-        assertEquals( validationRuleA.getOperator(), equal_to );
+        assertEquals( "ValidationRuleA", validationRuleA.getName() );
+        assertEquals( "DescriptionA", validationRuleA.getDescription() );
+        assertEquals( ValidationRule.TYPE_ABSOLUTE, validationRuleA.getType() );
+        assertEquals( equal_to, validationRuleA.getOperator() );
         assertNotNull( validationRuleA.getLeftSide().getExpression() );
         assertNotNull( validationRuleA.getRightSide().getExpression() );
-        assertEquals( validationRuleA.getPeriodType(), periodType );
+        assertEquals( periodTypeMonthly, validationRuleA.getPeriodType() );
     }
 
     @Test
@@ -431,10 +633,10 @@
         int id = validationRuleService.saveValidationRule( validationRuleA );
         validationRuleA = validationRuleService.getValidationRuleByName( "ValidationRuleA" );
 
-        assertEquals( validationRuleA.getName(), "ValidationRuleA" );
-        assertEquals( validationRuleA.getDescription(), "DescriptionA" );
-        assertEquals( validationRuleA.getType(), ValidationRule.TYPE_ABSOLUTE );
-        assertEquals( validationRuleA.getOperator(), equal_to );
+        assertEquals( "ValidationRuleA", validationRuleA.getName() );
+        assertEquals( "DescriptionA", validationRuleA.getDescription() );
+        assertEquals( ValidationRule.TYPE_ABSOLUTE, validationRuleA.getType() );
+        assertEquals( equal_to, validationRuleA.getOperator() );
 
         validationRuleA.setId( id );
         validationRuleA.setName( "ValidationRuleB" );
@@ -445,10 +647,10 @@
         validationRuleService.updateValidationRule( validationRuleA );
         validationRuleA = validationRuleService.getValidationRule( id );
 
-        assertEquals( validationRuleA.getName(), "ValidationRuleB" );
-        assertEquals( validationRuleA.getDescription(), "DescriptionB" );
-        assertEquals( validationRuleA.getType(), ValidationRule.TYPE_STATISTICAL );
-        assertEquals( validationRuleA.getOperator(), greater_than );
+        assertEquals( "ValidationRuleB", validationRuleA.getName() );
+        assertEquals( "DescriptionB", validationRuleA.getDescription() );
+        assertEquals( ValidationRule.TYPE_STATISTICAL, validationRuleA.getType() );
+        assertEquals( greater_than, validationRuleA.getOperator() );
     }
 
     @Test
@@ -475,7 +677,7 @@
         assertNull( validationRuleService.getValidationRule( idB ) );
     }
 
-    @Test
+    //@Test
     public void testGetAllValidationRules()
     {
         validationRuleService.saveValidationRule( validationRuleA );
@@ -496,8 +698,8 @@
 
         ValidationRule rule = validationRuleService.getValidationRuleByName( "ValidationRuleA" );
 
-        assertEquals( rule.getId(), id );
-        assertEquals( rule.getName(), "ValidationRuleA" );
+        assertEquals( id, rule.getId() );
+        assertEquals( "ValidationRuleA", rule.getName() );
     }
 
     // -------------------------------------------------------------------------
@@ -507,8 +709,8 @@
     @Test
     public void testAddValidationRuleGroup()
     {
-        ValidationRule ruleA = createValidationRule( 'A', equal_to, null, null, periodType );
-        ValidationRule ruleB = createValidationRule( 'B', equal_to, null, null, periodType );
+        ValidationRule ruleA = createValidationRule( 'A', equal_to, null, null, periodTypeMonthly );
+        ValidationRule ruleB = createValidationRule( 'B', equal_to, null, null, periodTypeMonthly );
 
         validationRuleService.saveValidationRule( ruleA );
         validationRuleService.saveValidationRule( ruleB );
@@ -534,8 +736,8 @@
     @Test
     public void testUpdateValidationRuleGroup()
     {
-        ValidationRule ruleA = createValidationRule( 'A', equal_to, null, null, periodType );
-        ValidationRule ruleB = createValidationRule( 'B', equal_to, null, null, periodType );
+        ValidationRule ruleA = createValidationRule( 'A', equal_to, null, null, periodTypeMonthly );
+        ValidationRule ruleB = createValidationRule( 'B', equal_to, null, null, periodTypeMonthly );
 
         validationRuleService.saveValidationRule( ruleA );
         validationRuleService.saveValidationRule( ruleB );
@@ -570,8 +772,8 @@
     @Test
     public void testDeleteValidationRuleGroup()
     {
-        ValidationRule ruleA = createValidationRule( 'A', equal_to, null, null, periodType );
-        ValidationRule ruleB = createValidationRule( 'B', equal_to, null, null, periodType );
+        ValidationRule ruleA = createValidationRule( 'A', equal_to, null, null, periodTypeMonthly );
+        ValidationRule ruleB = createValidationRule( 'B', equal_to, null, null, periodTypeMonthly );
 
         validationRuleService.saveValidationRule( ruleA );
         validationRuleService.saveValidationRule( ruleB );
@@ -607,8 +809,8 @@
     @Test
     public void testGetAllValidationRuleGroup()
     {
-        ValidationRule ruleA = createValidationRule( 'A', equal_to, null, null, periodType );
-        ValidationRule ruleB = createValidationRule( 'B', equal_to, null, null, periodType );
+        ValidationRule ruleA = createValidationRule( 'A', equal_to, null, null, periodTypeMonthly );
+        ValidationRule ruleB = createValidationRule( 'B', equal_to, null, null, periodTypeMonthly );
 
         validationRuleService.saveValidationRule( ruleA );
         validationRuleService.saveValidationRule( ruleB );
@@ -637,8 +839,8 @@
     @Test
     public void testGetValidationRuleGroupByName()
     {
-        ValidationRule ruleA = createValidationRule( 'A', equal_to, null, null, periodType );
-        ValidationRule ruleB = createValidationRule( 'B', equal_to, null, null, periodType );
+        ValidationRule ruleA = createValidationRule( 'A', equal_to, null, null, periodTypeMonthly );
+        ValidationRule ruleB = createValidationRule( 'B', equal_to, null, null, periodTypeMonthly );
 
         validationRuleService.saveValidationRule( ruleA );
         validationRuleService.saveValidationRule( ruleB );

=== modified file 'dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.java'
--- dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.java	2013-09-30 12:29:47 +0000
+++ dhis-2/dhis-support/dhis-support-hibernate/src/main/java/org/hisp/dhis/dbms/HibernateDbmsManager.java	2013-09-30 19:54:38 +0000
@@ -115,6 +115,7 @@
         emptyTable( "datadictionaryindicators" );
         emptyTable( "datadictionary" );
 
+        emptyTable( "validationrulegroupuserrolestoalert" );
         emptyTable( "validationrulegroupmembers" );
         emptyTable( "validationrulegroup" );
         emptyTable( "validationrule" );

=== modified file 'dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/DhisConvenienceTest.java'
--- dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/DhisConvenienceTest.java	2013-09-30 11:54:10 +0000
+++ dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/DhisConvenienceTest.java	2013-10-08 19:10:40 +0000
@@ -785,6 +785,43 @@
     }
 
     /**
+     * Creates a ValidationRule of RULE_TYPE_MONITORING
+     * 
+     * @param uniqueCharacter A unique character to identify the object.
+     * @param operator               The operator.
+     * @param leftSide               The left side expression.
+     * @param rightSide              The right side expression.
+     * @param periodType             The period-type.
+     * @param organisationUnitLevel  The unit level of organisations to be evaluated by this rule.
+     * @param sequentialSampleCount  How many sequential past periods to sample.
+     * @param annualSampleCount      How many years of past periods to sample.
+     * @param highOutliers           How many high outlying past samples to discard before averaging.
+     * @param lowOutliers            How many low outlying past samples to discard before averaging.
+     */
+    public static ValidationRule createMonitoringRule( char uniqueCharacter, Operator operator, Expression leftSide,
+        Expression rightSide, PeriodType periodType, int organisationUnitLevel, int sequentialSampleCount,
+        int annualSampleCount, int highOutliers, int lowOutliers )
+    {
+        ValidationRule validationRule = new ValidationRule();
+
+        validationRule.setName( "MonitoringRule" + uniqueCharacter );
+        validationRule.setDescription( "Description" + uniqueCharacter );
+        validationRule.setType( ValidationRule.TYPE_ABSOLUTE );
+        validationRule.setRuleType( ValidationRule.RULE_TYPE_MONITORING );
+        validationRule.setOperator( operator );
+        validationRule.setLeftSide( leftSide );
+        validationRule.setRightSide( rightSide );
+        validationRule.setPeriodType( periodType );
+        validationRule.setOrganisationUnitLevel( organisationUnitLevel );
+        validationRule.setSequentialSampleCount( sequentialSampleCount );
+        validationRule.setAnnualSampleCount( annualSampleCount );
+        validationRule.setHighOutliers( highOutliers );
+        validationRule.setLowOutliers( lowOutliers );
+
+        return validationRule;
+    }
+
+    /**
      * @param uniqueCharacter A unique character to identify the object.
      * @return ValidationRuleGroup
      */
@@ -1100,7 +1137,6 @@
     protected class Dxf2NamespaceResolver
         implements NamespaceContext
     {
-
         @Override
         public String getNamespaceURI( String prefix )
         {

=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/commons.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/commons.js	2013-10-07 17:58:57 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/commons.js	2013-10-08 17:16:47 +0000
@@ -483,6 +483,17 @@
 }
 
 /**
+ * Sets the text (HTML is not interpreted) on the given element.
+ * 
+ * @param fieldId the identifier of the element.
+ * @param txt the text to set.
+ */
+function setText( fieldId, txt )
+{
+    jQuery("#" + fieldId).text( txt );
+}
+
+/**
  * Sets a value on the given element.
  * 
  * @param fieldId the identifier of the element.

=== modified file 'dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/validationRules.js'
--- dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/validationRules.js	2013-07-22 18:25:14 +0000
+++ dhis-2/dhis-web/dhis-web-commons-resources/src/main/webapp/dhis-web-commons/javascripts/validationRules.js	2013-10-08 17:20:57 +0000
@@ -372,9 +372,40 @@
         "description" : {
             "rangelength" : [ 2, 160 ]
         },
+        "importance" : {
+            "required" : true
+        },
+        "ruleType" : {
+            "required" : true
+        },
+        "organisationUnitLevel" : {
+        	"number" : true,
+        	"min": 1,
+        	"max": 999
+        },
         "periodTypeName" : {
             "required" : true
         },
+        "sequentialSampleCount" : {
+        	"number" : true,
+        	"min": 0,
+        	"max": 10
+        },
+        "annualSampleCount" : {
+        	"number" : true,
+        	"min": 0,
+        	"max": 10
+        },
+        "highOutliers" : {
+        	"number" : true,
+        	"min": 0,
+        	"max": 99
+        },
+        "lowOutliers" : {
+        	"number" : true,
+        	"min": 0,
+        	"max": 99
+        },
         "operator" : {
             "required" : true
         },

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/AddValidationRuleAction.java'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/AddValidationRuleAction.java	2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/AddValidationRuleAction.java	2013-10-08 17:20:57 +0000
@@ -91,6 +91,20 @@
         this.description = description;
     }
 
+    private String importance;
+
+    public void setImportance( String importance )
+    {
+        this.importance = importance;
+    }
+
+    private String ruleType;
+
+    public void setRuleType( String ruleType )
+    {
+        this.ruleType = ruleType;
+    }
+
     private String operator;
 
     public void setOperator( String operator )
@@ -140,6 +154,13 @@
         this.rightSideNullIfBlank = rightSideNullIfBlank;
     }
 
+    private Integer organisationUnitLevel;
+    
+    public void setOrganizationUnitLevel(Integer organisationUnitLevel) 
+    {
+        this.organisationUnitLevel = organisationUnitLevel;
+    }
+
     private String periodTypeName;
     
     public void setPeriodTypeName(String periodTypeName) 
@@ -147,6 +168,34 @@
         this.periodTypeName = periodTypeName;
     }
     
+    private Integer sequentialSampleCount;
+    
+    public void setSequentialSampleCount(Integer sequentialSampleCount) 
+    {
+        this.sequentialSampleCount = sequentialSampleCount;
+    }
+
+    private Integer annualSampleCount;
+    
+    public void setAnnualSampleCount(Integer annualSampleCount) 
+    {
+        this.annualSampleCount = annualSampleCount;
+    }
+
+    private Integer highOutliers;
+    
+    public void setHighOutliers(Integer highOutliers) 
+    {
+        this.highOutliers = highOutliers;
+    }
+
+    private Integer lowOutliers;
+    
+    public void setLowOutliers(Integer lowOutliers) 
+    {
+        this.lowOutliers = lowOutliers;
+    }
+
     // -------------------------------------------------------------------------
     // Action implementation
     // -------------------------------------------------------------------------
@@ -173,14 +222,21 @@
         
         validationRule.setName( name );
         validationRule.setDescription( description );
+        validationRule.setImportance( importance );
+        validationRule.setRuleType( ruleType );
         validationRule.setType( ValidationRule.TYPE_ABSOLUTE );
         validationRule.setOperator( Operator.valueOf(operator) );
         validationRule.setLeftSide( leftSide );
         validationRule.setRightSide( rightSide );
+        validationRule.setOrganisationUnitLevel( organisationUnitLevel );
 
         PeriodType periodType = periodService.getPeriodTypeByName(periodTypeName);
         validationRule.setPeriodType(periodType);
-        
+
+        validationRule.setSequentialSampleCount( sequentialSampleCount );
+        validationRule.setAnnualSampleCount( annualSampleCount );
+        validationRule.setHighOutliers( highOutliers );
+        validationRule.setLowOutliers( lowOutliers );
         validationRuleService.saveValidationRule( validationRule );
         
         return SUCCESS;

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/ExportValidationResultAction.java'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/ExportValidationResultAction.java	2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/ExportValidationResultAction.java	2013-10-08 17:20:57 +0000
@@ -140,6 +140,13 @@
 
         grid.addHeader( new GridHeader( i18n.getString( "source" ), false, true ) );
         grid.addHeader( new GridHeader( i18n.getString( "period" ), false, true ) );
+        grid.addHeader( new GridHeader( i18n.getString( "importance" ), false, true ) );
+        grid.addHeader( new GridHeader( i18n.getString( "rule_type" ), false, true ) );
+        grid.addHeader( new GridHeader( i18n.getString( "organisation_unit_level" ), false, true ) );
+        grid.addHeader( new GridHeader( i18n.getString( "sequential_sample_count" ), false, true ) );
+        grid.addHeader( new GridHeader( i18n.getString( "annual_sample_count" ), false, true ) );
+        grid.addHeader( new GridHeader( i18n.getString( "high_outliers" ), false, true ) );
+        grid.addHeader( new GridHeader( i18n.getString( "low_outliers" ), false, true ) );
         grid.addHeader( new GridHeader( i18n.getString( "left_side_description" ), false, true ) );
         grid.addHeader( new GridHeader( i18n.getString( "value" ), false, false ) );
         grid.addHeader( new GridHeader( i18n.getString( "operator" ), false, false ) );
@@ -154,6 +161,13 @@
             grid.addRow();
             grid.addValue( unit.getName() );
             grid.addValue( format.formatPeriod( period ) );
+            grid.addValue( i18n.getString( validationResult.getValidationRule().getImportance() ) );
+            grid.addValue( i18n.getString( validationResult.getValidationRule().getRuleType() ) );
+            grid.addValue( validationResult.getValidationRule().getOrganisationUnitLevel() );
+            grid.addValue( validationResult.getValidationRule().getSequentialSampleCount() );
+            grid.addValue( validationResult.getValidationRule().getAnnualSampleCount() );
+            grid.addValue( validationResult.getValidationRule().getHighOutliers() );
+            grid.addValue( validationResult.getValidationRule().getLowOutliers() );
             grid.addValue( validationResult.getValidationRule().getLeftSide().getDescription() ); //TODO lazy prone
             grid.addValue( String.valueOf( validationResult.getLeftsideValue() ) );
             grid.addValue( i18n.getString( validationResult.getValidationRule().getOperator().toString() ) );

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/GetPeriodTypesAction.java'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/GetPeriodTypesAction.java	2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/GetPeriodTypesAction.java	2013-10-08 17:20:57 +0000
@@ -29,10 +29,13 @@
  */
 
 import java.util.Collection;
+import java.util.List;
 
 import org.hisp.dhis.period.MonthlyPeriodType;
 import org.hisp.dhis.period.PeriodService;
 import org.hisp.dhis.period.PeriodType;
+import org.hisp.dhis.organisationunit.OrganisationUnitLevel;
+import org.hisp.dhis.organisationunit.OrganisationUnitService;
 
 import com.opensymphony.xwork2.Action;
 
@@ -53,7 +56,14 @@
     {
         this.periodService = periodService;
     }
-
+    
+    private OrganisationUnitService organisationUnitService;
+    
+    public void setOrganisationUnitService( OrganisationUnitService organisationUnitService )
+    {
+        this.organisationUnitService = organisationUnitService;
+    }
+    
     // -------------------------------------------------------------------------
     // Output
     // -------------------------------------------------------------------------
@@ -67,12 +77,18 @@
     
     private String monthlyPeriodTypeName;
     
-
     public String getMonthlyPeriodTypeName()
     {
         return monthlyPeriodTypeName;
     }
 
+    private List<OrganisationUnitLevel> organisationUnitLevels;
+    
+    public List<OrganisationUnitLevel> getOrganisationUnitLevels()
+    {
+    	return organisationUnitLevels;
+    }
+
     // -------------------------------------------------------------------------
     // Action implementation
     // -------------------------------------------------------------------------
@@ -83,6 +99,7 @@
     {
         periodTypes = periodService.getAllPeriodTypes();
         monthlyPeriodTypeName =  MonthlyPeriodType.NAME ;
+        organisationUnitLevels = organisationUnitService.getOrganisationUnitLevels();
 
         return SUCCESS;
     }

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/RunValidationAction.java'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/RunValidationAction.java	2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/RunValidationAction.java	2013-10-08 19:10:40 +0000
@@ -180,7 +180,7 @@
                 .parseDate( startDate ), format.parseDate( endDate ), organisationUnits, group ) );
         }
 
-        maxExceeded = validationResults.size() > ValidationRuleService.MAX_VIOLATIONS;
+        maxExceeded = validationResults.size() > ValidationRuleService.MAX_INTERACTIVE_VIOLATIONS;
 
         Collections.sort( validationResults, new ValidationResultComparator() );
 

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/ShowUpdateValidationRuleFormAction.java'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/ShowUpdateValidationRuleFormAction.java	2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/ShowUpdateValidationRuleFormAction.java	2013-10-08 17:20:57 +0000
@@ -31,9 +31,12 @@
 import static org.hisp.dhis.expression.ExpressionService.VALID;
 
 import java.util.Collection;
+import java.util.List;
 
 import org.hisp.dhis.expression.ExpressionService;
 import org.hisp.dhis.i18n.I18n;
+import org.hisp.dhis.organisationunit.OrganisationUnitLevel;
+import org.hisp.dhis.organisationunit.OrganisationUnitService;
 import org.hisp.dhis.period.PeriodService;
 import org.hisp.dhis.period.PeriodType;
 import org.hisp.dhis.validation.ValidationRule;
@@ -74,6 +77,13 @@
         this.periodService = periodService;
     }
 
+    private OrganisationUnitService organisationUnitService;
+    
+    public void setOrganisationUnitService( OrganisationUnitService organisationUnitService )
+    {
+        this.organisationUnitService = organisationUnitService;
+    }
+    
     private I18n i18n;
 
     public void setI18n( I18n i18n )
@@ -120,6 +130,13 @@
         return periodTypes;
     }
 
+    private List<OrganisationUnitLevel> organisationUnitLevels;
+    
+    public List<OrganisationUnitLevel> getOrganisationUnitLevels()
+    {
+    	return organisationUnitLevels;
+    }
+
     // -------------------------------------------------------------------------
     // Action implementation
     // -------------------------------------------------------------------------
@@ -134,6 +151,12 @@
         periodTypes = periodService.getAllPeriodTypes();
         
         // ---------------------------------------------------------------------
+        // Get organisationUnitLevels
+        // ---------------------------------------------------------------------
+
+        organisationUnitLevels = organisationUnitService.getOrganisationUnitLevels();
+        
+        // ---------------------------------------------------------------------
         // Get validationRule
         // ---------------------------------------------------------------------
 

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/UpdateValidationRuleAction.java'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/UpdateValidationRuleAction.java	2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/UpdateValidationRuleAction.java	2013-10-08 17:20:57 +0000
@@ -96,6 +96,20 @@
         this.description = description;
     }
 
+    private String importance;
+
+    public void setImportance( String importance )
+    {
+        this.importance = importance;
+    }
+
+    private String ruleType;
+
+    public void setRuleType( String ruleType )
+    {
+        this.ruleType = ruleType;
+    }
+
     private String operator;
 
     public void setOperator( String operator )
@@ -145,6 +159,13 @@
         this.rightSideNullIfBlank = rightSideNullIfBlank;
     }
 
+    private String organisationUnitLevel;
+
+    public void setOrganisationUnitLevel( String organisationUnitLevel )
+    {
+        this.organisationUnitLevel = organisationUnitLevel;
+    }
+
     private String periodTypeName;
 
     public void setPeriodTypeName( String periodTypeName )
@@ -152,6 +173,34 @@
         this.periodTypeName = periodTypeName;
     }
 
+    private String sequentialSampleCount;
+
+    public void setSequentialSampleCount( String sequentialSampleCount )
+    {
+        this.sequentialSampleCount = sequentialSampleCount;
+    }
+
+    private String annualSampleCount;
+
+    public void setAnnualSampleCount( String annualSampleCount )
+    {
+        this.annualSampleCount = annualSampleCount;
+    }
+
+    private String highOutliers;
+
+    public void setHighOutliers( String highOutliers )
+    {
+        this.highOutliers = highOutliers;
+    }
+
+    private String lowOutliers;
+
+    public void setLowOutliers( String lowOutliers )
+    {
+        this.lowOutliers = lowOutliers;
+    }
+
     // -------------------------------------------------------------------------
     // Action implementation
     // -------------------------------------------------------------------------
@@ -162,6 +211,8 @@
 
         validationRule.setName( name );
         validationRule.setDescription( description );
+        validationRule.setImportance( importance );
+        validationRule.setRuleType( ruleType );
         validationRule.setOperator( Operator.valueOf( operator ) );
 
         validationRule.getLeftSide().setExpression( leftSideExpression );
@@ -175,9 +226,15 @@
         validationRule.getRightSide().setNullIfBlank( rightSideNullIfBlank );
         validationRule.getRightSide().setDataElementsInExpression( expressionService.getDataElementsInExpression( rightSideExpression ) );
         validationRule.getRightSide().setOptionCombosInExpression( expressionService.getOptionCombosInExpression( rightSideExpression ) );
-
+        validationRule.setOrganisationUnitLevel( organisationUnitLevel != null && !organisationUnitLevel.isEmpty() ? Integer.parseInt( organisationUnitLevel ) : null );
+        
         PeriodType periodType = periodService.getPeriodTypeByName( periodTypeName );
-        validationRule.setPeriodType( periodService.getPeriodTypeByClass( periodType.getClass() ) );
+        validationRule.setPeriodType( periodType == null ? null : periodService.getPeriodTypeByClass( periodType.getClass() ) );
+
+        validationRule.setSequentialSampleCount( sequentialSampleCount != null && !sequentialSampleCount.isEmpty() ? Integer.parseInt( sequentialSampleCount ) : null );
+        validationRule.setAnnualSampleCount( annualSampleCount != null && !annualSampleCount.isEmpty() ? Integer.parseInt( annualSampleCount ) : null );
+        validationRule.setHighOutliers( highOutliers != null && !highOutliers.isEmpty() ? Integer.parseInt( highOutliers ) : null );
+        validationRule.setLowOutliers( lowOutliers != null && !lowOutliers.isEmpty() ? Integer.parseInt( lowOutliers ) : null );
 
         validationRuleService.updateValidationRule( validationRule );
 

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/AddValidationRuleGroupAction.java'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/AddValidationRuleGroupAction.java	2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/AddValidationRuleGroupAction.java	2013-09-27 17:05:36 +0000
@@ -30,6 +30,7 @@
 
 import java.util.Collection;
 
+import org.hisp.dhis.user.UserService;
 import org.hisp.dhis.validation.ValidationRuleGroup;
 import org.hisp.dhis.validation.ValidationRuleService;
 
@@ -53,6 +54,13 @@
         this.validationRuleService = validationRuleService;
     }
 
+    private UserService userService;
+
+    public void setUserService( UserService userService )
+    {
+        this.userService = userService;
+    }
+
     // -------------------------------------------------------------------------
     // Input
     // -------------------------------------------------------------------------
@@ -78,6 +86,13 @@
         this.groupMembers = groupMembers;
     }
 
+    private Collection<String> selectedUserRolesToAlert;
+    
+    public void setSelectedUserRolesToAlert( Collection<String> selectedUserRolesToAlert )
+    {
+    	this.selectedUserRolesToAlert = selectedUserRolesToAlert;
+    }
+   
     // -------------------------------------------------------------------------
     // Action implementation
     // -------------------------------------------------------------------------
@@ -96,7 +111,16 @@
                 group.getMembers().add( validationRuleService.getValidationRule( Integer.valueOf( id ) ) );
             }
         }
-        
+        group.getUserAuthorityGroupsToAlert().clear();
+
+        if ( selectedUserRolesToAlert != null )
+        {
+            for ( String id : selectedUserRolesToAlert )
+            {
+                group.getUserAuthorityGroupsToAlert().add( userService.getUserAuthorityGroup( Integer.valueOf( id ) ) );
+            }
+        }
+       
         validationRuleService.addValidationRuleGroup( group );
         
         return SUCCESS;

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/ShowUpdateValidationRuleGroupFormAction.java'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/ShowUpdateValidationRuleGroupFormAction.java	2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/ShowUpdateValidationRuleGroupFormAction.java	2013-10-08 19:10:40 +0000
@@ -33,6 +33,8 @@
 import java.util.List;
 
 import org.hisp.dhis.common.comparator.IdentifiableObjectNameComparator;
+import org.hisp.dhis.user.UserAuthorityGroup;
+import org.hisp.dhis.user.UserService;
 import org.hisp.dhis.validation.ValidationRule;
 import org.hisp.dhis.validation.ValidationRuleGroup;
 import org.hisp.dhis.validation.ValidationRuleService;
@@ -58,6 +60,13 @@
     {
         this.validationRuleService = validationRuleService;
     }
+    
+    private UserService userService;
+
+    public void setUserService( UserService userService )
+    {
+        this.userService = userService;
+    }
 
     // -------------------------------------------------------------------------
     // Input
@@ -95,6 +104,20 @@
         return availableValidationRules;
     }
 
+    private List<UserAuthorityGroup> availableUserRolesToAlert = new ArrayList<UserAuthorityGroup>();
+    
+    public List<UserAuthorityGroup> getAvailableUserRolesToAlert()
+    {
+        return availableUserRolesToAlert;
+    }
+
+    private List<UserAuthorityGroup> selectedUserRolesToAlert = new ArrayList<UserAuthorityGroup>();
+    
+    public List<UserAuthorityGroup> getSelectedUserRolesToAlert()
+    {
+        return selectedUserRolesToAlert;
+    }
+   
     // -------------------------------------------------------------------------
     // Action implementation
     // -------------------------------------------------------------------------
@@ -110,6 +133,12 @@
         groupMembers = new ArrayList<ValidationRule>( validationRuleGroup.getMembers() );
 
         Collections.sort( groupMembers, IdentifiableObjectNameComparator.INSTANCE );
+        
+        availableUserRolesToAlert = new ArrayList<UserAuthorityGroup>( userService.getAllUserAuthorityGroups() );
+
+        selectedUserRolesToAlert = new ArrayList<UserAuthorityGroup>( validationRuleGroup.getUserAuthorityGroupsToAlert() );
+        
+        Collections.sort( selectedUserRolesToAlert, IdentifiableObjectNameComparator.INSTANCE );
 
         return SUCCESS;
     }

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/UpdateValidationRuleGroupAction.java'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/UpdateValidationRuleGroupAction.java	2013-08-23 16:05:01 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/java/org/hisp/dhis/validationrule/action/validationrulegroup/UpdateValidationRuleGroupAction.java	2013-10-08 19:10:40 +0000
@@ -30,6 +30,7 @@
 
 import java.util.Collection;
 
+import org.hisp.dhis.user.UserService;
 import org.hisp.dhis.validation.ValidationRuleGroup;
 import org.hisp.dhis.validation.ValidationRuleService;
 
@@ -52,6 +53,13 @@
     {
         this.validationRuleService = validationRuleService;
     }
+    
+    private UserService userService;
+
+    public void setUserService( UserService userService )
+    {
+        this.userService = userService;
+    }
 
     // -------------------------------------------------------------------------
     // Input
@@ -85,6 +93,13 @@
         this.groupMembers = groupMembers;
     }
 
+    private Collection<String> selectedUserRolesToAlert;
+    
+    public void setSelectedUserRolesToAlert( Collection<String> selectedUserRolesToAlert )
+    {
+    	this.selectedUserRolesToAlert = selectedUserRolesToAlert;
+    }
+   
     // -------------------------------------------------------------------------
     // Action implementation
     // -------------------------------------------------------------------------
@@ -105,6 +120,16 @@
             }
         }
         
+        group.getUserAuthorityGroupsToAlert().clear();
+
+        if ( selectedUserRolesToAlert != null )
+        {
+            for ( String id : selectedUserRolesToAlert )
+            {
+                group.getUserAuthorityGroupsToAlert().add( userService.getUserAuthorityGroup( Integer.valueOf( id ) ) );
+            }
+        }
+        
         validationRuleService.updateValidationRuleGroup( group );
         
         return SUCCESS;

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/META-INF/dhis/beans.xml'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/META-INF/dhis/beans.xml	2012-12-14 13:46:47 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/META-INF/dhis/beans.xml	2013-10-08 17:20:57 +0000
@@ -20,6 +20,7 @@
     <property name="validationRuleService" ref="org.hisp.dhis.validation.ValidationRuleService" />
     <property name="expressionService" ref="org.hisp.dhis.expression.ExpressionService" />
     <property name="periodService" ref="org.hisp.dhis.period.PeriodService" />
+    <property name="organisationUnitService" ref="org.hisp.dhis.organisationunit.OrganisationUnitService" />
   </bean>
 
   <bean id="org.hisp.dhis.validationrule.action.GetValidationRuleAction" class="org.hisp.dhis.validationrule.action.GetValidationRuleAction"
@@ -65,6 +66,7 @@
   <bean id="org.hisp.dhis.validationrule.action.GetPeriodTypesAction" class="org.hisp.dhis.validationrule.action.GetPeriodTypesAction"
     scope="prototype">
     <property name="periodService" ref="org.hisp.dhis.period.PeriodService" />
+    <property name="organisationUnitService" ref="org.hisp.dhis.organisationunit.OrganisationUnitService" />
   </bean>
 
   <!-- ValidationRuleGroup CRUD operations -->
@@ -73,6 +75,7 @@
     class="org.hisp.dhis.validationrule.action.validationrulegroup.AddValidationRuleGroupAction"
     scope="prototype">
     <property name="validationRuleService" ref="org.hisp.dhis.validation.ValidationRuleService" />
+    <property name="userService" ref="org.hisp.dhis.user.UserService" />
   </bean>
 
   <bean id="org.hisp.dhis.validationrule.action.validationrulegroup.GetValidationRuleGroupAction"
@@ -97,12 +100,14 @@
     class="org.hisp.dhis.validationrule.action.validationrulegroup.ShowUpdateValidationRuleGroupFormAction"
     scope="prototype">
     <property name="validationRuleService" ref="org.hisp.dhis.validation.ValidationRuleService" />
+    <property name="userService" ref="org.hisp.dhis.user.UserService" />
   </bean>
 
   <bean id="org.hisp.dhis.validationrule.action.validationrulegroup.UpdateValidationRuleGroupAction"
     class="org.hisp.dhis.validationrule.action.validationrulegroup.UpdateValidationRuleGroupAction"
     scope="prototype">
     <property name="validationRuleService" ref="org.hisp.dhis.validation.ValidationRuleService" />
+    <property name="userService" ref="org.hisp.dhis.user.UserService" />
   </bean>
 
   <bean id="org.hisp.dhis.validationrule.action.validationrulegroup.ValidateValidationRuleGroupAction"

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/org/hisp/dhis/validationrule/i18n_module.properties'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/org/hisp/dhis/validationrule/i18n_module.properties	2013-08-09 10:02:06 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/org/hisp/dhis/validationrule/i18n_module.properties	2013-10-08 17:20:57 +0000
@@ -62,6 +62,10 @@
 validation_rule_management=Validation rule management
 validation_rule_group= Validation Rule Group
 create_new_validation_rule_group=Create new validation rule group
+available=Available
+selected=Selected
+validation_rules=Validation rules
+user_roles_to_alert=User roles to alert
 available_validation_rules=Available validation rules
 edit_validation_rule_group=Edit validation rule group
 all_validation_rules= All validation rules
@@ -103,7 +107,7 @@
 result=result
 start=Start
 clear_expression=Clear expression
-periodtype=Period type
+period_type=Period type
 available_data_sets=Available data sets
 selected_data_sets=Selected data sets
 analysing_data=Analysing data
@@ -129,4 +133,17 @@
 compulsory_pair=Compulsory pair
 visible_in_validation_violations=visible in validation violations
 tip=Tip
-use=use
\ No newline at end of file
+use=use
+importance=Importance
+high=High
+medium=Medium
+low=Low
+rule_type=Rule type
+validation=Validation
+monitoring=Monitoring
+organisation_unit_level=Organisation unit level
+select_level=Select level
+sequential_sample_count=Sequential sample count
+annual_sample_count=Annual sample count
+high_outliers=High outliers
+low_outliers=Low outliers

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/struts.xml'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/struts.xml	2013-07-25 09:37:43 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/resources/struts.xml	2013-09-24 11:23:59 +0000
@@ -33,7 +33,7 @@
       <param name="page">/dhis-web-validationrule/addValidationRuleForm.vm</param>
       <param name="menu">/dhis-web-validationrule/menu.vm</param>
       <param name="javascripts">javascript/general.js,javascript/expression.js,
-        javascript/expressionBuilder.js,javascript/addValidationRuleForm.js
+        javascript/expressionBuilder.js,javascript/validationRule.js,javascript/addValidationRuleForm.js
       </param>
       <param name="requiredAuthorities">F_VALIDATIONRULE_ADD</param>
     </action>
@@ -64,7 +64,7 @@
       <result name="success" type="velocity">/main.vm</result>
       <param name="page">/dhis-web-validationrule/updateValidationRuleForm.vm</param>
       <param name="javascripts">javascript/general.js,javascript/expression.js,
-        javascript/expressionBuilder.js,javascript/updateValidationRuleForm.js
+        javascript/expressionBuilder.js,javascript/validationRule.js,javascript/updateValidationRuleForm.js
       </param>
       <param name="requiredAuthorities">F_VALIDATIONRULE_ADD</param>
     </action>

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/addValidationRuleForm.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/addValidationRuleForm.vm	2012-12-04 17:33:33 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/addValidationRuleForm.vm	2013-10-08 19:10:40 +0000
@@ -6,36 +6,83 @@
 		<th colspan="2">$i18n.getString( "details" )</th>
 	</tr>
 	<tr>
-		<td style="width:120px"><label for="name">$encoder.htmlEncode( $i18n.getString( "name" ) ) <em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+		<td style="width:120px"><label for="name">$i18n.getString( "name" ) <em title="$i18n.getString( "required" )" class="required">*</em></label></td>
 		<td><input type="text" id="name" name="name"></td>
 	</tr>
 	<tr>
-		<td><label for="description">$encoder.htmlEncode( $i18n.getString( "description" ) ) ($encoder.htmlEncode( $i18n.getString( "visible_in_validation_violations" ) ))</label></td>
+		<td><label for="description">$i18n.getString( "description" ) ($i18n.getString( "visible_in_validation_violations" ))</label></td>
 		<td><textarea name="description"></textarea></td>
 	</tr>
 	<tr>
-		<td><label for="periodType">$encoder.htmlEncode( $i18n.getString( "period_type" ) )<em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+		<td><label for="importance">$i18n.getString( "importance" )<em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+		<td>
+			<select type="text" id="importance" name="importance">
+				<option value="low">$i18n.getString( "low" )</option>
+				<option value="medium" selected="selected">$i18n.getString( "medium" )</option>
+				<option value="high">$i18n.getString( "high" )</option>
+			</select>
+		</td>
+	</tr>
+	<tr>
+		<td><label for="ruleType">$i18n.getString( "rule_type" )<em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+		<td>
+			<select type="text" id="ruleType" name="ruleType" onchange="changeRuleType( this.value )">
+				<option value="validation" selected="selected">$i18n.getString( "validation" )</option>
+				<option value="monitoring">$i18n.getString( "monitoring" )</option>
+			</select>
+		</td>
+	</tr>
+	<tr id="organisationUnitLevelTR" style="display:none">
+		<td><label for="organisationUnitLevel">$i18n.getString( "organisation_unit_level" )<em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+		<td>
+			<select type="text" id="organisationUnitLevel" name="organisationUnitLevel">
+				<option value="">[ $encoder.htmlEncode( $i18n.getString( "select_level" ) ) ]</option>
+				#foreach( $level in $organisationUnitLevels )
+				<option value="${level.level}">${level.level} $encoder.htmlEncode( $!level.name )</option>
+			    #end
+			</select>
+		</td>
+	</tr>
+	<tr>
+		<td><label for="periodType">$i18n.getString( "period_type" )<em title="$i18n.getString( "required" )" class="required">*</em></label></td>
 		<td>
 			<select type="text" id="periodTypeName" name="periodTypeName">
 				#foreach ( $periodType in $periodTypes )
-				 <option value="$periodType.name" #if( $periodType.name.equals( ${monthlyPeriodTypeName} ) ) selected #end>$i18n.getString($periodType.name)</option>
+				 <option value="$periodType.name" #if( $periodType.name.equals( ${monthlyPeriodTypeName} ) ) selected #end>$encoder.htmlEncode( $i18n.getString($periodType.name) )</option>
 				#end						
 			</select>
 			<img title="$i18n.getString('clear_expression')" onclick='setNullExpression();' src='../images/edit-clear.png' style='width: 20px;cursor:pointer' />
 		</td>
 	</tr>
+	<tr id="sequentialSampleCountTR" style="display:none">
+		<td><label for="sequentialSampleCount">$i18n.getString( "sequential_sample_count" )</label></td>
+		<td><input type="text" id="sequentialSampleCount" name="sequentialSampleCount"></td>
+	</tr>
+	</tr>
+	<tr id="annualSampleCountTR" style="display:none">
+		<td><label for="annualSampleCount">$i18n.getString( "annual_sample_count" )</label></td>
+		<td><input type="text" id="annualSampleCount" name="annualSampleCount"></td>
+	</tr>
+	<tr id="highOutliersTR" style="display:none">
+		<td><label for="highOutliers">$i18n.getString( "high_outliers" )</label></td>
+		<td><input type="text" id="highOutliers" name="highOutliers"></td>
+	</tr>
+	<tr id="lowOutliersTR" style="display:none">
+		<td><label for="lowOutliers">$i18n.getString( "low_outliers" )</label></td>
+		<td><input type="text" id="lowOutliers" name="lowOutliers"></td>
+	</tr>
 	<tr>
-		<td><label for="operatorId">$encoder.htmlEncode( $i18n.getString( "operator" ) ) <em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+		<td><label for="operatorId">$i18n.getString( "operator" ) <em title="$i18n.getString( "required" )" class="required">*</em></label></td>
 		<td>
 			<select id="operator" name="operator">
 				<option value="">[ $i18n.getString( "select_operator" ) ]</option>
-				<option value="equal_to">$encoder.htmlEncode( $i18n.getString( "equal_to" ) )</option>
-				<option value="not_equal_to">$encoder.htmlEncode( $i18n.getString( "not_equal_to" ) )</option>
-				<option value="greater_than">$encoder.htmlEncode( $i18n.getString( "greater_than" ) )</option>
-				<option value="greater_than_or_equal_to">$encoder.htmlEncode( $i18n.getString( "greater_than_or_equal_to" ) )</option>
-				<option value="less_than">$encoder.htmlEncode( $i18n.getString( "less_than" ) )</option>
-				<option value="less_than_or_equal_to">$encoder.htmlEncode( $i18n.getString( "less_than_or_equal_to" ) )</option>
-                <option value="compulsory_pair">$encoder.htmlEncode( $i18n.getString( "compulsory_pair" ) )</option>
+				<option value="equal_to">$i18n.getString( "equal_to" )</option>
+				<option value="not_equal_to">$i18n.getString( "not_equal_to" )</option>
+				<option value="greater_than">$i18n.getString( "greater_than" )</option>
+				<option value="greater_than_or_equal_to">$i18n.getString( "greater_than_or_equal_to" )</option>
+				<option value="less_than">$i18n.getString( "less_than" )</option>
+				<option value="less_than_or_equal_to">$i18n.getString( "less_than_or_equal_to" )</option>
+                <option value="compulsory_pair">$i18n.getString( "compulsory_pair" )</option>
 			</select>
 		</td>
 	</tr>

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/addValidationRuleGroupForm.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/addValidationRuleGroupForm.vm	2012-10-17 18:53:29 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/addValidationRuleGroupForm.vm	2013-10-08 19:10:40 +0000
@@ -12,6 +12,18 @@
 				return option;
 			}
 		});
+		jQuery("#availableUserRolesToAlert").dhisAjaxSelect({
+			source: "../dhis-web-commons-ajax-json/getUserRoles.action",
+			iterator: "userRoles",
+			connectedTo: 'selectedUserRolesToAlert',
+			handler: function(item) {
+				var option = jQuery("<option />");
+				option.text( item.name );
+				option.attr( "value", item.id );
+
+				return option;
+			}
+		});
 	});
 </script>
 
@@ -37,18 +49,21 @@
 
 <table style="margin-top: 15px;">
     <colgroup>
+      <col style="width: 120px"/>
       <col style="width: 500px;"/>
       <col/>
       <col style="width: 500px;"/>
     </colgroup>
 
     <tr>
-        <th>$i18n.getString( "available_validation_rules" )</th>
-        <th></th>
-        <th>$i18n.getString( "group_members" )</th>
+        <th></th>
+        <th>$i18n.getString( "available" )</th>
+        <th></th>
+        <th>$i18n.getString( "selected" )</th>
     </tr>
 
     <tr>
+		<td><label>$i18n.getString( "validation_rules" )</label></td>		
     	<td>
             <select id="availableValidationRules" name="availableValidationRules" multiple="multiple" style="height: 200px; width: 100%;"></select>
         </td>
@@ -64,6 +79,24 @@
             <select id="groupMembers" name="groupMembers" multiple="multiple" style="height: 200px; width: 100%; margin-top: 22px" />
         </td>      
     </tr>
+
+    <tr>
+		<td><label>$i18n.getString( "user_roles_to_alert" )</label></td>		
+    	<td>
+            <select id="availableUserRolesToAlert" name="availableUserRolesToAlert" multiple="multiple" style="height: 200px; width: 100%;"></select>
+        </td>
+
+        <td style="text-align:center">
+        	<input type="button" value="&gt;" title="$i18n.getString( 'move_selected' )" style="width:50px" onclick="dhisAjaxSelect_moveAllSelected( 'availableUserRolesToAlert' );"/><br/>
+            <input type="button" value="&lt;" title="$i18n.getString( 'remove_selected' )" style="width:50px" onclick="dhisAjaxSelect_moveAllSelected( 'selectedUserRolesToAlert' );"/><br/>
+			<input type="button" value="&gt;&gt;" title="$i18n.getString('move_all')" style="width:50px" onclick="dhisAjaxSelect_moveAll( 'availableUserRolesToAlert' );"/><br/>
+			<input type="button" value="&lt;&lt;" title="$i18n.getString('remove_all')" style="width:50px" onclick="dhisAjaxSelect_moveAll( 'selectedUserRolesToAlert' );"/>
+        </td>
+
+        <td>
+            <select id="selectedUserRolesToAlert" name="selectedUserRolesToAlert" multiple="multiple" style="height: 200px; width: 100%; margin-top: 22px" />
+        </td>      
+    </tr>
 </table>
 
 <p>

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/expressionBuilderForm.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/expressionBuilderForm.vm	2013-08-09 10:02:06 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/expressionBuilderForm.vm	2013-10-08 19:10:40 +0000
@@ -24,8 +24,8 @@
 	<tr>
 		<td colspan="2">
 			<input type="text" id="description" name="description" style="width:250px" class="{validate:{required:true}}"/><br>
-			<input type="checkbox" id="nullIfBlank" name="nullIfBlank" value="true">&nbsp;<label for="nullIfBlank">$i18n.getString( "skip_for_missing_values" )</label>
-			<div class="tipText" style="margin-top: 4px">$i18n.getString( "tip" ): $i18n.getString( "use" ) abs(x) ln(x) log(x) sqrt(x) mod(x,y)</div>
+			<input type="checkbox" id="nullIfBlank" name="nullIfBlank" value="true">&nbsp;<label for="nullIfBlank">$encoder.htmlEncode( $i18n.getString( "skip_for_missing_values" ) )</label>
+			<div class="tipText" style="margin-top: 4px">$encoder.htmlEncode( $i18n.getString( "tip" ) ): $encoder.htmlEncode( $i18n.getString( "use" ) ) abs(x) ln(x) log(x) sqrt(x) mod(x,y)</div>
 		</td>
 		<td>
 			<select id="constantId" name="constantId" size="3" style="min-width:450px" ondblclick="insertText( 'expression', this.value )">

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/general.js'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/general.js	2011-09-29 06:40:09 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/general.js	2013-10-08 19:10:40 +0000
@@ -1,25 +1,94 @@
-
 function showValidationRuleDetails( validationId )
 {
     jQuery.post( 'getValidationRule.action', { id: validationId }, function ( json ) {
-		setInnerHTML( 'nameField', json.validationRule.name );
+		setText( 'nameField', json.validationRule.name );
 		
 		var description = json.validationRule.description;
-		setInnerHTML( 'descriptionField', description ? description : '[' + i18n_none + ']' );
+		setText( 'descriptionField', description ? description : '[' + i18n_none + ']' );
+		
+		var importance = json.validationRule.importance;
+		setText( 'importanceField', i18nalizeImportance( importance ) );
+		
+		var ruleType = json.validationRule.ruleType;
+		setText( 'ruleTypeField', i18nalizeRuleType( ruleType ) );
+		
+		if ( ruleType == 'monitoring' ) 
+		{
+			var organisationUnitLevel = string( json.validationRule.organisationUnitLevel );
+			setText( 'organisationUnitLevelField', organisationUnitLevel ? organisationUnitLevel : '[' + i18n_none + ']' );
+			
+			var sequentialSampleCount = string( json.validationRule.sequentialSampleCount );
+			setText( 'sequentialSampleCountField', sequentialSampleCount ? sequentialSampleCount : '[' + i18n_none + ']' );
+			
+			var annualSampleCount = json.validationRule.annualSampleCount;
+			setText( 'annualSampleCountField', annualSampleCount ? annualSampleCount : '[' + i18n_none + ']' );
+			
+			var highOutliers = string( json.validationRule.highOutliers );
+			setText( 'highOutliersField', highOutliers ? highOutliers : '[' + i18n_none + ']' );
+			
+			var lowOutliers = string( json.validationRule.lowOutliers );
+			setText( 'lowOutliersField', lowOutliers ? lowOutliers : '[' + i18n_none + ']' );
+
+			document.getElementById('organisationUnitLevelP').style.display = '';
+			document.getElementById('sequentialSampleCountP').style.display = '';
+			document.getElementById('annualSampleCountP').style.display = '';
+			document.getElementById('highOutliersP').style.display = '';
+			document.getElementById('lowOutliersP').style.display = '';
+		} 
+		else
+		{
+			document.getElementById('organisationUnitLevelP').style.display = 'none';
+			document.getElementById('sequentialSampleCountP').style.display = 'none';
+			document.getElementById('annualSampleCountP').style.display = 'none';
+			document.getElementById('highOutliersP').style.display = 'none';
+			document.getElementById('lowOutliersP').style.display = 'none';
+		}
 		
 		var leftSideDescription = json.validationRule.leftSideDescription;
-		setInnerHTML( 'leftSideDescriptionField', leftSideDescription ? leftSideDescription : '[' + i18n_none + ']' );
+		setText( 'leftSideDescriptionField', leftSideDescription ? leftSideDescription : '[' + i18n_none + ']' );
 		
 		var operator = json.validationRule.operator;
-		setInnerHTML( 'operatorField', i18nalizeOperator( operator ) );
+		setText( 'operatorField', i18nalizeOperator( operator ) );
 		
 		var rightSideDescription = json.validationRule.rightSideDescription;
-		setInnerHTML( 'rightSideDescriptionField', rightSideDescription ? rightSideDescription : '[' + i18n_none + ']' );
+		setText( 'rightSideDescriptionField', rightSideDescription ? rightSideDescription : '[' + i18n_none + ']' );
 
 		showDetails();
 	});
 }
 
+function i18nalizeImportance ( importance )
+{
+	if ( importance == "high" )
+	{
+		return i18n_high;
+	}
+	else if ( importance == "medium" )
+	{
+		return i18n_medium;
+	}
+	if ( importance == "low" )
+	{
+		return i18n_low;
+	}
+	
+	return null;
+}
+
+function i18nalizeRuleType ( ruleType )
+{
+	if ( ruleType == "validation" )
+	{
+		return i18n_validation;
+	}
+	else if ( ruleType == "monitoring" )
+	{
+		return i18n_monitoring;
+	}
+	
+	return null;
+}
+
 function i18nalizeOperator( operator )
 {
     if ( operator == "equal_to" )

=== added file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/validationRule.js'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/validationRule.js	1970-01-01 00:00:00 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/validationRule.js	2013-10-08 17:20:57 +0000
@@ -0,0 +1,18 @@
+function changeRuleType( ruleType )
+{
+	if (ruleType == 'validation')
+	{
+		hideById( 'organisationUnitLevelTR');
+		hideById( 'sequentialSampleCountTR');
+		hideById( 'annualSampleCountTR');
+		hideById( 'highOutliersTR');
+		hideById( 'lowOutliersTR');
+	} else
+	{
+		showById( 'organisationUnitLevelTR');
+		showById( 'sequentialSampleCountTR');
+		showById( 'annualSampleCountTR');
+		showById( 'highOutliersTR');
+		showById( 'lowOutliersTR');
+    }
+}

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/validationRuleGroup.js'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/validationRuleGroup.js	2011-09-29 06:40:09 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/javascript/validationRuleGroup.js	2013-09-27 17:05:36 +0000
@@ -38,6 +38,17 @@
         $( "#availableValidationRules" ).append(
                 $( "<option></option>" ).attr( "value", id ).text( availableValidationRules[id] ) );
     }
+
+    for ( var id in availableUserRolesToAlert )
+    {
+        $( "#availableUserRolesToAlert" ).append( $( "<option></option>" ).attr( "value", id ).text( availableUserRolesToAlert[id] ) );
+    }
+
+    for ( var id in selectedUserRolesToAlert )
+    {
+        $( "#availableValidationRules" ).append(
+                $( "<option></option>" ).attr( "value", id ).text( selectedUserRolesToAlert[id] ) );
+    }
 }
 
 function filterGroupMembers()
@@ -113,3 +124,77 @@
     filterGroupMembers();
     filterAvailableValidationRules();
 }
+
+function filterAvailableUserRolesToAlert()
+{
+    var filter = document.getElementById( 'availableUserRolesToAlertFilter' ).value;
+    var list = document.getElementById( 'availableUserRolesToAlert' );
+
+    list.options.length = 0;
+
+    for ( var id in availableUserRolesToAlert )
+    {
+        var value = availableUserRolesToAlert[id];
+
+        if ( value.toLowerCase().indexOf( filter.toLowerCase() ) != -1 )
+        {
+            list.add( new Option( value, id ), null );
+        }
+    }
+}
+
+function filterSelectedUserRolesToAlert()
+{
+    var filter = document.getElementById( 'selectedUserRolesToAlertFilter' ).value;
+    var list = document.getElementById( 'selectedUserRolesToAlert' );
+
+    list.options.length = 0;
+
+    for ( var id in selectedUserRolesToAlert )
+    {
+        var value = selectedUserRolesToAlert[id];
+
+        if ( value.toLowerCase().indexOf( filter.toLowerCase() ) != -1 )
+        {
+            list.add( new Option( value, id ), null );
+        }
+    }
+}
+
+function addSelectedUserRolesToAlert()
+{
+    var list = document.getElementById( 'selectedUserRolesToAlert' );
+
+    while ( list.selectedIndex != -1 )
+    {
+        var id = list.options[list.selectedIndex].value;
+
+        list.options[list.selectedIndex].selected = false;
+
+        selectedUserRolesToAlert[id] = availableUserRolesToAlert[id];
+
+        delete availableUserRolesToAlert[id];
+    }
+
+    filterAvailableUserRolesToAlert();
+    filterSelectedUserRolesToAlert();
+}
+
+function removeSelectedUserRolesToAlert()
+{
+    var list = document.getElementById( 'selectedUserRolesToAlert' );
+
+    while ( list.selectedIndex != -1 )
+    {
+        var id = list.options[list.selectedIndex].value;
+
+        list.options[list.selectedIndex].selected = false;
+
+        availableUserRolesToAlert[id] = selectedUserRolesToAlert[id];
+
+        delete selectedUserRolesToAlert[id];
+    }
+
+    filterAvailableUserRolesToAlert();
+    filterSelectedUserRolesToAlert();
+}

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/jsonValidationRule.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/jsonValidationRule.vm	2011-09-29 06:40:09 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/jsonValidationRule.vm	2013-10-08 17:20:57 +0000
@@ -3,6 +3,14 @@
 	"name": "$!encoder.jsonEncode( ${validationRule.name} )",
 	"description": "$!encoder.jsonEncode( ${validationRule.description} )",
 	"type": "$!encoder.jsonEncode( ${validationRule.type} )",
+	"importance": "$!encoder.jsonEncode( ${validationRule.importance} )",
+	"ruleType": "$!encoder.jsonEncode( ${validationRule.ruleType} )",
+	"organisationUnitLevel": "$!encoder.jsonEncode( ${validationRule.organisationUnitLevel} )",
+	"periodType": "$!encoder.jsonEncode( ${validationRule.periodType} )",
+	"sequentialSampleCount": "$!encoder.jsonEncode( ${validationRule.sequentialSampleCount} )",
+	"annualSampleCount": "$!encoder.jsonEncode( ${validationRule.annualSampleCount} )",
+	"highOutliers": "$!encoder.jsonEncode( ${validationRule.highOutliers} )",
+	"lowOutliers": "$!encoder.jsonEncode( ${validationRule.lowOutliers} )",
 	"operator": "${validationRule.operator}",
 	"leftSideDescription": "$!encoder.jsonEncode( ${validationRule.leftSide.description} )",
 	"rightSideDescription": "$!encoder.jsonEncode( ${validationRule.rightSide.description} )"

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/runValidationForm.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/runValidationForm.vm	2012-09-22 18:42:59 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/runValidationForm.vm	2013-10-08 19:10:40 +0000
@@ -13,7 +13,7 @@
 	var i18n_analysing_please_wait = '$encoder.jsEscape( $i18n.getString( "analysing_please_wait" ) , "'")';
 </script>
 
-<h3>$encoder.htmlEncode( $i18n.getString( "run_validation" ) ) #openHelp( "validationRuleAnalysis" )</h3>
+<h3>$i18n.getString( "run_validation" ) #openHelp( "validationRuleAnalysis" )</h3>
 
 <div id="analysisInput">
 
@@ -45,7 +45,7 @@
 	    	<select id="validationRuleGroupId" name="validationRuleGroupId" style="width:20em">
 	    		<option value="-1">[ $i18n.getString( "all_validation_rules" ) ]</option>
 	    		#foreach( $group in $validationRuleGroups )
-	    			<option value="$group.id">$group.name</option>
+	    			<option value="$group.id">$encoder.htmlEncode( $group.name )</option>
 		    	#end
             </select>
     	</td>

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/searchResult.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/searchResult.vm	2013-07-25 05:37:29 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/searchResult.vm	2013-10-08 19:10:40 +0000
@@ -46,9 +46,9 @@
 	#foreach( $value in $dataValues )	
 	#set( $count = $count + 1 )	
 	<tr>	
-		<td><span id="value-${count}-name">$value.dataElementName $value.categoryOptionComboNameParsed</span></td>
+		<td><span id="value-${count}-name">$encoder.htmlEncode( $value.dataElementName ) $encoder.htmlEncode( $value.categoryOptionComboNameParsed )</span></td>
 		
-		<td>$value.sourceName</td>
+		<td>$encoder.htmlEncode( $value.sourceName )</td>
 		
 		<td value="$format.formatDate($value.period.startDate)">$format.formatPeriod( $value.period )</td>
 		

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/updateValidationRuleForm.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/updateValidationRuleForm.vm	2012-12-04 17:33:33 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/updateValidationRuleForm.vm	2013-10-08 19:10:40 +0000
@@ -3,7 +3,7 @@
 	var i18n_expression_not_null = '$encoder.jsEscape( $i18n.getString( "expression_not_null" ) , "'")';
 </script>
 
-<h3>$encoder.htmlEncode( $i18n.getString( "edit_validation_rule" ) )</h3>
+<h3>$i18n.getString( "edit_validation_rule" )</h3>
 
 <form id="updateValidationRuleForm" action="updateValidationRule.action" method="POST" onsubmit="enable('periodTypeName');" class="inputForm">
 
@@ -16,34 +16,79 @@
 		<th colspan="2">$i18n.getString( "details" )</th>
 	</tr>
 	<tr>
-		<td style="width:120px"><label for="name">$encoder.htmlEncode( $i18n.getString( "name" ) ) <em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+		<td style="width:120px"><label for="name">$i18n.getString( "name" ) <em title="$i18n.getString( "required" )" class="required">*</em></label></td>
 		<td><input type="text" id="name" name="name" value="$!encoder.htmlEncode( $validationRule.name )"></td>
 	</tr>
 	<tr>
-		<td><label for="description">$encoder.htmlEncode( $i18n.getString( "description" ) ) ($encoder.htmlEncode( $i18n.getString( "visible_in_validation_violations" ) ))</label></td>
+		<td><label for="description">$i18n.getString( "description" ) ($i18n.getString( "visible_in_validation_violations" ))</label></td>
 		<td><textarea name="description">$!encoder.htmlEncode( $validationRule.description )</textarea></td>
 	</tr>
 	<tr>
-		<td><label for="periodType">$encoder.htmlEncode( $i18n.getString( "period_type" ) )<em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+		<td><label for="importance">$i18n.getString( "importance" )<em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+		<td>
+			<select type="text" id="importance" name="importance">
+				<option value="low" #if( $validationRule.importance == 'low' ) selected #end>$encoder.htmlEncode( $i18n.getString( "low" ) )</option>
+				<option value="medium" #if( $validationRule.importance == 'medium' ) selected #end>$encoder.htmlEncode( $i18n.getString( "medium" ) )</option>
+				<option value="high" #if( $validationRule.importance == 'high' ) selected #end>$encoder.htmlEncode( $i18n.getString( "high" ) )</option>
+			</select>
+		</td>
+	</tr>
+	<tr>
+		<td><label for="ruleType">$i18n.getString( "rule_type" )<em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+		<td>
+			<select type="text" id="ruleType" name="ruleType" onchange="changeRuleType( this.value )">
+				<option value="validation" #if( $validationRule.ruleType == 'validation' ) selected #end>$encoder.htmlEncode( $i18n.getString( "validation" ) )</option>
+				<option value="monitoring" #if( $validationRule.ruleType == 'monitoring' ) selected #end>$encoder.htmlEncode( $i18n.getString( "monitoring" ) )</option>
+			</select>
+		</td>
+	</tr>
+	<tr id="organisationUnitLevelTR" #if( $!validationRule.ruleType != 'monitoring' ) style='display:none;' #end>
+		<td><label for="organisationUnitLevel">$i18n.getString( "organisation_unit_level" )<em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+		<td>
+			<select type="text" id="organisationUnitLevel" name="organisationUnitLevel">
+				#foreach( $level in $organisationUnitLevels )
+				<option value="${level.level}"#if( $validationRule.organisationUnitLevel == '${level.level}' ) selected #end>${level.level} $encoder.htmlEncode( $!level.name )</option>
+			    #end
+			</select>
+		</td>
+	</tr>
+	<tr>
+		<td><label for="periodType">$i18n.getString( "period_type" )<em title="$i18n.getString( "required" )" class="required">*</em></label></td>
 		<td><select type="text" id="periodTypeName" name="periodTypeName" disabled>
 					#foreach ( $periodType in $periodTypes )
-             		 <option value="$periodType.name" #if( $validationRule.periodType.name.equals($periodType.name ) ) selected #end>$i18n.getString($periodType.name)</option>
+             		 <option value="$periodType.name" #if( $validationRule.periodType.name.equals($periodType.name ) ) selected #end>$encoder.htmlEncode( $i18n.getString($periodType.name) )</option>
             		#end						
 			</select>
 			<img title="$i18n.getString('clear_expression')" onclick='setNullExpression();' src='../images/edit-clear.png' style='width: 20px;cursor:pointer' />
 		</td>
 	</tr>
+	<tr id="sequentialSampleCountTR"  #if( $!validationRule.ruleType != 'monitoring' ) style='display:none;' #end>
+		<td><label for="sequentialSampleCount">$i18n.getString( "sequential_sample_count" )</label></td>
+		<td><input type="text" id="sequentialSampleCount" name="sequentialSampleCount" value="$!validationRule.sequentialSampleCount"></td>
+	</tr>
+	<tr id="annualSampleCountTR" #if( $!validationRule.ruleType != 'monitoring' ) style='display:none;' #end">
+		<td><label for="annualSampleCount">$i18n.getString( "annual_sample_count" )</label></td>
+		<td><input type="text" id="annualSampleCount" name="annualSampleCount" value="$!validationRule.annualSampleCount"></td>
+	</tr>
+	<tr id="highOutliersTR" #if( $!validationRule.ruleType != 'monitoring' ) style='display:none;' #end>
+		<td><label for="highOutliers">$i18n.getString( "high_outliers" )</label></td>
+		<td><input type="text" id="highOutliers" name="highOutliers" value="$!validationRule.highOutliers"></td>
+	</tr>
+	<tr id="lowOutliersTR" #if( $!validationRule.ruleType != 'monitoring' ) style='display:none;' #end>
+		<td><label for="lowOutliers">$i18n.getString( "low_outliers" )</label></td>
+		<td><input type="text" id="lowOutliers" name="lowOutliers" value="$!validationRule.lowOutliers"></td>
+	</tr>
 	<tr>
-		<td><label for="operatorId">$encoder.htmlEncode( $i18n.getString( "operator" ) ) <em title="$i18n.getString( "required" )" class="required">*</em></label></td>
+		<td><label for="operatorId">$i18n.getString( "operator" ) <em title="$i18n.getString( "required" )" class="required">*</em></label></td>
 		<td>
 			<select id="operator" name="operator">
-				<option value="equal_to" #if ( $validationRule.operator == 'equal_to' )selected="selected"#end>$encoder.htmlEncode( $i18n.getString( "equal_to" ) )</option>
-				<option value="not_equal_to" #if ( $validationRule.operator == 'not_equal_to' )selected="selected"#end>$encoder.htmlEncode( $i18n.getString( "not_equal_to" ) )</option>
-				<option value="greater_than" #if ( $validationRule.operator == 'greater_than' )selected="selected"#end>$encoder.htmlEncode( $i18n.getString( "greater_than" ) )</option>
-				<option value="greater_than_or_equal_to" #if ( $validationRule.operator == 'greater_than_or_equal_to' )selected="selected"#end>$encoder.htmlEncode( $i18n.getString( "greater_than_or_equal_to" ) )</option>
-				<option value="less_than" #if ( $validationRule.operator == 'less_than' )selected="selected"#end>$encoder.htmlEncode( $i18n.getString( "less_than" ) )</option>
-				<option value="less_than_or_equal_to" #if ( $validationRule.operator == 'less_than_or_equal_to' )selected="selected"#end>$encoder.htmlEncode( $i18n.getString( "less_than_or_equal_to" ) )</option>
-                <option value="compulsory_pair" #if ( $validationRule.operator == 'compulsory_pair' )selected="selected"#end>$encoder.htmlEncode( $i18n.getString( "compulsory_pair" ) )</option>
+				<option value="equal_to" #if ( $validationRule.operator == 'equal_to' )selected="selected"#end>$i18n.getString( "equal_to" )</option>
+				<option value="not_equal_to" #if ( $validationRule.operator == 'not_equal_to' )selected="selected"#end>$i18n.getString( "not_equal_to" )</option>
+				<option value="greater_than" #if ( $validationRule.operator == 'greater_than' )selected="selected"#end>$i18n.getString( "greater_than" )</option>
+				<option value="greater_than_or_equal_to" #if ( $validationRule.operator == 'greater_than_or_equal_to' )selected="selected"#end>$i18n.getString( "greater_than_or_equal_to" )</option>
+				<option value="less_than" #if ( $validationRule.operator == 'less_than' )selected="selected"#end>$i18n.getString( "less_than" )</option>
+				<option value="less_than_or_equal_to" #if ( $validationRule.operator == 'less_than_or_equal_to' )selected="selected"#end>$i18n.getString( "less_than_or_equal_to" )</option>
+                <option value="compulsory_pair" #if ( $validationRule.operator == 'compulsory_pair' )selected="selected"#end>$i18n.getString( "compulsory_pair" )</option>
 			</select>
 		</td>
 	</tr>

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/updateValidationRuleGroupForm.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/updateValidationRuleGroupForm.vm	2013-04-30 08:03:51 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/updateValidationRuleGroupForm.vm	2013-10-08 19:10:40 +0000
@@ -12,6 +12,18 @@
 				return option;
 			}
 		});
+		jQuery("#availableUserRolesToAlert").dhisAjaxSelect({
+			source: "../dhis-web-commons-ajax-json/getUserRoles.action",
+			iterator: "userRoles",
+			connectedTo: 'selectedUserRolesToAlert',
+			handler: function(item) {
+				var option = jQuery("<option />");
+				option.text( item.name );
+				option.attr( "value", item.id );
+
+				return option;
+			}
+		});
 	});
 </script>
 
@@ -42,18 +54,21 @@
 
 <table style="margin-top: 15px;">
     <colgroup>
+      <col style="width: 120px"/>
       <col style="width: 500px;"/>
       <col/>
       <col style="width: 500px;"/>
     </colgroup>
 
     <tr>
-        <th>$i18n.getString( "available_validation_rules" )</th>
-        <th></th>
-        <th>$i18n.getString( "group_members" )</th>
+        <th></th>
+        <th>$i18n.getString( "available" )</th>
+        <th></th>
+        <th>$i18n.getString( "selected" )</th>
     </tr>
 
     <tr>
+		<td><label>$i18n.getString( "validation_rules" )</label></td>		
         <td>
             <select id="availableValidationRules" name="availableValidationRules" multiple="multiple" style="height: 200px; width: 100%;"></select>
         </td>
@@ -73,6 +88,28 @@
             </select>
         </td>      
     </tr>
+
+    <tr>
+		<td><label>$i18n.getString( "user_roles_to_alert" )</label></td>		
+    	<td>
+            <select id="availableUserRolesToAlert" name="availableUserRoles" multiple="multiple" style="height: 200px; width: 100%;"></select>
+        </td>
+
+        <td style="text-align:center">
+        	<input type="button" value="&gt;" title="$i18n.getString( 'move_selected' )" style="width:50px" onclick="dhisAjaxSelect_moveAllSelected( 'availableUserRolesToAlert' );"/><br/>
+            <input type="button" value="&lt;" title="$i18n.getString( 'remove_selected' )" style="width:50px" onclick="dhisAjaxSelect_moveAllSelected( 'selectedUserRolesToAlert' );"/><br/>
+			<input type="button" value="&gt;&gt;" title="$i18n.getString('move_all')" style="width:50px" onclick="dhisAjaxSelect_moveAll( 'availableUserRolesToAlert' );"/><br/>
+			<input type="button" value="&lt;&lt;" title="$i18n.getString('remove_all')" style="width:50px" onclick="dhisAjaxSelect_moveAll( 'selectedUserRolesToAlert' );"/>
+        </td>
+
+        <td>
+            <select id="selectedUserRolesToAlert" name="selectedUserRolesToAlert" multiple="multiple" style="height: 200px; width: 100%; margin-top: 22px" />
+			#foreach( $userRole in $selectedUserRolesToAlert )
+				<option value="$userRole.id">$encoder.htmlEncode( $userRole.displayName )</option>
+			#end
+            </select>
+        </td>      
+    </tr>
 </table>
 
 <p>

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/validationRule.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/validationRule.vm	2013-06-29 14:16:34 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/validationRule.vm	2013-10-08 19:10:40 +0000
@@ -1,4 +1,20 @@
 <script type="text/javascript">
+	// add jquery.tablesorter parser for sorting the importance column:
+	// high => 3 medium => 2 low => 1
+	jQuery.tablesorter.addParser({
+	    id: 'importanceSorter',
+	    is: function(s) {
+			// return false so this parser is not auto-detected
+	        return false;
+	    },
+	    format: function(s) {
+	        return s
+	            .replace( '$i18n.getString( "high" )', 3 )
+	            .replace( '$i18n.getString( "medium" )', 2 )
+	            .replace( '$i18n.getString( "low" )', 1 );
+	    },
+	    type: 'numeric'
+	});
 	jQuery(document).ready(function(){	
 		tableSorter( 'listTable' );	
 	});
@@ -10,6 +26,13 @@
 		exportPdfByType( type, params );
 	}
 	
+	var i18n_high = '$encoder.jsEscape( $i18n.getString( "high" ) , "'")';
+	var i18n_medium = '$encoder.jsEscape( $i18n.getString( "medium" ) , "'")';
+	var i18n_low = '$encoder.jsEscape( $i18n.getString( "low" ) , "'")';
+	
+	var i18n_validation = '$encoder.jsEscape( $i18n.getString( "validation" ) , "'")';
+	var i18n_monitoring = '$encoder.jsEscape( $i18n.getString( "monitoring" ) , "'")';
+	
 	var i18n_confirm_delete = '$encoder.jsEscape( $i18n.getString( "confirm_delete_validation_rule" ) , "'")';
 	var i18n_none = '$encoder.jsEscape( $i18n.getString( "none" ) , "'")';
 	var i18n_equal_to = '$encoder.jsEscape( $i18n.getString( "equal_to" ) , "'")';
@@ -29,7 +52,7 @@
 				<tr>
 					<td>#filterDiv( "validationRule" )</td>
 					<td style="text-align:right">
-						<input type="button" value="$i18n.getString( 'get_pdf' )" onclick="exportPDF( 'validationRule' );" style="width:80px"/>
+						<input type="button" value="$i18n.getString( 'get_pdf' )" onclick="exportPDF( 'validationRule' );" style="width:140px"/>
 						<input type="button" value="$i18n.getString( 'add_new' )" onclick="window.location.href='showAddValidationRuleForm.action'">
 					</td>
 				</tr>	
@@ -37,19 +60,23 @@
 			<table class="listTable" id="listTable"> 
 				<col/>
 				<col/>
+				<col/>
 				<col width="120px">				
 				<thead>							
 				<tr>
-					<th>$i18n.getString( "name" )</th>
-					<th  width="100px">$i18n.getString('periodtype')</th>
-					<th class="{sorter: false}">$i18n.getString( "operations" )</th>	
+					<th>$encoder.htmlEncode( $i18n.getString( "name" ) )&nbsp;&nbsp;&nbsp;&nbsp;</th>
+					<!-- Note: column-level sortInitialOrder only works in tablesorter 2.0.8 and later (an earlier version in use as of this writing.) --->
+					<th class="{sorter: 'importanceSorter', sortInitialOrder: 'desc'}">$encoder.htmlEncode( $i18n.getString("importance") )&nbsp;&nbsp;&nbsp;&nbsp;</th>
+					<th>$encoder.htmlEncode( $i18n.getString("period_type") )&nbsp;&nbsp;&nbsp;&nbsp;</th>
+					<th class="{sorter: false}">$encoder.htmlEncode( $i18n.getString( "operations" ) )</th>	
 				</tr>
 				</thead>			
 				<tbody id="list">
 				#foreach( $validationRule in $validationRulesList )
 				<tr id="tr${validationRule.id}">
 					<td onclick="showValidationRuleDetails( $validationRule.id )">$!encoder.htmlEncode( $validationRule.displayName )</td>
-					<td onclick="showValidationRuleDetails( $validationRule.id )">$i18n.getString($!validationRule.periodType.name) </td>
+					<td onclick="showValidationRuleDetails( $validationRule.id )">$encoder.htmlEncode( $i18n.getString($!validationRule.importance) ) </td>
+					<td onclick="showValidationRuleDetails( $validationRule.id )">$encoder.htmlEncode( $i18n.getString($!validationRule.periodType.name) ) </td>
 					<td style="text-align:right">
 						<a href="showUpdateValidationRuleForm.action?id=$validationRule.id" title="$i18n.getString( 'edit' )"><img src="../images/edit.png" alt="$i18n.getString( 'edit' )"></a>
 						<a href="javascript:translate( 'ValidationRule', '$validationRule.id' )"><img src="../images/i18n.png" alt="$i18n.getString( 'translation_translate' )"/></a>
@@ -74,6 +101,12 @@
 				</div>				
 				<p><label>$i18n.getString( "name" ):</label><br><span id="nameField"></span></p>
 				<p><label>$i18n.getString( "description" ):</label><br><span id="descriptionField"></span></p>
+				<p><label>$i18n.getString( "rule_type" ):</label><br><span id="ruleTypeField"></span></p>
+				<p id="organisationUnitLevelP"><label>$i18n.getString( "organisation_unit_level" ):</label><br><span id="organisationUnitLevelField"></span></p>
+				<p id="sequentialSampleCountP"><label>$i18n.getString( "sequential_sample_count" ):</label><br><span id="sequentialSampleCountField"></span></p>
+				<p id="annualSampleCountP"><label>$i18n.getString( "annual_sample_count" ):</label><br><span id="annualSampleCountField"></span></p>
+				<p id="highOutliersP"><label>$i18n.getString( "high_outliers" ):</label><br><span id="highOutliersField"></span></p>
+				<p id="lowOutliersP"><label>$i18n.getString( "low_outliers" ):</label><br><span id="lowOutliersField"></span></p>
 				<p><label>$i18n.getString( "left_side_description" ):</label><br><span id="leftSideDescriptionField"></span></p>
 				<p><label>$i18n.getString( "operator" ):</label><br><span id="operatorField"></span></p>
 				<p><label>$i18n.getString( "right_side_description" ):</label><br><span id="rightSideDescriptionField"></span></p>

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/viewValidationResultDetailsForm.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/viewValidationResultDetailsForm.vm	2011-07-28 08:10:02 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/viewValidationResultDetailsForm.vm	2013-10-08 19:10:40 +0000
@@ -10,11 +10,15 @@
     </tr>
     <tr>
     	<td>$i18n.getString( "name" )</th>
-    	<td>$!validationRule.name</td>
+    	<td>$encoder.htmlEncode( $!validationRule.name )</td>
     </tr>
     <tr>
     	<td>$i18n.getString( "description" )</td>
-    	<td>$!validationRule.description</td>
+    	<td>$encoder.htmlEncode( $!validationRule.description )</td>
+    </tr>
+    <tr>
+    	<td>$i18n.getString( "rule_type" )</td>
+    	<td>$encoder.htmlEncode( $i18n.getString( $!validationRule.ruleType ) )</td>
     </tr>
 </table>
 
@@ -29,8 +33,8 @@
 	</tr>
 	#foreach ( $dataElementName in $leftSideMap.keySet() )
 	<tr>
-		<td>$dataElementName</td>
-		<td style="text-align:center">$leftSideMap.get( $dataElementName )</td>
+		<td>$encoder.htmlEncode( $dataElementName )</td>
+		<td style="text-align:center">$encoder.htmlEncode( $leftSideMap.get( $dataElementName ) )</td>
 	</tr>
 	#end
 </table>
@@ -46,8 +50,8 @@
 	</tr>
 	#foreach ( $dataElementName in $rightSideMap.keySet() )
 	<tr>
-		<td>$dataElementName</td>
-		<td style="text-align:center">$rightSideMap.get( $dataElementName )</td>
+		<td>$encoder.htmlEncode( $dataElementName )</td>
+		<td style="text-align:center">$encoder.htmlEncode( $rightSideMap.get( $dataElementName ) )</td>
 	</tr>
 	#end
 </table>

=== modified file 'dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/viewValidationResultForm.vm'
--- dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/viewValidationResultForm.vm	2012-09-22 18:42:59 +0000
+++ dhis-2/dhis-web/dhis-web-validationrule/src/main/webapp/dhis-web-validationrule/viewValidationResultForm.vm	2013-10-08 19:10:40 +0000
@@ -1,33 +1,52 @@
 <script type="text/javascript">
-	jQuery(document).ready(function(){	
-		tableSorter( 'validationRuleList' );
+	// add jquery.tablesorter parser for sorting the importance column:
+	// high => 3 medium => 2 low => 1
+	jQuery.tablesorter.addParser({
+	    id: 'importanceSorter',
+	    is: function(s) {
+			// return false so this parser is not auto-detected
+	        return false;
+	    },
+	    format: function(s) {
+	        return s
+	            .replace( '$i18n.getString( "high" )', 3 )
+	            .replace( '$i18n.getString( "medium" )', 2 )
+	            .replace( '$i18n.getString( "low" )', 1 );
+	    },
+	    type: 'numeric'
 	});
-	
+    jQuery(function() {
+        $("#validationRuleList").tablesorter({ 
+        // sort on the first column, order asc 
+        sortList: [[0,0], [1,0], [2,1]] 
+    	});
+    });
+
 	var i18n_analysing_please_wait = '$encoder.jsEscape( $i18n.getString( "analysing_please_wait" ) , "'")';
 </script>
 
 <input type="hidden" id="organisationUnitId" value="$!{organisationUnit.id}" />
 
-<h3>$encoder.htmlEncode( $i18n.getString( "validation_violations" ) ) - $encoder.htmlEncode( $!{organisationUnit.name} )</h3>
+<h3>$i18n.getString( "validation_violations" ) - $encoder.htmlEncode( $!{organisationUnit.name} )</h3>
 
 <table>
 	<colgroup>
-	    <col width="150">
-	    <col width="150">
-        <col width="150">
-        <col width="150">
+	    <col width="100">
+	    <col width="120">
+        <col width="200">
+        <col width="200">
 	</colgroup>
 	<tr>
 		<td>$i18n.getString( "start_date" ):</td>
 		<td>$startDate</td>
-		<td><input type="button" value="$i18n.getString( 'get_report_as_pdf' )" style="width:140px" onclick="exportValidationResult( 'pdf' )"></td>
-		<td><input type="button" value="$i18n.getString( 'get_report_as_xls' )" style="width:140px" onclick="exportValidationResult( 'xls' )"></td>
+		<td><input type="button" value="$i18n.getString( 'get_report_as_pdf' )" style="width:180px" onclick="exportValidationResult( 'pdf' )"></td>
+		<td><input type="button" value="$i18n.getString( 'get_report_as_xls' )" style="width:180px" onclick="exportValidationResult( 'xls' )"></td>
     </tr>
 	<tr>
 		<td>$i18n.getString( "end_date" ):</td>
 		<td>$endDate</td>
-		<td><input type="button" value="$i18n.getString( 'get_report_as_csv' )" style="width:140px" onclick="exportValidationResult( 'csv' )"></td>
-		<td><input name="button" type="button" style="width:140px" onclick="window.location.href='showRunValidationForm.action'" value="$i18n.getString( 'done' )"></td>
+		<td><input type="button" value="$i18n.getString( 'get_report_as_csv' )" style="width:180px" onclick="exportValidationResult( 'csv' )"></td>
+		<td><input name="button" type="button" style="width:180px" onclick="window.location.href='showRunValidationForm.action'" value="$i18n.getString( 'done' )"></td>
 	</tr>
 	<tr>
 		<td colspan="4" height="15"></td>
@@ -54,6 +73,7 @@
 	    <col>
 	    <col>
 	    <col>
+	    <col>
 	    <col width="40">
 	    <col>
 	    <col>
@@ -61,8 +81,11 @@
 	</colgroup>
 	<thead>
 		<tr>
-			<th>$i18n.getString( "organisation_unit" )</th>
-			<th>$i18n.getString( "period" )</th>
+			<!-- Note: &nbsp;&nbsp;&nbsp;&nbsp; in the headers below keeps the jquery.tablesorter icons from overlapping the labels. -->
+			<th>$i18n.getString( "organisation_unit" )&nbsp;&nbsp;&nbsp;&nbsp;</th>
+			<th>$i18n.getString( "period" )&nbsp;&nbsp;&nbsp;&nbsp;</th>
+			<!-- Note: column-level sortInitialOrder only works in tablesorter 2.0.8 and later (an earlier version in use as of this writing.) --->
+			<th class="{sorter: 'importanceSorter', sortInitialOrder: 'desc'}">$i18n.getString( "importance" )&nbsp;&nbsp;&nbsp;&nbsp;</th>
 			<th class="{sorter: false}">$i18n.getString( "left_side_description" )</th>
 			<th class="{sorter: false}">$i18n.getString( "value" )</th>
 			<th class="{sorter: false}">$i18n.getString( "operator" )</th>
@@ -74,13 +97,14 @@
 	<tbody>
 		#foreach( $result in $validationResults )
 		<tr id="tr${result.id}">
-		  <td>$!result.source.name</td>
+		  <td>$encoder.htmlEncode( $!result.source.name )</td>
 		  <td>$!format.formatPeriod( $result.period )</td>
-		  <td>$!result.validationRule.leftSide.description</td>
-		  <td>$!result.leftsideValue</td>
-		  <td style="text-align:center">$i18n.getString( $!result.validationRule.operator.mathematicalOperator )</td>
-		  <td>$!result.rightsideValue</td>
-		  <td>$!result.validationRule.rightSide.description</td>
+		  <td>$encoder.htmlEncode( $i18n.getString( $!result.validationRule.importance ) )</td>
+		  <td>$encoder.htmlEncode( $!result.validationRule.leftSide.description )</td>
+		  <td>$encoder.htmlEncode( $!result.leftsideValue )</td>
+		  <td style="text-align:center">$encoder.htmlEncode( $i18n.getString( $!result.validationRule.operator.mathematicalOperator ) )</td>
+		  <td>$encoder.htmlEncode( $!result.rightsideValue )</td>
+		  <td>$encoder.htmlEncode( $!result.validationRule.rightSide.description )</td>
 		  <td style="text-align:center">
 		    <a href="#" onclick="viewValidationResultDetails( $result.validationRule.id, $result.source.id, $result.period.id )" title="$i18n.getString( 'show_details' )">
 		      <img src="../images/information.png" alt="$i18n.getString( 'show_details' )">