Files
cgrates/engine/timespans.go
2013-11-28 18:15:48 +01:00

341 lines
10 KiB
Go

/*
Rating system designed to be used in VoIP Carriers World
Copyright (C) 2013 ITsysCOM
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/>
*/
package engine
import (
//"fmt"
"github.com/cgrates/cgrates/utils"
"time"
)
/*
A unit in which a call will be split that has a specific price related interval attached to it.
*/
type TimeSpan struct {
TimeStart, TimeEnd time.Time
Cost float64
ratingInfo *RatingInfo
RateInterval *RateInterval
CallDuration time.Duration // the call duration so far till TimeEnd
overlapped bool // mark a timespan as overlapped by an expanded one
Increments Increments
MatchedSubject, MatchedPrefix string
}
type Increment struct {
Duration time.Duration
Cost float64
BalanceUuids []string // need more than one for minutes with cost
BalanceRateInterval *RateInterval
MinuteInfo *MinuteInfo
}
// Holds the minute information related to a specified timespan
type MinuteInfo struct {
DestinationId string
Quantity float64
Price float64
}
func (incr *Increment) Clone() *Increment {
nIncr := &Increment{
Duration: incr.Duration,
Cost: incr.Cost,
BalanceRateInterval: incr.BalanceRateInterval,
MinuteInfo: incr.MinuteInfo,
}
nIncr.BalanceUuids = make([]string, len(incr.BalanceUuids))
copy(nIncr.BalanceUuids, incr.BalanceUuids)
return nIncr
}
type Increments []*Increment
func (incs Increments) GetTotalCost() float64 {
cost := 0.0
for _, increment := range incs {
cost += increment.Cost
}
return cost
}
// Returns the duration of the timespan
func (ts *TimeSpan) GetDuration() time.Duration {
return ts.TimeEnd.Sub(ts.TimeStart)
}
// Returns true if the given time is inside timespan range.
func (ts *TimeSpan) Contains(t time.Time) bool {
return t.After(ts.TimeStart) && t.Before(ts.TimeEnd)
}
// Returns the cost of the timespan according to the relevant cost interval.
// It also sets the Cost field of this timespan (used for refound on session
// manager debit loop where the cost cannot be recalculated)
func (ts *TimeSpan) getCost() float64 {
if ts.RateInterval == nil {
return 0
}
ts.Cost = ts.RateInterval.GetCost(ts.GetDuration(), ts.GetGroupStart())
return ts.Cost
}
/*
Will set the interval as spans's interval if new Weight is lower then span's interval Weight
or if the Weights are equal and new price is lower then spans's interval price
*/
func (ts *TimeSpan) SetRateInterval(i *RateInterval) {
if ts.RateInterval == nil || ts.RateInterval.Weight < i.Weight {
ts.RateInterval = i
return
}
iPrice, _, _ := i.GetRateParameters(ts.GetGroupStart())
tsPrice, _, _ := ts.RateInterval.GetRateParameters(ts.GetGroupStart())
if ts.RateInterval.Weight == i.Weight && iPrice < tsPrice {
ts.RateInterval = i
}
}
func (ts *TimeSpan) createIncrementsSlice() {
if ts.RateInterval == nil {
return
}
ts.Increments = make([]*Increment, 0)
// create rated units series
rate, rateIncrement, rateUnit := ts.RateInterval.GetRateParameters(ts.GetGroupStart())
incrementCost := rate / rateUnit.Seconds() * rateIncrement.Seconds()
totalCost := 0.0
for s := 0; s < int(ts.GetDuration()/rateIncrement); s++ {
ts.Increments = append(ts.Increments, &Increment{Duration: rateIncrement, Cost: incrementCost})
totalCost += incrementCost
}
}
/*
Splits the given timespan according to how it relates to the interval.
It will modify the endtime of the received timespan and it will return
a new timespan starting from the end of the received one.
The interval will attach itself to the timespan that overlaps the interval.
*/
func (ts *TimeSpan) SplitByRateInterval(i *RateInterval) (nts *TimeSpan) {
//Logger.Debug("here: ", ts, " +++ ", i)
//log.Printf("TS: %+v", ts)
// if the span is not in interval return nil
if !(i.Contains(ts.TimeStart, false) || i.Contains(ts.TimeEnd, true)) {
//Logger.Debug("Not in interval")
//log.Printf("NOT in interval: %+v", i)
return
}
//Logger.Debug(fmt.Sprintf("TS: %+v", ts))
// split by GroupStart
if i.Rating != nil {
i.Rating.Rates.Sort()
for _, rate := range i.Rating.Rates {
// Logger.Debug(fmt.Sprintf("Rate: %+v", rate))
if ts.GetGroupStart() < rate.GroupIntervalStart && ts.GetGroupEnd() > rate.GroupIntervalStart {
// Logger.Debug(fmt.Sprintf("Splitting"))
ts.SetRateInterval(i)
splitTime := ts.TimeStart.Add(rate.GroupIntervalStart - ts.GetGroupStart())
//log.Print("SPLIT: ", splitTime)
nts = &TimeSpan{
TimeStart: splitTime,
TimeEnd: ts.TimeEnd,
}
nts.copyRatingInfo(ts)
ts.TimeEnd = splitTime
nts.SetRateInterval(i)
nts.CallDuration = ts.CallDuration
ts.SetNewCallDuration(nts)
// Logger.Debug(fmt.Sprintf("Group splitting: %+v %+v", ts, nts))
//log.Printf("Group splitting: %+v %+v", ts, nts)
return
}
}
}
//log.Printf("*************TS: %+v", ts)
// if the span is enclosed in the interval try to set as new interval and return nil
if i.Contains(ts.TimeStart, false) && i.Contains(ts.TimeEnd, true) {
//Logger.Debug("All in interval")
ts.SetRateInterval(i)
return
}
// if only the start time is in the interval split the interval to the right
if i.Contains(ts.TimeStart, false) {
//Logger.Debug("Start in interval")
splitTime := i.getRightMargin(ts.TimeStart)
ts.SetRateInterval(i)
if splitTime == ts.TimeStart || splitTime.Equal(ts.TimeEnd) {
return
}
nts = &TimeSpan{
TimeStart: splitTime,
TimeEnd: ts.TimeEnd,
}
nts.copyRatingInfo(ts)
ts.TimeEnd = splitTime
nts.CallDuration = ts.CallDuration
ts.SetNewCallDuration(nts)
// Logger.Debug(fmt.Sprintf("right: %+v %+v", ts, nts))
//log.Printf("right: %+v %+v", ts, nts)
return
}
// if only the end time is in the interval split the interval to the left
if i.Contains(ts.TimeEnd, true) {
//Logger.Debug("End in interval")
//tmpTime := time.Date(ts.TimeStart.)
splitTime := i.getLeftMargin(ts.TimeEnd)
splitTime = utils.CopyHour(splitTime, ts.TimeStart)
if splitTime.Equal(ts.TimeEnd) {
return
}
nts = &TimeSpan{
TimeStart: splitTime,
TimeEnd: ts.TimeEnd,
}
nts.copyRatingInfo(ts)
ts.TimeEnd = splitTime
nts.SetRateInterval(i)
nts.CallDuration = ts.CallDuration
ts.SetNewCallDuration(nts)
// Logger.Debug(fmt.Sprintf("left: %+v %+v", ts, nts))
//log.Printf("left: %+v %+v", ts, nts)
return
}
return
}
// Split the timespan at the given increment start
func (ts *TimeSpan) SplitByIncrement(index int) *TimeSpan {
if index <= 0 || index >= len(ts.Increments) {
return nil
}
timeStart := ts.GetTimeStartForIncrement(index)
newTs := &TimeSpan{
RateInterval: ts.RateInterval,
TimeStart: timeStart,
TimeEnd: ts.TimeEnd,
}
newTs.copyRatingInfo(ts)
newTs.CallDuration = ts.CallDuration
ts.TimeEnd = timeStart
newTs.Increments = ts.Increments[index:]
ts.Increments = ts.Increments[:index]
ts.SetNewCallDuration(newTs)
return newTs
}
// Split the timespan at the given second
func (ts *TimeSpan) SplitByDuration(duration time.Duration) *TimeSpan {
if duration <= 0 || duration >= ts.GetDuration() {
return nil
}
timeStart := ts.TimeStart.Add(duration)
newTs := &TimeSpan{
RateInterval: ts.RateInterval,
TimeStart: timeStart,
TimeEnd: ts.TimeEnd,
}
newTs.copyRatingInfo(ts)
newTs.CallDuration = ts.CallDuration
ts.TimeEnd = timeStart
// split the increment
for incrIndex, incr := range ts.Increments {
if duration-incr.Duration >= 0 {
duration -= incr.Duration
} else {
splitIncrement := ts.Increments[incrIndex].Clone()
splitIncrement.Duration -= duration
ts.Increments[incrIndex].Duration = duration
newTs.Increments = Increments{splitIncrement}
if incrIndex < len(ts.Increments)-1 {
newTs.Increments = append(newTs.Increments, ts.Increments[incrIndex+1:]...)
}
ts.Increments = ts.Increments[:incrIndex+1]
break
}
}
ts.SetNewCallDuration(newTs)
return newTs
}
// Splits the given timespan on activation period's activation time.
func (ts *TimeSpan) SplitByRatingPlan(rp *RatingInfo) (newTs *TimeSpan) {
if !ts.Contains(rp.ActivationTime) {
return nil
}
newTs = &TimeSpan{
TimeStart: rp.ActivationTime,
TimeEnd: ts.TimeEnd,
}
newTs.copyRatingInfo(ts)
newTs.CallDuration = ts.CallDuration
ts.TimeEnd = rp.ActivationTime
ts.SetNewCallDuration(newTs)
// Logger.Debug(fmt.Sprintf("RP SPLITTING: %+v %+v", ts, newTs))
//log.Printf("RP SPLITTING: %+v %+v", ts, newTs)
return
}
// Returns the starting time of this timespan
func (ts *TimeSpan) GetGroupStart() time.Duration {
s := ts.CallDuration - ts.GetDuration()
if s < 0 {
s = 0
}
return s
}
func (ts *TimeSpan) GetGroupEnd() time.Duration {
return ts.CallDuration
}
// sets the CallDuration attribute to reflect new timespan
func (ts *TimeSpan) SetNewCallDuration(nts *TimeSpan) {
d := ts.CallDuration - nts.GetDuration()
if d < 0 {
d = 0
}
ts.CallDuration = d
}
func (nts *TimeSpan) copyRatingInfo(ts *TimeSpan) {
nts.ratingInfo = ts.ratingInfo
nts.MatchedSubject = ts.ratingInfo.MatchedSubject
nts.MatchedPrefix = ts.ratingInfo.MatchedPrefix
}
// returns a time for the specified second in the time span
func (ts *TimeSpan) GetTimeStartForIncrement(index int) time.Time {
return ts.TimeStart.Add(time.Duration(int64(index) * ts.Increments[0].Duration.Nanoseconds()))
}
func (ts *TimeSpan) RoundToDuration(duration time.Duration) {
if duration < ts.GetDuration() {
duration = utils.RoundTo(duration, ts.GetDuration())
}
if duration > ts.GetDuration() {
initialDuration := ts.GetDuration()
ts.TimeEnd = ts.TimeStart.Add(duration)
ts.CallDuration = ts.CallDuration + (duration - initialDuration)
}
}