7月 132012

Call center management is both Arts and Sciences. While driving moral and setting up strategies is more about Arts, staffing and servicing level configuration based on call load is in the domain of Sciences.

The science part of call center management is based on Queueing Theory, which studies "the Phenomena of standing, waiting and serving" [1], [2].The queueing model abstracts the complex phenomena of call center acticities into an idealized mathematical framework, so that it is possible to study the key characteristics and dynamics of the real world. There are many different models that could be applied to Call Center management, one simple yet popular model is called Erlang-C model. Some other highly related models are Erlang-B and Erlang-A models, but they won't be covered in this post.

In this article, Erlang-C model will be briefly described and the computation of key parameters of this model will be discussed, followed by a complete suite of SAS code pieces. It is interesting that Erlang-C model is rarely touched in the SAS community. Searching through SAS-L archives, there were only two discussions relevant, and they are set apart by

Erlang-C model is a commonly used mathematical framework for many Call center staffing and management softwares. Based on Kendall notation, this model can be expressed as M/M/C/\inf, where the first M indicates a Poisson distribution for Arrival process, while the second M indicates an Exponential distribution for servicing time. Both 'M' refers markovian the 'C' indicates number of servers which is a non-negative integer, while the last \inf refers infinite number of places in the system. Erlang-C system has extra assumption that it is First In First Out (FIFO) with infinite calling population. Therefore, Erlang-C model describe a Call Center that serves customers in the order they arrive in, while customers arrives following a Poisson process and the servicing time is Exponentially distributed. When a customer is not immediately served, it is put on hold until next available server (CSR). The customers are assumed to be patient so that they won't drop out but instead wait how-long-it-takes until next CSR. The system can take infinite number of waiting customers.

In Erlang-C model, the traffic to a Call Center is measured in a so-called term of Offered Traffic Load, which is defined to be the ratio between the parameter \lambda of Poisson process and the parameter \mu of Exponential servicing time. Offered Traffic Load is therefore interpreted as the average number of services that are trying to happen simultaneously. It is free of time unit, but does has its own unit, called Erlangs.

At the core of Erlang-C model is the Erlang-C function. The Erlang C function computes the probability that an arriving customer in the Erlang C queueing model will find that all servers are busy. This is the same as the fraction of arriving customers that are delayed (i.e., must wait) before beginning service.

Erlang-C function is expressed as:

P_c(n, x) = dPoisson(n, x)/(dPoisson(n, x) - (1-x/n)*pPoisson(n, x))

where dPoisson(n, x) is the density function of Poisson distribution with support 0,1, .., n and shape parameter x. pPoisson(n, x) is the corresponding cumulation distribution.

In Erlang-C function, there are three key elements:

1. Probability of delay, P_c.

2. # of servers, n.

3. Offered Traffic Load, x.

Given any two, the last parameter can be calculated or approximated. For example, in the code pieces below:

1. Function

2. Function

3. Function

In addition to the three key numbers in Erlang-C model, there are some other paremeters of particular interests to Call Center management. For example, the probability an arriving call will be put in queue for no more than certain waiting time given the average handling time. Function

The results of this set of functions has been calibrated against the data from [5], specifically Table 1. of [5] is reproduced exactly. The DATA STEP at the end of the program below will reproduce the results, and here is the result my own run.

For a complete list of quantities that Erlang-C model will work with and compute for, the webpage of Abstract Micro Systems @ http://abstractmicro.com/erlang/helppages/mod-c.htm provides a very good introduction.

[1]. Kleinrock, Leonard (January 1975).

[2]. Kleinrock, Leonard (April 1976).

[3]. http://www.listserv.uga.edu/cgi-bin/wa?A2=ind0210C&L=sas-l&P=R10763

[4]. http://www.listserv.uga.edu/cgi-bin/wa?A2=ind1206C&L=sas-l&P=R45472

[5]. Tibor Mišuth, Erik Chromý, Ivan Baroňák.

All functions below have relatively detailed comments. The functions are provided as is without any implied and explicated warranties, and are for research purpose only.

