Sunday, May 18, 2014

StubHub Listing Manager +





"Software is Never Done..."


"...You just kinda choose when to stop working on it."  When I heard one of my gSchool instructors, Jeff Dean, say that, it really hit home.  It allowed me to give myself permission to stop working on my last personal project.  When I started the StubHub Listing Manager +, I had one clear goal:  get all my inventory and sale data form StubHub and put it in a format that easy for me to navigate and digest.  But once I'd done that, I had a million other ideas of what I could do, and truthfully, it became a little overwhelming.  I felt as if I'd never finish this project.

The Problem

StubHub is great for the average consumer who has a couple ticket groups they want to sell or buy.  But if you have a large inventory of tickets, navigating your inventory and sales is a cumbersome process because their take on inventory management is: "Let's list everything the user has for sale on a single page, in chronological order."  There isn't any sorting by date or by event.  There's no clear way to find out how many tickets you have a for given event, how close each event is, or where exactly your prices stand in comparison to other tickets on the market.  You can get that information, but it involves you clicking between multiple screens, writing down information, and manually comparing it to your own.  Simple if you have a couple tickets for sale.  Not simple if you have hundreds or even thousands of tickets for sale.

The Interim Solution

Most large sellers will get a third-party point-of-sale system that holds their inventory and allows them to view it in a sortable manner and provide them with detailed information about the current market, their individual ticket groups, and previous sales.  The catch is, these third-party point-of-sale systems cost between $200-$300 per month, charge you an additional fee per sale, require you to have a credit card processor, business phone lines, deal with chargebacks, handle shipping on your own, and take care of your own customer service.  I did that for a few years, but ultimately found that the overhead wasn't justified.  Well over $1000 per moth went into managing my own inventory.  And the headaches of customer service, dealing with credit card processing and shipping just weren't worth it.  Luckily, if you sell only on StubHub, they handle all that for you (sans inventory management) and charge you 15% per transaction, which is a little higher than handling it on your own, but well worth not having to deal with all the aforementioned headaches.

The New Solution

So after a few years of doing all on our own, we decided to shut it all down and only deal with StubHub.  The problem, as mentioned above, is that inventory management is a huge hassle because it has to be done manually.  So with my new programming super power, I decided to create my own makeshift inventory manager that GET's, POST's, PUT's and DELETE's from StubHub's API, essentially putting the burden on them.  In addition, to verify that a sale has been paid (which a point-of-sale would do for you), I integrated my inventory manager with the PayPal API to verify that the sale StubHub shows, has actually been paid out.

After some quick Bootstrap styling, it's as done as I want it to be... even though there's loads of other features I still think about adding.  Above is a brief video of the finished product.

PS

I learned that all API's are not made the same.  StubHub's API is poorly maintained, lacking a lot information, and their developer support is virtually non-existent.  Kind of sad for such a large company, but it taught me that sometimes you have to fiddle around... a lot... to get things to work.  Because that's the only way it's going to happen.  PayPal wasn't much different.  While they have new RESTful API's that are supposed to be easy to work with, all the information I needed had to come from their Classic (ahem.. Outdated) API's.  I ended up using their SDK gem instead of doing it all manually because it was such a huge pain in the rear.

24 Hours of Le Google (GovDev Hackathon)

1:30 AM - After two-thirds had gone to bed or given up

24 Hours of Le Google


I would imagine it's similar for most budding developers.  Their first endurance hackathon is an eye opening experience into the world of professional software development.  It's more a la Formula 1 than nerds (me included) lapping between their computers, pizza slices and empty Red Bull cans, as mainstream media or even my own pre-hackathon imagination would depict.  So what might you expect?

Off to the Races

Enter Galvanize Denver, the venue of 24 Hours of Le Google (GovDev Hackathon), registration booths manned with pre-printed name tags, free Google swag (t-shirts and water bottles), staff in matching orange shirts, Google colored race banners streaming the ceiling, catered breakfast, and in the main room... the horse and pony show.

Teams arrived with matching computer terminals, small tables packed with 27 inch Apple iMac's paired with 27 inch Apple external displays.  The most impressive team having four such stations all packed into a small table.  Many teams were split into 'pit' duties, the UX guy, the backend guy, the frontend guy, the mobile guy, the project manager ('guy' in the general, non-pejorative or exclusionary sense... some of these 'guys' were gals).  The air of competition was apparent. No one was too friendly or too talkative, they were all there to win.

