Meet the Innovation Team Building Alto's Next Big Bets

Aug 24, 2022

By

Jessica Lin & Michael Cohen

The Alto product and engineering team has long been focused on initiatives that improve our unit profitability. Now that we’ve laid a strong foundation, we've started to think about our next big bets: How do we expand our services beyond delivering prescriptions? How do we test new value-add offerings quickly and iteratively? That’s where the innovation team comes in.

Newly launched in 2022, the team is small and scrappy by design. We’re a product manager, two engineers, a part-time designer, and a part-time manager with a single mission: to launch at least two significant new business experiments per year. If an experiment is successful, we’ll scale it into a full workstream before handing it off to a stand-alone product team.

Innovation Team

We started with over-the-counter (OTC) medications. OTCs, or medications you can buy without a prescription, like Advil, are high-margin products that users are accustomed to purchasing at traditional, brick-and-mortar pharmacies. The goal of this experiment was to assess how much interest users have in adding OTCs to their order while checking out in the Alto app. We set a target conversion rate and offered OTCs only from our San Jose pharmacy, which is less busy than our San Francisco pharmacy but close enough to engineers in San Francisco for in-person troubleshooting.

Phase 1: Recommendations

Our initial hypothesis: if we present users with a small selection of OTCs targeted to their needs (based on the prescriptions in their cart), they’ll be more likely to convert. With support from our pharmacy partners, we carefully curated a recommendation algorithm that would suggest nine OTCs based on the medications already in the user’s cart.

Alto over-the-counter medication app feature

In the interest of lean experimentation, we wanted a scrappy technical solution that could be implemented quickly while meeting our high user experience standards and minimizing operational risk. We settled on the following architecture:

Alto over-the-counter architecture

We prioritized creating a clean API for Alto’s customer-facing app and using as much of our current infrastructure as possible. Although OTCs are distinct from prescriptions, we decided to reuse our existing prescription model rather than build a new one from scratch. This allowed us to leverage existing systems and operational workflows, reducing engineering time and risk of regression.

Behind the API, we opted to store our OTC formulary and associated metadata in in-memory data structures:

ADVIL = SkuId.new(sku: '...', sku_type: '...').freeze CLARITIN = SkuId.new(sku: '...', sku_type: '...').freeze FISH_OIL = SkuId.new(sku: '...', sku_type: '...').freeze ... METADATA_BY_SKU_ID = { ADVIL.hash_code => { image_url: '...', name: 'Advil Ibuprofen 200mg Coated Tablets', quantity_description: '100 tablets', product_description: 'Easy to swallow powerful pain reliever for headaches, joint and muscle pain.', }, CLARITIN.hash_code => { image_url: '...', name: 'Claritin 24 Hour Allergy Tablets', quantity_description: '30 tablets', product_description: 'Non-drowsy, 24 hour relief of symptoms due to allergies: sneezing, runny nose, itchy and watery eyes.', }, FISH_OIL.hash_code => { image_url: '...', name: 'Fish Oil 500mg Softgels', quantity_description: '130 softgels', product_description: 'Dietary supplement with EPA and DHA omega-3 fatty acids to support heart health and maintain normal triglyceride levels.', }, ... }.freeze

This gave us a nice balance of speed and flexibility. We knew that we could optimize once we had established our OTC offering and gained an understanding of how it would evolve within Alto.

The results of phase 1 were a bit disappointing, but very interesting: the conversion rate of users who checked out with an OTC was just under our target. However, nearly half of all users who saw the experience would scroll down to peruse all nine OTCs that were offered.

Based on this positive sign of engagement, we hypothesized that perhaps we just weren’t showing the right OTC products to the user. For phase 2, we decided to scrap the limited recommendations and allow users to explore an entire catalog of OTCs.

Phase 2: Categories

Phase 2 required categorizing our list of OTCs into buckets such as “Top Sellers,” “Covid Care,” and “Allergy & Asthma.” We built a new fetch_categorized endpoint to return our OTC catalog structured by category, adding search functionality into the client so that users could quickly filter for a specific product. This search field was also heavily tracked with analytics events to help us learn which products to add to our catalog in the future.