The science part of call center management is based on Queueing Theory, which studies "the Phenomena of standing, waiting and serving" [1], [2].The queueing model abstracts the complex phenomena of call center acticities into an idealized mathematical framework, so that it is possible to study the key characteristics and dynamics of the real world. There are many different models that could be applied to Call Center management, one simple yet popular model is called Erlang-C model. Some other highly related models are Erlang-B and Erlang-A models, but they won't be covered in this post.

In this article, Erlang-C model will be briefly described and the computation of key parameters of this model will be discussed, followed by a complete suite of SAS code pieces. It is interesting that Erlang-C model is rarely touched in the SAS community. Searching through SAS-L archives, there were only two discussions relevant, and they are set apart by

*[3], [4] ! The responders includes legendary David Cassell, SAS guru Daniel J. Nordlund, SAS computing expert Rick Wicklin (his SAS blog), and of course me.***10 years**Erlang-C model is a commonly used mathematical framework for many Call center staffing and management softwares. Based on Kendall notation, this model can be expressed as M/M/C/\inf, where the first M indicates a Poisson distribution for Arrival process, while the second M indicates an Exponential distribution for servicing time. Both 'M' refers markovian the 'C' indicates number of servers which is a non-negative integer, while the last \inf refers infinite number of places in the system. Erlang-C system has extra assumption that it is First In First Out (FIFO) with infinite calling population. Therefore, Erlang-C model describe a Call Center that serves customers in the order they arrive in, while customers arrives following a Poisson process and the servicing time is Exponentially distributed. When a customer is not immediately served, it is put on hold until next available server (CSR). The customers are assumed to be patient so that they won't drop out but instead wait how-long-it-takes until next CSR. The system can take infinite number of waiting customers.

In Erlang-C model, the traffic to a Call Center is measured in a so-called term of Offered Traffic Load, which is defined to be the ratio between the parameter \lambda of Poisson process and the parameter \mu of Exponential servicing time. Offered Traffic Load is therefore interpreted as the average number of services that are trying to happen simultaneously. It is free of time unit, but does has its own unit, called Erlangs.

At the core of Erlang-C model is the Erlang-C function. The Erlang C function computes the probability that an arriving customer in the Erlang C queueing model will find that all servers are busy. This is the same as the fraction of arriving customers that are delayed (i.e., must wait) before beginning service.

Erlang-C function is expressed as:

P_c(n, x) = dPoisson(n, x)/(dPoisson(n, x) - (1-x/n)*pPoisson(n, x))

where dPoisson(n, x) is the density function of Poisson distribution with support 0,1, .., n and shape parameter x. pPoisson(n, x) is the corresponding cumulation distribution.

In Erlang-C function, there are three key elements:

1. Probability of delay, P_c.

2. # of servers, n.

3. Offered Traffic Load, x.

Given any two, the last parameter can be calculated or approximated. For example, in the code pieces below:

1. Function

*calculates Probability of delay given number of servers and offered traffic Load in Erlangs units.***ErlcFractionDelay(Nsrv, traffErlang)**2. Function

*estimates the minimum number servers to reach below the max probability of delay given by Erlc_max for a given offered traffic load of traffErlang.***ErlcNsrv(traffErlang, Erlc_max)**3. Function

*Compute the max traffic load that Nserv servers can handle with queuing probability less or equal to fractionDelay.***ErlcTrafFromFractionDelay(Nsrv, fractionDelay)**In addition to the three key numbers in Erlang-C model, there are some other paremeters of particular interests to Call Center management. For example, the probability an arriving call will be put in queue for no more than certain waiting time given the average handling time. Function

**will calcualte this quantity.***ErlcGoS(Nsrv, traffErlang, AHT, AWT)*The results of this set of functions has been calibrated against the data from [5], specifically Table 1. of [5] is reproduced exactly. The DATA STEP at the end of the program below will reproduce the results, and here is the result my own run.

For a complete list of quantities that Erlang-C model will work with and compute for, the webpage of Abstract Micro Systems @ http://abstractmicro.com/erlang/helppages/mod-c.htm provides a very good introduction.

