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) }