Skip to content

Instantly share code, notes, and snippets.

@kaldas
Created April 11, 2016 00:34
Show Gist options
  • Save kaldas/dae22a89654d0067c01abc266076c050 to your computer and use it in GitHub Desktop.
Save kaldas/dae22a89654d0067c01abc266076c050 to your computer and use it in GitHub Desktop.
#define CONTRACTS_FULL
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using NUnit.Framework;
namespace EnergyQuoteService
{
class Program
{
static void Main(string[] args)
{
var quoteService = new QuoteService(new BasicRateCalculator());
PopulateDemoTariff(quoteService);
var quoteRequest = GetDemoQuoteRequest();
var cheapestQuote = quoteService.GetCheapestQuote(quoteRequest);
Console.WriteLine("Date: {0} Gas usage: {1} Electricity usage: {2} Cheapest tariff: {3} Annual cost: {4}",
cheapestQuote.QuoteCreationDate, cheapestQuote.GasUsage, cheapestQuote.ElectricityUsage, cheapestQuote.TariffName,
cheapestQuote.AnnualCost.ToString("F"));
Console.Read();
}
public static QuoteRequest GetDemoQuoteRequest()
{
var quoteRequest = new QuoteRequest
{
AnnualElectricityUsage = 4000,
AnnualGasUsage = 2000,
Email = "devjobs@energyhelpline.com",
ValidFrom = DateTime.Today
};
return quoteRequest;
}
public static void PopulateDemoTariff(QuoteService quoteService)
{
var energySaver = new Tariff()
{
Name = "Energy Saver",
GasInitialRate = 0.25M,
GasFinalRate = 0.50M,
ElectricityInitialRate = 0.30M,
ElectricityFinalRate = 0.60M,
InitialExpiryDate = new DateTime(2016, 06, 01)
};
var discountEnergy = new Tariff()
{
Name = "Discount Energy",
GasInitialRate = 0.20M,
GasFinalRate = 0.75M,
ElectricityInitialRate = 0.20M,
ElectricityFinalRate = 0.90M,
InitialExpiryDate = new DateTime(2016, 09, 15)
};
var standard = new Tariff()
{
Name = "Standard",
GasInitialRate = 0.65M,
ElectricityInitialRate = 0.80M,
};
var saveOnline = new Tariff()
{
Name = "Save Online",
GasInitialRate = 0.25M,
GasFinalRate = 0.60M,
ElectricityInitialRate = 0.10M,
ElectricityFinalRate = 1M,
InitialExpiryDate = new DateTime(2016, 08, 23)
};
quoteService.AddTariff(energySaver);
quoteService.AddTariff(discountEnergy);
quoteService.AddTariff(standard);
quoteService.AddTariff(saveOnline);
}
}
/// <summary>
/// The QuoteService class is responsible for providing the Cheapest Quote
/// </summary>
public class QuoteService
{
private readonly IRateCalculator _rateCalculator;
public List<Tariff> TariffList { get; }
private QuoteService() { /* this is a little neat trick to block incorrect instantiation :) */ }
/// <summary>
/// The default constructor for the QuoteService class
/// </summary>
/// <param name="rateCalculator"></param>
public QuoteService(IRateCalculator rateCalculator)
{
_rateCalculator = rateCalculator;
TariffList = new List<Tariff>();
}
/// <summary>
/// Adds a tariff to the list of known tariffs
/// </summary>
/// <param name="newTariff"></param>
public void AddTariff(Tariff newTariff)
{
TariffList.Add(newTariff);
}
/// <summary>
/// Clears the list of registered tariffs
/// </summary>
public void ClearTariffList()
{
TariffList.Clear();
}
/// <summary>
/// Gets the final cost for a quote based on a tariff
/// </summary>
/// <param name="quoteRequest"></param>
/// <param name="tariff"></param>
/// <returns></returns>
public decimal GetFinalCost(QuoteRequest quoteRequest, Tariff tariff)
{
var timeOnInitialRate = _rateCalculator.GetTimeOnInitialRate(quoteRequest.ValidFrom, tariff.InitialExpiryDate);
var initialElectricityRateUnitCost = _rateCalculator.GetRateUnitCost(timeOnInitialRate, quoteRequest.AnnualElectricityUsage, tariff.ElectricityInitialRate);
var initialGasRateUnitCost = _rateCalculator.GetRateUnitCost(timeOnInitialRate, quoteRequest.AnnualGasUsage, tariff.GasInitialRate);
var timeOnFinalUnitRate = _rateCalculator.GetTimeOnFinalRate(timeOnInitialRate);
var finalGasUnitRateCost = _rateCalculator.GetRateUnitCost(timeOnFinalUnitRate, quoteRequest.AnnualGasUsage, tariff.GasFinalRate);
var finalElectricityRateCost = _rateCalculator.GetRateUnitCost(timeOnFinalUnitRate,quoteRequest.AnnualElectricityUsage, tariff.ElectricityFinalRate);
var finalCost = initialElectricityRateUnitCost + initialGasRateUnitCost + finalElectricityRateCost + finalGasUnitRateCost;
return finalCost;
}
/// <summary>
/// Returns the cheapest quote for the desired quote request
/// </summary>
/// <param name="quoteRequest"></param>
/// <returns></returns>
public CheapestQuote GetCheapestQuote(QuoteRequest quoteRequest)
{
#region Contract-Pre-Condition
if (TariffList.Count == 0)
{
throw new ArgumentException("Cannot get cheapest tariff because there are no registered tariffs.");
}
#endregion
var cheapestQuote = new CheapestQuote();
foreach (var tariff in TariffList)
{
var finalCost = GetFinalCost(quoteRequest, tariff);
if(IsQuoteCheaper(cheapestQuote, finalCost))
{
cheapestQuote.AnnualCost = finalCost;
cheapestQuote.TariffName = tariff.Name;
cheapestQuote.ElectricityUsage = quoteRequest.AnnualElectricityUsage;
cheapestQuote.GasUsage = quoteRequest.AnnualGasUsage;
cheapestQuote.CustomerEmail = quoteRequest.Email;
}
}
return cheapestQuote;
}
/// <summary>
/// Returns true if a quote is cheaper
/// </summary>
/// <param name="cheapestQuote"></param>
/// <param name="finalCost"></param>
/// <returns></returns>
public bool IsQuoteCheaper(CheapestQuote cheapestQuote, decimal finalCost)
{
if (decimal.Compare(cheapestQuote.AnnualCost, finalCost) > 0)
{
return true;
}
if (cheapestQuote.TariffName == null)
{
return true;
}
return false;
}
}
/// <summary>
/// The interface IRateCalculator defines the contract for a Rate Calculator
/// </summary>
public interface IRateCalculator
{
decimal GetTimeOnInitialRate(DateTime startDate, DateTime endDate);
decimal GetTimeOnFinalRate(decimal daysInInitialRate);
decimal GetRateUnitCost(decimal days, decimal consumption, decimal rate);
}
/// <summary>
/// The BasicRateCalculator class is a concrete implementation containing the strategy
/// described in the technical exercise for calculating the quote.
/// </summary>
public sealed class BasicRateCalculator : IRateCalculator
{
/// <summary>
/// Gets the ammount of running days between the start and end date
/// </summary>
/// <param name="startDate"></param>
/// <param name="endDate"></param>
public decimal GetTimeOnInitialRate(DateTime startDate, DateTime endDate)
{
#region Contract-Post-Condition
Contract.Ensures(Contract.Result<decimal>() >= 0M);
#endregion
decimal timeOnInitialRate = 365;
if (endDate != DateTime.MinValue)
{
var timeSpan = endDate - startDate;
timeOnInitialRate = Convert.ToDecimal(timeSpan.TotalDays);
}
return timeOnInitialRate;
}
/// <summary>
/// Gets the ammount of running days until the end of the year
/// </summary>
/// <param name="daysInInitialRate"></param>
/// <returns></returns>
public decimal GetTimeOnFinalRate(decimal daysInInitialRate)
{
return 365M - daysInInitialRate;
}
/// <summary>
/// Gets the cost of the initial rate the client will pay
/// </summary>
/// <param name="days"></param>
/// <param name="consumption"></param>
/// <param name="rate"></param>
/// <returns></returns>
public decimal GetRateUnitCost(decimal days, decimal consumption, decimal rate)
{
return days / 365M * consumption * rate;
}
}
/// <summary>
/// The QuoteRequest class represents a request of a quote to the system
/// </summary>
public class QuoteRequest
{
#region Backing-Fields
private decimal _annualGasUsage;
private decimal _annualElectricityUsage;
private string _email;
#endregion
/// <summary>
/// The initial date this quote is valid from
/// </summary>
public DateTime ValidFrom { get; set; }
/// <summary>
/// The email address of the customer
/// </summary>
public string Email
{
get { return _email; }
set
{
#region Contract-Invariant
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException("The email address cannot be null or empty.", nameof(Email));
}
#endregion
_email = value;
}
}
/// <summary>
/// The annual gas usage of the customer
/// </summary>
public decimal AnnualGasUsage
{
get { return _annualGasUsage; }
set
{
#region Contract-Invariant
if (value < decimal.Zero)
{
throw new ArgumentOutOfRangeException(nameof(AnnualGasUsage), "The customer annual gas usage cannot be a negative value.");
}
#endregion
_annualGasUsage = value;
}
}
/// <summary>
/// The annual electricity usage of the customer
/// </summary>
public decimal AnnualElectricityUsage
{
get { return _annualElectricityUsage; }
set
{
#region Contract-Invariant
if (value < decimal.Zero)
{
throw new ArgumentOutOfRangeException(nameof(AnnualElectricityUsage), "The customer electricity usage cannot be a negative value.");
}
#endregion
_annualElectricityUsage = value;
}
}
}
/// <summary>
/// The Tariff class represents
/// </summary>
public class Tariff
{
/// <summary>
/// The name of the Tariff (i.e EnergySaver, Standard)
/// </summary>
public string Name { get; set; }
#region Backing-Fields
private decimal _gasInitialRate;
private decimal _gasFinalRate;
private decimal _electricityInitialRate;
private decimal _electricityFinalRate;
#endregion
/// <summary>
/// The initial gas rate of the tariff
/// </summary>
public decimal GasInitialRate
{
get { return _gasInitialRate; }
set
{
#region Contract-Invariant
if (value <= decimal.Zero)
{
throw new ArgumentOutOfRangeException(nameof(GasInitialRate), "The initial gas rate must be positive and higher than zero.");
}
#endregion
_gasInitialRate = value;
}
}
/// <summary>
/// The final gas rate of the tariff
/// </summary>
public decimal GasFinalRate
{
get { return _gasFinalRate; }
set
{
#region Contract-Invariant
if (value <= decimal.Zero)
{
throw new ArgumentOutOfRangeException(nameof(GasFinalRate), "The final gas rate must be positive and higher than zero.");
}
#endregion
_gasFinalRate = value;
}
}
/// <summary>
/// The initial electricity rate of the tariff
/// </summary>
public decimal ElectricityInitialRate
{
get { return _electricityInitialRate; }
set
{
#region Contract-Invariant
if (value <= decimal.Zero)
{
throw new ArgumentOutOfRangeException(nameof(ElectricityInitialRate), "The electricity initial gas rate must be positive and higher than zero.");
}
#endregion
_electricityInitialRate = value;
}
}
/// <summary>
/// The final electricity rate of the tariff
/// </summary>
public decimal ElectricityFinalRate
{
get { return _electricityFinalRate; }
set
{
#region Contract-Invariant
if (value <= decimal.Zero)
{
throw new ArgumentOutOfRangeException(nameof(ElectricityFinalRate), "The electricity final gas rate must be positive and higher than zero.");
}
#endregion
_electricityFinalRate = value;
}
}
/// <summary>
/// The date when this tariff will initially expire
/// </summary>
public DateTime InitialExpiryDate { get; set; }
}
/// <summary>
/// The CheapestQuote class is a simple Value Object for returning the cheapest tariff
/// </summary>
public class CheapestQuote
{
public CheapestQuote()
{
QuoteCreationDate = DateTime.Now;
}
/// <summary>
/// The name of the Cheapest Tariff
/// </summary>
public string TariffName { get; set; }
/// <summary>
/// The email address of the Customer
/// </summary>
public string CustomerEmail { get; set; }
/// <summary>
/// The annual cost of the tariff
/// </summary>
public decimal AnnualCost { get; set; }
/// <summary>
/// The electricity usage of the client
/// </summary>
public decimal ElectricityUsage { get; set; }
/// <summary>
/// The gas usage of the client
/// </summary>
public decimal GasUsage { get; set; }
/// <summary>
/// The date when the quote was generated
/// </summary>
public DateTime QuoteCreationDate { get; }
}
[TestFixture]
public class TestQuoteService
{
private QuoteService _quoteService;
private QuoteRequest _testQuoteRequest;
private Tariff _testTariff;
[OneTimeSetUp]
public void SetUp()
{
_quoteService = new QuoteService(new BasicRateCalculator());
_testQuoteRequest = new QuoteRequest()
{
Email = "rod@kaldas.io",
AnnualGasUsage = 1500M,
AnnualElectricityUsage = 3000M,
ValidFrom = new DateTime(2012, 12, 01)
};
_testTariff = new Tariff()
{
Name = "Example Saver",
GasInitialRate = 0.25M,
GasFinalRate = 0.50M,
ElectricityInitialRate = 0.30M,
ElectricityFinalRate = 0.60M,
InitialExpiryDate = new DateTime(2013, 06, 01)
};
}
[Test]
public void TestGetFinalCost()
{
var finalCost = _quoteService.GetFinalCost(_testQuoteRequest, _testTariff);
Assert.AreEqual("1914.25", finalCost.ToString("F"));
}
[Test]
public void TestExceptionEmptyTariffList()
{
_quoteService.ClearTariffList();
Assert.Throws<ArgumentException>(() => _quoteService.GetCheapestQuote(_testQuoteRequest));
}
[Test]
public void TestGetCheapestRate()
{
var quoteRequest = new QuoteRequest()
{
Email = "rod@kaldas.io",
AnnualGasUsage = 1500M,
AnnualElectricityUsage = 3000M,
ValidFrom = new DateTime(2014, 01, 01)
};
var energySaver = new Tariff()
{
Name = "Energy Saver",
GasInitialRate = 0.25M,
GasFinalRate = 0.50M,
ElectricityInitialRate = 0.30M,
ElectricityFinalRate = 0.60M,
InitialExpiryDate = new DateTime(2014, 06, 01)
};
var discountEnergy = new Tariff()
{
Name = "Discount Energy",
GasInitialRate = 0.20M,
GasFinalRate = 0.75M,
ElectricityInitialRate = 0.20M,
ElectricityFinalRate = 0.90M,
InitialExpiryDate = new DateTime(2014, 09, 15)
};
var standard = new Tariff()
{
Name = "Standard",
GasInitialRate = 0.65M,
ElectricityInitialRate = 0.80M,
};
var saveOnline = new Tariff()
{
Name = "Save Online",
GasInitialRate = 0.25M,
GasFinalRate = 0.60M,
ElectricityInitialRate = 0.10M,
ElectricityFinalRate = 1M,
InitialExpiryDate = new DateTime(2014, 08, 23)
};
_quoteService.AddTariff(energySaver);
_quoteService.AddTariff(discountEnergy);
_quoteService.AddTariff(standard);
_quoteService.AddTariff(saveOnline);
var cheapestQuote = _quoteService.GetCheapestQuote(quoteRequest);
Assert.AreEqual(cheapestQuote.TariffName, "Discount Energy");
}
[Test]
public void TestIsQuoteCheaperTrue()
{
var cheapestQuote = new CheapestQuote();
cheapestQuote.TariffName = "Test";
cheapestQuote.AnnualCost = 200M;
Assert.IsTrue(_quoteService.IsQuoteCheaper(cheapestQuote, 100M));
}
[Test]
public void TestIsQuoteCheaperFalse()
{
var cheapestQuote = new CheapestQuote();
cheapestQuote.TariffName = "Test";
cheapestQuote.AnnualCost = 200M;
Assert.IsFalse(_quoteService.IsQuoteCheaper(cheapestQuote, 300M));
}
[Test]
public void TestIsQuoteCheaperThenBlankQuote()
{
var cheapestQuote = new CheapestQuote();
Assert.IsTrue(_quoteService.IsQuoteCheaper(cheapestQuote, 100M));
}
}
[TestFixture]
public class TestQuoteRequest
{
private QuoteRequest _quoteRequest;
[OneTimeSetUp]
public void SetUp()
{
_quoteRequest = new QuoteRequest();
}
[Test]
public void TestExceptionInvalidEmail()
{
Assert.Throws<ArgumentException>(() => _quoteRequest.Email = "");
}
[Test]
public void TestExceptionAnnualGasUsageOutOfRange()
{
Assert.Throws<ArgumentOutOfRangeException>(() => _quoteRequest.AnnualGasUsage = -1M);
}
[Test]
public void TestExceptionAnnualElectricityUsageOutOfRange()
{
Assert.Throws<ArgumentOutOfRangeException>(() => _quoteRequest.AnnualElectricityUsage = -1M);
}
[Test]
public void TestEmail()
{
_quoteRequest.Email = "rod@kaldas.io";
Assert.AreEqual("rod@kaldas.io", _quoteRequest.Email);
}
}
[TestFixture]
public class TestBasicRateCalculator
{
private BasicRateCalculator _basicRateCalculator;
[OneTimeSetUp]
public void SetUp()
{
_basicRateCalculator = new BasicRateCalculator();
}
[Test]
public void TestGetTimeOnInitialRate()
{
var startDate = new DateTime(2016, 01, 01);
var endDate = new DateTime(2016, 09, 05);
Assert.AreEqual(248M, _basicRateCalculator.GetTimeOnInitialRate(startDate, endDate));
}
[Test]
public void TestGetTimeOnFinalRate()
{
Assert.AreEqual(183M, _basicRateCalculator.GetTimeOnFinalRate(182));
}
[Test]
public void TestGetRateUnitCost()
{
var rate = _basicRateCalculator.GetRateUnitCost(182M, 1500M, 0.25M);
var rateFormatted = rate.ToString("F");
Assert.AreEqual("186.99", rateFormatted);
}
}
[TestFixture]
public class TestTariff
{
private Tariff _tariff;
[OneTimeSetUp]
public void SetUp()
{
_tariff = new Tariff();
}
[Test]
public void TestExceptionSetGasInitialRateOutOfRange()
{
Assert.Throws<ArgumentOutOfRangeException>(() => _tariff.GasInitialRate = 0);
}
[Test]
public void TestExceptionSetGasFinalRateOutOfRange()
{
Assert.Throws<ArgumentOutOfRangeException>(() => _tariff.GasFinalRate = 0);
}
[Test]
public void TestExceptionSetElectricityInitialRateOutOfRange()
{
Assert.Throws<ArgumentOutOfRangeException>(() => _tariff.ElectricityInitialRate = 0);
}
[Test]
public void TestExceptionSetElectricityFinalRateOutOfRange()
{
Assert.Throws<ArgumentOutOfRangeException>(() => _tariff.ElectricityFinalRate = 0);
}
[Test]
public void TestGetGasInitialRate()
{
Assert.AreEqual(_tariff.GasInitialRate, default(decimal));
}
[Test]
public void TestGetGasFinalRate()
{
Assert.AreEqual(_tariff.GasFinalRate, default(decimal));
}
[Test]
public void TestGetElectricityInitialRate()
{
_tariff.ElectricityInitialRate = 1;
Assert.AreEqual(_tariff.ElectricityInitialRate, 1);
}
[Test]
public void TestGetElectricityFinalRate()
{
Assert.AreEqual(_tariff.ElectricityFinalRate, default(decimal));
}
[Test]
public void TestGetSetName()
{
_tariff.Name = "TestTariff";
Assert.AreEqual(_tariff.Name, "TestTariff");
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment