You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

227 lines
6.7 KiB

package cdr
import (
"fmt"
"strings"
"time"
)
func CombineTextCDRs(cdrLines []Line, prices []Price) ([]Text, error) {
var err []string
numTextCDRs := 0
for _, l := range cdrLines {
if l.Kind != TextLine {
continue
}
if l.Reason != ORIG {
err = append(err, fmt.Sprintf("text CDR did not match reason ORIG: %+v", l))
continue
}
numTextCDRs++
}
// Now convert each text CDR to a priced Text
ret := make([]Text, numTextCDRs)
i := 0
for _, l := range cdrLines {
if l.Kind != TextLine || l.Reason != ORIG {
continue
}
ret[i] = Text{
PricedLine{
Line: l,
Cost: TEXT_OUTGOING_BUY,
Price: TEXT_OUTGOING_SELL,
},
}
i++
}
if len(err) > 0 {
return nil, fmt.Errorf("error pricing text CDRs: %v", strings.Join(err, ", "))
}
return ret, nil
}
func CombineDataCDRs(cdrLines []Line, prices []Price) ([]Data, error) {
var err []string
numDataCDRs := 0
errSet := false
for _, l := range cdrLines {
errSet = false
if l.Kind != DataLine {
continue
}
if l.Reason != ORIG {
err = append(err, fmt.Sprintf("data CDR did not match reason ORIG: %+v", l))
errSet = true
}
if l.Destination != DATA_EXPECTED_DESTINATION {
err = append(err, fmt.Sprintf("data CDR did not match expected destination %q: %+v", DATA_EXPECTED_DESTINATION, l))
errSet = true
}
if errSet {
continue
}
numDataCDRs++
}
ret := make([]Data, numDataCDRs)
i := 0
for _, l := range cdrLines {
if l.Kind != DataLine || l.Reason != ORIG || l.Destination != DATA_EXPECTED_DESTINATION {
continue
}
// Initially set cost and price to national buy/sell values
dataCost := DATA_OUTGOING_NATIONAL_BUY
dataPrice := DATA_OUTGOING_NATIONAL_SELL
if l.Source != DATA_NATIONAL_SOURCE {
// Roaming CDR
// TODO: distinguish between Europe "Roam-like-at-home" and worldwide
dataCost = DATA_OUTGOING_ROAMING_BUY
dataPrice = DATA_OUTGOING_ROAMING_SELL
}
dataBytes := l.RawCount
dataCost *= dataBytes
dataPrice *= dataBytes
ret[i] = Data{
PricedLine: PricedLine{
Line: l,
Cost: int(dataCost / 1000.0), // compensate for cost specified per MB
Price: int(dataPrice / 1000.0), // compensate for price specified per MB
},
Bytes: l.RawCount,
}
i++
}
if len(err) > 0 {
return nil, fmt.Errorf("error pricing data CDRs: %v", strings.Join(err, ", "))
}
return ret, nil
}
func CombineCallCDRs(cdrLines []Line, prices []Price) ([]Call, error) {
// Preprocess the price list into a map of "destination" -> Price, filter out the Voice destinations
destinationToPrice := make(map[string]Price)
destinationToPrice[CALL_NATIONAL_DESTINATION_FIXED.Destination] = CALL_NATIONAL_DESTINATION_FIXED
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
nonMatchedDestinations := make(map[string]struct{})
for _, l := range cdrLines {
if l.Kind != VoiceLine {
continue
}
// Cache destination prices
_, knownNonPriced := nonMatchedDestinations[l.Destination]
if _, ok := destinationToPrice[l.Destination]; !ok && !knownNonPriced {
priceLoop:
for _, p := range prices {
if p.Type != PriceCall {
continue priceLoop
}
if p.Destination == l.Destination {
destinationToPrice[l.Destination] = p
break priceLoop
}
nonMatchedDestinations[l.Destination] = struct{}{}
}
}
}
for k := range nonMatchedDestinations {
fmt.Printf("[warning] no price for destination %v\n", k)
}
// a map from "%s_%s_%s": l.Time.Format(DateTimeFormat), l.CLI, l.From
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)
if _, ok := uniqueCalls[idx]; !ok {
uniqueCalls[idx] = make([]Line, 0)
}
uniqueCalls[idx] = append(uniqueCalls[idx], l)
}
ret := make([]Call, len(uniqueCalls))
i := 0
for _, v := range uniqueCalls {
/*
fmt.Printf("[%s] %d\n", k, len(v))
for i, c := range v {
fmt.Printf("\t%d (%d) [%25s]: (%12s): %12s->%12s (%s)\n", i, c.Leg, c.Account, c.CLI, c.From, c.To, c.Reason)
}
fmt.Println()
*/
for _, leg := range v {
legPrice := Price{}
if leg.CLI == leg.From {
switch {
case leg.Source == CALL_FROM_HANDSET_SOURCE && leg.Destination == CALL_FROM_HANDSET_DESTINATION:
// This leg consists of the airtime (outgoing call from the HS perspective) (HS->PBX, non-roaming)
legPrice = CALL_NATIONAL_DESTINATION_AIRTIME_FROM_HANDSET
case leg.From != leg.To && leg.Source == CALL_FROM_HANDSET_SOURCE: // Case 2, 3
// This leg consists of routing a call (from a PBX or Handset) through our platform. The costs are defined by the call's destination.
// Case 1: PBX->destination
// Case 2: HS->destination, non roaming
// Case 3: HS->destination, roaming
// Case 4: HS->PBX, roaming
fallthrough
case leg.Source != CALL_FROM_HANDSET_SOURCE && leg.Reason == ORIG: // Case 1
fallthrough
case leg.From != leg.To && leg.Source == CALL_FROM_PBX_SOURCE: // Case 4
legPrice = destinationToPrice[leg.Destination]
}
}
if leg.CLI != leg.From {
switch {
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
// source -> HS (roaming)
case leg.Source == CALL_TO_HANDSET_SOURCE && leg.Reason == ROAM:
// We only know the destination country
fallthrough
case leg.CLI == CALL_CLI_ANONYMOUS:
// Incoming call from a blocked CLI
legPrice = destinationToPrice[leg.Destination]
}
}
if legPrice == (Price{}) {
// error, weird category
_, 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)
}
// 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)
}
ret[i] = Call{
From: "asdf",
To: "asdkflj",
Duration: time.Duration(0),
Cost: 0,
Price: 0,
}
/*
ret[i].Legs = make([]PricedLine, len(v))
for j, leg := range v {
ret[i].Legs[j] = leg
}
*/
i++
}
fmt.Printf("destinationPrice: %+v\n", destinationToPrice)
return nil, fmt.Errorf("not yet implemented")
}