mirror of
https://github.com/cgrates/cgrates.git
synced 2026-02-12 18:46:24 +05:00
569 lines
16 KiB
Go
569 lines
16 KiB
Go
/*
|
|
Real-time Online/Offline Charging System (OerS) for Telecom & ISP environments
|
|
Copyright (C) ITsysCOM GmbH
|
|
|
|
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 utils
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/cgrates/cron"
|
|
"github.com/ericlagergren/decimal"
|
|
)
|
|
|
|
// RateProfile represents the configuration of a Rate profile
|
|
type RateProfile struct {
|
|
Tenant string
|
|
ID string
|
|
FilterIDs []string
|
|
ActivationInterval *ActivationInterval
|
|
Weights DynamicWeights
|
|
MinCost *Decimal
|
|
MaxCost *Decimal
|
|
MaxCostStrategy string
|
|
Rates map[string]*Rate
|
|
}
|
|
|
|
func (rp *RateProfile) TenantID() string {
|
|
return ConcatenatedKey(rp.Tenant, rp.ID)
|
|
}
|
|
|
|
func (rp *RateProfile) Compile() (err error) {
|
|
for _, rtP := range rp.Rates {
|
|
rtP.uID = ConcatenatedKey(rp.Tenant, rp.ID, rtP.ID)
|
|
if err = rtP.Compile(); err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Rate defines rate related information used within a RateProfile
|
|
type Rate struct {
|
|
ID string // RateID
|
|
FilterIDs []string // RateFilterIDs
|
|
ActivationTimes string // ActivationTimes is a cron formatted time interval
|
|
Weights DynamicWeights // RateWeight will decide the winner per interval start
|
|
Blocker bool // RateBlocker will make this rate recurrent, deactivating further intervals
|
|
IntervalRates []*IntervalRate
|
|
|
|
sched cron.Schedule // compiled version of activation times as cron.Schedule interface
|
|
uID string
|
|
}
|
|
|
|
type ExtRate struct {
|
|
ID string // RateID
|
|
FilterIDs []string // RateFilterIDs
|
|
ActivationTimes string // ActivationTimes is a cron formatted time interval
|
|
Weights DynamicWeights // RateWeight will decide the winner per interval start
|
|
Blocker bool // RateBlocker will make this rate recurrent, deactivating further intervals
|
|
IntervalRates []*ExtIntervalRate
|
|
|
|
sched cron.Schedule // compiled version of activation times as cron.Schedule interface
|
|
uID string
|
|
}
|
|
|
|
func (rT *Rate) AsExtRate() (eRt *ExtRate, err error) {
|
|
eRt = &ExtRate{
|
|
ID: rT.ID,
|
|
ActivationTimes: rT.ActivationTimes,
|
|
sched: rT.sched,
|
|
uID: rT.uID,
|
|
Blocker: rT.Blocker,
|
|
}
|
|
if rT.FilterIDs != nil {
|
|
eRt.FilterIDs = make([]string, len(rT.FilterIDs))
|
|
for idx, val := range rT.FilterIDs {
|
|
eRt.FilterIDs[idx] = val
|
|
}
|
|
}
|
|
if rT.Weights != nil {
|
|
eRt.Weights = rT.Weights
|
|
}
|
|
if rT.IntervalRates != nil {
|
|
eRt.IntervalRates = make([]*ExtIntervalRate, len(rT.IntervalRates))
|
|
for idx, val := range rT.IntervalRates {
|
|
if rcvIntv, err := val.AsExtIntervalRate(); err != nil {
|
|
return nil, err
|
|
} else {
|
|
eRt.IntervalRates[idx] = rcvIntv
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// UID returns system wide unique identifier
|
|
func (rt *Rate) UID() string {
|
|
return rt.uID
|
|
}
|
|
|
|
type IntervalRate struct {
|
|
IntervalStart *Decimal // Starting point when the Rate kicks in
|
|
FixedFee *Decimal
|
|
RecurrentFee *Decimal
|
|
Unit *Decimal // RateUnit
|
|
Increment *Decimal // RateIncrement
|
|
}
|
|
|
|
type ExtIntervalRate struct {
|
|
IntervalStart *float64 // Starting point when the Rate kicks in
|
|
FixedFee *float64
|
|
RecurrentFee *float64
|
|
Unit *float64 // RateUnit
|
|
Increment *float64 // RateIncrement
|
|
}
|
|
|
|
func (iR *IntervalRate) AsExtIntervalRate() (eIr *ExtIntervalRate, err error) {
|
|
eIr = new(ExtIntervalRate)
|
|
if iR.IntervalStart != nil {
|
|
if fltIntSt, ok := iR.IntervalStart.Big.Float64(); !ok {
|
|
return nil, errors.New("cannot convert decimal IntervalStart to float64")
|
|
} else {
|
|
eIr.IntervalStart = &fltIntSt
|
|
}
|
|
}
|
|
if iR.FixedFee != nil {
|
|
if fltFxdFee, ok := iR.FixedFee.Big.Float64(); !ok {
|
|
return nil, errors.New("cannot convert decimal FixedFee to float64")
|
|
} else {
|
|
eIr.FixedFee = &fltFxdFee
|
|
}
|
|
}
|
|
if iR.RecurrentFee != nil {
|
|
if fltRecFee, ok := iR.RecurrentFee.Big.Float64(); !ok {
|
|
return nil, errors.New("cannot convert decimal RecurrentFee to float64")
|
|
} else {
|
|
eIr.RecurrentFee = &fltRecFee
|
|
}
|
|
}
|
|
if iR.Unit != nil {
|
|
if fltUnit, ok := iR.Unit.Big.Float64(); !ok {
|
|
return nil, errors.New("cannot convert decimal Unit to float64")
|
|
} else {
|
|
eIr.Unit = &fltUnit
|
|
}
|
|
}
|
|
if iR.Increment != nil {
|
|
if fltIncr, ok := iR.Increment.Big.Float64(); !ok {
|
|
return nil, errors.New("cannot convert decimal Increment to float64")
|
|
} else {
|
|
eIr.Increment = &fltIncr
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (rt *Rate) Compile() (err error) {
|
|
aTime := rt.ActivationTimes
|
|
if aTime == EmptyString {
|
|
aTime = "* * * * *"
|
|
}
|
|
if rt.sched, err = cron.ParseStandard(aTime); err != nil {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// RunTimes returns the set of activation and deactivation times for this rate on the interval between >=sTime and <eTime
|
|
// aTimes is in the form of [][]
|
|
func (rt *Rate) RunTimes(sTime, eTime time.Time, verbosity int) (aTimes [][]time.Time, err error) {
|
|
sTime = sTime.Add(-time.Minute) // to make sure we can cover startTime
|
|
for i := 0; i < verbosity; i++ {
|
|
aTime := rt.sched.Next(sTime)
|
|
if aTime.IsZero() || !aTime.Before(eTime) { // #TestMe
|
|
return
|
|
}
|
|
iTime := rt.sched.NextInactive(aTime)
|
|
aTimes = append(aTimes, []time.Time{aTime, iTime})
|
|
if iTime.IsZero() || !eTime.After(iTime) { // #TestMe
|
|
return
|
|
}
|
|
sTime = iTime
|
|
}
|
|
// protect from memory leak
|
|
Logger.Warning(
|
|
fmt.Sprintf(
|
|
"maximum runTime iterations reached for Rate: <%+v>, sTime: <%+v>, eTime: <%+v>",
|
|
rt, sTime, eTime))
|
|
return nil, ErrMaxIterationsReached
|
|
}
|
|
|
|
// RateProfileWithAPIOpts is used in replicatorV1 for dispatcher
|
|
type RateProfileWithAPIOpts struct {
|
|
*RateProfile
|
|
APIOpts map[string]interface{}
|
|
}
|
|
|
|
// RateSInterval is used by RateS to integrate Rate info for one charging interval
|
|
type RateSInterval struct {
|
|
IntervalStart *Decimal
|
|
Increments []*RateSIncrement
|
|
CompressFactor int64
|
|
|
|
cost *decimal.Big // unexported total interval cost
|
|
}
|
|
|
|
type ExtRateSInterval struct {
|
|
IntervalStart *float64
|
|
Increments []*ExtRateSIncrement
|
|
CompressFactor int64
|
|
|
|
cost *float64 // unexported total interval cost
|
|
}
|
|
|
|
func (rI *RateSInterval) AsExtRateSInterval() (eRi *ExtRateSInterval, err error) {
|
|
eRi = &ExtRateSInterval{
|
|
CompressFactor: rI.CompressFactor,
|
|
}
|
|
if rI.Increments != nil {
|
|
eRi.Increments = make([]*ExtRateSIncrement, len(rI.Increments))
|
|
for idx, val := range rI.Increments {
|
|
if rcv, err := val.AsExtRateSIncrement(); err != nil {
|
|
return nil, err
|
|
} else {
|
|
eRi.Increments[idx] = rcv
|
|
}
|
|
}
|
|
}
|
|
if rI.IntervalStart != nil {
|
|
if fltIntStart, ok := rI.IntervalStart.Big.Float64(); !ok {
|
|
return nil, errors.New("Cannot convert decimal IntervalStart into float64 ")
|
|
} else {
|
|
eRi.IntervalStart = &fltIntStart
|
|
}
|
|
}
|
|
if rI.cost != nil {
|
|
if fltCost, ok := rI.cost.Float64(); !ok {
|
|
return nil, errors.New("Cannot convert decimal cost into float64 ")
|
|
} else {
|
|
eRi.cost = &fltCost
|
|
}
|
|
}
|
|
return
|
|
|
|
}
|
|
|
|
type RateSIncrement struct {
|
|
IncrementStart *Decimal
|
|
Rate *Rate
|
|
IntervalRateIndex int
|
|
CompressFactor int64
|
|
Usage *Decimal
|
|
|
|
cost *decimal.Big // unexported total increment cost
|
|
}
|
|
|
|
type ExtRateSIncrement struct {
|
|
IncrementStart *float64
|
|
Rate *ExtRate
|
|
IntervalRateIndex int
|
|
CompressFactor int64
|
|
Usage *float64
|
|
|
|
cost *float64 // unexported total increment cost
|
|
}
|
|
|
|
func (rI *RateSIncrement) AsExtRateSIncrement() (eRi *ExtRateSIncrement, err error) {
|
|
eRi = &ExtRateSIncrement{
|
|
IntervalRateIndex: rI.IntervalRateIndex,
|
|
CompressFactor: rI.CompressFactor,
|
|
}
|
|
if rI.Rate != nil {
|
|
eRi.Rate = new(ExtRate)
|
|
if rt, err := rI.Rate.AsExtRate(); err != nil {
|
|
return nil, err
|
|
} else {
|
|
eRi.Rate = rt
|
|
}
|
|
}
|
|
if rI.IncrementStart != nil {
|
|
if fltIncrStart, ok := rI.IncrementStart.Big.Float64(); !ok {
|
|
return nil, errors.New("Cannot convert decimal IncrementStart into float64 ")
|
|
} else {
|
|
eRi.IncrementStart = &fltIncrStart
|
|
}
|
|
}
|
|
if rI.Usage != nil {
|
|
if fltUsage, ok := rI.Usage.Big.Float64(); !ok {
|
|
return nil, errors.New("Cannot convert decimal Usage into float64 ")
|
|
} else {
|
|
eRi.Usage = &fltUsage
|
|
}
|
|
}
|
|
if rI.cost != nil {
|
|
if fltCost, ok := rI.cost.Float64(); !ok {
|
|
return nil, errors.New("Cannot convert decimal cost into float64 ")
|
|
} else {
|
|
eRi.cost = &fltCost
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Equals compares two RateSIntervals
|
|
func (rIl *RateSInterval) Equals(nRil *RateSInterval) (eq bool) {
|
|
if rIl.IntervalStart == nil && nRil.IntervalStart != nil ||
|
|
rIl.IntervalStart != nil && nRil.IntervalStart == nil ||
|
|
len(rIl.Increments) != len(nRil.Increments) {
|
|
return
|
|
}
|
|
if rIl.IntervalStart.Compare(nRil.IntervalStart) != 0 ||
|
|
rIl.CompressFactor != nRil.CompressFactor {
|
|
return
|
|
}
|
|
for i, rtIn := range rIl.Increments {
|
|
if !rtIn.Equals(nRil.Increments[i]) {
|
|
return
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (rI *RateSIncrement) Equals(rtIn *RateSIncrement) (eq bool) {
|
|
if rI.Usage == nil && rtIn.Usage != nil ||
|
|
rI.Usage != nil && rtIn.Usage == nil ||
|
|
rI.IncrementStart == nil && rtIn.IncrementStart != nil ||
|
|
rI.IncrementStart != nil && rtIn.IncrementStart == nil {
|
|
return
|
|
}
|
|
return rI.Usage.Compare(rtIn.Usage) == 0 &&
|
|
rI.IncrementStart.Compare(rtIn.IncrementStart) == 0 &&
|
|
rI.CompressFactor == rtIn.CompressFactor &&
|
|
rI.IntervalRateIndex == rtIn.IntervalRateIndex &&
|
|
rI.Rate.UID() == rtIn.Rate.UID()
|
|
}
|
|
|
|
// RateProfileCost is the cost returned by RateS at cost queries
|
|
type RateProfileCost struct {
|
|
ID string // RateProfileID
|
|
Cost float64
|
|
MinCost float64
|
|
MaxCost float64
|
|
MaxCostStrategy string
|
|
RateSIntervals []*RateSInterval
|
|
Altered []string
|
|
}
|
|
|
|
// CorrectCost should be called in final phase of cost calculation
|
|
// in order to apply further correction like Min/MaxCost or rounding
|
|
func (rPc *RateProfileCost) CorrectCost(rndDec *int, rndMtd string) {
|
|
if rPc.MinCost != 0 && rPc.Cost < rPc.MinCost {
|
|
rPc.Cost = rPc.MinCost
|
|
rPc.Altered = append(rPc.Altered, MinCost)
|
|
}
|
|
if rPc.MaxCost != 0 && rPc.Cost > rPc.MaxCost {
|
|
rPc.Cost = rPc.MaxCost
|
|
rPc.Altered = append(rPc.Altered, MaxCost)
|
|
}
|
|
if rndDec != nil {
|
|
rPc.Cost = Round(rPc.Cost, *rndDec, rndMtd)
|
|
rPc.Altered = append(rPc.Altered, RoundingDecimals)
|
|
}
|
|
}
|
|
|
|
// Sort will sort the IntervalRates from each Rate based on IntervalStart
|
|
func (rpp *RateProfile) Sort() {
|
|
for _, rate := range rpp.Rates {
|
|
sort.Slice(rate.IntervalRates, func(i, j int) bool {
|
|
return rate.IntervalRates[i].IntervalStart.Compare(rate.IntervalRates[j].IntervalStart) == -1
|
|
})
|
|
}
|
|
}
|
|
|
|
// CompressEquals compares two RateSIntervals for Compress function
|
|
func (rIv *RateSInterval) CompressEquals(rIv2 *RateSInterval) (eq bool) {
|
|
if len(rIv.Increments) != len(rIv2.Increments) {
|
|
return
|
|
}
|
|
for i, rIcr := range rIv.Increments {
|
|
if !rIcr.CompressEquals(rIv2.Increments[i]) {
|
|
return
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (rIv *RateSInterval) Cost() *decimal.Big {
|
|
if rIv.cost == nil {
|
|
rIv.cost = new(decimal.Big)
|
|
for _, incrm := range rIv.Increments {
|
|
rIv.cost = SumBig(rIv.cost, incrm.Cost())
|
|
}
|
|
}
|
|
return rIv.cost
|
|
}
|
|
|
|
// CompressEquals compares two RateSIncrement for Compress function
|
|
func (rIcr *RateSIncrement) CompressEquals(rIcr2 *RateSIncrement) (eq bool) {
|
|
return rIcr.Rate.UID() == rIcr2.Rate.UID() &&
|
|
rIcr.IntervalRateIndex == rIcr2.IntervalRateIndex &&
|
|
rIcr.Usage.Big.Cmp(rIcr2.Usage.Big) == 0
|
|
}
|
|
|
|
// Cost computes the Cost on RateSIncrement
|
|
func (rIcr *RateSIncrement) Cost() *decimal.Big {
|
|
if rIcr.cost == nil {
|
|
icrRt := rIcr.Rate.IntervalRates[rIcr.IntervalRateIndex]
|
|
if rIcr.Usage.Compare(NewDecimal(-1, 0)) == 0 { // FixedFee
|
|
rIcr.cost = icrRt.FixedFee.Big
|
|
} else {
|
|
rIcr.cost = icrRt.RecurrentFee.Big
|
|
if icrRt.Unit != icrRt.Increment {
|
|
rIcr.cost = DivideBig(
|
|
MultiplyBig(rIcr.cost, icrRt.Increment.Big),
|
|
icrRt.Unit.Big)
|
|
}
|
|
if rIcr.CompressFactor != 1 {
|
|
rIcr.cost = MultiplyBig(
|
|
rIcr.cost,
|
|
new(decimal.Big).SetUint64(uint64(rIcr.CompressFactor)))
|
|
}
|
|
}
|
|
}
|
|
return rIcr.cost
|
|
}
|
|
|
|
// CostForIntervals sums the costs for all intervals
|
|
func CostForIntervals(rtIvls []*RateSInterval) (cost *decimal.Big) {
|
|
cost = new(decimal.Big)
|
|
for _, rtIvl := range rtIvls {
|
|
cost = SumBig(cost, rtIvl.Cost())
|
|
}
|
|
return
|
|
}
|
|
|
|
// CompressIntervals will compress intervals which equal
|
|
func CompressIntervals(rtIvls []*RateSInterval) {
|
|
}
|
|
|
|
func (ext *APIRateProfile) AsRateProfile() (rp *RateProfile, err error) {
|
|
rp = &RateProfile{
|
|
Tenant: ext.Tenant,
|
|
ID: ext.ID,
|
|
FilterIDs: ext.FilterIDs,
|
|
ActivationInterval: ext.ActivationInterval,
|
|
MaxCostStrategy: ext.MaxCostStrategy,
|
|
}
|
|
if ext.Weights != EmptyString {
|
|
if rp.Weights, err = NewDynamicWeightsFromString(ext.Weights, ";", "&"); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if ext.MinCost != nil {
|
|
rp.MinCost = NewDecimalFromFloat64(*ext.MinCost)
|
|
}
|
|
if ext.MaxCost != nil {
|
|
rp.MaxCost = NewDecimalFromFloat64(*ext.MaxCost)
|
|
}
|
|
if len(ext.Rates) != 0 {
|
|
rp.Rates = make(map[string]*Rate)
|
|
for key, extRate := range ext.Rates {
|
|
if rp.Rates[key], err = extRate.AsRate(); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
if err = rp.Compile(); err != nil {
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
type APIRateProfile struct {
|
|
Tenant string
|
|
ID string
|
|
FilterIDs []string
|
|
ActivationInterval *ActivationInterval
|
|
Weights string
|
|
MinCost *float64
|
|
MaxCost *float64
|
|
MaxCostStrategy string
|
|
Rates map[string]*APIRate
|
|
APIOpts map[string]interface{}
|
|
}
|
|
|
|
func (ext *APIRate) AsRate() (rate *Rate, err error) {
|
|
rate = &Rate{
|
|
ID: ext.ID,
|
|
FilterIDs: ext.FilterIDs,
|
|
ActivationTimes: ext.ActivationTimes,
|
|
Blocker: ext.Blocker,
|
|
}
|
|
if ext.Weights != EmptyString {
|
|
if rate.Weights, err = NewDynamicWeightsFromString(ext.Weights, ";", "&"); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if len(ext.IntervalRates) != 0 {
|
|
rate.IntervalRates = make([]*IntervalRate, len(ext.IntervalRates))
|
|
for i, iRate := range ext.IntervalRates {
|
|
if rate.IntervalRates[i], err = iRate.AsIntervalRate(); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
type APIRate struct {
|
|
ID string // RateID
|
|
FilterIDs []string // RateFilterIDs
|
|
ActivationTimes string // ActivationTimes is a cron formatted time interval
|
|
Weights string // RateWeight will decide the winner per interval start
|
|
Blocker bool // RateBlocker will make this rate recurrent, deactivating further intervals
|
|
IntervalRates []*APIIntervalRate
|
|
}
|
|
|
|
func (ext *APIIntervalRate) AsIntervalRate() (iRate *IntervalRate, err error) {
|
|
iRate = new(IntervalRate)
|
|
if iRate.IntervalStart, err = NewDecimalFromUsage(ext.IntervalStart); err != nil {
|
|
return
|
|
}
|
|
if ext.FixedFee != nil {
|
|
iRate.FixedFee = NewDecimalFromFloat64(*ext.FixedFee)
|
|
}
|
|
if ext.RecurrentFee != nil {
|
|
iRate.RecurrentFee = NewDecimalFromFloat64(*ext.RecurrentFee)
|
|
}
|
|
if ext.Unit != nil {
|
|
iRate.Unit = NewDecimalFromFloat64(*ext.Unit)
|
|
}
|
|
if ext.Increment != nil {
|
|
iRate.Increment = NewDecimalFromFloat64(*ext.Increment)
|
|
}
|
|
return
|
|
}
|
|
|
|
type APIIntervalRate struct {
|
|
IntervalStart string
|
|
FixedFee *float64
|
|
RecurrentFee *float64
|
|
Unit *float64 // RateUnit
|
|
Increment *float64 // RateIncrement
|
|
}
|
|
|
|
type RemoveRPrfRates struct {
|
|
Tenant string
|
|
ID string
|
|
RateIDs []string
|
|
APIOpts map[string]interface{}
|
|
}
|