package org.activityinfo.model.expr.customer;
/*
* #%L
* ActivityInfo Server
* %%
* Copyright (C) 2009 - 2013 UNICEF
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/
import com.extjs.gxt.ui.client.data.PagingLoadResult;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import org.activityinfo.fixtures.InjectionSupport;
import org.activityinfo.legacy.shared.adapter.ResourceLocatorAdaptor;
import org.activityinfo.legacy.shared.command.*;
import org.activityinfo.legacy.shared.command.result.Bucket;
import org.activityinfo.legacy.shared.command.result.CreateResult;
import org.activityinfo.legacy.shared.model.*;
import org.activityinfo.legacy.shared.reports.content.DimensionCategory;
import org.activityinfo.legacy.shared.reports.model.AttributeGroupDimension;
import org.activityinfo.legacy.shared.reports.model.DateDimension;
import org.activityinfo.legacy.shared.reports.model.DateUnit;
import org.activityinfo.legacy.shared.reports.model.Dimension;
import org.activityinfo.model.form.FormClass;
import org.activityinfo.model.form.FormField;
import org.activityinfo.model.legacy.KeyGenerator;
import org.activityinfo.model.resource.ResourceId;
import org.activityinfo.model.type.Cardinality;
import org.activityinfo.model.type.enumerated.EnumItem;
import org.activityinfo.model.type.enumerated.EnumType;
import org.activityinfo.model.type.expr.CalculatedFieldType;
import org.activityinfo.model.type.number.QuantityType;
import org.activityinfo.server.command.CommandTestCase2;
import org.activityinfo.server.command.LocationDTOs;
import org.activityinfo.server.database.OnDataSet;
import org.activityinfo.server.endpoint.export.SiteExporter;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.activityinfo.core.client.PromiseMatchers.assertResolves;
import static org.activityinfo.model.legacy.CuidAdapter.activityFormClass;
import static org.activityinfo.model.legacy.CuidAdapter.getLegacyIdFromCuid;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.closeTo;
import static org.hamcrest.Matchers.hasItem;
import static org.junit.Assert.assertThat;
/**
* @author yuriyz on 8/1/14.
*/
@SuppressWarnings({"GwtClientClassFromNonInheritedModule", "NonJREEmulationClassesInClientCode"})
@RunWith(InjectionSupport.class)
@OnDataSet("/dbunit/schema1.db.xml")
public class CustomerCalcIndicatorTest extends CommandTestCase2 {
@BeforeClass
public static void setup() {
Logger.getLogger("org.activityinfo").setLevel(Level.ALL);
Logger.getLogger("org.hibernate.SQL").setLevel(Level.ALL);
Logger.getLogger("com.bedatadriven.rebar").setLevel(Level.ALL);
}
public static final Dimension INDICATOR_DIMENSION = new Dimension(DimensionType.Indicator);
public static final Dimension YEAR_DIMENSION = new DateDimension(DateUnit.YEAR);
public static final double EPSILON = 0.000000000000001;
FormClass formClass;
private KeyGenerator keyGenerator = new KeyGenerator();
@Test
public void calculations() {
formClass = createFormClass();
int activityId = getLegacyIdFromCuid(formClass.getId());
SiteDTO newSite = newSite(activityId);
newSite.setIndicatorValue(fieldId("EXP"), 3);
newSite.setIndicatorValue(fieldId("WATER_ALLOC"), 400);
newSite.setIndicatorValue(fieldId("PCT_INITIAL"), 50);
newSite.setIndicatorValue(fieldId("PCT_INITIAL_HARD"), 20);
newSite.setIndicatorValue(fieldId("PCT_INITIAL_SOFT"), 30);
CreateResult createSiteResult = execute(new CreateSite(newSite));
// let the client know the command has succeeded
newSite.setId(createSiteResult.getNewId());
PagingLoadResult<SiteDTO> loadResult = execute(GetSites.byId(newSite
.getId()));
Assert.assertEquals(1, loadResult.getData().size());
SiteDTO siteDTO = loadResult.getData().get(0);
assertThat(indicatorValue(siteDTO, "WATER_EXP"), closeTo(12, 0)); // WATER_EXP = EXP * (WATER_ALLOC / 100)
assertThat(indicatorValue(siteDTO, "INITIAL"), closeTo(6, 0)); // INITIAL = WATER_EXP * (PCT_INITIAL / 100)
assertThat(indicatorValue(siteDTO, "INITIAL_HARD"), closeTo(2.4, 0.001)); // INITIAL_HARD = WATER_EXP * (PCT_INITIAL_HARD / 100)
assertThat(indicatorValue(siteDTO, "INITIAL_SOFT"), closeTo(3.6, 0.001)); // INITIAL_SOFT = WATER_EXP * (PCT_INITIAL_HARD / 100)
assertThat(indicatorValue(siteDTO, "INITIAL_TOTAL"), closeTo(12d, 0.001)); // INITIAL_TOTAL = INITIAL + INITIAL_HARD + INITIAL_SOFT
}
@Test
public void calculationsWithMissingValues() {
formClass = createFormClass();
int activityId = getLegacyIdFromCuid(formClass.getId());
SiteDTO newSite = newSite(activityId);
newSite.setIndicatorValue(fieldId("EXP"), 3);
newSite.setIndicatorValue(fieldId("WATER_ALLOC"), 400);
newSite.setIndicatorValue(fieldId("PCT_INITIAL"), 50);
//newSite.setIndicatorValue(fieldId("PCT_INITIAL_HARD"), 0);
newSite.setIndicatorValue(fieldId("PCT_INITIAL_SOFT"), 30);
CreateResult createSiteResult = execute(new CreateSite(newSite));
// let the client know the command has succeeded
newSite.setId(createSiteResult.getNewId());
PagingLoadResult<SiteDTO> loadResult = execute(GetSites.byId(newSite.getId()));
Assert.assertEquals(1, loadResult.getData().size());
SiteDTO siteDTO = loadResult.getData().get(0);
assertThat(indicatorValue(siteDTO, "WATER_EXP"), closeTo(12, 0)); // WATER_EXP = EXP * (WATER_ALLOC / 100)
assertThat(indicatorValue(siteDTO, "INITIAL"), closeTo(6, 0)); // INITIAL = WATER_EXP * (PCT_INITIAL / 100)
assertThat(indicatorValue(siteDTO, "INITIAL_HARD"), equalTo(0d)); // INITIAL_HARD = WATER_EXP * (PCT_INITIAL_HARD / 100)
assertThat(indicatorValue(siteDTO, "INITIAL_SOFT"), closeTo(3.6, 0.001)); // INITIAL_SOFT = WATER_EXP * (PCT_INITIAL_HARD / 100)
assertThat(indicatorValue(siteDTO, "INITIAL_TOTAL"), closeTo(9.6, 0.001)); // INITIAL_TOTAL = INITIAL + INITIAL_HARD + INITIAL_SOFT
}
@Test
public void pivot() throws IOException {
formClass = createFormClass();
int activityId = getLegacyIdFromCuid(formClass.getId());
double alloc[] = new double[] { 20, 10, 50, 20, 10 };
int year[] = new int[] { 2011, 2012, 2013};
AttributeGroupDTO group = getAttributeGroup(activityId);
List<Command> batch = Lists.newArrayList();
for(int i=0;i!=50;++i) {
SiteDTO newSite = newSite(activityId);
newSite.setDate1((new GregorianCalendar(year[i % year.length], 1, 1)).getTime());
newSite.setDate2((new GregorianCalendar(year[i % year.length], 1, 1)).getTime());
newSite.setIndicatorValue(fieldId("EXP"), (i % 10) * 1000);
newSite.setIndicatorValue(fieldId("WATER_ALLOC"), alloc[i % alloc.length]);
newSite.setIndicatorValue(fieldId("PCT_INITIAL"), 50);
newSite.setIndicatorValue(fieldId("PCT_INITIAL_HARD"), 20);
newSite.setIndicatorValue(fieldId("PCT_INITIAL_SOFT"), 30);
if(i % 2 == 0) {
newSite.setAttributeValue(group.getAttributes().get(0).getId(), true);
} else {
newSite.setAttributeValue(group.getAttributes().get(1).getId(), true);
}
batch.add(new CreateSite(newSite));
}
execute(new BatchCommand(batch));
// Export to excel
ActivityFormDTO activity = execute(new GetActivityForm(activityId));
SiteExporter exporter = new SiteExporter(getDispatcherSync());
exporter.export(activity, Filter.filter().onActivity(activityId));
exporter.done();
try(FileOutputStream fos = new FileOutputStream(Paths.get("target", "calcs.xls").toFile())) {
exporter.getBook().write(fos);
}
// Get a full sum
fullPivot(activityId);
pivotByYear(activityId);
pivotByAttributeGroup(activityId);
}
private PivotSites fullPivot(int activityId) {
PivotSites pivot = new PivotSites();
pivot.setDimensions(new Dimension(DimensionType.Indicator));
pivot.setFilter(Filter.filter().onActivity(activityId));
List<Bucket> buckets = execute(pivot).getBuckets();
System.out.println(Joiner.on("\n").join(buckets));
assertThat(buckets, hasItem(total("Expenditure", 225000)));
assertThat(buckets, hasItem(total("Expenditure on water programme", 48500)));
assertThat(buckets, hasItem(total("Value of Initial Cost - Not specified", 24250)));
assertThat(buckets, hasItem(total("Value of Initial Cost - Cap Hard", 9700)));
assertThat(buckets, hasItem(total("Value of Initial Cost – Cap Soft", 14550)));
assertThat(buckets, hasItem(total("Total Value of Initial Cost", 48500)));
return pivot;
}
private PivotSites pivotByYear(int activityId) {
PivotSites pivot = new PivotSites();
pivot.setDimensions(INDICATOR_DIMENSION, YEAR_DIMENSION);
pivot.setFilter(Filter.filter().onActivity(activityId));
List<Bucket> buckets = execute(pivot).getBuckets();
System.out.println(Joiner.on("\n").join(buckets));
//
// assertThat(buckets, hasItem(yearTotal("Expenditure", 225000)));
// assertThat(buckets, hasItem(total("Expenditure on water programme", 48500)));
// assertThat(buckets, hasItem(total("Value of Initial Cost - Not specified", 24250)));
// assertThat(buckets, hasItem(total("Value of Initial Cost - Cap Hard", 9700)));
// assertThat(buckets, hasItem(total("Value of Initial Cost – Cap Soft", 14550)));
// assertThat(buckets, hasItem(total("Total Value of Initial Cost", 48500)));
return pivot;
}
private PivotSites pivotByAttributeGroup(int activityId) {
AttributeGroupDTO group = getAttributeGroup(activityId);
PivotSites pivot = new PivotSites();
pivot.setDimensions(INDICATOR_DIMENSION, new AttributeGroupDimension(group.getId()));
pivot.setFilter(Filter.filter().onActivity(activityId));
List<Bucket> buckets = execute(pivot).getBuckets();
System.out.println(Joiner.on("\n").join(buckets));
//
// assertThat(buckets, hasItem(yearTotal("Expenditure", 225000)));
// assertThat(buckets, hasItem(total("Expenditure on water programme", 48500)));
// assertThat(buckets, hasItem(total("Value of Initial Cost - Not specified", 24250)));
// assertThat(buckets, hasItem(total("Value of Initial Cost - Cap Hard", 9700)));
// assertThat(buckets, hasItem(total("Value of Initial Cost – Cap Soft", 14550)));
// assertThat(buckets, hasItem(total("Total Value of Initial Cost", 48500)));
return pivot;
}
private AttributeGroupDTO getAttributeGroup(int activityId) {
ActivityFormDTO form = execute(new GetActivityForm(activityId));
return form.getAttributeGroups().get(0);
}
private Matcher<Bucket> total(final String label, final double sum) {
return new TypeSafeMatcher<Bucket>() {
@Override
protected boolean matchesSafely(Bucket item) {
DimensionCategory category = item.getCategory(INDICATOR_DIMENSION);
if(category == null) {
return false;
}
return category.getLabel().equals(label) && item.getSum() == sum;
}
@Override
public void describeTo(Description description) {
description.appendText("Bucket with indicator label ").appendValue(label).appendText(" and sum ")
.appendValue(sum);
}
};
}
private Matcher<Bucket> yearTotal(final String label, int year, final double sum) {
return new TypeSafeMatcher<Bucket>() {
@Override
protected boolean matchesSafely(Bucket item) {
DimensionCategory category = item.getCategory(INDICATOR_DIMENSION);
if(category == null) {
return false;
}
return category.getLabel().equals(label) && item.getSum() == sum;
}
@Override
public void describeTo(Description description) {
description.appendText("Bucket with indicator label ").appendValue(label).appendText(" and sum ")
.appendValue(sum);
}
};
}
private Double indicatorValue(SiteDTO siteDTO, String nameInExpression) {
int indicatorId = getLegacyIdFromCuid(field(nameInExpression).getId());
return siteDTO.getIndicatorValue(indicatorId);
}
private FormField field(String code) {
for (FormField field : formClass.getFields()) {
if (code.equals(field.getCode())) {
return field;
}
}
throw new RuntimeException("Enable to find field with code: " + code);
}
private int fieldId(String nameInExpression) {
return getLegacyIdFromCuid(field(nameInExpression).getId());
}
private SiteDTO newSite(int activityId) {
LocationDTO location = LocationDTOs.newLocation();
execute(new CreateLocation(location));
SiteDTO newSite = new SiteDTO();
newSite.setId(keyGenerator.generateInt());
newSite.setActivityId(activityId);
newSite.setLocationId(location.getId());
newSite.setPartner(new PartnerDTO(1, "Foobar"));
newSite.setReportingPeriodId(keyGenerator.generateInt());
newSite.setDate1((new GregorianCalendar(2014, 1, 1)).getTime());
newSite.setDate2((new GregorianCalendar(2014, 1, 1)).getTime());
return newSite;
}
private FormClass createFormClass() {
SchemaDTO schema = execute(new GetSchema());
UserDatabaseDTO db = schema.getDatabaseById(1);
LocationTypeDTO locType = schema.getCountryById(1).getLocationTypes().get(0);
ActivityFormDTO act = new ActivityFormDTO();
act.setName("Calculated indicators");
act.setLocationType(locType);
act.setReportingFrequency(ActivityFormDTO.REPORT_ONCE);
CreateResult createResult = execute(CreateEntity.Activity(db, act));
ResourceId classId = activityFormClass(createResult.getNewId());
ResourceLocatorAdaptor resourceLocator = new ResourceLocatorAdaptor(getDispatcher());
FormClass formClass = assertResolves(resourceLocator.getFormClass(classId));
FormField typeField = new FormField(quantityId());
typeField.setType(new EnumType(Cardinality.SINGLE,
Arrays.asList(new EnumItem(EnumItem.generateId(), "Budgeted"),
new EnumItem(EnumItem.generateId(), "Spent"))));
typeField.setLabel("Typology");
formClass.addElement(typeField);
FormField expField = new FormField(quantityId());
expField.setType(new QuantityType().setUnits("currency"));
expField.setLabel("Expenditure");
expField.setCode("EXP");
FormField waterAllocField = new FormField(quantityId());
waterAllocField.setType(new QuantityType().setUnits("%"));
waterAllocField.setLabel("Allocation watter programme");
waterAllocField.setCode("WATER_ALLOC");
FormField pctInitialField = new FormField(quantityId());
pctInitialField.setType(new QuantityType().setUnits("%"));
pctInitialField.setLabel("Initial Cost - Not specified");
pctInitialField.setCode("PCT_INITIAL");
FormField pctInitialHardField = new FormField(quantityId());
pctInitialHardField.setType(new QuantityType().setUnits("%"));
pctInitialHardField.setLabel("Initial Cost - Cap Hard");
pctInitialHardField.setCode("PCT_INITIAL_HARD");
FormField pctInitialSoftField = new FormField(quantityId());
pctInitialSoftField.setType(new QuantityType().setUnits("%"));
pctInitialSoftField.setLabel("Initial Cost - Cap Soft");
pctInitialSoftField.setCode("PCT_INITIAL_SOFT");
FormField pctExtensionField = new FormField(quantityId());
pctExtensionField.setType(new QuantityType().setUnits("%"));
pctExtensionField.setLabel("Extension Cost - Not specified");
pctExtensionField.setCode("PCT_EXTENSION");
FormField pctExtensionHardField = new FormField(quantityId());
pctExtensionHardField.setType(new QuantityType().setUnits("%"));
pctExtensionHardField.setLabel("Extension Cost - Hard");
pctExtensionHardField.setCode("PCT_EXTENSION_HARD");
FormField pctExtensionSoftField = new FormField(quantityId());
pctExtensionSoftField.setType(new QuantityType().setUnits("%"));
pctExtensionSoftField.setLabel("Extension Cost - Soft");
pctExtensionSoftField.setCode("PCT_EXTENSION_SOFT");
FormField pctOpField = new FormField(quantityId());
pctOpField.setType(new QuantityType().setUnits("%"));
pctOpField.setLabel("Operational Cost");
pctOpField.setCode("PCT_OP");
FormField pctMaintenanceField = new FormField(quantityId());
pctMaintenanceField.setType(new QuantityType().setUnits("%"));
pctMaintenanceField.setLabel("Maintenance Cost");
pctMaintenanceField.setCode("PCT_MAINTENANCE");
FormField pctOpMaintField = new FormField(quantityId());
pctOpMaintField.setType(new QuantityType().setUnits("%"));
pctOpMaintField.setLabel("Operational & Maintenance Cost");
pctOpMaintField.setCode("PCT_OP_MAINT");
FormField waterExpField = new FormField(quantityId());
waterExpField.setType(new QuantityType().setUnits("%"));
waterExpField.setLabel("Expenditure on water programme");
waterExpField.setCode("WATER_EXP");
waterExpField.setType(new CalculatedFieldType("{EXP}*({WATER_ALLOC}/100)"));
FormField initialField = new FormField(quantityId());
initialField.setType(new QuantityType().setUnits("%"));
initialField.setLabel("Value of Initial Cost - Not specified");
initialField.setCode("INITIAL");
initialField.setType(new CalculatedFieldType("{WATER_EXP}*({PCT_INITIAL}/100)"));
FormField initialHardField = new FormField(quantityId());
initialHardField.setType(new QuantityType().setUnits("%"));
initialHardField.setLabel("Value of Initial Cost - Cap Hard");
initialHardField.setCode("INITIAL_HARD");
initialHardField.setType(new CalculatedFieldType("{WATER_EXP}*({PCT_INITIAL_HARD}/100)"));
FormField initialSoftField = new FormField(quantityId());
initialSoftField.setType(new QuantityType().setUnits("%"));
initialSoftField.setLabel("Value of Initial Cost – Cap Soft");
initialSoftField.setCode("INITIAL_SOFT");
initialSoftField.setType(new CalculatedFieldType("{WATER_EXP}*({PCT_INITIAL_SOFT}/100)"));
FormField initialTotalField = new FormField(quantityId());
initialTotalField.setType(new QuantityType().setUnits("%"));
initialTotalField.setLabel("Total Value of Initial Cost");
initialTotalField.setCode("INITIAL_TOTAL");
initialTotalField.setType(new CalculatedFieldType("{INITIAL}+{INITIAL_HARD}+{INITIAL_SOFT}"));
formClass.addElement(expField);
formClass.addElement(waterAllocField);
formClass.addElement(pctInitialField);
formClass.addElement(pctInitialHardField);
formClass.addElement(pctInitialSoftField);
formClass.addElement(pctExtensionField);
formClass.addElement(pctExtensionHardField);
formClass.addElement(pctExtensionSoftField);
formClass.addElement(pctOpField);
formClass.addElement(pctMaintenanceField);
formClass.addElement(pctOpMaintField);
formClass.addElement(waterExpField);
formClass.addElement(initialField);
formClass.addElement(initialHardField);
formClass.addElement(initialSoftField);
formClass.addElement(initialTotalField);
assertResolves(resourceLocator.persist(formClass));
FormClass reform = assertResolves(resourceLocator.getFormClass(formClass.getId()));
assertHasFieldWithLabel(reform, "Expenditure");
assertHasFieldWithLabel(reform, "Allocation watter programme");
assertHasFieldWithLabel(reform, "Initial Cost - Not specified");
assertHasFieldWithLabel(reform, "Initial Cost - Cap Hard");
assertHasFieldWithLabel(reform, "Initial Cost - Cap Soft");
assertHasFieldWithLabel(reform, "Extension Cost - Not specified");
assertHasFieldWithLabel(reform, "Extension Cost - Hard");
assertHasFieldWithLabel(reform, "Extension Cost - Soft");
assertHasFieldWithLabel(reform, "Operational Cost");
assertHasFieldWithLabel(reform, "Maintenance Cost");
assertHasFieldWithLabel(reform, "Operational & Maintenance Cost");
assertHasFieldWithLabel(reform, "Expenditure on water programme");
assertHasFieldWithLabel(reform, "Value of Initial Cost - Not specified");
assertHasFieldWithLabel(reform, "Value of Initial Cost - Cap Hard");
assertHasFieldWithLabel(reform, "Value of Initial Cost – Cap Soft");
assertHasFieldWithLabel(reform, "Total Value of Initial Cost");
return reform;
}
private ResourceId quantityId() {
return ResourceId.generateFieldId(QuantityType.TYPE_CLASS);
}
private static FormField assertHasFieldWithLabel(FormClass formClass, String label) {
for (FormField field : formClass.getFields()) {
if (label.equals(field.getLabel())) {
return field;
}
}
throw new RuntimeException("No field with label: " + label + " found: " + Joiner.on("\n")
.join(formClass.getFields()));
}
}