//Erlang functions for Google Sheets
//Converted from Erlang for Excel at www.erlang.co.uk
//by Lester Bromley on 29 April 2016, Copyright Kenshiki Ltd 2016
//***This version is a straight conversion with no optimisation for the JavaScript language***
//***Future versions may not expose all the functions below***
//There is a small variation between the Excel and Sheets versions due to differences with the implementation of numeric variables,
//this only affects results to more than 4 decimal places
//Constant values
var MaxAccuracy = 0.00001;
var MaxLoops = 100;
//
//Basic functions - these are normally not called directly
//
function ErlangB(Servers, Intensity) {
//Copyright Kenshiki Ltd 2016
//The Erlang B formula calculates the percentage likelyhood of the call
// being blocked, that is that all the trunks are in use and the caller
// will receive a busy signal.
// Servers = Number of telephone lines
// Intensity = Arrival rate of calls / Completion rate of calls
//   Arrival rate = the number of calls arriving per hour
//   Completion rate = the number of calls completed per hour
  var Val, Last, B, Retries, Attempts;
  var Count, MaxIterate;
  try {
    if ((Servers < 0) || (Intensity < 0)) {
      return 0;
    }
    MaxIterate = ~~Servers;  //truncate to integer
    Val = Intensity;
    Last = 1;
    for (Count = 1; Count <= MaxIterate; Count++) {
      B = (Val * Last) / (Count + (Val * Last));
      Last = B;
    }
    return MinMax(B, 0, 1);
  }
  catch (err) {
    return 0;
  }
}

