So I set out to solve the latter problem. I created an SMS notification system that sent text messages to parents letting them know what their swimmer's time was and more importantly, overall rank (note there are as many as 20+ heats for some events so you have no idea if they've qualified for the finals by simply watching). If a swimmer had a rank in the top sixteen, they'd have the opportunity to swim in the finals on Saturday.
As soon as the scorer's table has scored an event, they post the results to the web which was the trigger for the app to notify parents. It turned out to be hugely popular. So much so that I think there's a great opportunity to monetize it for next year's event. Parents at the meet and at work loved the little notes about their swimmers. Here's what one of my notifications looked like...
Tracy, Anna finished event 21 in 40.26, ranked 142
As much fun and useful as the app was, the most geeky and interesting element was actually the debugging part. The problem I faced was that I had no good way to test the app ahead of the meet. I had a pretty good idea how the meet scorers would format the data posted to the web, but there was no certainty. I also didn't have a lot of confidence that the app could actually follow the flow of the meet since the events don't go in order during the preliminary heats.
Since I didn't have a laptop or access to the codebase, there wouldn't be a way to triage issues during the meet using traditional debug tools. The solution was to create SMS hooks that let me tweak the app as the meet unfolded. I combined this with the implementation of multiple regular expressions when parsing the results. I built the app on App Engine so I created multiple memcache'd variables that could be controlled via text messages from my phone.
I coded in the following hooks...
- The ability to set/reset the swim event being polled so I can re-run (or skip) events if there was a mistake
- The ability to add new swimmers and phone numbers when parents wanted to be added to the app
- The ability to disable the entire app. I was paranoid that I'd made a hideous mistake that continuously sent text messages out to users and I wanted a way to shunt the entire app.
- The ability to query the app to determine the current event being monitored
- The ability to modify the URL base variable used to find the results on the web
In addition to these hooks being controlled with inbound SMS, I also had a few app events that triggered outbound SMS to my phone so I knew it was behaving correctly.
The hooks turned out to be invaluable on the first day. Both for keeping the app on the right event as well as adding parents to the app. I didn't actually have to use the emergency kill switch although it was nice to text 'disable' at the end of each day to make sure something didn't happen overnight.
The only downside to these hooks was actually a bug in the Google Voice app on my Droid. I was using a Google Voice number for the inbound events and one out of three texts I sent actually resulted in duplicate messages. The downside was when I added a new user, it added them twice (resulting in duplicate notifications when that swimmer swam!) and when I reset the event number, it reset it twice.
Here's a look at the main handler for the inbound Twilio messages...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
|
I forgot to take a picture in front of the results board at the meet. There's only one results board (for 1,700 swimmers), and each event is printed with a 10 point font. Most parents received their notification messages thirty minutes before the results board was updated!
I would be terrified of spamming texts and costing people $$, but sometimes I guess you have to take the leap to make something awesome. Just curious, why memcached instead of BigTable? Just ease of use?
ReplyDeleteThe memcache'd variables were backed by the datastore actually. I just left that detail out. After a lot of performance tuning and analysis on some other GAE apps, I'm now in the habit of memcaching everything. The downside of a slow query is significant - the command times out and fails - so I try to mitigate every slow moving part.
ReplyDeleteReally cool solution Greg. Elegantly solving a very specific problem. Coincidentally the senior level project in the MIS program at UW-La Crosse was developing a swim meet software system. Our professor had kids into swimming & he liked using it as a case study. Unfortunately, the technology he had us using was M$ Access and Visual Basic for Applications. No fun stuff like webservices, cloud deployment and text message interfaces.
ReplyDeleteI've used software like that. The tricky part with those programs is the feature set. They aren't terribly complicated, but you need lots of features to get to the point of being useful for teams and meets. Tough to do in a semester...
ReplyDelete