__Reference:__[1]. Kleinrock, Leonard (January 1975).

*Queueing Systems: Volume I – Theory*. New York: Wiley Interscience. ISBN 978-0471491101.[2]. Kleinrock, Leonard (April 1976).

*Queueing Systems: Volume II – Computer Applications*. New York: Wiley Interscience. ISBN 978-0471491118.[3]. http://www.listserv.uga.edu/cgi-bin/wa?A2=ind0210C&L=sas-l&P=R10763

[4]. http://www.listserv.uga.edu/cgi-bin/wa?A2=ind1206C&L=sas-l&P=R45472

[5]. Tibor Mišuth, Erik Chromý, Ivan Baroňák.

*Method for Fast Estimation of Contact Centre Parameters Using Erlang-C Model*, in Proceedings of 2010 Third International Conference on Communication Theory, Reliability, and Quality of Service, DOI 10.1109/CTRQ.2010.38All functions below have relatively detailed comments. The functions are provided as is without any implied and explicated warranties, and are for research purpose only.

proc fcmp outlib=sasuser.funcs.Erlc;

function ErlcFractionDelay(Nsrv, traffErlang);

/*

Entry : ErlcFractionDelay

Purpose : Compute the Probability of Delay when an arriving call is not

answered immediately and put in queue

Usage : P=ErlcFractionDelay(Nsrv, traffErlang)

Inputs : Nsrv=Number of Server (CSR)

traffErlang=Traffic in Erlang

Handle Error : Return -1 if traffic in Erlang is larger than the number of

servers;

Return -2 if either traffic in Erlang or Number of servers is

negative;

Return -3 if number of servers is not integer;

*/

if traffErlang>Nsrv then do;

*put "Erlang amount should be smaller than Number of Servers";

Ec=-1; return(Ec);

end;

else if (traffErlang<0) or (Nsrv<0) then do;

*put "Both Erlang amount and Number of Servers should be larger than 0";

Ec=-2; return(Ec);

end;

else if abs(int(Nsrv)-Nsrv)>1E-8 then do;

*put "Number of Servers should be integer";

Ec=-3; return(Ec);

end;

else do;

_p= PDF('POISSON', Nsrv, traffErlang);

denom = _p

+ (1-traffErlang/Nsrv)*CDF('POISSON', Nsrv-1, traffErlang);

Ec = _p / denom;

return (Ec);

end;

endsub;

function ErlcGoS(Nsrv, traffErlang, AHT, AWT);

/*

Entry : ErlcGoS

Purpose : Compute the Probability an arriving call will be put in

queue and wait for no more than AWT when the average

handling time is AHT. This term is also called Grade of

Service (GoS). Note that the probability that an

arriving call will NOT be put in queue is:

P=1-ErlcFractionDelay();

Usage : GoS=ErlcGoS(Nsrv, traffErlang, AHT, AWT)

Inputs : Nsrv=Number of Server (CSR)

traffErlang=Traffic in Erlang

AHT=Average Handling Time

AWT=Average Waiting Time

Handle Error : Return -1 if traffic in Erlang is larger than the number of

servers;

Return -2 if either traffic in Erlang or Number of servers is

negative;

Return -3 if number of servers is not integer;

Return -4 if either of two time parameters is negative;

*/

if traffErlang>Nsrv then do;

*put "Erlang amount should be smaller than Number of Servers";

GoS=-1; return(GoS);

end;

else if (traffErlang<0) or (Nsrv<0) then do;

*put "Both Erlang amount and Number of Servers should be larger than 0";

GoS=-2; return(GoS);

end;

else if abs(int(Nsrv)-Nsrv)>1E-8 then do;

*put "Number of Servers should be integer";

GoS=-3; return(GoS);

end;

else if AHT<0 or AWT<0 then do;

put "Both Average Holding Time and Average Waiting Time should be >0";

GoS=-4; return(GoS);

end;

else do;

GoS = 1-ErlcFractionDelay(Nsrv, traffErlang)*exp(-1*(Nsrv-traffErlang)*AWT/AHT);

return(GoS);

end;

