Here is a simple, open-source Python-based prototype for a digital pharmacy + telepharmacy system. It combines:
– Basic inventory management (medicines stock)
– Prescription handling (upload/view prescriptions — simulating telepharmacy)
– Patient & pharmacist authentication
– Simple dispensing / order processing
– Basic tele-consult note field (pharmacist can add advice after “review”)
This is a minimal viable example using Flask (lightweight web framework), SQLite (easy to run), and plain HTML/CSS/JS for the frontend. It is not production-ready (no proper encryption, no real file upload security, no video call integration, limited validation), but it’s open-source friendly and a good starting point to build upon.
You can extend it with:
– Real authentication (Flask-Login + bcrypt)
– File storage (e.g. AWS S3 or local secure folder)
– Video consultation (integrate Twilio, WebRTC, or Agora)
– Drug interaction checker API
– PDF prescription generation (ReportLab)
– NAFDAC/PCN compliance fields
“`python
# digital_pharmacy_telepharmacy.py
# Open-source prototype for Digital Pharmacy + Telepharmacy in Python (Flask + SQLite)
# MIT License – feel free to use, modify, and distribute
# Requirements: pip install flask flask-sqlalchemy werkzeug
from flask import Flask, render_template, request, redirect, url_for, flash, session, send_file
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
from werkzeug.utils import secure_filename
from datetime import datetime
import os
import io
app = Flask(__name__)
app.secret_key = ‘super-secret-key-change-in-production-2026’
app.config[‘SQLALCHEMY_DATABASE_URI’] = ‘sqlite:///digital_pharmacy.db’
app.config[‘SQLALCHEMY_TRACK_MODIFICATIONS’] = False
app.config[‘UPLOAD_FOLDER’] = ‘uploads’
app.config[‘ALLOWED_EXTENSIONS’] = {‘pdf’, ‘jpg’, ‘png’}
# Create upload folder if not exists
os.makedirs(app.config[‘UPLOAD_FOLDER’], exist_ok=True)
db = SQLAlchemy(app)
# —————— MODELS ——————
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password_hash = db.Column(db.String(200), nullable=False)
role = db.Column(db.String(20), nullable=False) # ‘patient’ or ‘pharmacist’
full_name = db.Column(db.String(100))
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
class Medicine(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
dosage = db.Column(db.String(50))
price = db.Column(db.Float, nullable=False)
stock = db.Column(db.Integer, default=0)
expiry_date = db.Column(db.Date)
class Prescription(db.Model):
id = db.Column(db.Integer, primary_key=True)
patient_id = db.Column(db.Integer, db.ForeignKey(‘user.id’), nullable=False)
pharmacist_id = db.Column(db.Integer, db.ForeignKey(‘user.id’))
filename = db.Column(db.String(200))
upload_date = db.Column(db.DateTime, default=datetime.utcnow)
status = db.Column(db.String(20), default=’pending’) # pending, reviewed, dispensed, rejected
tele_note = db.Column(db.Text) # Pharmacist’s advice / tele-pharmacy note
dispensed_date = db.Column(db.DateTime)
# Create database tables
with app.app_context():
db.create_all()
# Helper: allowed file
def allowed_file(filename):
return ‘.’ in filename and filename.rsplit(‘.’, 1)[1].lower() in app.config[‘ALLOWED_EXTENSIONS’]
# —————— ROUTES ——————
@app.route(‘/’)
def home():
if ‘user_id’ not in session:
return redirect(url_for(‘login’))
return redirect(url_for(‘dashboard’))
@app.route(‘/register’, methods=[‘GET’, ‘POST’])
def register():
if request.method == ‘POST’:
username = request.form[‘username’]
password = request.form[‘password’]
role = request.form[‘role’]
full_name = request.form.get(‘full_name’, ”)
if User.query.filter_by(username=username).first():
flash(‘Username already exists!’, ‘danger’)
return redirect(url_for(‘register’))
user = User(username=username, role=role, full_name=full_name)
user.set_password(password)
db.session.add(user)
db.session.commit()
flash(‘Registration successful! Please login.’, ‘success’)
return redirect(url_for(‘login’))
return render_template(‘register.html’)
@app.route(‘/login’, methods=[‘GET’, ‘POST’])
def login():
if request.method == ‘POST’:
username = request.form[‘username’]
password = request.form[‘password’]
user = User.query.filter_by(username=username).first()
if user and user.check_password(password):
session[‘user_id’] = user.id
session[‘role’] = user.role
session[‘username’] = user.username
flash(‘Login successful!’, ‘success’)
return redirect(url_for(‘dashboard’))
else:
flash(‘Invalid credentials’, ‘danger’)
return render_template(‘login.html’)
@app.route(‘/logout’)
def logout():
session.clear()
flash(‘Logged out successfully’, ‘info’)
return redirect(url_for(‘login’))
@app.route(‘/dashboard’)
def dashboard():
if ‘user_id’ not in session:
return redirect(url_for(‘login’))
user = User.query.get(session[‘user_id’])
if user.role == ‘patient’:
prescriptions = Prescription.query.filter_by(patient_id=user.id).all()
medicines = Medicine.query.all()
return render_template(‘patient_dashboard.html’, prescriptions=prescriptions, medicines=medicines)
elif user.role == ‘pharmacist’:
pending = Prescription.query.filter_by(status=’pending’).all()
reviewed = Prescription.query.filter_by(status=’reviewed’).all()
medicines = Medicine.query.all()
low_stock = [m for m in medicines if m.stock < 10]
return render_template(‘pharmacist_dashboard.html’, pending=pending, reviewed=reviewed, low_stock=low_stock)
return “Unknown role”
# —————— PATIENT ROUTES ——————
@app.route(‘/upload_prescription’, methods=[‘POST’])
def upload_prescription():
if ‘user_id’ not in session or session.get(‘role’) != ‘patient’:
flash(‘Access denied’, ‘danger’)
return redirect(url_for(‘dashboard’))
if ‘file’ not in request.files:
flash(‘No file part’, ‘danger’)
return redirect(url_for(‘dashboard’))
file = request.files[‘file’]
if file.filename == ”:
flash(‘No selected file’, ‘danger’)
return redirect(url_for(‘dashboard’))
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file_path = os.path.join(app.config[‘UPLOAD_FOLDER’], filename)
file.save(file_path)
pres = Prescription(
patient_id=session[‘user_id’],
filename=filename,
status=’pending’
)
db.session.add(pres)
db.session.commit()
flash(‘Prescription uploaded successfully!’, ‘success’)
else:
flash(‘Invalid file type (only pdf, jpg, png allowed)’, ‘danger’)
return redirect(url_for(‘dashboard’))
# —————— PHARMACIST ROUTES ——————
@app.route(‘/review_prescription/<int:pres_id>’, methods=[‘GET’, ‘POST’])
def review_prescription(pres_id):
if ‘user_id’ not in session or session.get(‘role’) != ‘pharmacist’:
flash(‘Access denied’, ‘danger’)
return redirect(url_for(‘dashboard’))
pres = Prescription.query.get_or_404(pres_id)
if request.method == ‘POST’:
action = request.form[‘action’]
tele_note = request.form.get(‘tele_note’, ”)
if action == ‘approve’:
pres.status = ‘reviewed’
pres.tele_note = tele_note
pres.pharmacist_id = session[‘user_id’]
flash(‘Prescription reviewed & approved. Tele-advice added.’, ‘success’)
elif action == ‘dispense’:
pres.status = ‘dispensed’
pres.dispensed_date = datetime.utcnow()
pres.tele_note = tele_note or pres.tele_note
flash(‘Prescription dispensed!’, ‘success’)
elif action == ‘reject’:
pres.status = ‘rejected’
pres.tele_note = tele_note
flash(‘Prescription rejected.’, ‘warning’)
db.session.commit()
return redirect(url_for(‘dashboard’))
return render_template(‘review_prescription.html’, prescription=pres)
@app.route(‘/view_prescription/<filename>’)
def view_prescription(filename):
if ‘user_id’ not in session:
flash(‘Please login’, ‘danger’)
return redirect(url_for(‘login’))
return send_file(os.path.join(app.config[‘UPLOAD_FOLDER’], filename), as_attachment=False)
@app.route(‘/add_medicine’, methods=[‘POST’])
def add_medicine():
if ‘user_id’ not in session or session.get(‘role’) != ‘pharmacist’:
return redirect(url_for(‘dashboard’))
name = request.form[‘name’]
dosage = request.form[‘dosage’]
price = float(request.form[‘price’])
stock = int(request.form[‘stock’])
expiry = datetime.strptime(request.form[‘expiry’], ‘%Y-%m-%d’).date()
med = Medicine(name=name, dosage=dosage, price=price, stock=stock, expiry_date=expiry)
db.session.add(med)
db.session.commit()
flash(‘Medicine added to inventory’, ‘success’)
return redirect(url_for(‘dashboard’))
# —————— TEMPLATES (save in templates/ folder) ——————
# You need to create these HTML files
# register.html, login.html (basic forms)
# patient_dashboard.html → list prescriptions + upload form + medicine list
# pharmacist_dashboard.html → pending/reviewed lists + low stock + add medicine form
# review_prescription.html → show image/pdf link + textarea for tele_note + approve/dispense/reject buttons
if __name__ == ‘__main__’:
print(“Digital Pharmacy & Telepharmacy Prototype”)
print(“Access: http://127.0.0.1:5000”)
app.run(debug=True)
“`
### Quick Start Instructions
1. Save the code as `digital_pharmacy.py`
2. Install dependencies:
“`bash
pip install flask flask-sqlalchemy werkzeug
“`
3. Create `templates/` folder and add basic HTML files (use Bootstrap for nicer UI)
4. Run:
“`bash
python digital_pharmacy.py
“`
5. Register → login as patient/pharmacist → test flows
### Important Legal & Safety Notes (especially for Nigeria)
– Never use in real patient care without proper validation, NAFDAC/PCN approval, data protection (NDPR), and secure infrastructure.
– Prescription verification must involve licensed pharmacists.
– Add audit logs, HTTPS, input sanitization, and role-based access control before production use.
Feel free to fork, improve, and share — happy coding! 🚀