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