endsub;

function ErlcNsrv(traffErlang, Erlc_max);

/*

Entry : ErlcNsrv

Purpose : Compute the required number of servers (CSRs) to handle

given traffic load with queuing probability no more than

Erlc_max

Usage : N=ErlcNsrv(traffErlang, Erlc_max)

Inputs : traffErlang=Traffic load in Erlang

Erlc_max=max Queuing probability

Handle Error : Return -1 if max queuing probability is not within (0, 1)

range so to violate probability definition;

Return -2 if traffic in Erlang is negative;

*/

/*REF:

Algorithm is based on that from paper "Method for Fast

Estimation of Contact Centre Parameters Using

Erlang C Model" by Tibor Mišuth, Erik Chromý

and Ivan Baronák from Slovak University of

Technology. This paper appeared at "The Third

International Conference on Communication

Theory, Reliability, and Quality of Service",

2010

*/

if Erlc_max>=1 or Erlc_max<=0 then do;

*put "Max Erlang-C value should be between 0 and 1";

N=-1; return(N);

end;

else if traffErlang<0 then do;

*put "Traffic in Erlang should be >0";

N=-2; return(N);

end;

else do;

_k=floor(traffErlang)+1; s=0;

do N=1 to _k;

s=(1+s)*N/traffErlang;

end;

_threshold=(1-Erlc_max)/((1-traffErlang/N)*Erlc_max);

do while (s<=_threshold);

N=N+1;

s=(1+s)*N/traffErlang;

_threshold=(1-Erlc_max)/((1-traffErlang/N)*Erlc_max);

end;

return(N);

end;

endsub;

function ErlcNsrvFromGoS( traffErlang, AHT, AWT, GoS);

/*

Entry : ErlcNsrvFromGoS

Purpose : Compute the required number of servers (CSRs) to handle

given traffic load with given service level (GoS):

Average Handling Time /

the Probability an arriving call is put in queue

and wait no more than Average Waiting Time

Usage : N=ErlcNsrvFromGoS( traffErlang, AHT, AWT, GoS)

Inputs : traffErlang=Traffic load in Erlang

AHT=Average Handling Time

AWT=Average Waiting Time

GoS=probability an arriving Call in put in queue

but wait no more than AWT

Handle Error : Return -1 if either time parameter is negative;

Return -2 if queuing probability is not in (0, 1) range;

Return -3 if traffic in Erlang is negative

*/

if AHT<0 or AWT<0 then do;

*put "Both Average Holding Time and Average Waiting Time should be >0";

N0=-1; return(N0);

end;

else if GoS<0 or GoS>1 then do;

*put "GoS should be between 0 and 1";

N0=-2; return(N0);

end;

else if traffErlang<0 then do;

*put "Traffic in Erlang must be positive real number";

N0=-3; return(N0);

end;

else do;

N0=floor(traffErlang)+1;

_GoS=ErlcGoS(N0, traffErlang, AHT, AWT);

do while (_GoS<=GoS);

N0=N0+1;

_GoS=ErlcGoS(N0, traffErlang, AHT, AWT);

end;

return(N0);

end;

endsub;

function ErlcNsrvFromWait( traffErlang, AHT, AWT);

/*

Entry : ErlcNsrvFromWait

Purpose : Compute the required number of servers (CSRs) to handle

given traffic load with given Average Handling Time and

no more than given Average Waiting Time

Usage : N=ErlcNsrvFromWait( traffErlang, AHT, AWT)

Inputs : traffErlang=Traffic load in Erlang

AHT=Average Handling Time

AWT=Average Waiting Time

Handle Error : Return -1 if either time parameter is negative;

Return -2 if traffic in Erlang is negative

*/

if AHT<0 or AWT<0 then do;

*put "Both Average Holding Time and Average Waiting Time should be >0";

Nsrv=-1; return(Nsrv);

end;

else if traffErlang<0 then do;

*put "Traffic in Erlang must be positive real number";

N0=-2; return(N0);

end;

else do;

Nsrv=floor(traffErlang)+1;

c=ErlcWait (Nsrv, traffErlang, AHT);

