← all projects
BDMS
πŸ‘Ύ Full-Stack ProductsJuly 2026

BDMS

Buprenorphine Dispensing Management System A prototype for tracking and regulating the dispensing of Buprenorphine across a network of de-addiction centres.

Next.jsSupabaseOAuth
GitHub β†—

// the_story

# BDMS β€” Buprenorphine Dispensing Management System


> A drug-distribution prototype built out of a conversation with my father. The hard part was never the code. It was deciding what the software should refuse to do β€” and then wondering whether anyone would let it.


## The Problem Statement


My Dad is a psychiatrist, and has spent years involved in the fight against drugs in Kashmir.
We were talking casually one day when the problem came up. **Buprenorphine** is the drug used at government de-addiction centres to bring heroin addicts through detox and hold them steady afterwards. It works. It is also, itself, an opioid β€” which means the treatment carries the shape of the problem inside it. It gets misused, and when it does, it defeats the entire purpose of being there.
Patients collect their doses daily or weekly[whatever prescribed]. And where the collection register is on paper, in a drawer, at one centre β€” nothing connects that drawer to the drawer at the next centre.
So a patient can collect on Monday at one centre and again on Tuesday at another. Both pharmacists did their job correctly. Neither of them had any way to know. Across **22 centres**, that is not an edge case. That is a standing, invisible leak, and every tablet that goes through it is a tablet that ends up somewhere it shouldn't.
I want to be honest about why I built this: **not** because it was an unsolved problem. Systems like this exist. It isn't even a hard piece of logic. I built it because I wanted to see the thing for myself β€” to find out whether the problem my father described actually collapses into something a database can hold, and how small that something turns out to be.


## The Reframe


The first useful thing I did was stop thinking of it as a drug-tracking problem.
Nothing about this requires tracking a substance. It requires two things the 22 centres didn't have: **one identity per patient across the whole network**, and one clock they all read from . Everything else follows.
That gave me three decisions:
One patient, one number, network-wide. A doctor registers a patient once, and the database issues a permanent ID β€” `BDMS-000001` β€” from a sequence, on insert, via a trigger. The centre where they registered doesn't own them. The network does.
Prescriptions carry an interval, not a schedule. This one mattered more than I expected. A prescription doesn't say "Mondays." It stores `cooldown_hours` β€” the minimum hours that must pass between collections. Daily is `24`. Weekly is `168`. A patient who collected last Monday at 9am and shows up this Monday at 8am is at 167 hours, and gets refused. A schedule would have let that through; an interval doesn't, because an interval has no opinion about what day it is.
Every attempt is written down β€” including the ones that fail. The dispensing log is append-only and records denials with a typed reason alongside approvals.


## The One Line That Actually Solves It


The eligibility check lives in the database, not the app β€” a Postgres function every client has to go through. It walks a fixed order: does the patient exist, are they active, do they have a live prescription, and has the cooldown elapsed. And the last question is the entire product:


SELECT * INTO v_last_log FROM dispensing_logs WHERE patient_id = p_patient_id AND status = 'approved' ORDER BY dispensed_at DESC LIMIT 1;


That absence is the fix. The pharmacist at Centre 11 is querying against the patient's entire history across all 22 centres, without knowing it and without being able to opt out. Monday's collection at Centre 03 is sitting right there in Tuesday's answer.
And the refusal is specific rather than a flat "no". If the cooldown hasn't elapsed, the function hands back the hours remaining, the hours elapsed, the timestamp of the last collection, and **which centre served them**. The pharmacist isn't left guessing; they're looking at the receipt.


> A denial is not the system failing. It is the only evidence anyone will ever have that somebody tried.
That's why denials are stored with a reason enum rather than discarded, and why the admin dashboard charts approved-versus-denied over time, denial reasons by cause, and activity per centre. Approvals tell you the programme is running. **Denials tell you it's working.**


## Rules the Application Can't Talk Its Way Out Of


The pattern I kept returning to: if something must always be true, don't defend it in application code β€” make the database physically unable to represent the alternative.
A patient must never hold two live prescriptions at once. That could have been a validation check in a form handler, one that some future code path eventually forgets to call. Instead:

CREATE UNIQUE INDEX uq_one_active_prescription ON prescriptions (patient_id) WHERE is_active = true;