Based on this positive sign of engagement, we hypothesized that perhaps we just weren’t showing the right OTC products to the user. For phase 2, we decided to scrap the limited recommendations and allow users to explore an entire catalog of OTCs.

Phase 2: Categories

Phase 2 required categorizing our list of OTCs into buckets such as “Top Sellers,” “Covid Care,” and “Allergy & Asthma.” We built a new fetch_categorized endpoint to return our OTC catalog structured by category, adding search functionality into the client so that users could quickly filter for a specific product. This search field was also heavily tracked with analytics events to help us learn which products to add to our catalog in the future.

Future Catalog

This new experience immediately outperformed phase 1. After a few performance improvements (speeding up our catalog and product image load times), we eventually hit our target conversion rate — even managing to surpass it on some weeks!

Our eventing did show that a significant number of users with multiple prescriptions or smaller screens were not even seeing the OTCs in Cart, as they were pushed below the fold. We tried experimenting with alternate spots to show our catalog:

1. An interstitial that shows after Cart and before Checkout - this didn’t perform any better than the control and was quickly scrapped.

2. A single carousel at the top of Cart - this out-performed the control but at the cost of our guardrail metric. With a decrease in the overall checkout rate, we decided to stick with the control for now.

Phase 3: Expansion

This phase of UI experimentation validated that our users wanted an offering of categorized OTCs. However, our initial technical solution wouldn’t scale as we rolled out to more of our pharmacies. The following questions led us to this conclusion.

How might we support changes to our OTC catalog on a per-pharmacy basis?

Given factors such as inventory, geography-related price sensitivities, and experimentation, we wanted to make sure that we could tweak the OTC experience for users getting deliveries from a specific pharmacy.

In the status quo implementation, that would have likely meant adding an additional layer to our data structures like so:

SKUS_BY_PHARMACY_AND_CATEGORY = { SF_PHARMACY_ID => [ AddOnOtcCategoryType::TopSellers => [ IHEALTH, FLONASE, ASPIRIN, ... ], ... ], ... }

We weren’t worried about memory space limits given our relatively small number of pharmacies and OTC catalog. We were concerned, however, that each modification would require an engineer to go in and make code changes, get PR approval, and wait for a full deploy for the update to go live. This would be a bad use of engineering time, and our CI/CD process would become a bottleneck to otherwise quick modifications.

How might we add and remove OTCs from our catalog?

Similarly, we knew that adding and removing OTCs from our catalog would also be a somewhat regular workflow. There are some other considerations when adding a new OTC (Is it in our product database? Do we need any new procurement/inventory workflows to make sure it gets stocked in our pharmacies? Do we have a nice image to render in the app?), but we wanted to automate the technical side as much as possible — or at least be able to automate it.

Moving to the database

The natural next step was to move our OTC catalog into the database.

Alto’s OTC Database Schema. Some columns excluded for brevity.

Now, rather than grabbing from in-memory data structures, we are efficiently querying the database for the OTC catalog of a user’s specific pharmacy, preloading all of the associated models needed for our API response.

OtcCategory .includes(otc_category_products: { otc_product: :otc_product_metadata }) .where(otc_category_products: { pharmacy_id: pharmacy_id }) .where(otc_product_metadata: { pharmacy_id: pharmacy_id })

At the time of writing, an engineer is still required to go in and make the changes at the database level. However, we plan to build an admin dashboard so that designated pharmacy partners can tweak our OTC catalog without the help of an engineer.

Phase 4: What’s next?

We’re excited about our next iteration, which will allow users to add OTCs to their upcoming deliveries at any point, not only when checking out. It will likely be live by the time you read this.

Once that’s released, we will continue to learn, iterate, and pay down technical debt — more specifically, maturing the OTC data model in our backend and eliminating the need for them masquerade as prescriptions.

Looking even further ahead, the innovation team has more exciting and impactful projects on our roadmap. We can’t wait to share what’s in store!