function ErlangBExt(Servers, Intensity, Retry) {
//Copyright Kenshiki Ltd 2016
//The Extended Erlang B formula calculates the percentage likelyhood of the call
// being blocked, that is that all the trunks are in use and the caller
// will receive a busy signal. The Extended version allows input of a percentage
// figure for those blocked callers who will immediately retry.
// Servers = Number of telephone lines
// Intensity = Arrival rate of calls / Completion rate of calls
//   Arrival rate = the number of calls arriving per hour
//   Completion rate = the number of calls completed per hour
// Retry = Number of blocked callers who will retry immediately (0.1 = 10%)
  var Val, Last, B, Retries, Attempts;
  var Count, MaxIterate;
  try {
    if ((Servers < 0) || (Intensity < 0)) {
      return 0;
    }
    MaxIterate = ~~Servers;  //truncate to integer
    Retries = MinMax(Retry, 0, 1);
    Val = Intensity;
    Last = 1;
    for (Count = 1; Count <= MaxIterate; Count++) {
      B = (Val * Last) / (Count + (Val * Last));
      Attempts = 1 / (1 - (B * Retries));
      B = (Val * Last * Attempts) / (Count + (Val * Last * Attempts));
      Last = B;
    }
    return MinMax(B, 0, 1);
  }
  catch (err) {
    return 0;
  }
}
function EngsetB(Servers, Events, Intensity) {
//Copyright Kenshiki Ltd 2016
//The Engset B formula calculates the percentage likelyhood of the call
// being blocked, that is that all the trunks are in use and the caller
// will receive a busy signal. This uses the Engset model, based on the
// hindrance formula.
// Servers = Number of telephone lines
// Events = Number of calls
// Intensity = average intensity per call
  var Val, Last, B, Ev;
  var Count, MaxIterate;
  try {
    if ((Servers < 0) || (Intensity < 0)) {
          return 0;
    }
    MaxIterate = ~~Servers;  //truncate to integer
    Val = Intensity;
    Ev = Events;
    Last = 1;
    for (Count = 1; Count <= MaxIterate; Count++) {
      B = (Last * (Count / ((Ev - Count) * Val))) + 1;
      Last = B;
    }
    if (B == 0) return 0;
    return MinMax((1 / B), 0, 1);
  }
  catch (err) {
    return 0;
  }
}
function ErlangC(Servers, Intensity) {
//Copyright Kenshiki Ltd 2016
//This formula gives the percentage likelyhood of the caller being
// placed in a queue.
// Servers = Number of agents
// Intensity = Arrival rate of calls / Completion rate of calls
//   Arrival rate = the number of calls arriving per hour
//   Completion rate = the number of calls completed per hour
  var B, C;
  var Count, MaxIterate;
  try {
    if ((Servers < 0) || (Intensity < 0)) {
      return 0;
    }
    B = ErlangB(Servers, Intensity);
    C = B / (((Intensity / Servers) * B) + (1 - (Intensity / Servers)));
    return MinMax(C, 0, 1);
  }
  catch (err) {
    var m = err.message;
    return 0;
  }
}
function NBTrunks(Intensity, Blocking) {
//Copyright Kenshiki Ltd 2016
//This function has been supplied by Edwin Barendse
//This formula gives the number of telephone lines required to
// handle the Busyhour traffic in Erlang against a required blocking factor
// Intensity = Busyhour Traffic in erlangs
// Blocking = blocking factor percentage e.g. 0.10  (10% of calls may receive busy tone)

  var B, Count, SngCount;
  var MaxIterate;
  try {
    MaxIterate = 0;
    if ((Intensity < 0) || (Blocking , 0)) {
      return 0;
    }
    MaxIterate = 65535;
    for (Count = Math.ceil(Intensity); Count <= MaxIterate; Count++) {
      SngCount = Count;
      B = ErlangB(SngCount, Intensity);
      if (B <= Blocking) {
        break;
      }
    }
    if (Count == MaxIterate) {
      Count = 0;  //answer not found after max loops
    }
    return Count;
  }
  catch (err) {
    return 0;
  }
}
function NumberTrunks(Servers, Intensity)
{
//Copyright Kenshiki Ltd 2016
//This formula gives the maximum number of telephone trunks required to
// handle the answered and queuing calls. (up to a maximum of 255)
// Servers = Number of agents
// Intensity = Arrival rate of calls / Completion rate of calls
//   Arrival rate = the number of calls arriving per hour
//   Completion rate = the number of calls completed per hour
  var B, Server;
  var Count, MaxIterate;
  try {
    if ((Servers < 0) || (Intensity < 0)) {
      return 0;
    }
    MaxIterate = 65535;
    for (Count = Math.ceil(Servers); Count <= MaxIterate; Count++) {
      Server = Count;
      B = ErlangB(Server, Intensity);
      if (B < 0.001) {
        break;
      }
    }
    return Count;
  }
  catch (err) {
    return 0;
  }
}
function Servers(Blocking, Intensity) {
//Copyright Kenshiki Ltd 2016
//The Servers formula calculates the number of servers required to
// service the given traffic intensity with the given blocking factor.
// Blocking = The blocking factor requires <1, e.g. 0.80 = 80%
// Intensity = Arrival rate of calls / Completion rate of calls
//   Arrival rate = the number of calls arriving per hour
//   Completion rate = the number of calls completed per hour
  var Val, Last, B;
  var Count;
  try  {
    if ((Blocking < 0) || (Intensity < 0)) {
          return 0;
    }
    Val = Intensity;
    Last = 1;
    B = 1;
    Count = 0;
    while ((B > Blocking)  && (B > 0.001)) {
          Count++;
          B = (Val * Last) / (Count + (Val * Last));
          Last = B;
    }
    return Count;
  }
  catch (err) {
    return 0;
  }
}
function Traffic(Servers, Blocking) {
//Copyright Kenshiki Ltd 2016
//The Traffic formula calculates the traffic intensity in erlangs for the number
// of servers (trunks) with the given blocking factor.// Servers = Number of trunks handling the traffic, whole number
// Blocking = The blocking factor achieved <1, e.g. 0.10 = 10%
  var B, Incr, Trunks;
  var MaxI;
  try {
    Trunks = ~~Servers;
    if ((Servers < 1) || (Blocking < 0)) {
      return 0;
    }
    MaxI = Trunks;
    B = ErlangB(Servers, MaxI);
    while (B < Blocking) {
      MaxI = MaxI * 2;
      B = ErlangB(Servers, MaxI);
    }
    Incr = 1;
    while (Incr <= (MaxI /100)) {
      Incr = Incr * 10;
    }
    return LoopingTraffic(Trunks, Blocking, Incr, MaxI, 0);
  }
  catch (err) {
    return 0;
  }
}
function LoopingTraffic(Trunks, Blocking, Increment, MaxIntensity, MinIntensity) {
//Copyright Kenshiki Ltd 2016
//This function tries values from MinIntensity to MaxIntensity, increasing the traffic by Increment until
// the approximate blocking is found, processing then loops with stepping of Increment/10 (e.g. 10, 1, 0.1, 0.01, 0.001)
// until the value is found to the precision required (defined by global constant MaxAccuracy)
  var Incr, LoopNo;
  var B, MinI, MaxI, Intensity;
  try {
    MinI = MinIntensity;
    MaxI = MaxIntensity;
    B = ErlangB(Trunks, MinI);
    if (B == Blocking) {
      return MinI;
    }
    Incr = Increment;
    Intensity = MinI;
    LoopNo = 0;
    while ((Incr >= MaxAccuracy) && (LoopNo < MaxLoops)) {
      B = ErlangB(Trunks, Intensity);
      if (B > Blocking) {
        MaxI = Intensity;
        Incr = Incr / 10;
        Intensity = MinI;
      }
      MinI = Intensity;
      Intensity = Intensity + Incr;
      LoopNo++;
    }
    return MinI;
  }
  catch (err) {
    return 0;
  }
}
//
//Call Centre functions
//
/**
 * Calculate the percentage of calls which will abandon after the time given
 *
 * @param {number} Agents The number of agents available.
 * @param {number} AbandonTime Time in seconds before the caller will abandon.
 * @param {number} CallsPerHour The number of calls received in one hour period.
 * @param {number} AHT (Average Handle Time) The call duration including after call work in seconds  e.g 180.
 * @return The abandonment rate.
 * @customfunction
 */
