← Back to team overview

dhis2-devs team mailing list archive

[Branch ~dhis2-devs-core/dhis2/trunk] Rev 2300: Impl support for totals in indicator formulas part 1

 

Merge authors:
  Lars Helge Øverland (larshelge)
------------------------------------------------------------
revno: 2300 [merge]
committer: Lars Helge Overland <larshelge@xxxxxxxxx>
branch nick: dhis2
timestamp: Mon 2010-12-06 23:31:55 +0100
message:
  Impl support for totals in indicator formulas part 1
modified:
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/aggregation/AggregationService.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/Expression.java
  dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/ExpressionService.java
  dhis-2/dhis-services/dhis-service-aggregationengine-default/src/main/java/org/hisp/dhis/aggregation/impl/indicator/IndicatorAggregation.java
  dhis-2/dhis-services/dhis-service-aggregationengine-default/src/test/java/org/hisp/dhis/aggregation/AggregationServiceTest.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/test/java/org/hisp/dhis/expression/ExpressionServiceTest.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/engine/DefaultDataMartEngine.java
  dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/indicator/DefaultIndicatorDataMart.java
  dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/DhisConvenienceTest.java


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

Your team DHIS 2 developers is subscribed to branch lp:dhis2.
To unsubscribe from this branch go to https://code.launchpad.net/~dhis2-devs-core/dhis2/trunk/+edit-subscription
=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/aggregation/AggregationService.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/aggregation/AggregationService.java	2010-07-10 15:37:10 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/aggregation/AggregationService.java	2010-12-06 16:29:39 +0000
@@ -47,6 +47,8 @@
      * in the given dataelement.
      * 
      * @param dataElement the DataElement to aggregate over.
+     * @param optionCombo the DataElementCategoryOptionCombo to aggregate over.
+     *        If null, the total value for the DataElement will be used.
      * @param startDate the start date of the aggregation period.
      * @param endDate the end date of the aggregation period.
      * @param organisationUnit the OrganisationUnit to aggregate over.

