Inspiration
Reading a handwritten prescription is slow, and a mistake can hurt someone. A pharmacist has to decode the writing, find the right product, and check what's in stock. If a brand is missing, they must also work out a safe substitute, all while stock expires quietly on the shelf.
We wanted software that takes over the tedious reading and searching and leaves the decisions to the pharmacist. That became our rule for the whole project: the software only suggests; a human confirms everything.
What it does
PharmaCare is a full-stack digital pharmacy system built around a pharmacist's review desk.
- The pharmacist uploads a photo of a prescription.
- The system cleans the image, reads it with OCR, and extracts the medicine lines (name, strength, frequency, duration).
- Each line is matched against the catalogue with a confidence level (High, Medium or Low). If a brand is out of stock, it suggests in-stock medicines with the same active ingredient.
- The pharmacist verifies or rejects every line, checks availability, and confirms dispensing.
- Stock is deducted from the earliest-expiring batch first (FEFO), and expired batches are never used.
Around that core it provides inventory and batch tracking, low-stock and expiry alerts, receipts, reports, CSV import, an audit log, and role-based access for admins and pharmacists.
How we built it
- Backend: FastAPI and SQLAlchemy 2.0, with SQLite for development and PostgreSQL via
DATABASE_URL. Passwords use argon2 and sessions use 8-hour JWTs. - Vision and OCR: OpenCV (resize, grayscale, denoise, CLAHE, Otsu threshold) feeding PaddleOCR, which loads lazily because it is slow to start.
- Parsing and matching: A regex parser keeps only lines that begin with a dosage form (Tab, Cap, Syp...). A blocklist drops doctor titles, addresses and vitals, and a small dictionary corrects known OCR misreads. Matching uses
rapidfuzzover name, brand, generic and composition. - Frontend: React 18, Vite and axios, with hand-written CSS in a forest-green and emerald theme.
- Architecture: Routers stay thin and business logic lives in services. Money is
Decimalend to end and travels as exact strings, never floats.
Challenges we ran into
- Making dispensing all-or-nothing. Stock, transaction rows, the status change and the audit entry must succeed or fail together. We lock batches with
SELECT ... FOR UPDATE, but SQLite ignores that, so we added an atomicUPDATE ... WHERE status = 'VERIFIED'to claim the prescription first. A double click or a race can now dispense only once. - Prescriptions are noisy. Letterheads, addresses, phone numbers and diagnoses look like text with numbers in it. The form-prefix rule plus the blocklist fixed most of it, and skipped lines are shown to the user instead of vanishing.
- Dangerous near-matches. "Crocin 650" scores almost the same as "Crocin 500". We added a strength check that lowers the score and flags the mismatch.
- Bad substitutes. Sharing the word "hydrochloride" is not sharing a drug, so we ignore salt words when comparing active ingredients.
- Bugs caught by tests. Rejecting a pending admin failed because their own audit row pointed to them, and the dashboard returned sales as a float. Both are fixed.
Accomplishments that we're proud of
- 63 automated tests pass, covering the safety rules: no stock change before verification, rollback on short stock, FEFO ordering, expired stock skipped, and single dispensing.
- An end-to-end flow from photo to receipt, with a complete audit trail.
- A review desk that makes the safe path the easy path. Each line needs an explicit decision, and dispensing sits behind a confirmation dialog.
- A runnable project with a README that states its assumptions openly.
What we learned
- In healthcare software the hard part is not the AI. It is designing so that a wrong guess can never reach the patient.
- Concurrency and transactions behave differently across databases, so assumptions should be tested rather than trusted.
- A confidence score is only useful when shown beside the evidence it came from: the scan, the OCR text and the alternatives.
- Writing the safety rules down first made them easy to turn into tests.
What's next for PharmaCare
- Test on real prescription photos, then tune the parser and misread dictionary. Real PaddleOCR output has not been verified yet, and handwriting is the biggest risk.
- Evaluate a handwriting-specific OCR model.
- Verify behaviour on PostgreSQL under concurrent users.
- Add barcode scanning, supplier and purchase-order management, and multi-branch support.
- Check controlled-substance rules (such as Schedule H1 records) with a pharmacist before any real deployment.
Log in or sign up for Devpost to join the conversation.