function Abandon(Agents, AbandonTime, CallsPerHour, AHT) {
//Copyright Kenshiki Ltd 2016

  var BirthRate, DeathRate, TrafficRate;
  var Aband, C, Server, Utilisation;
  try {
    BirthRate = CallsPerHour;
    DeathRate = 3600 / AHT;
    TrafficRate = BirthRate / DeathRate;
    Server = Agents;
    Utilisation = TrafficRate / Server;
    if (Utilisation >= 1) {
      Utilisation = 0.99;
    }
    C = ErlangC(Server, TrafficRate);
    Aband = C * Math.exp((TrafficRate - Server) * (AbandonTime / AHT));
    return MinMax(Aband, 0, 1);
  }
  catch (err) {
    return 0;
  }
}
/**
 * Calculate the number of agents required to service a given number of calls to meet the service level.
 * @param {number} SLA The % of calls to be answered within the ServiceTime period  e.g. 0.95  (95%).
 * @param {number} ServiceTime Target answer time in seconds e.g. 15.
 * @param {number} CallsPerHour The number of calls received in one hour period.
 * @param {number} AHT The call duration including after call work in seconds  e.g 180.
 * @customfunction
 */
function Agents(SLA, ServiceTime, CallsPerHour, AHT) {
//Copyright Kenshiki Ltd 2016
  var BirthRate, DeathRate, TrafficRate;
  var Erlangs, Utilisation, C, SLQueued;
  var NoAgents, MaxIterate, Count;
  var Server;
  try {
    if (SLA > 1) {
      SLA = 1;
    }
    BirthRate = CallsPerHour;
    DeathRate = 3600 / AHT;
    TrafficRate = BirthRate / DeathRate;
    Erlangs = ~~((BirthRate * AHT) / 3600 + 0.5);
    if (Erlangs < 1) {
      NoAgents = 1;
    }
    else {
      NoAgents = ~~Erlangs;
    }
    Utilisation = TrafficRate / NoAgents;
    while (Utilisation >= 1) {
      NoAgents++;
      Utilisation = TrafficRate / NoAgents;
    }
    MaxIterate = NoAgents * 100;
    for (Count = 1; Count <= MaxIterate; Count++) {
      Utilisation = TrafficRate / NoAgents;
      if (Utilisation < 1) {
        Server = NoAgents;
        C = ErlangC(Server, TrafficRate);
        SLQueued = 1 - C * Math.exp((TrafficRate - Server) * ServiceTime / AHT);
        if (SLQueued < 0) {
          SLQueued = 0;
        }
        if (SLQueued >= SLA) {
          break;
        }
        if (SLQueued > (1 - MaxAccuracy)) {
          break;
        }
      }
      if (Count != MaxIterate) {
        NoAgents++;
      }
    }
    return NoAgents;
  }
  catch (err) {
    return 0;
  }
}
/**
 * Calculate the number of agents required to service a given number of calls to meet the average speed of answer.
 * @param {number} ASA The Average speed of Answer in seconds.
 * @param {number} CallsPerHour The number of calls received in one hour period.
 * @param {number} AHT The call duration including after call work in seconds  e.g 180.
 * @customfunction
 */
