Thursday, July 12, 2018

Who needs patience when you've got data? A Data Driven DMV Strategy

When's the best time to visit the DMV? Never. Fair enough, so when's the second best time to visit the DMV?

That's the question I pondered yesterday, as I had an errand to run that required just such a visit.

I was pleased to learn that Virginia shows the wait times for their DMV locations. For example:

After an hour of hitting refresh on that page I realized that I needed a better solution. Looking at the source of the page, I see that the data is just a simple curl call away:

$ curl -s https://www.dmv.virginia.gov/data/api/v2/csc/182 |  jq . | head -5
{
  "unitId": "223",
  "parentUnitId": "16",
  "workingHoursId": "231",
  "parentunitName": "FFX North District",

Using jq I was able to put together this Unix one-liner:

$ while true ; do date ; curl -s 'https://www.dmv.virginia.gov/data/api/v2/csc/5' | \
     jq -r '.webWaitTimes | .[] |  .estimatedWaitTime + " | " + .peopleWaiting + " | " + .serviceName' ; \
     sleep 5m ; done
Thu, Jul 12, 2018 10:02:59 AM
0:35:29 | 8 | Original Driver's License/ID Card (without testing)
0:20:24 | 1 | Learner’s Permit and All Testing
0:28:21 | 2 | Driver’s License and Vehicle Combination Transactions
0:03:06 | 1 | * Replacement/Renewal of Driver’s License or ID Card, and Address Change
0:21:53 | 0 | * Reinstate Driving Privilege
0:12:34 | 2 | * Vehicle Registration Transactions, Registration Renewal, Disabled Parking Placards, Surrender Plates, Permits, and Record Requests
0:31:29 | 2 | All Title Transactions
0:21:53 | 0 | Motor Carrier
0:21:53 | 0 | Other Government Services: Vital Records, Hunting and Fishing Licenses, Boat Titles and Registration and More

This was a vast improvement over hitting refresh on a web page. Though I was curious if I could enhance this further to show the wait time trend. For that, I wrote a proper shell script. The script is below, but here's a screenshot of it in action:

I was able to annotate the wait time and number of people in line with either ^, v or = depending on whether the value is increasing, decreasing or holding steady. I didn't graph the data, but that would have been my next move.

At 3pm Shira called me to see if I'd been to the DMV yet. I asked, did I really need to go today? In a couple of days I'd have the data I needed to truly optimize what time I visited. Her response: Just. Go. Now. I did and after a mere 21 minutes of waiting, I took care of my required transaction.

This experience was a reminder that data is hiding in the most unsuspecting places. I'm not sure what I can do with this wait time information, but it seems like I should be hording it and analyzing it to tell me some greater truth.

Here's the full source code to my shell script. Hopefully you'll never need it.

#!/bin/bash

##
## What's the wait time on the DMV?
##

DATA_URL=https://www.dmv.virginia.gov/data/api/v2/csc/5
LAST_DATA=/tmp/$$.last
NOW_DATA=/tmp/$$.now
SLEEP_BETWEEN_RUNS=5m

flatten() {
  jq -r '.webWaitTimes | .[] |  .estimatedWaitTime + " | " + .peopleWaiting + " | " + .serviceName'  |
    sed 's/[*]/-/'
}

trend() {
  now=$(echo $1 | tr -d :)
  last=$(echo $2 | tr -d :)

  if [ "$now" -eq "$last" ] ; then
    echo "="
  elif [ "$now" -gt "$last" ] ; then
    echo "^"
  else [ "$now" -lt "$last" ]
       echo "v"
  fi
  
}

curl -s $DATA_URL | flatten > $NOW_DATA

while true ; do
  mv $NOW_DATA $LAST_DATA
  curl -s $DATA_URL | flatten > $NOW_DATA
  lines=$(cat $NOW_DATA | wc -l | cut -f 1 -d ' ')

  echo 
  date
  for i in $(seq 1 $lines) ; do
    now_line=$(sed -n "${i}p" $NOW_DATA)
    last_line=$(sed -n "${i}p" $LAST_DATA)

    service=$(echo $now_line | cut -f 3 -d '|')
    
    now_wait=$(echo $now_line | cut -f 1 -d '|')
    last_wait=$(echo $last_line | cut -f 1 -d '|')
    sym_wait=$(trend $now_wait $last_wait)

    now_people=$(echo $now_line | cut -f 2 -d '|')
    last_people=$(echo $last_line | cut -f 2 -d '|')
    sym_people=$(trend $now_people $last_people)

    printf "%s%s | %s%2s | %s\n" \
           $sym_wait $now_wait \
           $sym_people $now_people \
           "$service"
  done
  sleep $SLEEP_BETWEEN_RUNS
done

And a post about the DMV wouldn't be complete without at least one pic of someone doing what the DMV does best: making us wait.

No comments:

Post a Comment