One of the perks of working at gap intelligence is access to San Diego Padres season tickets. Despite our beloved Padres maintaining their position of dead last in their division, these tickets remain highly coveted. They include a spectacular view of home plate, access to the Omni premier club, a full bar and buffet, and waiter service. Needless to say, fairly distributing 84 games, with 2 seats each, among 49 gappers was no easy task.

Working as a software engineer here at gap intelligence, I saw an opportunity for improvement and innovation. Or maybe I just couldn’t deal with the chaos that ensued as we all raced to the abruptly announced Google Doc to try to claim our games while not stepping on each other’s toes (literally and figuratively).

Once a month, developers at gap intelligence hold a dev day where we can work on anything we want, typically outside of our normal sprint commits. This is how Golden Ticket was born. Myself and another back-end developer spent three dev days (no test coverage, obviously) creating a web application to manage the distribution and selection of baseball tickets.

Golden Ticket logo

Technical Overview

Golden Ticket is a Ruby on Rails application built on top of a Postgres database. The front end is HTML5, CSS3 and JavaScript, which our talented front-end developer so graciously beautified on his own dev day, which I’ll give you a glimpse into later on. The meat of the application is in the back end where the number of tickets, picking order and selection times are assigned to each user. But first, here’s a quick overview of our data model.

Data model chart

The data model is set up around the Season object. Each Season has many Games and each Game has many Tickets, as you would expect. The distribution logic is built around the Picks. Each User (in our case, an employee of gap intelligence) begins with having many Picks, which belongs to a particular Season. For example, I have 2 picks for the Padres 2016 season. Each Pick has a certain number of Tickets. In our case, all of our picks have 2 tickets so we can bring a guest to each game, but the app does allow for flexibility if we someday wanted to pick 1 seat at a time. I’ll describe the logic for distributing these picks next.

Determining the Pick Order

The main thing we wanted to accomplish with this application was to add fairness to a previously chaotic process. This is what the SeasonPicksShuffler is responsible for. It creates a schedule of picks. This schedule is randomly generated, giving each user specific times to make their picks. Here’s a snippet of the shuffler code:

class SeasonPicksShuffler

  def shuffle

    position = starting_position

    tiers.each do |tier|

      tier_picks = picks.where(tier: tier)

      shuffled_picks(tier_picks).each do |pick|

        pick.update_attribute(:position, position)

        position += 1

      end

    end

  end

  def starting_position

    picks.maximum(:position).to_i + 1

  end

  def picks

    season.picks.where(date_time: nil)

  end

  def tiers

    picks.order(tier: :asc).distinct.pluck(:tier)

  end

end

One thing I didn’t previously mention was the concept of Tiers. You’ll see above, the shuffle method begins by looping though each tier but preserving their order. This may not be useful to everyone but allows us to have groups (i.e. the senior management) to pick first in their own random order.

You’ll see the actual shuffling happens in shuffled_picks, below:

def shuffled_picks(filtered_picks)

  10.times { filtered_picks = filtered_picks.shuffle }

  filtered_picks

end

No, we didn’t implement some crazy algorithm or even include any lottery/raffle gems (which we did initially look into). Instead we simply call Ruby’s Array shuffle and call it a day. Or really, we call it 10 times, because it calling it once seemed too simple. And that’s it.

Setting the Pick Times

Once we have the picking order, we use another service, the impetuously named SeasonDateTimeSetter, to assign actual times to each of the picks (see below).

class SeasonPicksDateTimeSetter

  def set

    if season.start_at

      start_time = season.start_at

      season.picks.order(position: :asc).each do |pick|

        pick.update_attribute(:date_time, start_time)

        start_time = next_start_time(start_time)

      end

    end

  end

end

You’ll notice a reference to next_start_time, which could be customized to whatever makes sense for your group. I won’t dive the logic it but, in our case, it keeps all the times on weekdays between 9am and 5pm, spaced out 1 hour each. One thing to note is the picking time is not a 1 hour window in which you HAVE to make your pick, it’s simply the time that you become eligible to pick. And our 1 hour buffer allows you to make your pick knowing that no one else will become eligible for another hour and the people before you have probably already finished their picks.

The User Interface

Below is a glimpse of our interface, which I mentioned earlier. You’ll see most of the selections are crossed out, as this season is in progress and picks have already been made. Notice the ‘Un-Claim’ button next to my name, which would show as ‘Claim’ if the game was still available to pick.

It’s worth noting, that on the right hand column, before the schedule is created, a big, bright ‘GENERATE’ button sits there which we press live (scary) at our company meetings. This provides some excitement as well as some trust (hopefully), that we, the dev team, did not rig it.

The Future

At this point, the future of Golden Ticket is unknown. We’ve discussed everything from open-sourcing it, to adding multi-tenancy and making it available to the public, or just keeping as is. For right now, we’re not making many updates as it works great for our company baseball and recently added hockey tickets. If you’re at all interested in using it, contributing to it or just have questions about it, don’t hesitate to contact Katie Hess at katie@gapintelligence.com. Thanks!