function AgentsASA(ASA, CallsPerHour, AHT) {
//Copyright Kenshiki Ltd 2016
  var BirthRate, DeathRate, TrafficRate;
  var Erlangs, Utilisation, C, AnswerTime
  var NoAgents, MaxIterate, Count;
  var Server;
  try {
    if (ASA < 1) {
      ASA = 1;
    }
    BirthRate = CallsPerHour;
    DeathRate = 3600 / AHT;
    TrafficRate = BirthRate / DeathRate;
    Erlangs = ~~((BirthRate * AHT) / 3600 + 0.5);
    if (Erlangs < 1) {
      NoAgents = 1;
    }
    else {
      NoAgents = ~~Erlangs;
    }
    Utilisation = TrafficRate / NoAgents;
    while (Utilisation >= 1) {
      NoAgents++;
      Utilisation = TrafficRate / NoAgents;
    }
    MaxIterate = NoAgents * 100;
    for (Count = 1; Count <= MaxIterate; Count++) {
      Server = NoAgents;
      Utilisation = TrafficRate / NoAgents;
      C = ErlangC(Server , TrafficRate);
      AnswerTime = C / (Server * DeathRate * (1 - Utilisation));
      if ((AnswerTime * 3600)  <= ASA) {
        break;
      }
      if (Count != MaxIterate) {
        NoAgents++;
      }
    }
    return NoAgents;
  }
  catch (err) {
    return 0;
  }
}
/**
 * Calculate the number of telephone lines required to handle the busy hour traffic for a required blocking factor.
 * @param {number} CallsPerHour The number of calls received in one hour period.
 * @param {number} AvgSA The average speed of answer.
 * @param {number} AvgHT The call duration including after call work in seconds  e.g 180.
 * @customfunction
 */
function NBAgents(CallsPerHour, AvgSA, AvgHT) {
//Copyright Kenshiki Ltd 2016
  var B, Count, SngCount;
  var MaxIterate;
  try {
    MaxIterate = 0;
    if ((CallsPerHour <= 0) || (AvgSA <= 0) || (AvgHT <= 0)) {
          return 0;
    }
    MaxIterate = 65535;
    for (Count = 1; Count <= MaxIterate; Count++) {
      SngCount = Count;
      B = ASA(SngCount, CallsPerHour, AvgHT)
      if (B <= AvgSA) {
        break;
      }
    }
    if (Count == MaxIterate) {
      Count = 0;  //did not find the answer so return As Error
    }
    return Count
  }
  catch (err) {
    return 0;
  }
}
/**
 * Calculate the Average Speed to Answer for the given number of agents.
 * @param {number} Agents The number of agents available.
 * @param {number} CallsPerHour The number of calls received in one hour period.
 * @param {number} AHT The call duration including after call work in seconds  e.g 180.
 * @customfunction
 */
function ASA(Agents, CallsPerHour, AHT) {
//Copyright Kenshiki Ltd 2016
  var BirthRate, DeathRate, TrafficRate;
  var Utilisation, AnswerTime, AveAnswer;
  var C, Server;

  try  {
    BirthRate = CallsPerHour;
    DeathRate = 3600 / AHT;
    TrafficRate = BirthRate / DeathRate;
    Server = Agents;
    Utilisation = TrafficRate / Server;
    if (Utilisation >= 1) {
      Utilisation = 0.99;
    }
    C = ErlangC(Server, TrafficRate);
    AnswerTime = C / (Server * DeathRate * (1 - Utilisation));
    AveAnswer = Secs(AnswerTime);
    return AveAnswer;
  }
  catch (err) {
    return 0;
  }
}
/**
 * Calculate the number of calls which can be handled by the given number of agents whilst still achieving the grade of service.
 * @param {number} NoAgents The number of agents available.
 * @param {number} SLA Target percentage of calls to be answered e.g. 0.85 = 85%.
 * @param {number} ServiceTime Target answer time in seconds e.g. 15.
 * @param {number} AHT The call duration including after call work in seconds  e.g 180.
 * @customfunction
 */