Leading up to 'go time', there was a parade of who's who in state tech and Google big-wigs.  Talks from the CEO of this, the CIO of that, and the COO of the other, all sharing their ideas of why this was an important race and why what we were doing was furthering private citizen/sector involvement in making government more efficient.  They sold me, I was excited to be part of it (and still am).  After the talks came the challenges:  take this data, make it public and understandable to the layman; take these horrible relics of bureaucracy (repetitive paper forms), digitize and streamline them so that they're easy to complete on a mobile device and pass the completed product to all affected departments to streamline coordination; and finally, take this paper-based donation and dissemination program, make it mobile and seamless.  The last two challenges having to do with disaster response and preparedness, the first challenge dealing with government expenditures and transparency.


We Had no Idea

What started as a team of three (@FindingFixes, @ScottSkender, and I), had no idea the complexity of the tasks we'd been presented with.  We chose to take the challenge of making government expenditures transparent.  It seemed straight forward. Take these CSV files with a half-million line items and make it understandable to Jane and Joe Doe.  Create a filterable dashboard with charts, include map integration, and toss in a few Google API's to meet the minimum requirements.  Much to my surprise, our little laptops didn't like CSV files with a half-million lines.  They were too big!  And taking legacy data formats and converting them into usable content (like converting to JSON or importing into a PostgreSQL database) was more difficult than anticipated.  After 3.5 hours of hacking away (and getting nowhere), one of our members decided it was time to call it day.


And Then There Were Two

I'd had had it with this 'big data' challenge, it was more than I had bargained for and was a touch outside of our team's technical savvy.  So my partner (Seth Musulin) and I started on another challenge that seemed to compliment our technical chops.  The inventory management challenge to track donations and dissemination of donations.  It was to be a CRUD app of donation items, victims, and the donation items they'd been given (that's the superficial view of it).  After 3.5 hours of getting nowhere on the first challenge, having created a database table and an app that CRUD's donation items in the first 30 minutes felt like a huge accomplishment!  We felt as if we'd made a massive accomplishment... until we started understanding what else was needed. hahaha.  Now that our app can take in donations, we need to CRUD people, then we need to checkout these donations to people, then allow staff to search for donations by location, item type, donor, etc.  Easy enough, right?  Unfortunately, that wasn't the hard part.  While doing all that took our team of two until 1AM (having started at noon), we now had to make it work seamlessly from a user experience point of view... and... it had to look good.  But it was 1AM and having seen other teams nearly complete with some super-sexy, ultra functional, and utterly seamless apps... we knew there was no way we could get it done it time.  So my partner and packed our bags and called it a day.  Considering two-thirds of the original competitors had already left, I feel like we hung with the best of them... although our app surely didn't.

Lesson Learned

These guys take endurance hackathons seriously!  They plan teams where each person is a well-oiled, integral cog in a finely tuned software producing machine.  They don't play around.  They know their strengths and they avoid spinning their wheels on tasks that don't fit their skill-set.  For future endeavors, I'll be part of larger team with varied disciplines and I'll stick to what I know.  While it was a great opportunity to learn some new technologies, like Apache Solr (for fulltext and location-based search), and JavaScript location services, it was certainly not the venue to 'learn' if your intent was to finish and possibly even win.  That said, I'll do it again, and again... and again!  It was a great time, but certainly not what I'd expected.


Tuesday, May 13, 2014

T's OBGYN Patient Intake App - My Very First App!

My first personal project at gSchool was a very simple Sinatra + PostgreSQL app for my lady.  She had been complaining that she had to type out these ridiculously long text messages to her team every time she took in a new patient in triage, and that her life would be so much simpler if technology could do it for her.  Lucky her!  I happened to know exactly how to do it :)

So I made a simple form, styled with Bootstrap, that took all the required inputs, formatted them, saved them to a database (in case she wanted needed the data in the future), then gave her the option to: a) text the results to herself or someone else, or b) copy and paste the results.



There's a lot of fields to this form, but it kept her from having to manually type each one.   A little bit of background on its purpose.  She's an OBGYN and every time a new patient comes in, she has to get a history for each patient including such things as: how many times they've been pregnant, number of miscarriages, successful births, results of various tests, the weight of previous children at birth, the current baby's weight, etc.  Luckily, all of the information is anonymous, in that there is no identifying information that could possibly link this info to patient, so no need to worry about HIPAA.  Anyway, when the information is gathered, it's sent out to the team on call so everyone is aware of the current patients, their history, and possible complications.

Since all of the fields above are attributes of a single patient, I put them all in the patient class, which was ugly... nearly 20 instance variables!  Luckily there's a way to make that look decent:

