|
|
|
|
package cdr
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type (
|
|
|
|
|
// LineKind defines several types of records
|
|
|
|
|
LineKind int
|
|
|
|
|
// ReasonKind is metadata about a certain Line
|
|
|
|
|
ReasonKind int
|
|
|
|
|
// CallKind defines if the call came through a user's PBX
|
|
|
|
|
CallKind int
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
// UnknownLine aka "never seen this LineKind before"
|
|
|
|
|
UnknownLine LineKind = iota
|
|
|
|
|
// VoiceLine aka "this line belongs to (a part of) a voice call"
|
|
|
|
|
VoiceLine
|
|
|
|
|
// TextLine aka "this line belongs to a text message"
|
|
|
|
|
TextLine
|
|
|
|
|
// DataLine aka "this line belongs to a data session"
|
|
|
|
|
DataLine
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// LineKindMap translates string from CSV to our typed LineKind
|
|
|
|
|
var LineKindMap = map[string]LineKind{
|
|
|
|
|
"Voice": VoiceLine,
|
|
|
|
|
"Data": DataLine,
|
|
|
|
|
"SMS": TextLine,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
// UnknownReason aka "never seen this reason before"
|
|
|
|
|
UnknownReason ReasonKind = iota
|
|
|
|
|
// ORIG aka "originated"
|
|
|
|
|
ORIG
|
|
|
|
|
// CFIM aka "call forward immediately"
|
|
|
|
|
CFIM
|
|
|
|
|
// CFNA aka "call forward not available"
|
|
|
|
|
CFNA
|
|
|
|
|
// CFBS aka "call forward busy"
|
|
|
|
|
CFBS
|
|
|
|
|
// CFOR aka "call forward out of reach"
|
|
|
|
|
CFOR
|
|
|
|
|
// ROAM aka "roaming"
|
|
|
|
|
ROAM
|
|
|
|
|
// PBXOR aka "PBX out of reach"
|
|
|
|
|
PBXOR
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func (r ReasonKind) String() string {
|
|
|
|
|
switch r {
|
|
|
|
|
case ORIG:
|
|
|
|
|
return "ORIG"
|
|
|
|
|
case CFIM:
|
|
|
|
|
return "CFIM"
|
|
|
|
|
case CFNA:
|
|
|
|
|
return "CFNA"
|
|
|
|
|
case CFBS:
|
|
|
|
|
return "CFBS"
|
|
|
|
|
case CFOR:
|
|
|
|
|
return "CFOR"
|
|
|
|
|
case ROAM:
|
|
|
|
|
return "ROAM"
|
|
|
|
|
case PBXOR:
|
|
|
|
|
return "PBXOR"
|
|
|
|
|
default:
|
|
|
|
|
return "UNKN"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ReasonKindMap translates string from CSV to our typed ReasonKind
|
|
|
|
|
var ReasonKindMap = map[string]ReasonKind{
|
|
|
|
|
"ORIG": ORIG,
|
|
|
|
|
"CFIM": CFIM,
|
|
|
|
|
"CFNA": CFNA,
|
|
|
|
|
"CFBS": CFBS,
|
|
|
|
|
"CFOR": CFOR,
|
|
|
|
|
"ROAM": ROAM,
|
|
|
|
|
"PBXOR": PBXOR,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
UnknownCall CallKind = iota
|
|
|
|
|
Regular
|
|
|
|
|
PBX
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var CallKindMap = map[string]CallKind{
|
|
|
|
|
"Regular": Regular,
|
|
|
|
|
"PBX": PBX,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Line contains the original metadata of a (call) detail record
|
|
|
|
|
type Line struct {
|
|
|
|
|
Id string
|
|
|
|
|
Time time.Time
|
|
|
|
|
CLI string
|
|
|
|
|
From string
|
|
|
|
|
To string
|
|
|
|
|
Account string
|
|
|
|
|
Source string
|
|
|
|
|
Destination string
|
|
|
|
|
RawCount int
|
|
|
|
|
RawCosts int
|
|
|
|
|
Leg int
|
|
|
|
|
Reason ReasonKind
|
|
|
|
|
Kind LineKind
|
|
|
|
|
|
|
|
|
|
// non-public properties
|
|
|
|
|
pkg string
|
|
|
|
|
package_cost int
|
|
|
|
|
network_cost int
|
|
|
|
|
service_cost int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// PricedLine connects a cost (buy) and price (sell) to a particular Line
|
|
|
|
|
type PricedLine struct {
|
|
|
|
|
Line
|
|
|
|
|
|
|
|
|
|
Cost int
|
|
|
|
|
Price int
|
|
|
|
|
OurRef string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Call is a record of one or more PricedLines that ultimately form "a call".
|
|
|
|
|
type Call struct {
|
|
|
|
|
From string
|
|
|
|
|
To string
|
|
|
|
|
Time time.Time
|
|
|
|
|
Duration time.Duration
|
|
|
|
|
RawCost int
|
|
|
|
|
Cost int
|
|
|
|
|
Price int
|
|
|
|
|
Kind CallKind
|
|
|
|
|
Account string
|
|
|
|
|
Description string
|
|
|
|
|
Legs []PricedLine
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *Call) String() string {
|
|
|
|
|
diff := float64(c.RawCost-c.Cost) / 10000.0
|
|
|
|
|
return fmt.Sprintf("%s (%d) (%12s->%12s) cost: %.4f (%.4f=>%+.4f), price: %.4f (dur: %v): %v",
|
|
|
|
|
c.Time.Format(DateTimeFormat), len(c.Legs), c.From, c.To,
|
|
|
|
|
float64(c.Cost)/10000.0, float64(c.RawCost)/10000.0, diff, float64(c.Price)/10000.0, c.Duration, c.Description)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Text is a specific PricedLine for a text message.
|
|
|
|
|
type Text struct {
|
|
|
|
|
PricedLine
|
|
|
|
|
|
|
|
|
|
Account string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (t *Text) String() string {
|
|
|
|
|
return fmt.Sprintf("%s (%12s->%12s) textCost: %.4f, textPrice: %.4f", t.PricedLine.Line.Time.Format(DateTimeFormat), t.PricedLine.Line.From, t.PricedLine.Line.To, float64(t.PricedLine.Cost)/10000.0, float64(t.PricedLine.Price)/10000.0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Data is a specific PricedLine that annotates the number of bytes consumed in that session.
|
|
|
|
|
type Data struct {
|
|
|
|
|
PricedLine
|
|
|
|
|
|
|
|
|
|
Account string
|
|
|
|
|
Kilobytes int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (d *Data) String() string {
|
|
|
|
|
return fmt.Sprintf("%s (%s) %v kilobytes, cost: %.4f, price: %.4f", d.PricedLine.Line.Time.Format(DateTimeFormat), d.Account, d.Kilobytes, float64(d.PricedLine.Cost)/10000.0, float64(d.PricedLine.Price)/10000.0)
|
|
|
|
|
}
|