function CallCapacity(NoAgents, SLA, ServiceTime, AHT) {
//Copyright Kenshiki Ltd 2016
  var Calls, xAgent, MaxIterate, xNoAgent
  try {
    xNoAgent = ~~NoAgents;
    Calls = Math.ceil(3600 / AHT) * xNoAgent;
    xAgent = Agents(SLA, ServiceTime, Calls, AHT);
    while ((xAgent > xNoAgent) && (Calls > 0)) {
      Calls--;
      xAgent = Agents(SLA, ServiceTime, Calls, AHT);
    }
    return Calls;
  }
  catch(err) {
    return 0;
  }
}
/**
 * Calculate the number of agents required to service a given number of calls to meet the service level.
 * @param {number} SLA The % of calls to be answered within the ServiceTime period  e.g. 0.95  (95%).
 * @param {number} ServiceTime Target answer time in seconds e.g. 15.
 * @param {number} CallsPerHour The number of calls received in one hour period.
 * @param {number} AHT The call duration including after call work in seconds  e.g 180.
 * @customfunction
 */
function FractionalAgents(SLA, ServiceTime, CallsPerHour, AHT) {
//Copyright Kenshiki Ltd 2016
  var BirthRate, DeathRate, TrafficRate;
  var Erlangs, Utilisation, C, SLQueued;
  var LastSLQ, Fract, OneAgent, NoAgentsSng;
  var NoAgents, MaxIterate, Count;
  var Server;

  try {
    if (SLA > 1) {
      SLA = 1;
    }
    BirthRate = CallsPerHour;
    DeathRate = 3600 / AHT;
    TrafficRate = BirthRate / DeathRate;
    Erlangs = ~~((BirthRate * (AHT)) / 3600 + 0.5)
    if (Erlangs < 1) {
      NoAgents = 1;
    }
    else {
      NoAgents = ~~Erlangs;
    }
    Utilisation = TrafficRate / NoAgents;
    while (Utilisation >= 1) {
      NoAgents = NoAgents + 1;
      Utilisation = TrafficRate / NoAgents;
    }
    SLQueued = 0;
    MaxIterate = NoAgents * 100;
    for (Count = 1; Count <= MaxIterate; Count++) {
      LastSLQ = SLQueued;
      Utilisation = TrafficRate / NoAgents;
      if (Utilisation < 1) {
        Server = NoAgents;
        C = ErlangC(Server, TrafficRate);
//find the level of SLA with this number of agents
        SLQueued = 1 - C * Math.exp((TrafficRate - Server) * ServiceTime / AHT);
        SLQueued = MinMax(SLQueued, 0, 1);
        if (SLQueued >= SLA) {
          Count = MaxIterate;
        }
//put a limit on the accuracy required (it will never actually get to 100%)
        if (SLQueued > (1 - MaxAccuracy)) {
          Count = MaxIterate;
        }
      }
      if (Count != MaxIterate) {
        NoAgents = NoAgents + 1;
      }
    }
    NoAgentsSng = NoAgents;
    if (SLQueued > SLA) {  //any fraction to calculate?
      OneAgent = SLQueued - LastSLQ;  //difference made by 1 agent
      Fract = SLA - LastSLQ;  //difference we want
      NoAgentsSng = (Fract / OneAgent) + (NoAgents - 1);
    }
    return NoAgentsSng;
  }
  catch (err) {
    return 0;
  }
}
/**
 * Calculate the number of calls which can be handled by the given number of agents whilst still achieving the grade of service.
 * @param {number} NoAgents The number of agents available.
 * @param {number} SLA The % of calls to be answered within the ServiceTime period  e.g. 0.95 (95%).
 * @param {number} ServiceTime Target answer time in seconds e.g. 15.
 * @param {number} AHT The call duration including after call work in seconds  e.g 180.
 * @customfunction
 */
