Browse Source

WIP: pricing of CDRs, it's not enough to filter per destination

master
Gerdriaan Mulder 6 years ago
parent
commit
9db39c125b
  1. 52
      combine.go
  2. 13
      import_price.go
  3. 1
      record.go

52
combine.go

@ -2,6 +2,7 @@ package cdr
import (
"fmt"
"math"
"strings"
"time"
)
@ -112,6 +113,9 @@ func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) {
destinationToPrice[CALL_NATIONAL_DESTINATION_AIRTIME_TO_HANDSET.Destination] = CALL_NATIONAL_DESTINATION_AIRTIME_TO_HANDSET
destinationToPrice[CALL_NATIONAL_DESTINATION_AIRTIME_FROM_HANDSET.Destination] = CALL_NATIONAL_DESTINATION_AIRTIME_FROM_HANDSET
destinationToPrice[CALL_NATIONAL_DESTINATION_MOBILE.Destination] = CALL_NATIONAL_DESTINATION_MOBILE
destinationToPrice[CALL_NATIONAL_DESTINATION_FREEPHONE.Destination] = CALL_NATIONAL_DESTINATION_FREEPHONE
destinationToPrice[CALL_NATIONAL_DESTINATION_PREMIUM_NOSETUP.Destination] = CALL_NATIONAL_DESTINATION_PREMIUM_NOSETUP
destinationToPrice[CALL_NATIONAL_DESTINATION_VOICEMAIL.Destination] = CALL_NATIONAL_DESTINATION_VOICEMAIL
nonMatchedDestinations := make(map[string]struct{})
for _, l := range cdrLines {
@ -121,15 +125,22 @@ func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) {
// Cache destination prices
_, knownNonPriced := nonMatchedDestinations[l.Destination]
if _, ok := destinationToPrice[l.Destination]; !ok && !knownNonPriced {
matched := false
priceLoop:
for _, p := range prices {
if p.Type != PriceCall {
fmt.Printf("[debug] skipping this price: %+v\n", p)
continue priceLoop
}
if p.Destination == l.Destination {
destinationToPrice[l.Destination] = p
matched = true
break priceLoop
}
}
if !matched {
fmt.Printf("[debug] non-matched price for destination %q\n", l.Destination)
nonMatchedDestinations[l.Destination] = struct{}{}
}
}
@ -139,13 +150,13 @@ func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) {
fmt.Printf("[warning] no price for destination %v\n", k)
}
// a map from "%s_%s_%s": l.Time.Format(DateTimeFormat), l.CLI, l.From
// a map from "%s_%s_%s_%s": l.Time.Format(DateTimeFormat), l.CLI, l.From, l.Account
uniqueCalls := make(map[string][]Line)
for _, l := range cdrLines {
if l.Kind != VoiceLine {
continue
}
idx := fmt.Sprintf("%s_%s_%s", l.Time.Format(DateTimeFormat), l.CLI, l.From)
idx := fmt.Sprintf("%s_%s_%s_%s", l.Time.Format(DateTimeFormat), l.CLI, l.From, l.Account)
if _, ok := uniqueCalls[idx]; !ok {
uniqueCalls[idx] = make([]Line, 0)
}
@ -162,6 +173,8 @@ func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) {
}
fmt.Println()
*/
callCost := 0.0
callPrice := 0.0
for _, leg := range v {
legPrice := Price{}
if leg.CLI == leg.From {
@ -188,6 +201,8 @@ func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) {
case leg.From == leg.To && leg.Source == CALL_TO_HANDSET_SOURCE && leg.Destination == CALL_TO_HANDSET_DESTINATION:
// This leg consists of the airtime (PBX->HS, roaming + non-roaming)
legPrice = CALL_NATIONAL_DESTINATION_AIRTIME_TO_HANDSET
case leg.Destination == CALL_TO_VOICEMAIL && (leg.Reason == CFNA || leg.Reason == CFBS || leg.Reason == CFIM):
legPrice = CALL_NATIONAL_DESTINATION_VOICEMAIL
// source -> HS (roaming)
case leg.Source == CALL_TO_HANDSET_SOURCE && leg.Reason == ROAM:
// We only know the destination country
@ -202,23 +217,34 @@ func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) {
_, mapOk := destinationToPrice[leg.Destination]
fmt.Printf("[warning] non-matched CDR leg (cid: %q, dt: %v, CLI: %s, %s->%s, src: %s, dest: %s (%s) %v\n",
leg.Id, leg.Time.Format(DateTimeFormat), leg.CLI, leg.From, leg.To, leg.Source, leg.Destination, leg.Reason, mapOk)
} else {
// RawCount is in seconds. Buy/SellUnit is in cents*100, per minute
callCost += float64(legPrice.BuyEach) + float64(leg.RawCount)*(float64(legPrice.BuyUnit)/60.0)
callPrice += float64(legPrice.SellEach) + float64(leg.RawCount)*(float64(legPrice.SellUnit)/60.0)
fmt.Printf("(%12s->%12s) leg %d, callCost: %.4f, callPrice: %.4f (dst: %q)\n", leg.CLI, leg.To, leg.Leg, callCost, callPrice, legPrice.OurRef)
}
// Source -> HS (non-roaming) (i.e. incoming calls) does not exist in the CSV
fmt.Printf("(%12s->%12s) leg %d, price: %+v\n", leg.CLI, leg.To, leg.Leg, legPrice)
//fmt.Printf("(%12s->%12s) leg %d, price: %+v\n", leg.CLI, leg.To, leg.Leg, legPrice)
}
if len(v) == 1 {
callDuration, _ := time.ParseDuration(fmt.Sprintf("%ds", v[0].RawCount))
// CLI == From, then this leg's CLI is our "From" field, and this leg's "To" likewise
ret[i] = Call{
From: "asdf",
To: "asdkflj",
Duration: time.Duration(0),
Cost: 0,
Price: 0,
From: v[0].CLI,
To: v[0].To,
Duration: callDuration,
Time: v[0].Time,
Cost: int(math.Round(callCost)),
Price: int(math.Round(callPrice)),
}
/*
ret[i].Legs = make([]PricedLine, len(v))
for j, leg := range v {
ret[i].Legs[j] = leg
} else {
}
*/
fmt.Printf("==> %+v\n", ret[i])
//ret[i].Legs = make([]PricedLine, len(v))
//for j, leg := range v {
//ret[i].Legs[j] = leg
//}
i++
}

13
import_price.go

@ -23,6 +23,7 @@ type Price struct {
SellEach int
SellUnit int
Destination string
OurRef string
}
const FieldsPerPricingLine = 14
@ -94,7 +95,17 @@ func ImportPricesFile(fn string) ([]Price, error) {
}
for _, priceType := range priceTypes {
destination := line[PriceImportRegion] + " - " + line[PriceImportDestination] + " - " + priceType
var destination string
if line[PriceImportRegion] == "Europe" {
destination = line[PriceImportDestination] + " - " + priceType + " - "
if priceType == "Fixed" {
destination = destination + "Proper"
} else {
destination = destination + priceType
}
} else {
destination = line[PriceImportRegion] + " - " + line[PriceImportDestination] + " - " + priceType
}
priceKind := PriceCall // TODO: import Text and Data through the same CSV
var buyUnit, sellUnit int

1
record.go

@ -128,6 +128,7 @@ type PricedLine struct {
type Call struct {
From string
To string
Time time.Time
Duration time.Duration
Cost int
Price int

Loading…
Cancel
Save