=== modified file 'dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/Expression.java'
--- dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/Expression.java	2010-12-05 18:52:18 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/Expression.java	2010-12-06 09:27:07 +0000
@@ -53,6 +53,8 @@
     implements Serializable
 {
     public static final String SEPARATOR = ".";
+    public static final String EXP_OPEN = "[";
+    public static final String EXP_CLOSE = "]";
     
     /**
      * The unique identifier for this Expression.

=== 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	2010-12-05 18:52:18 +0000
+++ dhis-2/dhis-api/src/main/java/org/hisp/dhis/expression/ExpressionService.java	2010-12-06 21:06:40 +0000
@@ -53,6 +53,11 @@
     final String DATAELEMENT_DOES_NOT_EXIST = "data_element_does_not_exist";
     final String CATEGORYOPTIONCOMBO_DOES_NOT_EXIST = "category_option_combo_does_not_exist";
     final String EXPRESSION_NOT_WELL_FORMED = "expression_not_well_formed";
+
+    final String NULL_REPLACEMENT = "0";
+    final String SPACE = " ";
+    
+    final String FORMULA_EXPRESSION = "\\[.+?\\]";
     
     /**
      * Adds a new Expression to the database.
@@ -174,6 +179,15 @@
     String replaceCDEsWithTheirExpression( String expression );
     
     /**
+     * Replaces references to data element totals with references to all
+     * category option combos in the category combo for that data element.
+     * 
+     * @param expression the expression to explode.
+     * @return the exploded expression string.
+     */
+    String explodeExpression( String expression );
+    
+    /**
      * Converts an expression on the form [34] + [23], where the numbers are 
      * IDs of DataElements, to the form 200 + 450, where the numbers are the 
      * values of the DataValues registered for the Period and Source. "0" is

=== modified file 'dhis-2/dhis-services/dhis-service-aggregationengine-default/src/main/java/org/hisp/dhis/aggregation/impl/indicator/IndicatorAggregation.java'
--- dhis-2/dhis-services/dhis-service-aggregationengine-default/src/main/java/org/hisp/dhis/aggregation/impl/indicator/IndicatorAggregation.java	2010-07-10 14:50:48 +0000
+++ dhis-2/dhis-services/dhis-service-aggregationengine-default/src/main/java/org/hisp/dhis/aggregation/impl/indicator/IndicatorAggregation.java	2010-12-06 21:06:40 +0000
@@ -27,7 +27,6 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-import static org.hisp.dhis.expression.Expression.SEPARATOR;
 import static org.hisp.dhis.system.util.DateUtils.DAYS_IN_YEAR;
 import static org.hisp.dhis.system.util.DateUtils.getDays;
 import static org.hisp.dhis.system.util.MathUtils.INVALID;
@@ -41,7 +40,9 @@
 import org.hisp.dhis.dataelement.DataElement;
 import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
 import org.hisp.dhis.dataelement.DataElementCategoryService;
+import org.hisp.dhis.dataelement.DataElementOperand;
 import org.hisp.dhis.dataelement.DataElementService;
+import org.hisp.dhis.expression.ExpressionService;
 import org.hisp.dhis.indicator.Indicator;
 import org.hisp.dhis.organisationunit.OrganisationUnit;
 
@@ -51,9 +52,7 @@
  */
 public class IndicatorAggregation
 {
-    private static final String NULL_REPLACEMENT = "0";
-
-    private static final Pattern OPERAND_PATTERN = Pattern.compile( "(\\[\\d+\\" + SEPARATOR + "\\d+\\])" );
+    private static final Pattern FORMULA_PATTERN = Pattern.compile( ExpressionService.FORMULA_EXPRESSION );
     
     // -------------------------------------------------------------------------
     // Dependencies
@@ -156,36 +155,25 @@
     {    	
         try
         {        	
-            Matcher matcher = OPERAND_PATTERN.matcher( formula );
+            Matcher matcher = FORMULA_PATTERN.matcher( formula );
             
             StringBuffer buffer = new StringBuffer();            
             
             while ( matcher.find() )
             {
-                String replaceString = matcher.group().replaceAll( "[\\[\\]]", "" );
-                
-                String dataElementIdString = replaceString.substring( 0, replaceString.indexOf( SEPARATOR ) );                
-                String optionComboIdString = replaceString.substring( replaceString.indexOf( SEPARATOR ) + 1, replaceString.length() );
-                
-                int dataElementId = Integer.parseInt( dataElementIdString );
-                int optionComboId = Integer.parseInt( optionComboIdString );             
-                
-                DataElement dataElement = dataElementService.getDataElement( dataElementId );
-                
-                DataElementCategoryOptionCombo optionCombo = categoryService.getDataElementCategoryOptionCombo( optionComboId );
-
-                Double aggregatedValue = aggregationCache.getAggregatedDataValue( dataElement, optionCombo, startDate, endDate, organisationUnit );                
-                
-                if ( aggregatedValue == null )
-                {
-                    replaceString = NULL_REPLACEMENT;
-                }
-                else
-                {
-                    replaceString = String.valueOf( aggregatedValue );
-                }
-
-                matcher.appendReplacement( buffer, replaceString );
+                String match = matcher.group();
+                
+                DataElementOperand operand = DataElementOperand.getOperand( match );
+                
+                DataElement dataElement = dataElementService.getDataElement( operand.getDataElementId() );
+                
+                DataElementCategoryOptionCombo optionCombo = !operand.isTotal() ? categoryService.getDataElementCategoryOptionCombo( operand.getOptionComboId() ) : null;
+
+                Double aggregatedValue = aggregationCache.getAggregatedDataValue( dataElement, optionCombo, startDate, endDate, organisationUnit );   
+                
+                match = aggregatedValue == null ? ExpressionService.NULL_REPLACEMENT : String.valueOf( aggregatedValue );
+                
+                matcher.appendReplacement( buffer, match );
             }
 
             matcher.appendTail( buffer );

=== modified file 'dhis-2/dhis-services/dhis-service-aggregationengine-default/src/test/java/org/hisp/dhis/aggregation/AggregationServiceTest.java'
--- dhis-2/dhis-services/dhis-service-aggregationengine-default/src/test/java/org/hisp/dhis/aggregation/AggregationServiceTest.java	2010-09-17 21:30:59 +0000
+++ dhis-2/dhis-services/dhis-service-aggregationengine-default/src/test/java/org/hisp/dhis/aggregation/AggregationServiceTest.java	2010-12-06 21:06:40 +0000
@@ -28,14 +28,15 @@
  */
 
 import static junit.framework.Assert.assertEquals;
+import static org.hisp.dhis.expression.Expression.SEPARATOR;
 
-import java.util.Collection;
 import java.util.Date;
-import java.util.HashSet;
 
 import org.hisp.dhis.DhisTest;
 import org.hisp.dhis.dataelement.DataElement;
+import org.hisp.dhis.dataelement.DataElementCategory;
 import org.hisp.dhis.dataelement.DataElementCategoryCombo;
+import org.hisp.dhis.dataelement.DataElementCategoryOption;
 import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
 import org.hisp.dhis.dataelement.DataElementCategoryService;
 import org.hisp.dhis.dataelement.DataElementService;
@@ -43,7 +44,9 @@
 import org.hisp.dhis.dataset.DataSetService;
 import org.hisp.dhis.datavalue.DataValueService;
 import org.hisp.dhis.expression.ExpressionService;
+import org.hisp.dhis.indicator.Indicator;
 import org.hisp.dhis.indicator.IndicatorService;
+import org.hisp.dhis.indicator.IndicatorType;
 import org.hisp.dhis.organisationunit.OrganisationUnit;
 import org.hisp.dhis.organisationunit.OrganisationUnitService;
 import org.hisp.dhis.period.MonthlyPeriodType;
@@ -63,17 +66,31 @@
     private final String F = "false";
     
     private AggregationService aggregationService;
+
+    private DataElementCategoryOption categoryOptionA;
+    private DataElementCategoryOption categoryOptionB;
     
+    private DataElementCategory category;
+
     private DataElementCategoryCombo categoryCombo;
     
-    private DataElementCategoryOptionCombo categoryOptionCombo;
-
-    private Collection<Integer> dataElementIds;
-    private Collection<Integer> periodIds;
-    private Collection<Integer> organisationUnitIds;
+    private DataElementCategoryOptionCombo categoryOptionComboA;
+    private DataElementCategoryOptionCombo categoryOptionComboB;    
+    
+    private int categoryOptionComboIdA;
     
     private DataElement dataElementA;
     private DataElement dataElementB;
+    private DataElement dataElementC;
+    
+    private int dataElementIdA;
+    private int dataElementIdB;
+    private int dataElementIdC;
+    
+    private IndicatorType indicatorType;
+    
+    private Indicator indicatorA;
+    private Indicator indicatorB;
     
     private DataSet dataSet;
     
@@ -118,29 +135,66 @@
         dataValueService = (DataValueService) getBean( DataValueService.ID );
 
         expressionService = (ExpressionService) getBean( ExpressionService.ID );
-        
-        categoryCombo = categoryService.getDataElementCategoryComboByName( DataElementCategoryCombo.DEFAULT_CATEGORY_COMBO_NAME );
-        
-        categoryOptionCombo = categoryService.getDefaultDataElementCategoryOptionCombo();
-        
-        // ---------------------------------------------------------------------
-        // Setup identifier Collections
-        // ---------------------------------------------------------------------
-
-        dataElementIds = new HashSet<Integer>();
-        periodIds = new HashSet<Integer>();
-        organisationUnitIds = new HashSet<Integer>();
-        
+
+        // ---------------------------------------------------------------------
+        // Setup Dimensions
+        // ---------------------------------------------------------------------
+
+        categoryOptionA = new DataElementCategoryOption( "Male" );
+        categoryOptionB = new DataElementCategoryOption( "Female" );
+        
+        categoryService.addDataElementCategoryOption( categoryOptionA );
+        categoryService.addDataElementCategoryOption( categoryOptionB );
+
+        category = new DataElementCategory( "Gender" );
+        category.getCategoryOptions().add( categoryOptionA );
+        category.getCategoryOptions().add( categoryOptionB );
+
+        categoryService.addDataElementCategory( category );
+
+        categoryCombo = new DataElementCategoryCombo( "Gender" );
+        categoryCombo.getCategories().add( category );        
+        
+        categoryService.addDataElementCategoryCombo( categoryCombo );
+        
+        categoryOptionComboA = createCategoryOptionCombo( categoryCombo, categoryOptionA );
+        categoryOptionComboB = createCategoryOptionCombo( categoryCombo, categoryOptionB );
+        
+        categoryOptionComboIdA = categoryService.addDataElementCategoryOptionCombo( categoryOptionComboA );
+        categoryService.addDataElementCategoryOptionCombo( categoryOptionComboB );
+
         // ---------------------------------------------------------------------
         // Setup DataElements
         // ---------------------------------------------------------------------
 
         dataElementA = createDataElement( 'A', DataElement.VALUE_TYPE_INT, DataElement.AGGREGATION_OPERATOR_SUM, categoryCombo );
         dataElementB = createDataElement( 'B', DataElement.VALUE_TYPE_BOOL, DataElement.AGGREGATION_OPERATOR_SUM, categoryCombo );
-
-        dataElementIds.add( dataElementService.addDataElement( dataElementA ) );
-        dataElementIds.add( dataElementService.addDataElement( dataElementB ) );
-
+        dataElementC = createDataElement( 'C', DataElement.VALUE_TYPE_INT, DataElement.AGGREGATION_OPERATOR_SUM, categoryCombo );
+
+        dataElementIdA = dataElementService.addDataElement( dataElementA );
+        dataElementIdB = dataElementService.addDataElement( dataElementB );
+        dataElementIdC = dataElementService.addDataElement( dataElementC );
+
+        // ---------------------------------------------------------------------
+        // Setup Indicators
+        // ---------------------------------------------------------------------
+
+        indicatorType = createIndicatorType( 'A' );
+        indicatorType.setFactor( 100 );
+        
+        indicatorService.addIndicatorType( indicatorType );
+        
+        indicatorA = createIndicator( 'A', indicatorType );
+        indicatorA.setNumerator( "[" + dataElementIdA + SEPARATOR + categoryOptionComboIdA + "]+150" );
+        indicatorA.setDenominator( "[" + dataElementIdB + SEPARATOR + categoryOptionComboIdA + "]" );
+        
+        indicatorB = createIndicator( 'B', indicatorType );
+        indicatorB.setNumerator( "[" + dataElementIdC + "]" );
+        indicatorB.setDenominator( "1" );
+        
+        indicatorService.addIndicator( indicatorA );
+        indicatorService.addIndicator( indicatorB );
+        
         // ---------------------------------------------------------------------
         // Setup DataSets (to get correct PeriodType for DataElements)
         // ---------------------------------------------------------------------
@@ -151,8 +205,10 @@
         dataSetService.addDataSet( dataSet );
         dataElementA.getDataSets().add( dataSet );
         dataElementB.getDataSets().add( dataSet );
+        dataElementC.getDataSets().add( dataSet );
         dataElementService.updateDataElement( dataElementA );
         dataElementService.updateDataElement( dataElementB );
+        dataElementService.updateDataElement( dataElementC );
         
         // ---------------------------------------------------------------------
         // Setup Periods
@@ -164,9 +220,9 @@
         periodB = createPeriod( monthly, apr01, apr30 );
         periodC = createPeriod( monthly, may01, may31 );
         
-        periodIds.add( periodService.addPeriod( periodA ) );
-        periodIds.add( periodService.addPeriod( periodB ) );
-        periodIds.add( periodService.addPeriod( periodC ) );
+        periodService.addPeriod( periodA );
+        periodService.addPeriod( periodB );
+        periodService.addPeriod( periodC );
         
         // ---------------------------------------------------------------------
         // Setup OrganisationUnits
@@ -182,62 +238,65 @@
         unitH = createOrganisationUnit( 'H', unitF );
         unitI = createOrganisationUnit( 'I' );
 
-        organisationUnitIds.add( organisationUnitService.addOrganisationUnit( unitA ) );
-        organisationUnitIds.add( organisationUnitService.addOrganisationUnit( unitB ) );
-        organisationUnitIds.add( organisationUnitService.addOrganisationUnit( unitC ) );
-        organisationUnitIds.add( organisationUnitService.addOrganisationUnit( unitD ) );
-        organisationUnitIds.add( organisationUnitService.addOrganisationUnit( unitE ) );
-        organisationUnitIds.add( organisationUnitService.addOrganisationUnit( unitF ) );
-        organisationUnitIds.add( organisationUnitService.addOrganisationUnit( unitG ) );
-        organisationUnitIds.add( organisationUnitService.addOrganisationUnit( unitH ) );
-        organisationUnitIds.add( organisationUnitService.addOrganisationUnit( unitI ) );
+        organisationUnitService.addOrganisationUnit( unitA );
+        organisationUnitService.addOrganisationUnit( unitB );
+        organisationUnitService.addOrganisationUnit( unitC );
+        organisationUnitService.addOrganisationUnit( unitD );
+        organisationUnitService.addOrganisationUnit( unitE );
+        organisationUnitService.addOrganisationUnit( unitF );
+        organisationUnitService.addOrganisationUnit( unitG );
+        organisationUnitService.addOrganisationUnit( unitH );
+        organisationUnitService.addOrganisationUnit( unitI );
         
         // ---------------------------------------------------------------------
         // Setup DataValues
         // ---------------------------------------------------------------------
 
-        dataValueService.addDataValue( createDataValue( dataElementA, periodA, unitC, "90", categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementA, periodA, unitD, "10", categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementA, periodA, unitE, "35", categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementA, periodA, unitF, "25", categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementA, periodA, unitG, "20", categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementA, periodA, unitH, "60", categoryOptionCombo ) );
-        
-        dataValueService.addDataValue( createDataValue( dataElementA, periodB, unitC, "70", categoryOptionCombo ) );        
-        dataValueService.addDataValue( createDataValue( dataElementA, periodB, unitD, "40", categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementA, periodB, unitE, "65", categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementA, periodB, unitF, "55", categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementA, periodB, unitG, "20", categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementA, periodB, unitH, "15", categoryOptionCombo ) );
-        
-        dataValueService.addDataValue( createDataValue( dataElementA, periodC, unitC, "95", categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementA, periodC, unitD, "40", categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementA, periodC, unitE, "45", categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementA, periodC, unitF, "30", categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementA, periodC, unitG, "50", categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementA, periodC, unitH, "70", categoryOptionCombo ) );
-        
-        dataValueService.addDataValue( createDataValue( dataElementB, periodA, unitC, T, categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementB, periodA, unitD, T, categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementB, periodA, unitE, F, categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementB, periodA, unitF, T, categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementB, periodA, unitG, F, categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementB, periodA, unitH, T, categoryOptionCombo ) );
-        
-        dataValueService.addDataValue( createDataValue( dataElementB, periodB, unitC, T, categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementB, periodB, unitD, F, categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementB, periodB, unitE, T, categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementB, periodB, unitF, T, categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementB, periodB, unitG, F, categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementB, periodB, unitH, T, categoryOptionCombo ) );
-        
-        dataValueService.addDataValue( createDataValue( dataElementB, periodC, unitC, F, categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementB, periodC, unitD, T, categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementB, periodC, unitE, F, categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementB, periodC, unitF, T, categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementB, periodC, unitG, T, categoryOptionCombo ) );
-        dataValueService.addDataValue( createDataValue( dataElementB, periodC, unitH, T, categoryOptionCombo ) );
-        
+        dataValueService.addDataValue( createDataValue( dataElementA, periodA, unitC, "90", categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementA, periodA, unitD, "10", categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementA, periodA, unitE, "35", categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementA, periodA, unitF, "25", categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementA, periodA, unitG, "20", categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementA, periodA, unitH, "60", categoryOptionComboA ) );
+        
+        dataValueService.addDataValue( createDataValue( dataElementA, periodB, unitC, "70", categoryOptionComboA ) );        
+        dataValueService.addDataValue( createDataValue( dataElementA, periodB, unitD, "40", categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementA, periodB, unitE, "65", categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementA, periodB, unitF, "55", categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementA, periodB, unitG, "20", categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementA, periodB, unitH, "15", categoryOptionComboA ) );
+        
+        dataValueService.addDataValue( createDataValue( dataElementA, periodC, unitC, "95", categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementA, periodC, unitD, "40", categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementA, periodC, unitE, "45", categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementA, periodC, unitF, "30", categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementA, periodC, unitG, "50", categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementA, periodC, unitH, "70", categoryOptionComboA ) );
+        
+        dataValueService.addDataValue( createDataValue( dataElementB, periodA, unitC, T, categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementB, periodA, unitD, T, categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementB, periodA, unitE, F, categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementB, periodA, unitF, T, categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementB, periodA, unitG, F, categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementB, periodA, unitH, T, categoryOptionComboA ) );
+        
+        dataValueService.addDataValue( createDataValue( dataElementB, periodB, unitC, T, categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementB, periodB, unitD, F, categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementB, periodB, unitE, T, categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementB, periodB, unitF, T, categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementB, periodB, unitG, F, categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementB, periodB, unitH, T, categoryOptionComboA ) );
+        
+        dataValueService.addDataValue( createDataValue( dataElementB, periodC, unitC, F, categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementB, periodC, unitD, T, categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementB, periodC, unitE, F, categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementB, periodC, unitF, T, categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementB, periodC, unitG, T, categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementB, periodC, unitH, T, categoryOptionComboA ) );
+        
+        dataValueService.addDataValue( createDataValue( dataElementC, periodA, unitB, "30", categoryOptionComboA ) );
+        dataValueService.addDataValue( createDataValue( dataElementC, periodA, unitB, "20", categoryOptionComboB ) );
+                
         aggregationService.clearCache();
     }
 
@@ -248,27 +307,39 @@
     }
 
     @Test
+    public void indicator()
+    {
+        assertEquals( 10000.0, aggregationService.getAggregatedIndicatorValue( indicatorA, mar01, mar31, unitB ) );
+        
+        assertEquals( 30.0, aggregationService.getAggregatedDataValue( dataElementC, categoryOptionComboA, mar01, mar31, unitB ) );
+        assertEquals( 20.0, aggregationService.getAggregatedDataValue( dataElementC, categoryOptionComboB, mar01, mar31, unitB ) );
+        assertEquals( 50.0, aggregationService.getAggregatedDataValue( dataElementC, null, mar01, mar31, unitB ) );
+        
+        assertEquals( 5000.0, aggregationService.getAggregatedIndicatorValue( indicatorB, mar01, mar31, unitB ) );
+    }
+    
+    @Test
     public void sumIntDataElement()
     {
-        assertEquals( 90.0, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionCombo, mar01, mar31, unitC ) );
-        assertEquals( 105.0, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionCombo, mar01, mar31, unitF ) );
-        assertEquals( 150.0, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionCombo, mar01, mar31, unitB ) );
+        assertEquals( 90.0, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionComboA, mar01, mar31, unitC ) );
+        assertEquals( 105.0, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionComboA, mar01, mar31, unitF ) );
+        assertEquals( 150.0, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionComboA, mar01, mar31, unitB ) );
 