1:  require 'patient_repository'  
2:    
3:  class Patient  
4:    
5:   ATTRIBUTES = [  
6:     :age, :g, :p, :at_weeks, :at_days, :by_weeks, :by_days, :ultrasound, :prenatal_care,  
7:     :placenta, :group_b_strep, :one_hr_diabetes, :three_hr_diabetes, :history, :times_delivery_type,  
8:     :largest_baby_birthed, :estimated_fetal_weight_lbs, :estimated_fetal_weight_oz, :efw_by,  
9:     :sterile_vaginal_exam_time, :sve_dilation, :sve_effacement, :sve_station, :comment, :time  
10:   ]  
11:    
12:   attr_accessor *ATTRIBUTES  
13:    
14:   def initialize(options)  
15:    ATTRIBUTES.each do |attr|  
16:     instance_variable_set(:"@#{attr}", options[attr])  
17:    end  
18:   end  
19:    
20:   def attributes  
21:    Hash[ATTRIBUTES.map { |name| [name, instance_variable_get("@#{name}")] }]  
22:   end  
23:    
24:   def validate  
25:    ATTRIBUTES.each do |attribute|  
26:     replace_attribute = ['', '--'].include?(instance_variable_get("@#{attribute}"))  
27:     instance_variable_set("@#{attribute}", nil) if replace_attribute  
28:    end  
29:   end  
30:  end  

Instead of initializing ~20 instance variables, I put them into an attributes array and iterated through the array in the initialize method.  This did two things, solved a potential security vulnerability and also made the class look a ton cleaner.

Also, since not all the values were required and the database wasn't too keen on taking empty strings or '--' values in integer cells, I made a validate method that checked the form inputs for these and replaced them with nil.

Below is a screenshot of what the database:




After all the values have been input and submitted, below is the results.  What really made my day was being told that this little app now saves my darling about hour per day!  Nothing like producing software (in my first month of gSchool) that is actually used in a professional setting :)


If there are any OBGYN's out there that think this would be useful for them, feel free to use it: 

http://www.tiare.herokupapp.com

It's optimized for mobile, and kind of looks poopy on desktop.  But hey, it was my first app, ever!

As a PS, I looked all over GitHub for a for an active gem that allowed sending text messages through Google Voice.  Unfortunately, there weren't any active gems... so I had use some code from a blog I read and repurpose it for my app.  I'll have to scour Google SERPs for the blog (because he deserves the credit) and add it later, since I didn't bookmark it.


1:  class TextMessage  
2:    
3:   attr_accessor :data, :phone_number  
4:    
5:   def initialize(data, phone_number)  
6:    @data = data  
7:    @phone_number = phone_number  
8:   end  
9:    
10:    
11:   def text_message  
12:    login_response = ''  
13:    
14:    url = URI.parse('https://www.google.com/accounts/ClientLogin')  
15:    login_req = Net::HTTP::Post.new(url.path)  
16:    login_req.form_data = {'accountType' => 'GOOGLE', 'Email' => 'pt.intake.generator@gmail.com', 'Passwd' => '****************', 'service' => 'grandcentral', 'source' => 'tiare.herokuapp.com'}  
17:    login_con = Net::HTTP.new(url.host, url.port)  
18:    login_con.use_ssl = true  
19:    login_con.start { |http| login_response = http.request(login_req) }  
20:    
21:    url = URI.parse('https://www.google.com/voice/sms/send/')  
22:    req = Net::HTTP::Post.new(url.path, {'Content-type' => 'application/x-www-form-urlencoded', 'Authorization' => 'GoogleLogin auth='+login_response.body.match("Auth\=(.*)")[0].gsub("Auth=", "")})  
23:    
24:    req.form_data = {'id' => '', 'phoneNumber' => @phone_number, 'text' => @data, '_rnr_se' => '********************'}  
25:    con = Net::HTTP.new(url.host, url.port)  
26:    con.use_ssl = true  
27:    con.start { |http| response = http.request(req) }  
28:   end  
29:    
30:  end  

I totally lied...

A while back ... my last post... I stated that I was going to make my own blog from scratch.  Needless to say, my ambitions got the best of me.  I had no idea what I was in for at gSchool.  No one told me that the ability to program was like a super power and once you know (even a little) how to program, you'll want to take over the planet with all the ideas that cross your mind.   At least that's what happened to me.   Anyway, I'm not making a new blog, yet.  This will do while I work on more interesting projects.  

In my next post, I'll show off a couple apps I've made recently:  A patient intake generator that takes input, generates a formatted output and allows you to text it via SMS (through a Google Voice hack) that I made for my significant other; and a StubHub inventory manager that utilizes StubHub's API to present inventory and sales in a better format and also ties in with PayPal's API to verify that StubHub sale has been paid.  

Maybe I'll do a rant on gSchool, too. :)