Excel-like class to support calculations – Mortgage calculator demo

There are scenarios in which a project has the requirement of implementing a method that performs calculations and returns a list of calculated values; an example is a mortgage calculator that returns the amortization table. This article describes a mechanism that can be used to implement calculations. It demonstrates how to use a Table class by implementing a mortgage calculator.

For this type of requirement a Business Analyst (BA) may create a Microsoft Excel document with the inputs, columns, and formulas for the developer.

A Business Analyst can use Excel to represent requirements in the form of customized columns and formulas.

A Developer could use an in-memory calculated table to model an Excel Worksheet.

 

Demo Application

The demo application is implemented using C# and the source code can be found at http://sps.codeplex.com folder ComputedTable.

The solution ComputedTable has two projects:

  1.        ComputedTable.Lib: Computed Table framework
  2.        MortgageCalculator.Demo: Mortgage Calculator Demo

The class MortgageCalculator defines a mortgage calculator. The MortgageCalculator.Calculate() method takes an input object as parameter and returns an output object with the results. The result is printed to the console and the amortization table is saved to CSV and XML files using the methods Table.SaveToCsv() and Table.SaveToXml().

The input contains the properties purchase price, interest, loan amount, duration of the loan, loan start date, and the property tax.

The output contains the amortization table plus the monthly loan payments, total loan payments, total interest paid, and total monthly payments.

The MortgageCalculator.Calculate() uses a Table class to define the rows and columns and to perform the calculations.

 

Steps inside MortgageCalculator.Calculate()

  1.        Instantiate the output object and calculate the properties that do not depend on the amortization calculation.
  2.        Instantiate the objects used by the table class and instantiate the table.
  3.        Set the values and formulas used by the cells in the table.
  4.        Execute the calculation.
  5.        Set the output with the results of the calculation.
  6.        Save to CSV and XML file.

 

Defining and instantiating a Table object

 

Definition of the Table class:

 

public class Table<TRowId, TColumnId, TOperationContext> : ITable<TRowId, TColumnId>

       where TOperationContext : IOperationContext<TRowId, TColumnId>

 

 

TRowId is the type of the id used as row index. The demo uses an int because the payment number is used as row index.

TColumnId is the type of the id used as column index. The demo uses a string.

TOperationContext is the type of the context object.

 

The Table constructor takes three parameters:

 

public Table(IRowResolver<TRowId> rowResolver, IColumnResolver<TColumnId> columnResolver,

TOperationContext operationContext)

 

 

 

Instantiating the Table object:

 

var rowResolver = new RowResolver<int>(Enumerable.Range(1, input.DurationOfLoan).ToList());

var colResolver = new ColumnResolver<string>(AmortizationTableColumns.ColumnsWithOrderOfOperations);

var context = new AmortizationTableOperationContext { Input = input };

var table = new Table<int, string, AmortizationTableOperationContext>(rowResolver, colResolver, context);

 

 

rowResolver represents the rows in the table. The row resolver used in the demo defines one row per payment number.

colResolver represents the columns in the table. The columns are defined in the static AmortizationTableColumns.ColumnsWithOrderOfOperations.

operationContext is an object passed to each cell when performing the calculation. The operation context must inherit from OperationContex.

 

Accessing and defining cell values

 

A cell can be referenced by using an indexer that takes the rowId and colId in the Table.

 

Indexer property of the Table class:

 

public Cell<TRowId, TColumnId> this[TRowId row, TColumnId col]

 

 

Example of accessing the property tax paid in the payment number 10:

 

table[10, AmortizationTableColumns.PropertyTax]

 

 

The property Value of the cell returns the object that was assigned to the cell or the calculated value. Methods GetValueAsInt(), GetValueAsDecimal(), GetValueAsDateTime(), GetValueAsText() return the value as a specific type.

 

There are several ways to define the value of a cell.

  1.        Accessing one cell and assigning a value:

 

table[10, AmortizationTableColumns.PropertyTax].Value = 400;

 

 

  1.        Setting the values for all the cells in a specific column:

 

table.SetColumn(AmortizationTableColumns.PropertyTax, 400);

 

 

  1.        Passing a delegate as the formula to calculate the value of all the cells in a specific column:

 