-        assertEquals( 255.0, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionCombo, mar01, may31, unitC ) );
-        assertEquals( 345.0, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionCombo, mar01, may31, unitF ) );
-        assertEquals( 580.0, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionCombo, mar01, may31, unitB ) );
+        assertEquals( 255.0, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionComboA, mar01, may31, unitC ) );
+        assertEquals( 345.0, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionComboA, mar01, may31, unitF ) );
+        assertEquals( 580.0, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionComboA, mar01, may31, unitB ) );
     }
     
     @Test
     public void sumBoolDataElement()
     {
-        assertEquals( 1.0, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionCombo, mar01, mar31, unitC ) );
-        assertEquals( 2.0, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionCombo, mar01, mar31, unitF ) );
-        assertEquals( 3.0, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionCombo, mar01, mar31, unitB ) );
+        assertEquals( 1.0, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionComboA, mar01, mar31, unitC ) );
+        assertEquals( 2.0, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionComboA, mar01, mar31, unitF ) );
+        assertEquals( 3.0, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionComboA, mar01, mar31, unitB ) );
 
-        assertEquals( 2.0, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionCombo, mar01, may31, unitC ) );
-        assertEquals( 7.0, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionCombo, mar01, may31, unitF ) );
-        assertEquals( 10.0, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionCombo, mar01, may31, unitB ) );
+        assertEquals( 2.0, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionComboA, mar01, may31, unitC ) );
+        assertEquals( 7.0, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionComboA, mar01, may31, unitF ) );
+        assertEquals( 10.0, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionComboA, mar01, may31, unitB ) );
     }
     
     @Test
