Personality
If Andrew were a real-life person, he would be a hotel concierge, albeit a "global" concierge able to help you with any hotel. As someone with more than a decade of experience in his job (and enjoys it, since this special concierge job lets him experience the world's best hotels), he is ever-ready to share his knowledge with users. While the name Andrew is Greek for "strong" or "manly", these are not necessarily the traits which this Andrew embodies; instead, the name references a personal friend who could do very well as a global concierge.
As a hotel concierge, Andrew is clean, polite, and professional. As a well-traveled man, he is confident in his knowledge, but at the same time is a reasonable man who knows what he can and can't do. When Andrew speaks, he does so with clarity and patience. For example, if the user asks for help, he won't mind repeating instructions which he might have uttered just seconds ago. While he has to keep a professional distance between himself and the user, he also strives to avoid being stiffly formal in a way which might alienate the user. Andrew is always thinking about how things that can go wrong, but as a busy concierge serving many users at once, he can't spend all of his time on any one of them. So if a user repeatedly makes bad requests, that user may start to notice a little bite in Andrew's response...
Technical Implementation
Andrew was developed with Twilio routing SMS messages to a Sinatra back-end server hosted on Heroku. The Sinatra app has a single endpoint, /incoming_sms, which handles and responds to all the text messages Twilio forwards. Sinatra in turn gets most of the data requested by the user from a travel API. Expedia was originally the API of choice; however, because there is a barrier to getting access to their API (one must be an Expedia affiliate), the allmyles API was used instead. allmyles provides a hotel API which accomplishes roughly the same thing for hotels that the Expedia API does, but there are some differences between the two. (This is why some features in the matrix are marked as could not implement.)
In addition to the gems used in the skills development exercises earlier in the course, two gems (found in the discovery phase of this project) were key in making a functional Andrew. The first is RestClient, a general-purpose gem for making calls to any RESTful API. RestClient was particularly useful because, unlike Expedia, the allmyles API does not have a Ruby gem-wrapper for it. The second is Chronic, a gem which can parse dates - even relative dates - from bodies of text. For example, if today is Friday, December 16th, Chronic would be able to tell that "I'm flying to Seattle next Monday" refers to Monday, December 19th. While Andrew implements the rest of the language processing himself, dates are the easiest thing for him to handle due to how effective Chronic is.
In the original proposal, an SQL database was only necessary to implement certain "bells-and-whistles" features. None of these were implemented, but a database became desirable for two unforeseen reasons. First, unlike the Expedia API, the allmyles API expects city codes (e.g. SFO for San Francisco) instead of city names when searching for hotels, so a database table was created to store city name-to-code mappings. Second, calls to the allmyles API tend to be lengthy - almost a minute in some cases - which means that Sinatra is unable to handle certain requests before the Twilio or Heorku timeouts. While Sinatra can reach back out to the user once the request is complete, it is no longer possible to store session state in cookies (session variables), so the database is used instead for this purpose.
The diagram below shows the mentioned tables. The City table maps city names to city codes. The State table is equivalent to a Sinatra session variable: for each mobile number (the "from" column) there is a row in the State table, if the user behind that number is in the second or final stage of the workflow. (In the first stage, there is no information to keep track of since the user is just starting.) A state row records the stage the user is in, the number of hotels shown so far and the maximum number of hotels Andrew has to show, the number of bad requests the user has made, the allmyles API cookie, and the user's initial search criteria - city and date. The Result table stores hotel search results for each active session: each row corresponds to a hotel and has basic information about it, and there may be up to 30 rows for each mobile number. Results are tied to a session and cleared when that session ends; it may be more efficient to pre-fetch and store them for future use, but results are ephemeral. That is, they are only intended for the user who conducted the search at the time the search was conducted. (This is part of the reason a cookie is needed for a supposedly RESTful API. For more information, see the allmyles documentation, "Quick Summary" and "Common Gotchas".)