/* ====================================================================
* Limited Evaluation License:
*
* This software is open source, but licensed. The license with this package
* is an evaluation license, which may not be used for productive systems. If
* you want a full license, please contact us.
*
* The exclusive owner of this work is the OpenRate project.
* This work, including all associated documents and components
* is Copyright of the OpenRate project 2006-2015.
*
* The following restrictions apply unless they are expressly relaxed in a
* contractual agreement between the license holder or one of its officially
* assigned agents and you or your organisation:
*
* 1) This work may not be disclosed, either in full or in part, in any form
* electronic or physical, to any third party. This includes both in the
* form of source code and compiled modules.
* 2) This work contains trade secrets in the form of architecture, algorithms
* methods and technologies. These trade secrets may not be disclosed to
* third parties in any form, either directly or in summary or paraphrased
* form, nor may these trade secrets be used to construct products of a
* similar or competing nature either by you or third parties.
* 3) This work may not be included in full or in part in any application.
* 4) You may not remove or alter any proprietary legends or notices contained
* in or on this work.
* 5) This software may not be reverse-engineered or otherwise decompiled, if
* you received this work in a compiled form.
* 6) This work is licensed, not sold. Possession of this software does not
* imply or grant any right to you.
* 7) You agree to disclose any changes to this work to the copyright holder
* and that the copyright holder may include any such changes at its own
* discretion into the work
* 8) You agree not to derive other works from the trade secrets in this work,
* and that any such derivation may make you liable to pay damages to the
* copyright holder
* 9) You agree to use this software exclusively for evaluation purposes, and
* that you shall not use this software to derive commercial profit or
* support your business or personal activities.
*
* This software is provided "as is" and any expressed or impled warranties,
* including, but not limited to, the impled warranties of merchantability
* and fitness for a particular purpose are disclaimed. In no event shall
* The OpenRate Project or its officially assigned agents be liable to any
* direct, indirect, incidental, special, exemplary, or consequential damages
* (including but not limited to, procurement of substitute goods or services;
* Loss of use, data, or profits; or any business interruption) however caused
* and on theory of liability, whether in contract, strict liability, or tort
* (including negligence or otherwise) arising in any way out of the use of
* this software, even if advised of the possibility of such damage.
* This software contains portions by The Apache Software Foundation, Robert
* Half International.
* ====================================================================
*/
package OpenRate.process;
import OpenRate.cache.ICacheManager;
import OpenRate.cache.RateCache;
import OpenRate.exception.InitializationException;
import OpenRate.exception.ProcessingException;
import OpenRate.record.IRecord;
import OpenRate.record.RateMapEntry;
import OpenRate.record.RatingBreakdown;
import OpenRate.record.RatingResult;
import OpenRate.resource.CacheFactory;
import OpenRate.utils.PropertyUtils;
import java.util.ArrayList;
/**
* Please
* <a target='new' href='http://www.open-rate.com/wiki/index.php?title=Rate_Calculation'>click
* here</a> to go to wiki page.
* <br>
* <p>
* This class provides the abstract base for a rating plug in. A raw rate object
* is retrieved from the RateCache object, and this class provides the
* primitives required for performing rating.
*/
public abstract class AbstractRateCalc extends AbstractPlugIn {
// This is the object will be using the find the cache manager
private ICacheManager CMR = null;
// The zone model object
private RateCache RC;
// -----------------------------------------------------------------------------
// ------------------ Start of inherited Plug In functions ---------------------
// -----------------------------------------------------------------------------
/**
* Initialise the module. Called during pipeline creation to initialise: -
* Configuration properties that are defined in the properties file. - The
* references to any cache objects that are used in the processing - The
* symbolic name of the module
*
* @param PipelineName The name of the pipeline this module is in
* @param ModuleName The name of this module in the pipeline
* @throws OpenRate.exception.InitializationException
*/
@Override
public void init(String PipelineName, String ModuleName)
throws InitializationException {
String CacheObjectName;
// Do the inherited work, e.g. setting the symbolic name etc
super.init(PipelineName, ModuleName);
// Get the cache object reference
CacheObjectName = PropertyUtils.getPropertyUtils().getPluginPropertyValue(PipelineName,
ModuleName,
"DataCache");
CMR = CacheFactory.getGlobalManager(CacheObjectName);
if (CMR == null) {
message = "Could not find cache entry for <" + CacheObjectName + ">";
throw new InitializationException(message, getSymbolicName());
}
// Load up the mapping arrays, but only if we are the right type. This
// allows us to build up ever more complex rating models, matching the
// right model to the right cache
if (CMR.get(CacheObjectName) instanceof RateCache) {
RC = (RateCache) CMR.get(CacheObjectName);
if (RC == null) {
message = "Could not find cache entry for <" + CacheObjectName + ">";
throw new InitializationException(message, getSymbolicName());
}
}
}
// -----------------------------------------------------------------------------
// ------------------ Start of inherited Plug In functions ---------------------
// -----------------------------------------------------------------------------
/**
* This is called when the synthetic Header record is encountered, and has the
* meaning that the stream is starting.
*
* @return
*/
@Override
public IRecord procHeader(IRecord r) {
return r;
}
/**
* This is called when the synthetic trailer record is encountered, and has
* the meaning that the stream is now finished.
*
* @return
*/
@Override
public IRecord procTrailer(IRecord r) {
return r;
}
// -----------------------------------------------------------------------------
// ------------------------ Start of utiity functions --------------------------
// -----------------------------------------------------------------------------
/**
* This function does the rating based on the chosen price model and the RUM
* (Rateable Usage Metric) value. The model processes all of the tiers up to
* the value of the RUM, calculating the cost for each tier and summing the
* tier costs. This is different to the "threshold" mode where all of the RUM
* is used from the tier that is reached.
*
* @param priceModel The price model to use
* @param valueToRate The value to rate
* @param valueOffset The offset for the start of the tier, if there is one
* @param cdrDate The date to use for price model version selection
* @return the price for the rated record
* @throws OpenRate.exception.ProcessingException
*/
public double rateCalculateTiered(String priceModel, double valueToRate, double valueOffset, long cdrDate)
throws ProcessingException {
ArrayList<RateMapEntry> tmpRateModel;
RatingResult tmpRatingResult;
// Look up the rate model to use
tmpRateModel = RC.getPriceModel(priceModel);
// perform the rating using the selected rate model
tmpRatingResult = performRateEvaluationTiered(priceModel, tmpRateModel, valueToRate, valueOffset, cdrDate, false);
// return the rated value
return tmpRatingResult.RatedValue;
}
/**
* This function does the rating based on the chosen price model and the RUM
* (Rateable Usage Metric) value. The model locates the correct tier to use
* and then rates all of the RUM according to that tier. This is different to
* the "tiered" mode, where the individual contributing tier costs are
* calculated and then summed.
*
* @param priceModel The price model to use
* @param valueToRate The value to rate
* @param valueOffset The offset for the start of the tier, if there is one
* @param CDRDate The date to use for price model version selection
* @return the price for the rated record
* @throws OpenRate.exception.ProcessingException
*/
public double rateCalculateThreshold(String priceModel, double valueToRate, double valueOffset, long CDRDate)
throws ProcessingException {
ArrayList<RateMapEntry> tmpRateModel;
RatingResult tmpRatingResult;
// Look up the rate model to use
tmpRateModel = RC.getPriceModel(priceModel);
// perform the rating using the selected rate model
tmpRatingResult = performRateEvaluationThreshold(priceModel, tmpRateModel, valueToRate, valueOffset, CDRDate, false);
// return the rated value
return tmpRatingResult.RatedValue;
}
/**
* This function does the rating based on the chosen price model and the RUM
* (Rateable Usage Metric) value. It is a simplified version of the "tiered"
* model that just does a multiplication of valueToRate*Rate, without having
* to calculate tiers. This of course does not support singularity rating.
*
* @param priceModel The price model to use
* @param valueToRate the duration that should be rated in seconds
* @param CDRDate The date to use for price model version selection
* @return the price for the rated record
* @throws OpenRate.exception.ProcessingException
*/
public double rateCalculateFlat(String priceModel, double valueToRate, long CDRDate)
throws ProcessingException {
ArrayList<RateMapEntry> tmpRateModel;
RatingResult tmpRatingResult;
// Look up the rate model to use
tmpRateModel = RC.getPriceModel(priceModel);
// perform the rating using the selected rate model
tmpRatingResult = performRateEvaluationFlat(priceModel, tmpRateModel, valueToRate, CDRDate, false);
// return the rated value
return tmpRatingResult.RatedValue;
}
/**
* This function does the rating based on the chosen price model and the RUM
* (Rateable Usage Metric) value. It is a simplified version of the "tiered"
* model that just does returns the event price.
*
* @param priceModel The price model to use
* @param CDRDate The date to use for price model version selection
* @param valueToRate The value to rate for
* @return the price for the rated record
* @throws OpenRate.exception.ProcessingException
*/
public double rateCalculateEvent(String priceModel, long valueToRate, long CDRDate)
throws ProcessingException {
ArrayList<RateMapEntry> tmpRateModel;
RatingResult tmpRatingResult;
// Look up the rate model to use
tmpRateModel = RC.getPriceModel(priceModel);
// perform the rating using the selected rate model
tmpRatingResult = performRateEvaluationEvent(priceModel, tmpRateModel, valueToRate, CDRDate, false);
// return the rated value
return tmpRatingResult.RatedValue;
}
/**
* This method is used to calculate the number of RUM units (e.g. seconds)
* which can be purchased for the available credit. The credit is calculated
* from the difference between the current balance and the credit limit. In
* pre-paid scenarios, the current balance will tend to be > 0 and the credit
* limit will tend to be 0. In post paid scenarios, both values will tend to
* be negative.
*
* The clever thing about this method is the fact that it uses the standard
* rating price model in order to arrive at the value, simplifying greatly the
* management of pre-paid balances.
*
* This method uses the TIERED rating model.
*
* @param priceModel The price model to use
* @param availableBalance The current balance the user has available to them,
* positive
* @param CDRDate The date to rate at
* @return The number of RUM units that can be purchased for the available
* balance
* @throws ProcessingException
*/
public double authCalculateTiered(String priceModel, double availableBalance, long CDRDate)
throws ProcessingException {
if (availableBalance <= 0) {
return 0;
}
ArrayList<RateMapEntry> tmpRateModel;
double tmpcalculationResult;
// Look up the rate model to use
tmpRateModel = RC.getPriceModel(priceModel);
// perform the calculation using the selected rate model
tmpcalculationResult = performAuthEvaluationTiered(priceModel, tmpRateModel, availableBalance, CDRDate);
return tmpcalculationResult;
}
/**
* This method is used to calculate the number of RUM units (e.g. seconds)
* which can be purchased for the available credit. The credit is calculated
* from the difference between the current balance and the credit limit. In
* pre-paid scenarios, the current balance will tend to be > 0 and the credit
* limit will tend to be 0. In post paid scenarios, both values will tend to
* be negative.
*
* The clever thing about this method is the fact that it uses the standard
* rating price model in order to arrive at the value, simplifying greatly the
* management of pre-paid balances.
*
* This method uses the THRESHOLD rating model.
*
* @param priceModel The price model to use
* @param availableBalance The current balance the user has available to them,
* positive
* @param CDRDate The date to rate at
* @return The number of RUM units that can be purchased for the available
* balance
* @throws ProcessingException
*/
public double authCalculateThreshold(String priceModel, double availableBalance, long CDRDate)
throws ProcessingException {
if (availableBalance <= 0) {
return 0;
}
ArrayList<RateMapEntry> tmpRateModel;
double tmpcalculationResult;
// Look up the rate model to use
tmpRateModel = RC.getPriceModel(priceModel);
// perform the calculation using the selected rate model
tmpcalculationResult = performAuthEvaluationThreshold(priceModel, tmpRateModel, availableBalance, CDRDate);
return tmpcalculationResult;
}
/**
* This method is used to calculate the number of RUM units (e.g. seconds)
* which can be purchased for the available credit. The credit is calculated
* from the difference between the current balance and the credit limit. In
* pre-paid scenarios, the current balance will tend to be > 0 and the credit
* limit will tend to be 0. In post paid scenarios, both values will tend to
* be negative.
*
* The clever thing about this method is the fact that it uses the standard
* rating price model in order to arrive at the value, simplifying greatly the
* management of pre-paid balances.
*
* This method uses the FLAT rating model.
*
* @param priceModel The price model to use
* @param availableBalance The current balance the user has available to them,
* positive
* @param CDRDate The date to rate at
* @return The number of RUM units that can be purchased for the available
* balance
* @throws ProcessingException
*/
public double authCalculateFlat(String priceModel, double availableBalance, long CDRDate)
throws ProcessingException {
if (availableBalance <= 0) {
return 0;
}
ArrayList<RateMapEntry> tmpRateModel;
double tmpcalculationResult;
// Look up the rate model to use
tmpRateModel = RC.getPriceModel(priceModel);
// perform the calculation using the selected rate model
tmpcalculationResult = performAuthEvaluationFlat(priceModel, tmpRateModel, availableBalance, CDRDate);
return tmpcalculationResult;
}
/**
* This method is used to calculate the number of RUM units (e.g. seconds)
* which can be purchased for the available credit. The credit is calculated
* from the difference between the current balance and the credit limit. In
* pre-paid scenarios, the current balance will tend to be > 0 and the credit
* limit will tend to be 0. In post paid scenarios, both values will tend to
* be negative.
*
* The clever thing about this method is the fact that it uses the standard
* rating price model in order to arrive at the value, simplifying greatly the
* management of pre-paid balances.
*
* This method uses the EVENT rating model.
*
* @param priceModel The price model to use
* @param availableBalance The current balance the user has available to them,
* positive
* @param CDRDate The date to rate at
* @return The number of RUM units that can be purchased for the available
* balance
* @throws ProcessingException
*/
public double authCalculateEvent(String priceModel, double availableBalance, long CDRDate)
throws ProcessingException {
if (availableBalance <= 0) {
return 0;
}
ArrayList<RateMapEntry> tmpRateModel;
double tmpRatingResult;
// Look up the rate model to use
tmpRateModel = RC.getPriceModel(priceModel);
// perform the calculation using the selected rate model
tmpRatingResult = performAuthEvaluationEvent(priceModel, tmpRateModel, availableBalance, CDRDate);
return tmpRatingResult;
}
/**
* Performs the rating calculation of the value given at the CDR date using
* the given rating model. Tiered splits the value to be rated up into
* segments according to the steps defined and rates each step individually,
* summing up the charges from all steps.
*
* TIERED Calculation SINGULARITY Yes BEAT BASED Yes CHARGE BASE Yes STEP
* FROM-TO Yes RATING beatCount * factor * beat / chargeBase;
*
* @param PriceModel The price model name we are using
* @param tmpRateModel The price model definition
* @param valueToRate The value to rate
* @param valueOffset The offset for the start of the tier, if there is one
* @param CDRDate The date to rate at
* @param BreakDown Produce a charge breakdown or not
* @return The rating result
* @throws OpenRate.exception.ProcessingException
*/
protected RatingResult performRateEvaluationTiered(String PriceModel, ArrayList<RateMapEntry> tmpRateModel, double valueToRate, double valueOffset, long CDRDate, boolean BreakDown) throws ProcessingException {
RatingResult tmpRatingResult = new RatingResult();
int index = 0;
double AllTiersValue = 0;
RatingBreakdown tmpBreakdown;
// check that we have something to work on
if (tmpRateModel == null) {
throw new ProcessingException("Price Model <" + PriceModel + "> not defined", getSymbolicName());
}
// For multi-packet rating, we have to apply the offset
double effectiveValueToRate = valueToRate + valueOffset;
// set the default value
double rumValueUsed = 0;
double rumValueUsedOffset = 0;
double roundedRUMUsed = 0;
tmpRatingResult.RatedValue = 0;
tmpRatingResult.RUMUsed = 0;
// We need to loop through all the tiers until we have finshed
// consuming all the rateable input
while (index < tmpRateModel.size()) {
// Get the root price model
RateMapEntry tmpEntry = tmpRateModel.get(index);
// Get the validty for this cdr
tmpEntry = getRateModelEntryForTime(tmpEntry, CDRDate);
// Validate that we have something we can work with
if (tmpEntry == null) {
message = "CDR with <" + CDRDate + "> date not rated by model <"
+ PriceModel + "> because of missing validity coverage";
throw new ProcessingException(message, getSymbolicName());
}
// For positive rating cases
double thisTierValue;
double thisTierRUMUsed = 0;
long thisTierBeatCount = 0;
double thisTierRoundedRUM;
// For offset rating cases
double thisTierOffsetRUMUsed = 0;
long thisTierOffsetBeatCount = 0;
// ********************* Deal with the rating value **********************
// See if this event crosses the lower tier threshold
if (effectiveValueToRate > tmpEntry.getFrom()) {
// see if we use all of the tier
if (effectiveValueToRate >= tmpEntry.getTo()) {
// Calculate the amount in this tier
thisTierRUMUsed = (tmpEntry.getTo() - tmpEntry.getFrom());
rumValueUsed += thisTierRUMUsed;
// Get the number of beats in this tier
thisTierBeatCount = Math.round(thisTierRUMUsed / tmpEntry.getBeat());
// Deal with unfinished beats
if ((thisTierRUMUsed - thisTierBeatCount * tmpEntry.getBeat()) > 0) {
thisTierBeatCount++;
}
// Deal with the empty beat
if (thisTierBeatCount == 0) {
thisTierBeatCount += 1;
}
} else {
// Partial tier to do, and then we have finished
thisTierRUMUsed = (effectiveValueToRate - tmpEntry.getFrom());
rumValueUsed += thisTierRUMUsed;
thisTierBeatCount = Math.round(thisTierRUMUsed / tmpEntry.getBeat());
// Deal with unfinished beats
if ((thisTierRUMUsed - thisTierBeatCount * tmpEntry.getBeat()) > 0) {
thisTierBeatCount++;
}
// Deal with the empty beat
if (thisTierBeatCount == 0) {
thisTierBeatCount = 1;
}
}
}
// *********************** Deal with the offset ************************
if (valueOffset != 0) {
// See if this event crosses the lower tier threshold
if (valueOffset > tmpEntry.getFrom()) {
// see if we use all of the tier
if (valueOffset >= tmpEntry.getTo()) {
// Calculate the amount in this tier
thisTierOffsetRUMUsed = (tmpEntry.getTo() - tmpEntry.getFrom());
rumValueUsedOffset += thisTierOffsetRUMUsed;
// Get the number of beats in this tier
thisTierOffsetBeatCount = Math.round(thisTierOffsetRUMUsed / tmpEntry.getBeat());
// Deal with unfinished beats
if ((thisTierOffsetRUMUsed - thisTierOffsetBeatCount * tmpEntry.getBeat()) > 0) {
thisTierOffsetBeatCount++;
}
// Deal with the empty beat
if (thisTierOffsetBeatCount == 0) {
thisTierOffsetBeatCount = 1;
}
} else {
// Partial tier to do, and then we have finished
thisTierOffsetRUMUsed = (valueOffset - tmpEntry.getFrom());
rumValueUsedOffset += thisTierOffsetRUMUsed;
thisTierOffsetBeatCount = Math.round(thisTierOffsetRUMUsed / tmpEntry.getBeat());
// Deal with unfinished beats
if ((thisTierOffsetRUMUsed - thisTierOffsetBeatCount * tmpEntry.getBeat()) > 0) {
thisTierOffsetBeatCount++;
}
// Deal with the empty beat
if (thisTierOffsetBeatCount == 0) {
thisTierOffsetBeatCount = 1;
}
}
}
}
// Now roll up the rating values
thisTierRoundedRUM = (thisTierBeatCount - thisTierOffsetBeatCount) * tmpEntry.getBeat();
thisTierValue = (thisTierRoundedRUM * tmpEntry.getFactor()) / tmpEntry.getChargeBase();
// Only count rounded RUM used for non-singularity steps, otherwise we count more than once
if (tmpEntry.getFrom() != tmpEntry.getTo()) {
roundedRUMUsed += thisTierRoundedRUM;
}
// provide the rating breakdown if it is required
if (BreakDown) {
// initialise the breakdown if necessary
if (tmpRatingResult.breakdown == null) {
tmpRatingResult.breakdown = new ArrayList<>();
}
// provide the charging breakdown
tmpBreakdown = new RatingBreakdown();
tmpBreakdown.beat = tmpEntry.getBeat();
tmpBreakdown.beatCount = thisTierBeatCount - thisTierOffsetBeatCount;
tmpBreakdown.factor = tmpEntry.getFactor();
tmpBreakdown.chargeBase = tmpEntry.getChargeBase();
tmpBreakdown.ratedAmount = thisTierValue;
tmpBreakdown.RUMRated = thisTierRUMUsed - thisTierOffsetRUMUsed;
tmpBreakdown.stepUsed = index;
tmpBreakdown.tierFrom = tmpEntry.getFrom();
tmpBreakdown.tierTo = tmpEntry.getTo();
tmpBreakdown.validFrom = tmpEntry.getStartTime();
// Store the breakdown
tmpRatingResult.breakdown.add(tmpBreakdown);
}
// Increment the tier counter
index++;
// Accumulate the tier value
AllTiersValue += thisTierValue;
}
// Roll up the final values
tmpRatingResult.RatedValue = AllTiersValue;
tmpRatingResult.RUMUsed = rumValueUsed - rumValueUsedOffset;
tmpRatingResult.RUMUsedRounded = roundedRUMUsed;
// return OK
return tmpRatingResult;
}
/**
* Performs the rating calculation of the value given at the CDR date using
* the given rating model. Threshold calculation locates the step that covers
* the maximum value to be rated and calculates the whole value to be rated
* using that step.
*
* THRESHOLD Calculation SINGULARITY Yes BEAT BASED Yes CHARGE BASE Yes STEP
* FROM-TO Yes RATING beatCount * factor * beat / chargeBase;
*
* @param PriceModel The price model name we are using
* @param tmpRateModel The price model definition
* @param valueToRate The value to rate
* @param valueOffset The offset for the start of the tier, if there is one
* @param CDRDate The date to rate at
* @param BreakDown Produce a charge breakdown or not
* @return The rating result
* @throws OpenRate.exception.ProcessingException
*/
protected RatingResult performRateEvaluationThreshold(String PriceModel, ArrayList<RateMapEntry> tmpRateModel, double valueToRate, double valueOffset, long CDRDate, boolean BreakDown) throws ProcessingException {
int index = 0;
double AllTiersValue = 0;
RatingResult tmpRatingResult = new RatingResult();
RatingBreakdown tmpBreakdown;
// check that we have something to work on
if (tmpRateModel == null) {
throw new ProcessingException("Price Model <" + PriceModel + "> not defined", getSymbolicName());
}
// For multi-packet rating, we have to apply the offset
double effectiveValueToRate = valueToRate + valueOffset;
// set the default value
double rumValueUsed = 0;
double rumValueUsedOffset = 0;
// We need to loop through all the tiers until we have finshed
// consuming all the rateable input
while (index < tmpRateModel.size()) {
// Get the root price model
RateMapEntry tmpEntry = tmpRateModel.get(index);
// Get the validty for this cdr
tmpEntry = getRateModelEntryForTime(tmpEntry, CDRDate);
// Validate that we have something we can work with
if (tmpEntry == null) {
message = "CDR with <" + CDRDate + "> date not rated by model <"
+ PriceModel + "> because of missing validity coverage";
throw new ProcessingException(message, getSymbolicName());
}
// For positive rating cases
double thisTierValue;
double thisTierRUMUsed = 0;
long thisTierBeatCount = 0;
// ********************* Deal with the rating value **********************
// See if this event crosses the lower tier threshold
if (effectiveValueToRate > tmpEntry.getFrom()) {
// see if we are in this tier
if (effectiveValueToRate <= tmpEntry.getTo()) {
// Calculate the amount in this tier - we use the offset to locate the
// tier to use, but rate the original amount
thisTierRUMUsed = valueToRate;
rumValueUsed += thisTierRUMUsed;
// Get the number of beats in this tier
thisTierBeatCount = Math.round(thisTierRUMUsed / tmpEntry.getBeat());
// Deal with unfinished beats
if ((thisTierRUMUsed - thisTierBeatCount * tmpEntry.getBeat()) > 0) {
thisTierBeatCount++;
}
// Deal with the empty beat
if (thisTierBeatCount == 0) {
thisTierBeatCount = 1;
}
} else if (tmpEntry.getFrom() == tmpEntry.getTo()) {
// Singularity rate
// Get the number of beats in this tier
thisTierBeatCount = 1;
}
}
// Calculate the value of the tier
thisTierValue = (thisTierBeatCount * tmpEntry.getFactor()) * tmpEntry.getBeat() / tmpEntry.getChargeBase();
// provide the rating breakdown if it is required
if (BreakDown) {
// initialise the breakdown if necessary
if (tmpRatingResult.breakdown == null) {
tmpRatingResult.breakdown = new ArrayList<>();
}
// provide the charging breakdown
tmpBreakdown = new RatingBreakdown();
tmpBreakdown.beat = tmpEntry.getBeat();
tmpBreakdown.beatCount = thisTierBeatCount;
tmpBreakdown.factor = tmpEntry.getFactor();
tmpBreakdown.chargeBase = tmpEntry.getChargeBase();
tmpBreakdown.ratedAmount = thisTierValue;
tmpBreakdown.RUMRated = thisTierRUMUsed;
tmpBreakdown.stepUsed = index;
tmpBreakdown.tierFrom = tmpEntry.getFrom();
tmpBreakdown.tierTo = tmpEntry.getTo();
tmpBreakdown.validFrom = tmpEntry.getStartTime();
// Store the breakdown
tmpRatingResult.breakdown.add(tmpBreakdown);
}
// Increment the tier counter
index++;
// Accumulate the tier value
AllTiersValue += thisTierValue;
}
// Roll up the final values
tmpRatingResult.RatedValue = AllTiersValue;
tmpRatingResult.RUMUsed = rumValueUsed - rumValueUsedOffset;
// return OK
return tmpRatingResult;
}
/**
* Performs the rating calculation of the value given at the CDR date using
* the given rating model. Does *NOT* take into account the step FROM and TO
* values.
*
* FLAT Calculation SINGULARITY No BEAT BASED No CHARGE BASE Yes STEP FROM-TO
* No RATING valueToRate * factor / chargeBase
*
* @param PriceModel The price model name we are using
* @param tmpRateModel The price model definition
* @param valueToRate The value to rate
* @param CDRDate The date to rate at
* @param BreakDown Produce a charge breakdown or not
* @return The rating result
* @throws OpenRate.exception.ProcessingException
*/
protected RatingResult performRateEvaluationFlat(String PriceModel, ArrayList<RateMapEntry> tmpRateModel, double valueToRate, long CDRDate, boolean BreakDown) throws ProcessingException {
double AllTiersValue;
RateMapEntry tmpEntry;
double RUMValueUsed;
RatingResult tmpRatingResult = new RatingResult();
RatingBreakdown tmpBreakdown;
// check that we have something to work on
if (tmpRateModel == null) {
throw new ProcessingException("Price Model <" + PriceModel + "> not defined", getSymbolicName());
}
// Get just the first tier
tmpEntry = tmpRateModel.get(0);
// Get the validty for this cdr
tmpEntry = getRateModelEntryForTime(tmpEntry, CDRDate);
if (tmpEntry == null) {
message = "CDR with <" + CDRDate + "> date not rated by model <"
+ PriceModel + "> because of missing validity coverage";
throw new ProcessingException(message, getSymbolicName());
}
// Calculate the value of the entry - there should be no others
AllTiersValue = (valueToRate * tmpEntry.getFactor()) / tmpEntry.getChargeBase();
RUMValueUsed = valueToRate;
// provide the rating breakdown if it is required
if (BreakDown) {
// initialise the breakdown if necessary
if (tmpRatingResult.breakdown == null) {
tmpRatingResult.breakdown = new ArrayList<>();
}
// provide the charging breakdown
tmpBreakdown = new RatingBreakdown();
tmpBreakdown.beat = 1;
tmpBreakdown.beatCount = (long) valueToRate;
tmpBreakdown.factor = tmpEntry.getFactor();
tmpBreakdown.chargeBase = tmpEntry.getChargeBase();
tmpBreakdown.ratedAmount = AllTiersValue;
tmpBreakdown.RUMRated = RUMValueUsed;
tmpBreakdown.stepUsed = 1;
tmpBreakdown.tierFrom = tmpEntry.getFrom();
tmpBreakdown.tierTo = tmpEntry.getTo();
tmpBreakdown.validFrom = tmpEntry.getStartTime();
// Store the breakdown
tmpRatingResult.breakdown.add(tmpBreakdown);
}
// return OK
tmpRatingResult.RatedValue = AllTiersValue;
tmpRatingResult.RUMUsed = RUMValueUsed;
return tmpRatingResult;
}
/**
* Performs the rating calculation of the value given at the CDR date using
* the given rating model. It does take into account the step FROM and TO
* values. Step from and step to values are integers in this case.
*
* EVENT Calculation SINGULARITY No BEAT BASED No CHARGE BASE No STEP FROM-TO
* Yes RATING valueToRate * factor
*
* @param PriceModel The price model name we are using
* @param tmpRateModel The price model definition
* @param valueToRate The value to rate for
* @param CDRDate The date to rate at
* @param BreakDown Produce a charge breakdown or not
* @return The rating result
* @throws OpenRate.exception.ProcessingException
*/
protected RatingResult performRateEvaluationEvent(String PriceModel, ArrayList<RateMapEntry> tmpRateModel, long valueToRate, long CDRDate, boolean BreakDown) throws ProcessingException {
RatingResult tmpRatingResult = new RatingResult();
int Index = 0;
double ThisTierValue;
double ThisTierRUMUsed;
double AllTiersValue = 0;
RateMapEntry tmpEntry;
double RUMValueUsed = 0;
RatingBreakdown tmpBreakdown;
// check that we have something to work on
if (tmpRateModel == null) {
throw new ProcessingException("Price Model <" + PriceModel + "> not defined", getSymbolicName());
}
// set the default value
tmpRatingResult.RatedValue = 0;
tmpRatingResult.RUMUsed = 0;
// We need to loop through all the tiers until we have finshed
// consuming all the rateable input
while (Index < tmpRateModel.size()) {
tmpEntry = tmpRateModel.get(Index);
ThisTierValue = 0;
// See if this event crosses the lower tier threshold
if (valueToRate > tmpEntry.getFrom()) {
// see if we use all of the tier
if (valueToRate >= tmpEntry.getTo()) {
// Get the validty for this cdr
tmpEntry = getRateModelEntryForTime(tmpEntry, CDRDate);
if (tmpEntry == null) {
message = "CDR with <" + CDRDate + "> date not rated by model <"
+ PriceModel + "> because of missing validity coverage";
throw new ProcessingException(message, getSymbolicName());
}
// Calculate the amount in this tier
ThisTierRUMUsed = (tmpEntry.getTo() - tmpEntry.getFrom());
// Deal with the case that we have the empty beat
if (ThisTierRUMUsed == 0) {
ThisTierRUMUsed++;
}
RUMValueUsed += ThisTierRUMUsed;
// Calculate the value of the tier
ThisTierValue = ThisTierRUMUsed * tmpEntry.getFactor();
// provide the rating breakdown if it is required
if (BreakDown) {
// initialise the breakdown if necessary
if (tmpRatingResult.breakdown == null) {
tmpRatingResult.breakdown = new ArrayList<>();
}
// provide the charging breakdown
tmpBreakdown = new RatingBreakdown();
tmpBreakdown.beat = tmpEntry.getBeat();
tmpBreakdown.beatCount = (long) ThisTierRUMUsed;
tmpBreakdown.factor = tmpEntry.getFactor();
tmpBreakdown.chargeBase = tmpEntry.getChargeBase();
tmpBreakdown.ratedAmount = ThisTierValue;
tmpBreakdown.RUMRated = ThisTierRUMUsed;
tmpBreakdown.stepUsed = Index;
tmpBreakdown.tierFrom = tmpEntry.getFrom();
tmpBreakdown.tierTo = tmpEntry.getTo();
tmpBreakdown.validFrom = tmpEntry.getStartTime();
// Store the breakdown
tmpRatingResult.breakdown.add(tmpBreakdown);
}
} else {
// Get the validty for this cdr
tmpEntry = getRateModelEntryForTime(tmpEntry, CDRDate);
if (tmpEntry == null) {
message = "CDR with <" + CDRDate + "> date not rated by model <"
+ PriceModel + "> because of missing validity coverage";
throw new ProcessingException(message, getSymbolicName());
}
// Partial tier to do, and then we have finished
ThisTierRUMUsed = (valueToRate - tmpEntry.getFrom());
// Deal with the case that we have the empty beat
if (ThisTierRUMUsed == 0) {
ThisTierRUMUsed++;
}
RUMValueUsed += ThisTierRUMUsed;
ThisTierValue = ThisTierRUMUsed * tmpEntry.getFactor();
// provide the rating breakdown if it is required
if (BreakDown) {
// initialise the breakdown if necessary
if (tmpRatingResult.breakdown == null) {
tmpRatingResult.breakdown = new ArrayList<>();
}
// provide the charging breakdown
tmpBreakdown = new RatingBreakdown();
tmpBreakdown.beat = tmpEntry.getBeat();
tmpBreakdown.beatCount = (long) ThisTierRUMUsed;
tmpBreakdown.factor = tmpEntry.getFactor();
tmpBreakdown.chargeBase = tmpEntry.getChargeBase();
tmpBreakdown.ratedAmount = ThisTierValue;
tmpBreakdown.RUMRated = ThisTierRUMUsed;
tmpBreakdown.stepUsed = Index;
tmpBreakdown.tierFrom = tmpEntry.getFrom();
tmpBreakdown.tierTo = tmpEntry.getTo();
tmpBreakdown.validFrom = tmpEntry.getStartTime();
// Store the breakdown
tmpRatingResult.breakdown.add(tmpBreakdown);
}
}
}
// Increment the tier counter
Index++;
// Accumulate the tier value
AllTiersValue += ThisTierValue;
}
// return OK
tmpRatingResult.RatedValue = AllTiersValue;
tmpRatingResult.RUMUsed = RUMValueUsed;
return tmpRatingResult;
}
/**
* Performs the authorisation calculation of the value given at the CDR date.
* Evaluates all the tiers in the model one at a time, and accumulates the
* contribution from the tier into the final result.
*
* Matches the rating in performRateEvaluationTiered
*
* @param PriceModel The price model name we are using
* @param tmpRateModel The price model definition
* @param availableBalance The balance available
* @param CDRDate The date to rate at
* @return The rating result
* @throws OpenRate.exception.ProcessingException
*/
protected double performAuthEvaluationTiered(String PriceModel, ArrayList<RateMapEntry> tmpRateModel, double availableBalance, long CDRDate) throws ProcessingException {
int Index = 0;
double ThisTierValue;
double ThisTierRUMUsed;
long ThisTierBeatCount;
double AllTiersValue = 0;
RateMapEntry tmpEntry;
double RUMValueUsed = 0;
boolean breakFlag = false;
// check that we have something to work on
if (tmpRateModel == null) {
throw new ProcessingException("Price Model <" + PriceModel + "> not defined", getSymbolicName());
}
// We need to loop through all the tiers until we have finished
// consuming all the rateable input
while (Index < tmpRateModel.size()) {
tmpEntry = tmpRateModel.get(Index);
tmpEntry = getRateModelEntryForTime(tmpEntry, CDRDate);
if (tmpEntry == null) {
message = "Rate Model entry not valid for CDR with <" + CDRDate + "> date, model <"
+ PriceModel + ">";
throw new ProcessingException(message, getSymbolicName());
}
// Deal with the case that we have a tier without cost
if (tmpEntry.getFactor() == 0) {
// we just skip over - nothing we can do with 0 priced tiers
continue;
}
// Calculate the amount in this tier
ThisTierRUMUsed = (tmpEntry.getTo() - tmpEntry.getFrom());
// Get the number of beats in this tier
ThisTierBeatCount = Math.round(ThisTierRUMUsed / tmpEntry.getBeat());
// Deal with unfinished beats
if ((ThisTierRUMUsed - ThisTierBeatCount * tmpEntry.getBeat()) > 0) {
ThisTierBeatCount++;
}
// Deal with the empty beat
if (ThisTierBeatCount == 0) {
ThisTierBeatCount = 1;
}
// Calculate the value of the tier
ThisTierValue = (ThisTierBeatCount * tmpEntry.getFactor()) * tmpEntry.getBeat() / tmpEntry.getChargeBase();
if (AllTiersValue + ThisTierValue > availableBalance) {
ThisTierValue = availableBalance - AllTiersValue;
ThisTierBeatCount = Math.round((ThisTierValue * tmpEntry.getChargeBase()) / (tmpEntry.getFactor() * tmpEntry.getBeat()));
ThisTierRUMUsed = tmpEntry.getBeat() * ThisTierBeatCount;
breakFlag = true;
}
// Accumulate the tier value
AllTiersValue += ThisTierValue;
RUMValueUsed += ThisTierRUMUsed;
if (breakFlag == true) {
break;
}
// Increment the tier counter
Index++;
}
// Set the value to maximum if available balance is a lot higher than all tiered???
// if(availableBalance > AllTiersValue){
// RUMValueUsed = Double.MAX_VALUE;
// }
return RUMValueUsed;
}
/**
* Performs the authorisation calculation of the value given at the CDR date.
* We have to perform an evaluation for each of the threshold steps in the
* model and find the lowest non-zero result. When the authorisation runs out,
* the same process can happen again with less available balance. Not very
* beautiful, but functional given the non-linear nature of the model.
*
* Matches the rating in performRateEvaluationThreshold
*
* @param PriceModel The price model name we are using
* @param tmpRateModel The price model definition
* @param availableBalance The balance available
* @param CDRDate The date to rate at
* @return The rating result
* @throws OpenRate.exception.ProcessingException
*/
protected double performAuthEvaluationThreshold(String PriceModel, ArrayList<RateMapEntry> tmpRateModel, double availableBalance, long CDRDate) throws ProcessingException {
int Index = 0;
double ThisTierRUMUsed;
long ThisTierBeatCount;
RateMapEntry tmpEntry;
double RUMValueUsed = 0;
// check that we have something to work on
if (tmpRateModel == null) {
throw new ProcessingException("Price Model <" + PriceModel + "> not defined", getSymbolicName());
}
// We need to loop through all the tiers and evaluate them, then return the
// shortest.
while (Index < tmpRateModel.size()) {
tmpEntry = tmpRateModel.get(Index);
Index++;
tmpEntry = getRateModelEntryForTime(tmpEntry, CDRDate);
ThisTierBeatCount = Math.round((availableBalance * tmpEntry.getChargeBase()) / (tmpEntry.getFactor() * tmpEntry.getBeat()));
ThisTierRUMUsed = tmpEntry.getBeat() * ThisTierBeatCount;
// is this a better non-zero result
if (RUMValueUsed == 0) {
RUMValueUsed = ThisTierRUMUsed;
} else if (RUMValueUsed > 0 && ThisTierRUMUsed > 0
&& RUMValueUsed > ThisTierRUMUsed
&& ThisTierRUMUsed < tmpEntry.getTo()) {
RUMValueUsed = ThisTierRUMUsed;
}
}
return RUMValueUsed;
}
/**
* Performs the authorisation calculation of the value given at the CDR date.
*
* Matches the rating in performRateEvaluationFlat
*
* @param PriceModel The price model name we are using
* @param tmpRateModel The price model definition
* @param availableBalance The balance available
* @param CDRDate The date to rate at
* @return The rating result
* @throws OpenRate.exception.ProcessingException
*/
protected double performAuthEvaluationFlat(String PriceModel, ArrayList<RateMapEntry> tmpRateModel, double availableBalance, long CDRDate) throws ProcessingException {
RateMapEntry tmpEntry;
double tmpcalculationResult;
// check that we have something to work on
if (tmpRateModel == null) {
throw new ProcessingException("Price Model <" + PriceModel + "> not defined", getSymbolicName());
}
// Get the validty for this cdr
tmpEntry = getRateModelEntryForTime(tmpRateModel.get(0), CDRDate);
if (tmpEntry == null) {
message = "Rate Model entry not found for CDR with <" + CDRDate + "> date, model <"
+ PriceModel + ">";
throw new ProcessingException(message, getSymbolicName());
} else if (tmpEntry.getFactor() == 0 || tmpEntry.getChargeBase() == 0) {
message = "Rate Model entry not valid for CDR with <" + CDRDate + "> date, model <"
+ PriceModel + ">, factor <" + tmpEntry.getFactor() + "and charge base <" + tmpEntry.getChargeBase() + ">";
throw new ProcessingException(message, getSymbolicName());
}
// Calculate the value of the entry - there should be no others
tmpcalculationResult = (availableBalance / tmpEntry.getFactor()) * tmpEntry.getChargeBase();
return tmpcalculationResult;
}
/**
* Performs the authorisation calculation of the value given at the CDR date.
*
* Matches the rating in performRateEvaluationEvent
*
* @param PriceModel The price model name we are using
* @param tmpRateModel The price model definition
* @param availableBalance The balance available
* @param CDRDate The date to rate at
* @return The rating result
* @throws OpenRate.exception.ProcessingException
*/
protected double performAuthEvaluationEvent(String PriceModel, ArrayList<RateMapEntry> tmpRateModel, double availableBalance, long CDRDate) throws ProcessingException {
double tmpcalculationResult;
RateMapEntry tmpEntry;
// check that we have something to work on
if (tmpRateModel == null) {
throw new ProcessingException("Price Model <" + PriceModel + "> not defined", getSymbolicName());
}
// Get just the first tier
tmpEntry = tmpRateModel.get(0);
// Get the validity for this cdr
tmpEntry = getRateModelEntryForTime(tmpEntry, CDRDate);
if (tmpEntry == null) {
message = "Rate Model entry not valid for CDR with <" + CDRDate + "> date, model <"
+ PriceModel + ">";
throw new ProcessingException(message, getSymbolicName());
}
tmpcalculationResult = availableBalance / tmpEntry.getFactor();
return tmpcalculationResult;
}
/**
* Runs through the validity periods in a rate map, and returns the one valid
* for a given date, or null if no match
*
* @param tmpEntry The rate map object to search
* @param CDRDate The long UTC date to search for
* @return The relevant rate map entry, or null if there was no match at the
* time
*/
protected RateMapEntry getRateModelEntryForTime(RateMapEntry tmpEntry, long CDRDate) {
RateMapEntry result = tmpEntry;
while (tmpEntry.getStartTime() > CDRDate) {
// try to move down the list
if (tmpEntry.getChild() == null) {
// no more children, so we can't rate this
return null;
} else {
// move down the list
result = result.getChild();
}
return result;
}
// return the right bit
return tmpEntry;
}
}