A partial unique index. Now a second active prescription isn't a bug to be caught β€” it's a row Postgres will not accept, from any client, forever. When a doctor revises a prescription, the old one is deactivated and the new one inserted, and the invariant holds itself.
The same instinct shaped the rest. Three roles β€” admin, doctor, pharmacist β€” and **no public sign-up at all**; every account is created by an admin through a server-only route holding the service key. A pharmacist's centre lives on their profile, assigned by the admin, and is never something they type at dispensing time. So every log line knows where it happened, and nobody can quietly attribute a collection to the wrong place.
The stack is deliberately unremarkable: **Next.js 16** App Router with server components, **Supabase** for Postgres and auth, row-level security, role-based routing in the proxy layer, Recharts for the admin analytics. None of it is the interesting part, which I think is the point.


## The Kitchen-Table Test


Once the prototype worked, the three of us β€” my father, my sister Zoeya and I β€” sat down and role-played it. One admin, one pharmacist dispensing, one patient turning up to collect.
I'd recommend this to anyone building software for a process they've never personally stood inside. Clicking through your own app as the developer, you follow the path you built. Handing a role to someone else and watching them walk up to the counter is a different exercise entirely β€” my father wasn't testing my code, he was checking it against a room he has actually stood in. And I got to watch the software from the side of the counter it will be used from: the side where someone is being told *no*, by a screen, in front of other people.
It's a small thing and it took an evening. It taught me more about the design than any amount of re-reading the schema would have.


## What I Know Is Wrong With It


This is a prototype, and I'd rather say what's broken than let it look finished:
- **The eligibility check is enforced in the UI, not at write time.** The client asks the database whether the patient is eligible, and then separately inserts the log. A modified client could write an `approved` row for someone who isn't. The real version puts both inside one privileged function that re-runs the check atomically.- **Two centres at the same instant would both see "eligible."** Check-then-insert with no lock in between. The precise scenario the whole thing exists to prevent survives, in miniature, as a race condition.- **Identity is a typed-in number.** Nothing stops someone reading out an ID that isn't theirs. This wants a photo, a card, or biometrics β€” and that's a policy decision, not a coding one.- **"Every tablet accounted for" is only half true.** The log counts *collection events*, not tablets. You can reconcile who collected and when; you cannot reconcile physical stock. Closing that gap means inventory, and inventory means the pharmacy's actual supply chain.
The prototype demonstrates the idea. It does not survive contact with an adversary, and it was never asked to.


## The Part I'm Still Thinking About


Two things have stayed with me, and neither of them is technical.
The first is how *small* this is. There is a real problem here and real lives at stake and the core of the answer is a table with a timestamp column and a query that forgot to filter by location.

We are all sprinting at AI, quantum, agents,and whatever the next big buzzword happens to be. Meanwhile, there are problems where real lives are at stake, and the solution is… a timestamp, a unique identifier, and a database query.

Today, with modern frameworks and AI-assisted development, a functional prototype like this can be built in days. With enough effort, it could become production-ready and scalable.

The most technically interesting problems and the most important problems are not always the same.

I don’t think we’re honest enough about how rarely they overlap.

Sometimes, simple software changes can solve surprisingly large real-world problems.

#Will They ? Wont They….


The second thing is harder, and it's the one I don't have an answer to.
Whoever I talk to about this thing in Kashmir They just tell me one thing β€œYes ok good problem to solve but will people actually adopt it ? Will They wont try to bypass it ? Will the pharmacist stop his business ? Will a Tech like this be strictly adopted in a place like Kashmir? β€œ

Why people has lost so much hope from Kashmir. Its not problem of Luddism.
It’s about the mindset. The question is about discipline, moral and the vision to become better as an individual and as a society : whether a system that exists to say no to you gets used as intended when nothing is forcing it.
If the government enforces it strictly, fine, that's a different conversation β€” you don't need people to agree with a rule they can't get around. But strip the enforcement out and what remains is a pharmacist who could just not check, and a patient who wants their dose, and a screen quietly asking both of them to choose the harder correct thing over the easier immediate one.
I can build the software that makes the right thing possible. I have no idea how to build the part where we choose and embrace it.


That's the part I got to skip in this physical world but not in my mind.

// gallery

BDMSBDMSBDMSBDMS