function FractionalCallCapacity(NoAgents, SLA, ServiceTime, AHT) {
//Copyright Kenshiki Ltd 2016
  var Calls, xAgent, MaxIterate, xNoAgent;

  try {
    xNoAgent = NoAgents;
    Calls = Math.ceil(3600 / AHT * xNoAgent);
    xAgent = FractionalAgents(SLA, ServiceTime, Calls, AHT);
    while ((xAgent > xNoAgent) && (Calls > 0)) {
        Calls--;
        xAgent = FractionalAgents(SLA, ServiceTime, Calls, AHT);
    }
    return Calls
  }
  catch (err) {
    return 0;
  }
}
/**
 * Calculate the percentage of calls which will queue for the given number of agents.
 * @param {number} Agents The number of agents available.
 * @param {number} CallsPerHour The number of calls received in one hour period.
 * @param {number} AHT The call duration including after call work in seconds  e.g 180.
 * @customfunction
 */
function Queued(Agents, CallsPerHour, AHT) {
//Copyright Kenshiki Ltd 2016
  var BirthRate, DeathRate, TrafficRate;
  var Q, Server;
  try {
    BirthRate = CallsPerHour;
    DeathRate = 3600 / AHT;
    TrafficRate = BirthRate / DeathRate;
    Server = Agents;
    Q = ErlangC(Server, TrafficRate);
    return MinMax(Q, 0, 1);
  }
  catch (err) {
    return 0;
  }
}
/**
 * Calculate the average queue size for a given number of agents.
 * @param {number} Agents The number of agents available.
 * @param {number} CallsPerHour The number of calls received in one hour period.
 * @param {number} AHT The call duration including after call work in seconds  e.g 180.
 * @customfunction
 */
function QueueSize(Agents, CallsPerHour, AHT) {
//Copyright Kenshiki Ltd 2016
  var BirthRate, DeathRate, TrafficRate;
  var C, Server, QSize, Utilisation;
  
  try {
    BirthRate = CallsPerHour;
    DeathRate = 3600 / AHT;
    TrafficRate = BirthRate / DeathRate;
    Server = Agents;
    Utilisation = TrafficRate / Server;
    if (Utilisation >= 1) {
      Utilisation = 0.99;
    }
    C = ErlangC(Server, TrafficRate);
    QSize = (Utilisation * C) / (1 - Utilisation);
    return ~~(QSize + 0.5);
  }
  catch (err) {
    return 0;
  }
}
/**
 * Calculate the average queue time for those calls which will queue.
 * @param {number} Agents The number of agents available.
 * @param {number} CallsPerHour The number of calls received in one hour period.
 * @param {number} AHT The call duration including after call work in seconds  e.g 180.
 * @customfunction
 */
function QueueTime(Agents, CallsPerHour, AHT) {
//Copyright Kenshiki Ltd 2016
  var BirthRate, DeathRate, TrafficRate;
  var C, Server, QTime, Utilisation;

  try {
    BirthRate = CallsPerHour;
    DeathRate = 3600 / AHT;
    TrafficRate = BirthRate / DeathRate;
    Server = Agents;
    Utilisation = TrafficRate / Server;
    if (Utilisation >= 1) {
      Utilisation = 0.99;
    }
    QTime = 1 / (Server * DeathRate * (1 - Utilisation));
    return Secs(QTime)
  }
  catch (err) {
    return 0;
  }
}
/**
 * Calculate the average waiting time in which a given percentage of the calls will be answered.
 * @param {number} NoAgents The number of agents available.
 * @param {number} SLA The % of calls to be answered within the ServiceTime period  e.g. 0.95 (95%).
 * @param {number} CallsPerHour The number of calls received in one hour period.
 * @param {number} AHT The call duration including after call work in seconds  e.g 180.
 * @customfunction
 */