@@ -277,14 +348,13 @@
         dataElementA.setAggregationOperator( DataElement.AGGREGATION_OPERATOR_AVERAGE );
         dataElementService.updateDataElement( dataElementA );
         
-        assertEquals( 90.0, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionCombo, mar01, mar31, unitC ) );
-        assertEquals( 105.0, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionCombo, mar01, mar31, unitF ) );
-        assertEquals( 150.0, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionCombo, mar01, mar31, unitB ) );
+        assertEquals( 90.0, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionComboA, mar01, mar31, unitC ) );
+        assertEquals( 105.0, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionComboA, mar01, mar31, unitF ) );
+        assertEquals( 150.0, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionComboA, mar01, mar31, unitB ) );
 
-        assertEquals( 85.2, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionCombo, mar01, may31, unitC ), 0.1 );
-        assertEquals( 115.3, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionCombo, mar01, may31, unitF ), 0.1 );
-        // assertEquals( 193.3, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionCombo, mar01, may31, unitB ), 0.1 );
-        // got 193.80681818181816
+        assertEquals( 85.2, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionComboA, mar01, may31, unitC ), 0.1 );
+        assertEquals( 115.3, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionComboA, mar01, may31, unitF ), 0.1 );
+        assertEquals( 193.3, aggregationService.getAggregatedDataValue( dataElementA, categoryOptionComboA, mar01, may31, unitB ), 0.6 );
     }
     
     @Test