do while(c>AWT);

Nsrv=Nsrv+1;

c=ErlcWait (Nsrv, traffErlang, AHT);;

end;

return (Nsrv);

end;

endsub;

function ErlcNwaiting(Nsrv, traffErlang);

/*

Entry : ErlcNwaiting

Purpose : Compute the average number of calls waiting

in queue, given number of servers and traffic

in Erlang

Usage : N=ErlcNwaiting(Nsrv, traffErlang)

Inputs : Nsrv=Number of Server

traffErlang=Traffic load in Erlang

Handle Error : Return -1 if traffic in Erlang is larger than number

of servers;

Return -2 if number of servers is negative

Return -3 if traffic in Erlang is negative

Return -4 if number of servers is not integer

*/

if traffErlang>Nsrv then do;

*put "Erlang amount should be smaller than Number of Servers";

Q=-1; return(Q);

end;

else if (Nsrv<0) then do;

*put "Both Erlang amount and Number of Servers should be larger than 0";

Q=-2; return(Q);

end;

else if traffErlang<0 then do;

*put "Traffic in Erlang must be positive real number";

Q=-3; return(Q);

end;

else if abs(int(Nsrv)-Nsrv)>1E-8 then do;

*put "Number of Servers should be integer";

Q=-4; return(Q);

end;

else do;

Q=traffErlang/(Nsrv-traffErlang)*ErlcFractionDelay(Nsrv, traffErlang);

return(Q);

end;

endsub;

function ErlcTrafFromFractionDelay(Nsrv, fractionDelay);

/*

Entry : ErlcTrafFromFractionDelay

Purpose : Compute the max traffic load that N servers

can handle with queuing probability less or equal

to fractionDelay.

Usage : A=ErlcTrafFromFractionDelay(Nsrv, fractionDelay)

Inputs : Nsrv=Number of Server

fractionDelay=Probability of waiting in queue

when a call arrives

Handle Error : Return -1 if probability of waiting in Queue is not

in range of (0, 1)

Return -2 if number of servers is negative

Return -3 if number of servers is not integer

*/

/* NOTE:

Using Bisection Method to solve for Erlang

given N server and Erlang C number. Bisection

is simple and robust. Speed is not of primiary

concern here.

*/

if fractionDelay>1 or fractionDelay<0 then do;

*put "Number of servers must be positive integers";

c=-1; return(c);

end;

else if Nsrv<=0 then do;

*put "Fraction of Delay must be real number between 0 and 1";

c=-2; return(c);

end;

else if abs(int(Nsrv)-Nsrv)>1E-8 then do;;

c=-3; return(c);

end;

else do;

min=1E-8; max=Nsrv-1E-8;

itermax=500; iter=1;

do while (iter<=itermax);

c=(min+max)/2;

_x=ErlcFractionDelay(Nsrv, c)-fractionDelay;

if abs(_x)<1E-8 or abs(max-min)/2<1E-8 then do;

iter=itermax+1;

end;

else do;

iter=iter+1;

_tmp=ErlcFractionDelay(Nsrv, max)-fractionDelay;

if sign(_x)=sign(_tmp) then max=c;

else min=c;

end;

end;

return (c);

end;

endsub;

function ErlcWait (Nsrv, traffErlang, AHT);

/*

Entry : ErlcWait

Purpose : Compute the Average Speed of Answer (ASA, also called

Average Waiting Time, AWT), given the number of servers,

traffic in Erlang and average handling time.

Usage : N=ErlcWait(Nsrv, traffErlang, AHT)

Inputs : Nsrv=Number of Server

traffErlang=Traffic load in Erlang

AHT=Average Handling Time

Handle Error : Return -1 if Average Handling Time is negative

Return -2 if number of servers is negative

Return -3 if traffic in Erlang is negative

Return -4 if number of servers is not integer

Return -5 if traffic in Erlang is larger than the

number of servers

*/

if AHT<0 then do;

*put "Average Holding Time should be >0";

ASA=-1; return(ASA);

end;

else if (Nsrv<0) then do;