function ServiceTime(NoAgents, SLA, CallsPerHour, AHT) {
//Copyright Kenshiki Ltd 2016
  var BirthRate, DeathRate, TrafficRate, Utilisation;
  var C, Server, STime, QTime;
  var Ag, Adjust;

  try {
    Adjust = 0;
    BirthRate = CallsPerHour;
    DeathRate = 3600 / AHT;
    TrafficRate = BirthRate / DeathRate;
    C = ErlangC(NoAgents, TrafficRate);
    if (C < (1 - SLA)) {
      return 0; //none will be queued so return 0 seconds
    }
    Server = NoAgents;
    Utilisation = TrafficRate / Server;
    if (Utilisation >= 1) {
      Utilisation = 0.99;
    }
    QTime = 1 / (Server * DeathRate * (1 - Utilisation)) * 3600;
    STime = QTime * (1 - ((1 - SLA) / C));
//check rounding errors here and adjust
    Ag = Agents(SLA, ~~STime, CallsPerHour, AHT);
    if (Ag != NoAgents) {
      Adjust = 1;
    }
    return ~~(STime + Adjust);
  }
  catch (err) {
    return 0;
  }
}
/**
 * Calculate the service level achieved for the given number of agents.
 * @param {number} Agents The number of agents available.
 * @param {number} ServiceTime Target answer time in seconds e.g. 15.
 * @param {number} CallsPerHour The number of calls received in one hour period.
 * @param {number} AHT The call duration including after call work in seconds  e.g 180.
 * @customfunction
 */
function SLA(Agents, ServiceTime, CallsPerHour, AHT) {
//Copyright Kenshiki Ltd 2016
  var BirthRate, DeathRate, TrafficRate;
  var Utilisation, C, SLQueued;
  var Server;

  try {
    BirthRate = CallsPerHour;
    DeathRate = 3600 / AHT;
    TrafficRate = BirthRate / DeathRate;
    Utilisation = TrafficRate / Agents;
    if (Utilisation >= 1) {
      Utilisation = 0.99;
    }
    Server = Agents;
    C = ErlangC(Server, TrafficRate);
//now calculate SLA % as those not queuing plus those queuing
//revised formula with thanks to Tim Bolte and Jørn Lodahl for their input
    SLQueued = 1 - C * Math.exp((TrafficRate - Server) * ServiceTime / AHT);
    return MinMax(SLQueued, 0, 1);
  }
  catch (err) {
    return 0;
  }
}
/**
 * Calculate the number of telephone lines required to service a given number of calls and agents.
 * @param {number} Agents The number of agents available.
 * @param {number} CallsPerHour The number of calls received in one hour period.
 * @param {number} AHT The call duration including after call work in seconds  e.g 180.
 * @customfunction
 */
function Trunks(Agents, CallsPerHour, AHT) {
//Copyright Kenshiki Ltd 2016
  var BirthRate, DeathRate, TrafficRate;
  var Utilisation, C, AnswerTime;
  var NoTrunks;
  var Server, R;
          
  try {
    BirthRate = CallsPerHour;
    DeathRate = 3600 / AHT;
    TrafficRate = BirthRate / DeathRate;
    Server = Agents;
    Utilisation = TrafficRate / Server;
    if (Utilisation >= 1) {
      Utilisation = 0.99;
    }
    C = ErlangC(Server, TrafficRate);
    AnswerTime = C / (Server * DeathRate * (1 - Utilisation));
//now calculate new intensity using average life time of call (queuing time + handle time)
    R = BirthRate / (3600 / (AHT + Secs(AnswerTime)));
    NoTrunks = NumberTrunks(Server, R);
//if there is traffic (Trafficrate>0) then always return at least 1 trunk
    if ((NoTrunks < 1) && (TrafficRate > 0)) {
      NoTrunks = 1;
    }
    return NoTrunks;
  }
  catch (err) {
    return 0;
  }
}
/**
 * Calculate the utilisation percentage for the given number of agents.
 * @param {number} Agents The number of agents available.
 * @param {number} CallsPerHour The number of calls received in one hour period.
 * @param {number} AHT The call duration including after call work in seconds  e.g 180.
 * @customfunction
 */
function Utilisation(Agents, CallsPerHour, AHT) {
//Copyright Kenshiki Ltd 2016
  var BirthRate, DeathRate, TrafficRate;
  var Util;

  try {
     BirthRate = CallsPerHour;
     DeathRate = 3600 / AHT;
     TrafficRate = BirthRate / DeathRate;
     Util = TrafficRate / Agents;
     return MinMax(Util, 0, 1);
  }
  catch (err) {
    return 0;
  }
}
//Misc functions
function MinMax(input, min, max)
{
  return Math.min(Math.max(input, min), max);
}
function Secs(Amount) {
//Convert a number of hours into seconds
     return ~~(Amount * 3600 + 0.5) //truncate to integer
}