table.SetColumnWithFormula(AmortizationTableColumns.PaymentNumber, (ctx) =>

           {

               return new CellOperationResult(ctx.CurrentCell.RowId);

           });

 

 

The delegate must be of type Func<TOperationContext, CellOperationResult>, it takes the parameter context; the context contains a reference to the cell being calculated. The delegate must return a type CellOperationResult. In the previous example, the value of each cell in the column Payment Number is set to the RowId of the cell.

  1.        Defining a class used to calculate the value of all the cells in a specific column.

 

table.SetColumnWithCalculator<PrincipalCalculator>(AmortizationTableColumns.Principal);

 

 

PrincipalCalculator implements the interface ICalculator. The method Execute() takes a context as parameter and returns a CellOperationResult.

There are a total of five implementers of ICalculators defined in the file ColumnCalculators.cs.

 

Executing the calculation

 

After all the cells have been defined with values, delegates, and/or classes, the method Table.Compute() forces the execution of the calculation for all cells traversing the table from left to right and top to bottom. This method returns true when the calculation in all cells succeeded. If the calculation failed in one of the cells, the method returns false and the CellOperationResult returned by the cell is exposed in the property Table.LastOperationResult.

Forcing the calculation and using the result:

 

var computeResult = table.Compute();

if (!computeResult)

{

output.Error = context.LastOperationResult.Message;

}

 

 

The table has three events that are raised for the calculation of each cell in the following order:

  1.        Table.BeforeCellValueComputed: raised before a cell is calculated.
  2.        Table.CellCalculationError: raised after a cell is calculated and the property CellOperationResult indicates that the calculation failed.
  3.        Table.AfterCellValueComputed: raised after a cell is calculated.

 

The three events receive the same event arguments type TableEventArgs<TRowId, TColumnId, TOperationContext>. The event arguments object has a reference to the operation context and a flag called CanContinue that allows the event handler to decide whether to continue performing the calculation with the rest of the cells.

 

In the event Table.CellCalculationError the flag CanContinue is already set to false. This sets the default behavior of the calculation. The default behavior is stopping the calculation if the calculation of a cell fails.

 

Operation Context

 

The operation context object is passed when calculating the value of each cell.

The operation context that is already predefined has the properties CurrentCell, LastOperationResult, and Table. Defining and using a descendant operation context allows adding custom properties that may be used during the cell calculations.

MortgageCalculatorOperationContext is a custom operation context with new properties:

  1.        Reference to the input of the calculation.
  2.        Properties to aggregate cell values during the calculation for calculating totals.
  3.        List of warnings to demonstrate how to generate metadata about the calculation process.

In the InterestCalculator the context’s property TotalInterestPaid is used to aggregate the values in the “Interest” column. TotalInterestPaid is compared with the inputs’ property MaxInterestPaidWarning and a warning is created when TotalInterestPaid is greater.

 

CellOperationResult

 

The CellOperationResult that is returned from a calculation delegate or from an ICalculator indicates whether the calculation succeeded or failed.

 

The OpeningBalanceCalculator demonstrates a calculation that may fail due to a business rule. When calculating the value for the row in the middle of the duration (duration of loan/2), the value of the cell is compared with the input’s property MaxBalanceAtMiddleDuration. If the value is greater the cell calculation fails specifying the reason.

Decrease the property MaxBalanceAtMiddleDuration of the input to force the condition and see how the calculation stops and a message is printed to the console.

 

Conclusion

 

This Table calculation mechanism can be use in for requirements in different industries.

Benefits:

  •          The Table class can be used to implement different calculation algorithms allowing a team to become familiar with a standardized framework.
  •          Facilitates communication between developers and non-developers. It is easy for a non-developer to understand how the calculation algorithm works when it resembles an Excel Worksheet.
  •          Debugging the calculation of a specific cell can be performed by adding a conditional breakpoint using the row id of the current cell in the condition.
  •          Defining the formula for the columns using a delegate or a separated class makes it easy for several developers to work simultaneously on different parts of the calculation.
  •          The delegates and classes that perform the calculation can be unit tested.
  •          The context object can be used for adding new properties to support custom calculation algorithms.

Last edited Jan 20, 2016 at 3:19 AM by valcarcel, version 5