mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-15 13:19:53 +05:00
RateS computeRateSIntervals implementation, engine.CostForIntervals
This commit is contained in:
@@ -3091,6 +3091,9 @@ func (dm *DataManager) GetRateProfile(tenant, id string, cacheRead, cacheWrite b
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err = rpp.Compile(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cacheWrite {
|
||||
if errCh := Cache.Set(utils.CacheRateProfiles, tntID, rpp, nil,
|
||||
cacheCommit(transactionID), transactionID); errCh != nil {
|
||||
|
||||
@@ -73,7 +73,7 @@ type Rate struct {
|
||||
Blocker bool // RateBlocker will make this rate recurrent, deactivating further intervals
|
||||
IntervalRates []*IntervalRate
|
||||
|
||||
sched cron.Schedule // compiled version of activation time as cron.Schedule interface
|
||||
sched cron.Schedule // compiled version of activation times as cron.Schedule interface
|
||||
uID string
|
||||
}
|
||||
|
||||
@@ -88,7 +88,9 @@ type IntervalRate struct {
|
||||
Increment time.Duration // RateIncrement
|
||||
Value float64 // RateValue
|
||||
|
||||
val *utils.Decimal // cached version of the Decimal
|
||||
decVal *utils.Decimal // cached version of the Value converted to Decimal for operations
|
||||
decUnit *utils.Decimal // cached version of the Unit converted to Decimal for operations
|
||||
decIcrm *utils.Decimal // cached version of the Increment converted to Decimal for operations
|
||||
}
|
||||
|
||||
func (rt *Rate) Compile() (err error) {
|
||||
@@ -99,6 +101,11 @@ func (rt *Rate) Compile() (err error) {
|
||||
if rt.sched, err = cron.ParseStandard(aTime); err != nil {
|
||||
return
|
||||
}
|
||||
for _, iRt := range rt.IntervalRates {
|
||||
iRt.decVal = utils.NewDecimalFromFloat64(iRt.Value)
|
||||
iRt.decUnit = utils.NewDecimalFromUint64(uint64(iRt.Unit))
|
||||
iRt.decIcrm = utils.NewDecimalFromUint64(uint64(iRt.Increment))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -126,6 +133,21 @@ func (rt *Rate) RunTimes(sTime, eTime time.Time, verbosity int) (aTimes [][]time
|
||||
return nil, utils.ErrMaxIterationsReached
|
||||
}
|
||||
|
||||
// DecimalValue exports the decVal variable
|
||||
func (rIt *IntervalRate) DecimalValue() *utils.Decimal {
|
||||
return rIt.decVal
|
||||
}
|
||||
|
||||
// DecimalUnit exports the decUnit variable
|
||||
func (rIt *IntervalRate) DecimalUnit() *utils.Decimal {
|
||||
return rIt.decUnit
|
||||
}
|
||||
|
||||
// DecimalIncrement exports the decUnit variable
|
||||
func (rIt *IntervalRate) DecimalIncrement() *utils.Decimal {
|
||||
return rIt.decIcrm
|
||||
}
|
||||
|
||||
// RateProfileWithOpts is used in replicatorV1 for dispatcher
|
||||
type RateProfileWithOpts struct {
|
||||
*RateProfile
|
||||
@@ -138,16 +160,16 @@ type RateSInterval struct {
|
||||
Increments []*RateSIncrement
|
||||
CompressFactor int64
|
||||
|
||||
cost *utils.Decimal // unexported total cost
|
||||
cost *utils.Decimal // unexported total interval cost
|
||||
}
|
||||
|
||||
type RateSIncrement struct {
|
||||
Rate *Rate
|
||||
UsageStart time.Duration
|
||||
Usage time.Duration
|
||||
Rate *Rate
|
||||
IntervalRateIndex int
|
||||
CompressFactor int64
|
||||
|
||||
Cost float64
|
||||
cost *utils.Decimal // unexported total increment cost
|
||||
}
|
||||
|
||||
@@ -165,6 +187,15 @@ func (rIv *RateSInterval) CompressEquals(rIv2 *RateSInterval) (eq bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func (rIv *RateSInterval) Cost() *utils.Decimal {
|
||||
if rIv.cost == nil {
|
||||
for _, incrm := range rIv.Increments {
|
||||
rIv.cost = utils.NewDecimal().Add(rIv.cost, incrm.Cost())
|
||||
}
|
||||
}
|
||||
return rIv.cost
|
||||
}
|
||||
|
||||
// CompressEquals compares two RateSIncrement for Compress function
|
||||
func (rIcr *RateSIncrement) CompressEquals(rIcr2 *RateSIncrement) (eq bool) {
|
||||
if rIcr.Rate.UID() != rIcr2.Rate.UID() {
|
||||
@@ -173,11 +204,33 @@ func (rIcr *RateSIncrement) CompressEquals(rIcr2 *RateSIncrement) (eq bool) {
|
||||
if rIcr.Usage != rIcr2.Usage {
|
||||
return
|
||||
}
|
||||
if rIcr.Cost != rIcr2.Cost {
|
||||
return
|
||||
}
|
||||
if rIcr.CompressFactor != rIcr2.CompressFactor {
|
||||
return
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Cost computes the Cost on RateSIncrement
|
||||
func (rIcr *RateSIncrement) Cost() *utils.Decimal {
|
||||
if rIcr.cost == nil {
|
||||
icrRt := rIcr.Rate.IntervalRates[rIcr.IntervalRateIndex]
|
||||
icrCost := icrRt.DecimalValue()
|
||||
if icrRt.Unit != icrRt.Increment {
|
||||
icrCost = utils.NewDecimal().Divide(
|
||||
utils.NewDecimal().Multiply(icrCost, icrRt.DecimalIncrement()),
|
||||
icrRt.DecimalUnit())
|
||||
}
|
||||
rIcr.cost = utils.NewDecimal().Multiply(
|
||||
icrCost,
|
||||
utils.NewDecimalFromUint64(uint64(rIcr.CompressFactor)))
|
||||
}
|
||||
return rIcr.cost
|
||||
}
|
||||
|
||||
// CostForIntervals sums the costs for all intervals
|
||||
func CostForIntervals(rtIvls []*RateSInterval) (cost *utils.Decimal) {
|
||||
for _, rtIvl := range rtIvls {
|
||||
cost = utils.NewDecimal().Add(cost, rtIvl.Cost())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -177,25 +177,72 @@ func orderRatesOnIntervals(aRts []*engine.Rate, sTime time.Time, usage time.Dura
|
||||
return
|
||||
}
|
||||
|
||||
// costWithRates will give out the cost projection for the given orderedRates and usage
|
||||
func costWithRates(rts []*orderedRate, usage time.Duration) (rtIvls []*engine.RateSInterval, err error) {
|
||||
//var usageSIdx time.Duration // usageStart for one rate
|
||||
// computeRateSIntervals will give out the cost projection for the given orderedRates and usage
|
||||
func computeRateSIntervals(rts []*orderedRate, usage time.Duration) (rtIvls []*engine.RateSInterval, err error) {
|
||||
var rtUsageSIdx time.Duration // rtUsageSIdx for one rate
|
||||
for i, rt := range rts {
|
||||
var usageEIdx time.Duration
|
||||
if i != len(rts)-1 {
|
||||
usageEIdx = rts[i+1].Duration
|
||||
isLastRt := i == len(rts)-1
|
||||
var rtUsageEIdx time.Duration
|
||||
if !isLastRt {
|
||||
rtUsageEIdx = rts[i+1].Duration
|
||||
} else {
|
||||
rtUsageEIdx = usage
|
||||
}
|
||||
var iRts []*engine.IntervalRate
|
||||
for _, iRt := range rt.IntervalRates {
|
||||
if usageEIdx == 0 || iRt.IntervalStart < usageEIdx {
|
||||
iRts = append(iRts, iRt)
|
||||
var rIcmts []*engine.RateSIncrement
|
||||
iRtUsageSIdx := rtUsageSIdx
|
||||
iRtUsageEIdx := rtUsageEIdx
|
||||
for j, iRt := range rt.IntervalRates {
|
||||
if iRtUsageSIdx >= rtUsageEIdx { // charged enough for interval
|
||||
break
|
||||
}
|
||||
// make sure we bill from start
|
||||
if j == 0 && iRt.IntervalStart > iRtUsageSIdx {
|
||||
return nil, fmt.Errorf("intervalStart for rate: <%s> higher than usage: %v",
|
||||
rt.UID(), iRtUsageSIdx)
|
||||
}
|
||||
isLastIRt := j == len(rt.IntervalRates)-1
|
||||
if iRt.IntervalStart > iRtUsageSIdx ||
|
||||
(!isLastIRt && rt.IntervalRates[j+1].IntervalStart <= iRtUsageSIdx) {
|
||||
break // the rates should be already ordered, break here
|
||||
}
|
||||
if !isLastIRt {
|
||||
iRtUsageEIdx = rt.IntervalRates[j+1].IntervalStart
|
||||
} else {
|
||||
iRtUsageEIdx = rtUsageEIdx
|
||||
}
|
||||
iRtUsage := iRtUsageEIdx - iRtUsageSIdx
|
||||
if iRtUsageEIdx == time.Duration(0) {
|
||||
return nil, fmt.Errorf("zero usage to be charged with rate: <%s>", rt.UID())
|
||||
}
|
||||
if iRt.Increment == time.Duration(0) {
|
||||
return nil, fmt.Errorf("zero increment to be charged within rate: <%s>", rt.UID())
|
||||
}
|
||||
intUsage := int64(iRtUsage)
|
||||
intIncrm := int64(iRt.Increment)
|
||||
cmpFactor := intUsage / intIncrm
|
||||
if intUsage%intIncrm != 0 {
|
||||
cmpFactor += 1 // int division has used math.Floor, need Ceil
|
||||
}
|
||||
rIcrm := &engine.RateSIncrement{
|
||||
UsageStart: iRtUsageSIdx,
|
||||
Usage: iRtUsage,
|
||||
Rate: rt.Rate,
|
||||
IntervalRateIndex: j,
|
||||
CompressFactor: cmpFactor,
|
||||
}
|
||||
rIcmts = append(rIcmts, rIcrm)
|
||||
iRtUsageSIdx += iRtUsage
|
||||
|
||||
}
|
||||
//fmt.Printf("iRts: %+v\n", iRts)
|
||||
if usageEIdx == 0 {
|
||||
rtIvls = append(rtIvls,
|
||||
&engine.RateSInterval{
|
||||
UsageStart: rtUsageSIdx,
|
||||
Increments: rIcmts,
|
||||
CompressFactor: 1})
|
||||
if iRtUsageSIdx >= usage { // charged enough for the usage
|
||||
break
|
||||
}
|
||||
//usageSIdx = usageEIdx // continue for the next interval
|
||||
rtUsageSIdx = rtUsageEIdx // continue for the next interval
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1575,7 +1575,7 @@ func TestOrderRatesOnIntervalStartLowerThanEndIdx(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCostWithRates(t *testing.T) {
|
||||
func TestComputeRateSIntervals(t *testing.T) {
|
||||
rt0 := &engine.Rate{
|
||||
ID: "RATE0",
|
||||
IntervalRates: []*engine.IntervalRate{
|
||||
@@ -1627,9 +1627,49 @@ func TestCostWithRates(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
//eRtIvls := []*engine.RateSInterval{}
|
||||
var eRtIvls []*engine.RateSInterval
|
||||
if rtIvls, err := costWithRates(rts, time.Duration(130*time.Second)); err != nil {
|
||||
eRtIvls := []*engine.RateSInterval{
|
||||
{
|
||||
UsageStart: time.Duration(0),
|
||||
Increments: []*engine.RateSIncrement{
|
||||
{
|
||||
UsageStart: time.Duration(0),
|
||||
Usage: time.Duration(time.Minute),
|
||||
Rate: rt0,
|
||||
IntervalRateIndex: 0,
|
||||
CompressFactor: 1,
|
||||
},
|
||||
{
|
||||
UsageStart: time.Duration(time.Minute),
|
||||
Usage: time.Duration(30 * time.Second),
|
||||
Rate: rt0,
|
||||
IntervalRateIndex: 1,
|
||||
CompressFactor: 30,
|
||||
},
|
||||
},
|
||||
CompressFactor: 1,
|
||||
},
|
||||
{
|
||||
UsageStart: time.Duration(90 * time.Second),
|
||||
Increments: []*engine.RateSIncrement{
|
||||
{
|
||||
UsageStart: time.Duration(90 * time.Second),
|
||||
Usage: time.Duration(30 * time.Second),
|
||||
Rate: rt1,
|
||||
IntervalRateIndex: 0,
|
||||
CompressFactor: 30,
|
||||
},
|
||||
{
|
||||
UsageStart: time.Duration(2 * time.Minute),
|
||||
Usage: time.Duration(10 * time.Second),
|
||||
Rate: rt1,
|
||||
IntervalRateIndex: 1,
|
||||
CompressFactor: 10,
|
||||
},
|
||||
},
|
||||
CompressFactor: 1,
|
||||
},
|
||||
}
|
||||
if rtIvls, err := computeRateSIntervals(rts, time.Duration(130*time.Second)); err != nil {
|
||||
t.Error(err)
|
||||
} else if !reflect.DeepEqual(eRtIvls, rtIvls) {
|
||||
t.Errorf("expecting: %+v, received: %+v", eRtIvls, rtIvls)
|
||||
|
||||
@@ -26,6 +26,10 @@ func NewDecimalFromFloat64(x float64) *Decimal {
|
||||
return &Decimal{new(decimal.Big).SetFloat64(x)}
|
||||
}
|
||||
|
||||
func NewDecimalFromUint64(x uint64) *Decimal {
|
||||
return &Decimal{new(decimal.Big).SetUint64(x)}
|
||||
}
|
||||
|
||||
func NewDecimal() *Decimal {
|
||||
return &Decimal{new(decimal.Big)}
|
||||
}
|
||||
@@ -41,15 +45,24 @@ func (d *Decimal) Float64() (f float64) {
|
||||
}
|
||||
|
||||
func (d *Decimal) MarshalJSON() ([]byte, error) {
|
||||
if d.Big == nil {
|
||||
d.Big = new(decimal.Big)
|
||||
}
|
||||
return d.Big.MarshalText()
|
||||
}
|
||||
|
||||
func (d *Decimal) UnmarshalJSON(data []byte) error {
|
||||
if d.Big == nil {
|
||||
d.Big = new(decimal.Big)
|
||||
}
|
||||
return d.Big.UnmarshalJSON(data)
|
||||
}
|
||||
|
||||
func (d *Decimal) Divide(x, y *Decimal) *Decimal {
|
||||
d.Big.Quo(x.Big, y.Big)
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *Decimal) Multiply(x, y *Decimal) *Decimal {
|
||||
d.Big.Mul(x.Big, y.Big)
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *Decimal) Add(x, y *Decimal) *Decimal {
|
||||
d.Big.Add(x.Big, y.Big)
|
||||
return d
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user