Business Hours on a Calendar!!

As a followup from previous posts around time of day routing and schedules in Webex Contact Center, I wanted to pull the logic of time of day out of Webex CC completely and let a smarter app do the real logic (as far as scheduling APIs go).  My first thought was to create a spreadsheet similar to the on call sheet I used in the last blog, but I thought there were too many variables and I wanted to be super flexible when doing the schedule. Whenever these scheduling questions come up, on call included, people seem to just want some calendar to put the schedule in open it in Outlook and share or something super easy. So why not? There’s a few reasons, but the end result is pretty awesome if you can see past the potential for human error.

Disclaimer: So the “potential for human error” part.  This method evolves a shared calendar and when you pull information from it you need to be able to determine the open/closed/whatever state from that calendar event. If “OPEN” is always “OPEN” great, but “close” doesn’t equal “CLOSE” or “closed” or even “not open” so you’ll have to either check and sanitize the data really good or try and find a way to force syntax in the events.  Also what does “Company Wide Meeting’ mean or “Bob and Jill 1:1” because somebody put something on the wrong calendar.  Or when there’s OPEN and HOLIDAY events that happen at the same time… Again this is more of a “can it be done and how cool would that be” than something I’d suggest dropping everything and migrating to right now, but here goes.

How awesome is that view.  Honestly it got even better as I started to build it out.  With a calendar the recurring events and stuff is really convenient.  Working “4 on 4 off” is easy: repeat every 4 days.  Also odd ball days and holidays etc are easy to do since you just find the day and delete or create events, adjust time frames etc as needed. Even on call schedules look a lot better and blow the socks off that spreadsheet I was using. What if the on call resource was just an attendee of the event?  Then it’s on both calendars…  and you could pull their contact info from the directory…

Part 1: The Google Side

So a few things that are required.  First we need a Calendar that our app can read.  This is super easy and can use that same service account we used on the spreadsheet by just adding the calendar permissions to it and then share the calendar with that account. The next part is the harder one of defining and sticking to the format you want to use.  We can read the whole invite so use accordingly.  Maybe there’s JSON info in the body.  Maybe we just care about the subject or who was invited etc.  Just figure up something and stick to it. I used the subject to make it easy.  All that “unplanned space”?  I assume if there isn’t an event scheduled that we are closed.

This API is powered by a Google Cloud Function in Ruby the same as our last few have been.  When the contact center gets the call it does a simple API call. We don’t have to even worry about the timezone in the request because now is now and the calendar takes care of when now is. Our app then checks the calendar to see what event is “now” and parses what it needs from it.

require "functions_framework"
require "json"
require "google/apis/calendar_v3"
require "googleauth"
require "googleauth/stores/file_token_store"
require "date"
require "fileutils"
require "active_support/all"
GOOGLE_CALENDAR = ENV['GOOGLE_CALENDAR'] || nil # can be overwritten by 'calendar' variable in GET

FunctionsFramework.http "schedule" do |request|
   # Initialize the Sheets API based on assumed environment variable stored RSA key and creds
   auth = ::Google::Auth::ServiceAccountCredentials.make_creds(scope: 'https://www.googleapis.com/auth/calendar')
   calendar = Google::Apis::CalendarV3::CalendarService.new
   calendar.authorization = auth

   # Fetch the current event from "this minute" in time
   calendar_id = request.params['calendar'] || GOOGLE_CALENDAR # WbxCC Schedule Calendar
   calendar_tz = calendar.get_calendar_list(calendar_id) rescue "America/New_York" # get calendar timezone or default
   current_event = calendar.list_events(
      calendar_id,
      max_results: 1, # pull 1 event only to get current
      single_events: true,
      order_by: "startTime",
      time_min: Time.now.in_time_zone(calendar_tz).to_datetime.rfc3339, # get Current Time for active  events "2021-12-20T12:00:00.00-05:00"
      time_max: (Time.now + 60).in_time_zone(calendar_tz).to_datetime.rfc3339 # End time + 60sec "2021-12-20T12:01:00.00-05:00"
   ) rescue false

   if current_event && current_event.items.empty?
      response = { "status"=> "CLOSED" } # if items are empty then nothing is on the calendar so closed is the default
   else
      current_event = current_event.items.first # get first event returned (expect only 1)
      response = {
         "status"=> current_event.summary.upcase, # set status as all upper to help reading
         "prompt"=> current_event.description, # prompt return based on description
         "start_time"=> [ current_event.start.date_time.to_i, current_event.start.date_time.to_s ], # event start array epoch,string
         "stop_time"=> [ current_event.end.date_time.to_i, current_event.end.date_time.to_s ], # event stop
         "current_time" => [ Time.now.in_time_zone(calendar_tz).to_i, Time.now.in_time_zone(calendar_tz) ] # provide current time just because
      }
   end

   return [200, {"Content-Type"=> "application/json"}, [response.to_json]] # return json response
end

The script on this is pretty simple since all we are really doing is pulling up a calendar and responding with what’s there.  Did you catch that we are pulling the prompt too? So the user can define whatever verbiage they want during whatever event and we let Text to Speech take it from there.  Text to Speech has really come a long way in the last year in my opinion. If I had to choose between tracking down someone willing to record their own voice or using text to speech: I’ll take the robot any day at this point.

Part 2: The Contact Center Side

In Webex CC the Flow is pretty simple too.  Because we are expecting the names of everything to be set, we can just do the initial REST call and then use a Case statement to parse out whats expected.

If you couldn’t tell I enjoyed this little project a lot more than I expected.  Once the syntax for pulling the calendar was figured out it all came together really quick and the more I started looking at it, the more possibilities I saw. Like the last posts, this is platform independent because we are just making a REST call so easily ported logic any platform that can fetch and parse a web page.