@@ -293,12 +363,12 @@
         dataElementB.setAggregationOperator( DataElement.AGGREGATION_OPERATOR_AVERAGE );
         dataElementService.updateDataElement( dataElementB );
         
-        assertEquals( 1.0, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionCombo, mar01, mar31, unitC ) );
-        assertEquals( 0.67, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionCombo, mar01, mar31, unitF ), 0.01 );
-        assertEquals( 0.6, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionCombo, mar01, mar31, unitB ) );
+        assertEquals( 1.0, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionComboA, mar01, mar31, unitC ) );
+        assertEquals( 0.67, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionComboA, mar01, mar31, unitF ), 0.01 );
+        assertEquals( 0.6, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionComboA, mar01, mar31, unitB ) );
         
-        assertEquals( 0.66, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionCombo, mar01, may31, unitC ), 0.01 );
-        assertEquals( 0.78, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionCombo, mar01, may31, unitF ), 0.01 );
-        assertEquals( 0.67, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionCombo, mar01, may31, unitB ), 0.01 );
+        assertEquals( 0.66, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionComboA, mar01, may31, unitC ), 0.01 );
+        assertEquals( 0.78, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionComboA, mar01, may31, unitF ), 0.01 );
+        assertEquals( 0.67, aggregationService.getAggregatedDataValue( dataElementB, categoryOptionComboA, mar01, may31, unitB ), 0.01 );
     }
 }

=== 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	2010-12-05 18:52:18 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/expression/DefaultExpressionService.java	2010-12-06 21:06:40 +0000
@@ -27,7 +27,7 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-import static org.hisp.dhis.expression.Expression.SEPARATOR;
+import static org.hisp.dhis.expression.Expression.*;
 import static org.hisp.dhis.system.util.MathUtils.calculateExpression;
 
 import java.util.Collection;
@@ -44,6 +44,7 @@
 import org.hisp.dhis.common.GenericStore;
 import org.hisp.dhis.dataelement.CalculatedDataElement;
 import org.hisp.dhis.dataelement.DataElement;
+import org.hisp.dhis.dataelement.DataElementCategoryCombo;
 import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
 import org.hisp.dhis.dataelement.DataElementCategoryService;
 import org.hisp.dhis.dataelement.DataElementOperand;
