January 2024 (7 months ago)

Alto Pharmacy Full-stack Internship Experiences

§
7 min read (1300 words)
· · ·

An Internship Bug Fix Example

Key concepts: Ruby, Snowflake, rspec/FactoryBot, Redux, team communication

Schedule II (C2) drugs have a high potential for abuse and dependence, thus they cannot be transferred automatically between pharmacies.

It was noted that transfers with C2 drugs such as Ritalin were automatically transferred, so I took to understanding how to fix this error. It ended up being a bit lengthier than I expected, so here’s a write-up of everything I did and learned.

Changing the Backend

It appeared that the validate function was not properly checking C2 medications. I also needed to check C2 medications into two places, so I refactored it out into a different model and reused the function.

But I ran into a string matching problem. Postgres’ case-insensitive pattern matching ILIKE would match everything if I wrapped the medication name in the sequence wildcard character like %NAME%, so even Rita would match Ritalin. Simultaneously, dosages such as Ritalin 20mg had to match Ritalin.

Well, time to take a look at all the C2 medications. Snowflake was the right tool to visualize the Ruby code querying the database. I translated Product.where(active: true, dea_schedule: 2), a custom Product model, into SQL to see what was going on in the database.

Snowflake dashboard

After looking at the medications list, they could break down into a few cases:

  • Drugs with hyphenated names such as Oxycodone-Acetaminophen
  • Drugs with spaced names such as Fentanyl Citrate
  • Drugs with single names such as Dilaudid

I realized it would be possible to match strings just by taking the first word of the medications given. Generally, C2 drugs names are with something like ER (extended release), Citrate or Suflate or HCL (Hydrochloric Acid) as something combined to the drug, and occasionally percentages such as 0.9%. Therefore, ILIKE FIRSTWORD% worked well.

Working with Design

It was heavily reiterated that we have product designers for the purpose of creating a cohesive user experience and that as an engineer, I shouldn’t just randomly put something in.

My manager consulted with our team’s product designer and she got some nice flows whipped up. I realized there was a contradiction between design and possibility.

design flow option 1 design flow option 2

In the flows above, only the second flow is possible. The error only comes through when the transfer request is submitted. Trying to show the error on adding a C2 drug would only be possible when doing:

  • sending a request to the database to get a list of C2 meds and comparing them on each med addition (inefficient)
  • hard-coding all C2 meds on the client

Also, I had personally felt like the error copy was too long. In this situation, it makes sense not to just straight out change it, but rather put screenshots of the two versions out there and say that I had noticed that. When making changes unilaterally without consulting the opinions of others, misunderstandings may arise.

Changing the Frontend

The frontend involved two parts: web and native. First, I had to understand the background of storing things in Redux and the one-way data flow associated with it. The errors were stored in Redux and I had to use a selector to get the specific error associated with our Prescription Transfer flow.

const { createTransferError } = useSelectorNative(getErrors)

After getting the error, it was a simple useEffect hook which would show a Toast with the error message. Here’s a gif of what I did on web and native.

Results of the error change

Culture

The people were different, in a good way. When I worked at NCR, the office chatter would be about the UGA-Georgia Tech football game, there would be a rooftop bar every Thursday, and Atlanta was the big city to them.

With the people I talked to at Alto (mostly California-based), I noticed how open-minded and caring they were. They would say, “tell me more” rather than imposing judgment quickly. People had personal goals, there was a focus on organic and local food (one manager dude brought in his own honey), and I genuinely do believe that people go to California (or the Bay Area) for the culture. But at the same time, I haven’t been there or understand the downsides well enough.

Expansion and Analytics

The company expanded a lot recently and brought in C-suite leadership from Amazon. I have mixed feelings about this. While I’m sure they’re able to grow a company effectively and become profitable, how does that make people who had been at the company from the beginning feel? How does that affect company morale and their will to work harder? I struggled to find alignment in the on-calls where the talk was: here are these percentages, they have changed this much, we are on the track to profitability. Although I was passionate about metrics and numbers and understanding how those worked at a companies, I felt like such a thing wasn’t a humanistic way of making decisions. You’d have to be really narrowly focused on making your “number” increase without considering the context and quality of what’s being done. The book Seeing like a State reminds me that increased legibility often misses detail. I haven’t fully understood how analytics and data-driven decision making exactly works, but XmR charts seem like a good start, which has been written about by CommonCog.

More profit would come from better optimization of the processes and app so that the gross profits per shipment would be positive, then it’s just a matter of scaling it. It could definitely present a strong alternative to CVS/Walgreens who have not built a strong technology team. But deep in my mind there was this nagging feeling that a world where people used more medications wasn’t one I wanted. Barring necessary medicines like insulin, I would want to minimize the pills we as a society take and maximize the healthy food.

On People, Operations, and Levels

I worked with all types of people, including those from well-known/good schools. It was significant to realize that no matter what background, everyone has work, everyone gets up in the morning, and everyone is human. It’s just that the texture of life is different.

I came to the understanding that “Operations” means day-to-day activities. Working as a cashier could be “Operations.”

It was interesting to observe the power disparity between delivery drivers and HQ staff. The drivers would write some long message in the chat about XYZ, then HQ would say something calm and basically say that they’re looking into it. To my best understanding, this dynamic is that each driver (and protester, basically) thinks that their individual voice matters, so they say it out loud. The manager has rapport with numerous people who run the day-to-day business, so they listen to the manager rather than the protestor. The problem of power is a problem of trust and connection. Managers in this situation usually note something, but it doesn’t significantly change their planning. But another equal level player could use these protestors to attack someone else.

Industry and terms

Understood the industry a bit better. How they bought a bunch of medical stuff from McKesson, how there were these things called Parata machines that are like a vending machine for pharmacists, the distinction between Schedule I, II, etc. substances.

The interview process was nice because they allowed usage of the internet. It was an example relevant to day-to-day work.

Some things I became aware of during my time here that I did not know of in the past:

  • Grafana
  • Flamegraphs
  • Looker
  • Protocol Buffers
  • OnFleet
  • Feature Flags
  • On Call (PagerDuty)
  • Postmortems
  • Analytics/auditing
  • Optimizely
  • Text expanders (operations people have a list of pre-made phrases that they can send to customers)
  • TeamRetro

Conclusions

There were also two common meta gotchas I ran into:

  • Creating tests requires setting up the data. Using Jest, I had to set up the Redux store. In Ruby I had to set up a FactoryBot for rspec tests.
  • Doing work on one branch and then creating a new branch will carry that branch over. When writing PRs, remember to go back to main and pull after merging said PR.

Overall, working on this bug over the course of a week gave me good insight into frontend, backend, analytics, and their respective constraints. I saw how different teams work together and have different responsibilities and understand how to communicate changes without miscommunication.