*put "Both Erlang amount and Number of Servers should be larger than 0";

ASA=-2; return(ASA);

end;

else if traffErlang<0 then do;

*put "Traffic in Erlang must be positive real number";

ASA=-3; return(ASA);

end;

else if abs(int(Nsrv)-Nsrv)>1E-8 then do;

*put "Number of Servers should be integer";

ASA=-4; return(ASA);

end;

else if traffErlang>Nsrv then do;

ASA=-5; return(ASA);

end;

else do;

ASA=ErlcFractionDelay(Nsrv, traffErlang)*AHT/(Nsrv-traffErlang);

return(ASA);

end;

endsub;

function ErlcK (Nsrv, traffErlang);

/*

Entry : ErlcK

Purpose : Compute the average number of calls in queue, given

number of servers and traffic in Erlang. It is the

sum of average number of calls being served (traffErlang)

and average number of calls waiting in queue.

Usage : N=ErlcK(Nsrv, traffErlang)

Inputs : Nsrv=Number of Server

traffErlang=Traffic load in Erlang

Handle Error : Return -1 if traffic in Erlang is larger than number

of servers;

Return -2 if number of servers is negative

Return -3 if traffic in Erlang is negative

Return -4 if number of servers is not integer

*/

if traffErlang>Nsrv then do;

*put "Erlang amount should be smaller than Number of Servers";

K=-1; return(K);

end;

else if (Nsrv<0) then do;

*put "Both Erlang amount and Number of Servers should be larger than 0";

K=-2; return(K);

end;

else if traffErlang<0 then do;

*put "Traffic in Erlang must be positive real number";

K=-3; return(K);

end;

else if abs(int(Nsrv)-Nsrv)>1E-8 then do;

*put "Number of Servers should be integer";

K=-4; return(K);

end;

else do;

K = traffErlang*(1+ ErlcFractionDelay(Nsrv, traffErlang)/(Nsrv-traffErlang));

return (K);

end;

endsub;

function ErlcT( Nsrv, traffErlang, AHT);

/*

Entry : ErlcT

Purpose : Compute the average time a call will spend in the system,

which is the sum of average handling time and average

waiting time (ASA). Three relavent parameters are

number of servers, traffic in Erlang and average handling

time.

Usage : N=ErlcT(Nsrv, traffErlang, AHT)

Inputs : Nsrv=Number of Server

traffErlang=Traffic load in Erlang

AHT=Average Handling Time

Handle Error : Return -1 if average handling time is negative;

Return -2 if number of servers is negative

Return -3 if traffic in Erlang is negative

Return -4 if number of servers is not integer

Return -5 if traffic in Erlang is larger than number

of servers

*/

if AHT<0 then do;

*put "Average Holding Time should be >0";

T=-1; return(T);

end;

else if (Nsrv<0) then do;

*put "Both Erlang amount and Number of Servers should be larger than 0";

T=-2; return(T);

end;

else if traffErlang<0 then do;

*put "Traffic in Erlang must be positive real number";

T=-3; return(T);

end;

else if abs(int(Nsrv)-Nsrv)>1E-8 then do;

*put "Number of Servers should be integer";

T=-4; return(T);

end;

else if traffErlang>Nsrv then do;

T=-5; return(T);

end;

else do;

T=AHT + AHT*ErlcFractionDelay(Nsrv, traffErlang)/((Nsrv-traffErlang));

return(T);

end;

endsub;

run;

options cmplib=sasuser.funcs;

data _null_;

lambda=667; AHT=150; AWT=20;

A=lambda*150/3600;

do j=1 to 10;

N=floor(A)+j;

Pc=ErlcFractionDelay(N, A);

GoS=ErlcGoS(N, A, AHT, AWT);

Q=ErlcNwaiting(N, A);

T=ErlcT(N, A, AHT);

K=ErlcK(N, A);

rho=A/N;

put N= @8 Pc= percent.4 @16 K= bestd8.1 @26 T= bestd8.1

@38 Q= bestd8.1 @50 Gos= bestd8.1 @62 rho= percent8.4;

end;

run;