@@ -70,11 +71,6 @@
 {
     private static final Log log = LogFactory.getLog( DefaultExpressionService.class );
     
-    private static final String NULL_REPLACEMENT = "0";
-    private static final String SPACE = " ";
-    
-    private static final String FORMULA_EXPRESSION = "\\[.+?\\]";
-    
     private final Pattern FORMULA_PATTERN = Pattern.compile( FORMULA_EXPRESSION );
 
     // -------------------------------------------------------------------------
@@ -203,7 +199,7 @@
                     
                     match = NULL_REPLACEMENT;
                 }
-                else if ( mappedCategoryOptionComboId == null )
+                else if ( !operand.isTotal() && mappedCategoryOptionComboId == null )
                 {
                     log.info( "Category option combo identifer refers to non-existing object: " + operand.getOptionComboId() );
                     
@@ -211,7 +207,7 @@
                 }
                 else
                 {
-                    match = "[" + mappedDataElementId + SEPARATOR + mappedCategoryOptionComboId + "]";
+                    match = EXP_OPEN + mappedDataElementId + SEPARATOR + mappedCategoryOptionComboId + EXP_CLOSE;
                 }
                 
                 matcher.appendReplacement( convertedFormula, match );
@@ -306,9 +302,9 @@
 
             while ( matcher.find() )
             {
-                String replaceString = matcher.group();
+                String match = matcher.group();
                 
-                final DataElementOperand operand = DataElementOperand.getOperand( replaceString );
+                final DataElementOperand operand = DataElementOperand.getOperand( match );
                 
                 final DataElement dataElement = dataElementService.getDataElement( operand.getDataElementId() );
                 final DataElementCategoryOptionCombo categoryOptionCombo = 
@@ -320,20 +316,20 @@
                         + operand.getDataElementId() );
                 }
 
-                if ( categoryOptionCombo == null )
+                if ( !operand.isTotal() && categoryOptionCombo == null )
                 {
                     throw new IllegalArgumentException( "Identifier does not reference a category option combo: "
                         + operand.getOptionComboId() );
                 }
 
-                replaceString = dataElement.getName();
+                match = dataElement.getName();
                 
                 if ( !categoryOptionCombo.isDefault() )
                 {
-                    replaceString += SPACE + categoryOptionCombo.getName();
+                    match += SPACE + categoryOptionCombo.getName();
                 }
 
-                matcher.appendReplacement( buffer, replaceString );
+                matcher.appendReplacement( buffer, match );
             }
 
             matcher.appendTail( buffer );
@@ -370,7 +366,7 @@
 
                 for ( DataElement dataElement : caclulatedDataElementsInExpression )
                 {
-                    if ( replaceString.startsWith( "[" + dataElement.getId() + SEPARATOR ) )
+                    if ( replaceString.startsWith( EXP_OPEN + dataElement.getId() + SEPARATOR ) )
                     {
                         replaceString = ((CalculatedDataElement) dataElement).getExpression().getExpression();
 
@@ -387,6 +383,45 @@
         return buffer != null ? buffer.toString() : null;
     }
 
+    public String explodeExpression( String expression )
+    {
+        StringBuffer buffer = null;
+        
+        if ( expression != null )
+        {
+            final Matcher matcher = FORMULA_PATTERN.matcher( expression );
+            
+            buffer = new StringBuffer();
+            
+            while ( matcher.find() )
+            {
+                final DataElementOperand operand = DataElementOperand.getOperand( matcher.group() );
+
+                if ( operand.isTotal() )
+                {
+                    StringBuilder replace = new StringBuilder();
+                    
+                    DataElement dataElement = dataElementService.getDataElement( operand.getDataElementId() );
+                    
+                    DataElementCategoryCombo categoryCombo = dataElement.getCategoryCombo();
+                    
+                    for ( DataElementCategoryOptionCombo categoryOptionCombo : categoryCombo.getOptionCombos() )
+                    {
+                        replace.append( EXP_OPEN ).append( dataElement.getId() ).append( SEPARATOR ).append( categoryOptionCombo.getId() ).append( EXP_CLOSE ).append( "+" );
+                    }
+                    
+                    replace.deleteCharAt( replace.length() - 1 );
+                    
+                    matcher.appendReplacement( buffer, replace.toString() );
+                }
+            }
+            
+            matcher.appendTail( buffer );
+        }
+        
+        return buffer != null ? buffer.toString() : null;
+    }
+    
     public String generateExpression( String expression, Period period, Source source, boolean nullIfNoValues, boolean aggregated )
     {
         StringBuffer buffer = null;
@@ -399,9 +434,9 @@
 
             while ( matcher.find() )
             {
-                String replaceString = matcher.group();
+                String match = matcher.group();
 
-                final DataElementOperand operand = DataElementOperand.getOperand( replaceString );
+                final DataElementOperand operand = DataElementOperand.getOperand( match );
                 
                 String value = null;
               
@@ -421,9 +456,9 @@
                     return null;
                 }
                 
-                replaceString = ( value == null ) ? NULL_REPLACEMENT : value;
+                match = ( value == null ) ? NULL_REPLACEMENT : value;
                 
-                matcher.appendReplacement( buffer, replaceString );
+                matcher.appendReplacement( buffer, match );
             }
 
             matcher.appendTail( buffer );

=== modified file 'dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/expression/ExpressionServiceTest.java'
--- dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/expression/ExpressionServiceTest.java	2010-12-05 18:52:18 +0000
+++ dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/expression/ExpressionServiceTest.java	2010-12-06 09:27:07 +0000
@@ -31,7 +31,7 @@
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
-import static org.hisp.dhis.expression.Expression.SEPARATOR;
+import static org.hisp.dhis.expression.Expression.*;
 
 import java.util.Collection;
 import java.util.HashMap;
@@ -41,6 +41,9 @@
 
 import org.hisp.dhis.DhisTest;
 import org.hisp.dhis.dataelement.DataElement;
+import org.hisp.dhis.dataelement.DataElementCategory;
+import org.hisp.dhis.dataelement.DataElementCategoryCombo;
+import org.hisp.dhis.dataelement.DataElementCategoryOption;
 import org.hisp.dhis.dataelement.DataElementCategoryOptionCombo;
 import org.hisp.dhis.dataelement.DataElementCategoryService;
 import org.hisp.dhis.dataelement.DataElementOperand;
@@ -61,19 +64,31 @@
 {        
     private SourceStore sourceStore;
     
+    private DataElementCategoryOption categoryOptionA;
+    private DataElementCategoryOption categoryOptionB;
+    private DataElementCategoryOption categoryOptionC;
+    private DataElementCategoryOption categoryOptionD;
+    
+    private DataElementCategory categoryA;
+    private DataElementCategory categoryB;
+    
+    private DataElementCategoryCombo categoryCombo;
+    
     private DataElement dataElementA;
     private DataElement dataElementB;
     private DataElement dataElementC;
     private DataElement dataElementD;
+    private DataElement dataElementE;
     
     private Period period;
     
     private Source source;
     
-    private int dataElementIdA;    
-    private int dataElementIdB;    
-    private int dataElementIdC;    
+    private int dataElementIdA;
+    private int dataElementIdB;
+    private int dataElementIdC;
     private int dataElementIdD;
+    private int dataElementIdE;
 
     private DataElementCategoryOptionCombo categoryOptionCombo;
     
@@ -81,6 +96,7 @@
     
     private String expressionA;
     private String expressionB;
+    private String expressionC;
     
     private String descriptionA;
     private String descriptionB;
@@ -105,15 +121,44 @@
         
         sourceStore = (SourceStore) getBean( SourceStore.ID );
         
+        categoryOptionA = new DataElementCategoryOption( "Under 5" );
+        categoryOptionB = new DataElementCategoryOption( "Over 5" );
+        categoryOptionC = new DataElementCategoryOption( "Male" );
+        categoryOptionD = new DataElementCategoryOption( "Female" );
+        
+        categoryService.addDataElementCategoryOption( categoryOptionA );
+        categoryService.addDataElementCategoryOption( categoryOptionB );
+        categoryService.addDataElementCategoryOption( categoryOptionC );
+        categoryService.addDataElementCategoryOption( categoryOptionD );
+                
+        categoryA = new DataElementCategory( "Age" );
+        categoryB = new DataElementCategory( "Gender" );
+        
+        categoryA.getCategoryOptions().add( categoryOptionA );
+        categoryA.getCategoryOptions().add( categoryOptionB );
+        categoryB.getCategoryOptions().add( categoryOptionC );
+        categoryB.getCategoryOptions().add( categoryOptionD );
+        
+        categoryService.addDataElementCategory( categoryA );
+        categoryService.addDataElementCategory( categoryB );
+                
+        categoryCombo = new DataElementCategoryCombo( "Age and gender" );
+        categoryCombo.getCategories().add( categoryA );
+        categoryCombo.getCategories().add( categoryB );
+        
+        categoryService.addDataElementCategoryCombo( categoryCombo );
+        
         dataElementA = createDataElement( 'A' );
         dataElementB = createDataElement( 'B' );
         dataElementC = createDataElement( 'C' );
-        dataElementD = createDataElement( 'D' );        
+        dataElementD = createDataElement( 'D' );  
+        dataElementE = createDataElement( 'E', categoryCombo );        
         
         dataElementIdA = dataElementService.addDataElement( dataElementA );
         dataElementIdB = dataElementService.addDataElement( dataElementB );
         dataElementIdC = dataElementService.addDataElement( dataElementC );
         dataElementIdD = dataElementService.addDataElement( dataElementD );
+        dataElementIdE = dataElementService.addDataElement( dataElementE );
         
         categoryOptionCombo = categoryService.getDefaultDataElementCategoryOptionCombo();
         
@@ -125,8 +170,9 @@
         
         sourceStore.addSource( source );
         
-        expressionA = "[" + dataElementIdA + SEPARATOR + categoryOptionComboId + "] + [" + dataElementIdB + SEPARATOR + categoryOptionComboId + "]";
-        expressionB = "[" + dataElementIdC + SEPARATOR + categoryOptionComboId + "] - [" + dataElementIdD + SEPARATOR + categoryOptionComboId + "]";
+        expressionA = "[" + dataElementIdA + SEPARATOR + categoryOptionComboId + "]+[" + dataElementIdB + SEPARATOR + categoryOptionComboId + "]";
+        expressionB = "[" + dataElementIdC + SEPARATOR + categoryOptionComboId + "]-[" + dataElementIdD + SEPARATOR + categoryOptionComboId + "]";
+        expressionC = "[" + dataElementIdA + SEPARATOR + categoryOptionComboId + "]+[" + dataElementIdE + "]-10";
         
         descriptionA = "Expression A";
         descriptionB = "Expression B";
@@ -135,11 +181,12 @@
         dataElements.add( dataElementB );
         dataElements.add( dataElementC );
         dataElements.add( dataElementD );
+        dataElements.add( dataElementE );
 
         dataValueService.addDataValue( createDataValue( dataElementA, period, source, "10", categoryOptionCombo ) );
         dataValueService.addDataValue( createDataValue( dataElementB, period, source, "5", categoryOptionCombo ) );        
     }
-
+    
     @Override
     public boolean emptyDatabaseAfterTest()
     {
@@ -151,6 +198,23 @@
     // -------------------------------------------------------------------------
 
     @Test
+    public void testExplodeExpression()
+    {
+        categoryService.generateOptionCombos( categoryCombo );
+        
+        String actual = expressionService.explodeExpression( expressionC );
+        
+        Set<DataElementCategoryOptionCombo> categoryOptionCombos = categoryCombo.getOptionCombos();
+        
+        assertTrue( actual.contains( "[" + dataElementIdA + SEPARATOR + categoryOptionComboId + "]" ) );
+        
+        for ( DataElementCategoryOptionCombo categoryOptionCombo : categoryOptionCombos )
+        {
+            assertTrue( actual.contains( "[" + dataElementIdE + SEPARATOR + categoryOptionCombo.getId() + "]" ) );
+        }
+    }
+
+    @Test
     public void testGetExpressionValue()
     {
         Expression expression = new Expression( expressionA, descriptionA, dataElements );
@@ -202,8 +266,8 @@
         categoryOptionComboMapping.put( 1, 6 );
         categoryOptionComboMapping.put( 2, 7);
         
-        String expression = "[1.1] + 2 + [2.2]";
-        String expected = "[4.6] + 2 + [5.7]";
+        String expression = "[1.1]+2+[2.2]";
+        String expected = "[4.6]+2+[5.7]";
         
         assertEquals( expected, expressionService.convertExpression( expression, dataElementMapping, categoryOptionComboMapping ) );
     }
@@ -244,7 +308,7 @@
     {
         String description = expressionService.getExpressionDescription( expressionA );        
         
-        assertEquals( "DataElementA + DataElementB", description );
+        assertEquals( "DataElementA+DataElementB", description );
     }
 
     @Test
@@ -252,11 +316,11 @@
     {
         String expression = expressionService.generateExpression( expressionA, period, source, false, false );
         
-        assertEquals( "10 + 5", expression );
+        assertEquals( "10+5", expression );
         
         expression = expressionService.generateExpression( expressionB, period, source, false, false );
         
-        assertEquals( "0 - 0", expression );
+        assertEquals( "0-0", expression );
     }
     
     // -------------------------------------------------------------------------

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/engine/DefaultDataMartEngine.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/engine/DefaultDataMartEngine.java	2010-12-04 00:05:04 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/engine/DefaultDataMartEngine.java	2010-12-06 09:27:07 +0000
@@ -218,7 +218,7 @@
         aggregatedDataValueService.deleteAggregatedIndicatorValues( indicatorIds, periodIds, organisationUnitIds );
 
         log.info( "Deleted existing aggregated data: " + TimeUtils.getHMS() );
-
+        
         // ---------------------------------------------------------------------
         // Get objects
         // ---------------------------------------------------------------------
@@ -229,6 +229,16 @@
         Collection<CalculatedDataElement> calculatedDataElements = dataElementService.getCalculatedDataElements( dataElementIds );
         Collection<DataElement> nonCalculatedDataElements = dataElementService.getDataElements( dataElementIds );
         nonCalculatedDataElements.removeAll( calculatedDataElements );
+
+        // ---------------------------------------------------------------------
+        // Explode indicator expressions
+        // ---------------------------------------------------------------------
+
+        for ( Indicator indicator : indicators )
+        {
+            indicator.setExplodedNumerator( expressionService.explodeExpression( indicator.getNumerator() ) );
+            indicator.setExplodedDenominator( expressionService.explodeExpression( indicator.getDenominator() ) );
+        }
         
         // ---------------------------------------------------------------------
         // Filter and get operands
@@ -392,10 +402,10 @@
         
         for ( Indicator indicator : indicators )
         {
-            Set<DataElementOperand> temp = expressionService.getOperandsInExpression( indicator.getNumerator() );
+            Set<DataElementOperand> temp = expressionService.getOperandsInExpression( indicator.getExplodedNumerator() );
             operands.addAll( temp != null ? temp : new HashSet<DataElementOperand>() );
             
-            temp = expressionService.getOperandsInExpression( indicator.getDenominator() );            
+            temp = expressionService.getOperandsInExpression( indicator.getExplodedDenominator() );            
             operands.addAll( temp != null ? temp : new HashSet<DataElementOperand>() );
         }
         

=== modified file 'dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/indicator/DefaultIndicatorDataMart.java'
--- dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/indicator/DefaultIndicatorDataMart.java	2010-12-02 21:24:43 +0000
+++ dhis-2/dhis-services/dhis-service-datamart-default/src/main/java/org/hisp/dhis/datamart/indicator/DefaultIndicatorDataMart.java	2010-12-06 09:27:07 +0000
@@ -172,8 +172,8 @@
                 
                 for ( final Indicator indicator : indicators )
                 {
-                    final double numeratorValue = calculateExpression( generateExpression( indicator.getNumerator(), valueMap ) );                    
-                    final double denominatorValue = calculateExpression( generateExpression( indicator.getDenominator(), valueMap ) );
+                    final double numeratorValue = calculateExpression( generateExpression( indicator.getExplodedNumerator(), valueMap ) );                    
+                    final double denominatorValue = calculateExpression( generateExpression( indicator.getExplodedDenominator(), valueMap ) );
 
                     // ---------------------------------------------------------
                     // AggregatedIndicatorValue

=== 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	2010-11-15 16:26:51 +0000
+++ dhis-2/dhis-support/dhis-support-test/src/main/java/org/hisp/dhis/DhisConvenienceTest.java	2010-12-06 17:04:03 +0000
@@ -440,6 +440,25 @@
 
         return categoryOptionCombo;
     }
+    
+    /**
+     * @param categoryCombo the category combo.
+     * @param categoryOptions the category options.
+     * @return
+     */
+    public static DataElementCategoryOptionCombo createCategoryOptionCombo( DataElementCategoryCombo categoryCombo, DataElementCategoryOption... categoryOptions )
+    {
+        DataElementCategoryOptionCombo categoryOptionCombo = new DataElementCategoryOptionCombo();
+        
+        categoryOptionCombo.setCategoryCombo( categoryCombo );
+        
+        for ( DataElementCategoryOption categoryOption : categoryOptions )
+        {
+            categoryOptionCombo.getCategoryOptions().add( categoryOption );
+        }
+        
+        return categoryOptionCombo;
+    }
 
     /**
      * @param uniqueCharacter A unique character to identify the object.