MedPharm ERP — Medical & Pharmaceutical Enterprise Resource Planning System. PyQt6 desktop app, Flask patient portal, Windows app, macOS app, iOS app, Android app, and SQLAlchemy database with 75+ medications (default) and ALL pharmaceutical, medicine, and herbal/vitamins database updates. Copyright (C) 2026 Enlightec Ltd. (www.enlightec.com)
Medical & Pharmaceutical Enterprise Resource Planning System
A multi-platform ERP system for medical practices and pharmacies, featuring native clients for Android, iOS, macOS, and Windows, a PyQt6 desktop application for clinical staff, a Flask web portal for patients, and a cloud REST API backend. Built on a unified SQLAlchemy database with 75+ real-world medications, 30+ symptoms, 25+ conditions, drug interaction checking, insurance claims, prescription management, billing, and analytics.
Developed by Robert Andrew Stillwell at Enlightec Ltd. — Creativity in Productivity.
- ngrok integration end-to-end.
./install.sh --ngrok-loginopens the ngrok dashboard in your browser, prompts for the auth token with hidden input, and persists it in both~/.config/ngrok/ngrok.ymland~/.config/medpharm/ngrok.env(mode 0600) so every launcher script picks it up automatically. Newstart_ngrok.shboots the tunnel, polls the local inspector for the public URL, and writesdata/ngrok_public_url.txt,data/ngrok_client_config.json, anddata/ngrok_qr.png.docker-compose.hub.ymlships an opt-inngrokprofile (./start_docker_hub.sh ngrok) that brings the API + ngrok container up together. - One-tap client onboarding. Android and iOS Login screens have a Scan QR button that auto-fills the Server URL from the QR code generated by the server (Google Code Scanner on Android, AVFoundation on iOS — no extra runtime permissions on Android). macOS and Windows have a Paste-from-clipboard button. A shared
QRConfigParseraccepts a bare URL, a bare ngrok host, or the full client-config JSON. - PDF rendering fixes. Volume III (Developer Manual) was emitting a navy backdrop on every body page (text was unreadable) and a blank page before each part divider. Both are fixed in 1.7.6. All three manuals are now produced as PDF 1.5 with Fast Web View linearization, which is what GitHub's pdf.js viewer streams — earlier builds frequently appeared as a blank box in the in-browser preview.
- Qt About dialog now shows the correct version (was hard-coded to v1.1).
./update.sh— new standalone updater that polls GitHub for new commits, optionally backs up the database, and fast-forwards the local working tree.- Volume III Developer Manual added in this release line as the engineer-facing companion to Volume I (Technical Reference) and Volume II (Service Manual).
The 1.7.6 product line carries a series of operator- and engineer-facing improvements published as manual revisions A through E without re-tagging the underlying release. Service Manual is at 1.7.6-E, Developer Manual at 1.7.6-D, Volume I tracks the headline v1.7.6 cover stamp.
- Click-medication → MedlinePlus in every client. iOS / macOS / Android rows are tappable, the Qt desktop has a "🔎 MedlinePlus" button in the medication detail panel, the patient web portal hyperlinks the brand name. URL helper centralised so the data source is one swap.
- iOS / macOS builds from a non-Mac host.
.github/workflows/{ios,macos}-build.ymldrivexcodebuildon amacos-14runner; the Cirrus CI fallback at.cirrus.ymlprovides free macOS-on-M1 minutes when GitHub Actions is billing-blocked. Helpersios/build_via_actions.sh,ios/build_via_cirrus.sh, andios/swift_lint.sh(offline Foundation-only compile-check on the Linux Swift toolchain). - Auto-update with database backup.
./update.shgains--auto,--install-schedule[=PERIOD],--uninstall-schedule,--show-schedulefor unattended runs via systemd--usertimer (preferred) or crontab (fallback). Concurrency lock at.update.lock.d/, dirty-tree refusal in--auto, DB-backup-before-pull guarantee. - Native systemd services via
./install-services.sh. Migrates the tree to/opt/medpharm, creates themedpharmsystem user (UID 1000,/usr/sbin/nologin), installs sandboxedmedpharm-api.service+medpharm-web.serviceunits. Aligned with the Docker image and k8s deployment so all three deployment paths converge on the same security model (runAsNonRoot,capabilities.drop=[ALL],seccompProfile: RuntimeDefault). - Medications catalogue bulk loaders.
database/load_fda_data.pyingests the FDA NDC Directory + NIH DSLD (~360k Rx + OTC + dietary-supplement entries).database/load_dailymed_spl.pyenriches existing rows with prescribing information (indications, contraindications, side effects, dosage, warnings) extracted from HL7 V3 SPL XML by LOINC section code. Idempotent, streaming, resumable. Schema gainsdata_source+ 7 other columns and three indexes;search_medicationsand/medications/searchpaginate. Full operator guide:docs/MEDICATIONS_DATABASE.md. - Code-health pass.
datetime.utcnow()(deprecated in Python 3.12, removed in 3.14) replaced across 35 call sites with a centralised_naive_utc_now()helper. Schema migration is now dialect-aware (functionallower(col)indexes on SQLite + Postgres, plain indexes on MySQL/other). SAWarning noise from functional-index reflection silenced. AndroidMedicationsResponsecarries optional pagination metadata so the new "Showing N of M — refine your search" footer can fire when the catalogue overflows the page. - In-app user guides. Every long-lived client now ships its own bundled User Guide. The Qt desktop opens
qt_app/resources/help.htmlfrom Help → User Guide (F1); the Flask Patient Portal rendersweb/templates/help.htmlatGET /helpand links it from the right-hand user dropdown alongside Profile and Logout. Both documents are themed to match their host UI (dark navy + teal), use a sticky table of contents, and cover every screen, action, role/permission boundary, and troubleshooting recipe a user is likely to need. - HIPAA documentation expansion. Eight new policy + playbook documents under
docs/— Notice of Privacy Practices, Risk Analysis, Contingency Plan, Sanctions Policy, Workforce Training, Minimum Necessary, Patient Rights, Data Retention — paired with a substantially expandeddocs/HIPAA_COMPLIANCE.mdhub that maps every Security Rule standard to its implementation or its policy doc and ships a deployment checklist. New runtime helpers insecurity/:deidentify.py(Safe Harbor 18-identifier removal per § 164.514(b)(2)),rate_limit.py(sliding-window rate limiter),log_redaction.py(PHI redaction logging filter).encryption.pynow refuses to boot in production without thecryptographypackage andMEDPHARM_FIELD_KEY;sessions.pyadds an absolute-session-lifetime cap and an opt-in strict CSP (MEDPHARM_STRICT_CSP=1).
- What's new in 1.7.6
- Features
- Architecture
- Project Structure
- Requirements
- Installation
- Docker Hub Images
- Quick Start
- Remote Access via ngrok
- Usage Guide
- Documentation
- Default Credentials
- License
- Dashboard — Real-time KPIs, today's appointments, recent activity with 60-second auto-refresh
- Patient Management — Master-detail view with demographics, vitals, allergies, diagnoses, and 7-tab detail panel
- Prescriptions — Create prescriptions with automatic drug-drug interaction checking across 24 known interaction pairs
- Medication Database — Searchable catalog of 75+ FDA-referenced medications with NDC codes, scheduling, and pricing
- Appointments — Calendar-based scheduling with status management (scheduled, checked-in, in-progress, completed, cancelled)
- Medical Records — Patient-filtered clinical records with type-based filtering
- Billing — Invoice management, payment recording, insurance claim submission and processing, and revenue summary
- Symptoms & Conditions — Searchable medical reference database with 30+ symptoms and 25+ conditions, body system filtering, and emergency indicators
- Analytics — Four interactive matplotlib charts: revenue trends, patient demographics, top medications, and provider workload
- JWT Authentication — HMAC-SHA256 token-based auth with access/refresh tokens, patient and staff login flows
- Patient Self-Service — Dashboard, prescriptions, billing, records, appointments, medications, profile, notifications
- Staff Endpoints — Dashboard, patient management, prescriptions, appointments, analytics (revenue, demographics, top meds)
- Insurance Claims — Submit claims, view claim status, staff claim processing with approved/denied/paid workflow
- Medical Reference — Searchable symptoms and conditions database with body system and category filtering
- Medication Search — Full medication catalog search by name, drug class, or schedule
- CORS-enabled — Configurable origins for cross-platform client access
- Production-ready — Gunicorn support, environment variable configuration, health check endpoint
- Configurable client endpoint — Every mobile and desktop client exposes a Server URL field on the login screen (with a "Reset to default" control) so operators can point the app at any compatible MedPharm API host. Default is
https://medpharm-erp.enlightec.com:8080/api/v1. Overrides persist across launches:UserDefaults(iOS / macOS),SharedPreferences(Android),%APPDATA%\MedPharm\settings.json(Windows).
- MVVM Architecture — ViewModel + LiveData + Repository pattern with Retrofit/OkHttp networking
- Patient Portal — Dashboard, prescriptions with refill requests, billing with payments, appointments, medical records
- Insurance Claims — File claims against invoices using on-file insurance records
- Medication Browser — Searchable medication reference database
- Security — EncryptedSharedPreferences for token storage, OkHttp auth interceptor for automatic token injection
- Material Design — Material 3 components, dark theme, swipe-to-refresh, bottom navigation
- Native SwiftUI — Async/await networking, NavigationStack, searchable modifiers
- Patient Portal — Dashboard with KPI cards, prescriptions with refill, billing with payment sheet, appointments, records
- Insurance Claims — File claims with insurance picker and submit via REST API
- Keychain Storage — Secure token persistence via iOS Keychain Services
- Tab Navigation — Dashboard, Prescriptions, Billing, Appointments, and More (records, medications, symptoms, profile)
- NavigationSplitView — Native macOS sidebar navigation with master-detail layout
- Full Feature Parity — Dashboard, prescriptions, billing, appointments, records, medications, insurance claims, profile
- SwiftUI Table — Native macOS table components for prescriptions and medications
- Shared Codebase — Shares Models and APIClient with the iOS application
- MVVM with CommunityToolkit — Clean architecture with HttpClient, Newtonsoft.Json, DPAPI token encryption
- Sidebar Navigation — Dashboard, prescriptions, billing, appointments, insurance claims
- Payment & Claims — Pay invoices and file insurance claims with provider selection
- DPAPI Security — Windows Data Protection API for encrypted token storage
- Secure Registration — 4-factor patient identity verification (name, date of birth, SSN last 4, insurance ID)
- Prescription Viewer — Active, past, and detailed prescription views with refill request capability
- Bill Pay — View invoices and submit payments with credit card, debit card, or ACH
- Medical Records — Browse personal records filtered by type
- Appointments — View upcoming and past appointment history
- Medication Info — Current medications with dosage, frequency, and drug information
- Profile Management — Update contact info, insurance details, and allergy records
- In-Portal User Guide — A 17-section walkthrough of every screen, action, and notification, accessible from the user dropdown (top-right, next to Profile and Logout)
- 23 SQLAlchemy ORM models with 18 Python enums and full relationship mapping
- 75+ real medications seeded with NDC codes, drug classes, schedules, and pricing
- 24 drug-drug interactions with severity levels and clinical descriptions
- 30+ symptoms with body system classification and emergency indicators
- 25+ conditions with ICD-10 codes, categories, and prevalence data
- Insurance claims workflow — SUBMITTED → IN_REVIEW → APPROVED → PAID / DENIED with automatic payment creation
- PBKDF2-SHA256 password hashing via Werkzeug
- Role-based access control — Doctor, Psychiatrist, Pharmacist, Admin, and Patient roles
- Audit logging for compliance tracking
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Android │ │ iOS │ │ macOS │ │ Windows │
│ (Kotlin) │ │ (SwiftUI) │ │ (SwiftUI) │ │ (.NET/WPF) │
│ Material 3 │ │ Async/Await │ │ NavSplit │ │ MVVM/DPAPI │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │ │
└────────────────┴────────┬───────┴────────────────┘
▼
┌────────────────────────┐
│ Flask REST API │
│ /api/v1/* │
│ JWT + CORS │
│ (run_cloud.py) │
└───────────┬────────────┘
│
┌─────────────────────┐ │ ┌─────────────────────┐
│ PyQt6 Desktop │ │ │ Flask Web Portal │
│ (Clinical Staff) │ │ │ (Patients) │
└────────┬────────────┘ │ └──────────┬───────────┘
│ │ │
└──────────────┬───────┴────────────────────┘
▼
┌─────────────────────┐
│ DatabaseManager │
│ (SQLAlchemy 2.0) │
└──────────┬──────────┘
▼
┌─────────────────────┐
│ SQLite Database │
│ 23 Models │
│ 75+ Medications │
│ 30+ Symptoms │
│ 25+ Conditions │
│ 24 Interactions │
└─────────────────────┘
MedPharm/
├── __init__.py
├── run_qt.py # Desktop application launcher
├── run_web.py # Web portal launcher
├── run_cloud.py # Cloud API server launcher
├── install.sh # Automated installer (cross-platform)
├── uninstall.sh # Companion uninstaller — reverses install.sh
├── start_desktop.sh # Desktop quick-launch script
├── start_web.sh # Web portal quick-launch script
├── start_cloud.sh # Cloud API quick-launch script
├── generate_docs.sh # PDF documentation generator script
├── requirements.txt # Qt desktop + web portal dependencies
├── requirements-cloud.txt # Cloud API server dependencies
├── Dockerfile # Docker image (API only, Ubuntu 24.04)
├── docker-compose.yml # Docker Compose — API (build from source)
├── docker-compose.hub.yml # Docker Compose — API (pull from Docker Hub)
├── start_docker_hub.sh # Interactive launcher for Docker Hub images
├── .dockerignore # Docker build exclusions
├── LICENSE # GNU General Public License v3.0
│
├── .github/workflows/
│ └── docker-publish.yml # CI/CD: build & push to Docker Hub
│
├── server/ # ── Docker Server Package ───────
│ ├── Dockerfile # Full stack (Ubuntu 24.04 LTS)
│ ├── docker-compose.yml # Full stack — build from source
│ ├── docker-compose.hub.yml # Full stack — pull from Docker Hub
│ ├── entrypoint.sh # DB init & process bootstrap
│ ├── healthcheck.sh # Docker health check script
│ ├── supervisord.conf # Process manager config
│ ├── requirements-server.txt# Combined server dependencies
│ ├── .env.example # Environment variable template
│ ├── .dockerignore # Server build exclusions
│ └── nginx/
│ └── medpharm.conf # Nginx reverse proxy config
│
├── database/
│ ├── models.py # 23 SQLAlchemy ORM models & 18 enums
│ ├── db_manager.py # DatabaseManager — all CRUD & business logic
│ ├── seed_data.py # 55 medications, 24 interactions, sample data
│ └── seed_expanded.py # 30+ symptoms, 25+ conditions, 20+ additional meds
│
├── api/
│ ├── app.py # Cloud API Flask application factory
│ ├── auth.py # JWT HMAC-SHA256 authentication
│ └── routes.py # REST API endpoints (/api/v1/*)
│
├── qt_app/
│ ├── main_window.py # QMainWindow with sidebar navigation
│ ├── styles.py # Dark theme QSS stylesheet (teal accents)
│ ├── dialogs/
│ │ └── login_dialog.py # Staff authentication dialog
│ └── widgets/
│ ├── dashboard_widget.py
│ ├── patient_widget.py
│ ├── prescription_widget.py
│ ├── medication_widget.py
│ ├── appointment_widget.py
│ ├── records_widget.py
│ ├── billing_widget.py # Includes insurance claim submission
│ ├── symptoms_widget.py # Symptoms & conditions reference browser
│ └── analytics_widget.py
│
├── web/
│ ├── app.py # Flask application factory
│ ├── routes.py # All route handlers & API endpoints
│ ├── static/
│ │ ├── css/style.css # Dark theme portal stylesheet
│ │ └── js/app.js # Client-side interactions
│ └── templates/ # 14 Jinja2 templates
│
├── android/ # Android app (Kotlin, MVVM, Retrofit)
│ └── app/src/main/
│ ├── java/com/enlightec/medpharm/
│ │ ├── data/ # API service, repository, models
│ │ ├── ui/ # Fragments, ViewModels, Adapters
│ │ └── util/ # AuthInterceptor, Resource wrapper
│ └── res/ # Layouts, drawables, strings, themes
│
├── ios/ # iOS app (SwiftUI, async/await)
│ ├── MedPharm/MedPharm/
│ │ ├── Models/ # Codable data models
│ │ ├── Services/ # APIClient, AuthManager, KeychainHelper
│ │ └── Views/ # SwiftUI views by feature
│ ├── Package.swift # SPM facade for swift-on-Linux compile-check
│ ├── swift_lint.sh # Local Foundation-only build (no Mac needed)
│ ├── build_via_actions.sh # Trigger GitHub Actions build from any platform
│ └── build_via_cirrus.sh # Trigger Cirrus CI fallback (no GitHub billing)
│
├── macos/ # macOS app (SwiftUI, NavigationSplitView)
│ ├── MedPharm/MedPharm/
│ │ ├── Models/ # Shared with iOS
│ │ ├── Services/ # APIClient, AuthManager
│ │ └── Views/ # macOS-optimized views
│ ├── Package.swift # SPM facade for swift-on-Linux compile-check
│ ├── swift_lint.sh # Local Foundation-only build (no Mac needed)
│ ├── build_via_actions.sh # Trigger GitHub Actions build from any platform
│ └── build_via_cirrus.sh # Trigger Cirrus CI fallback (no GitHub billing)
│
├── windows/ # Windows app (.NET 8, WPF)
│ └── MedPharm/
│ ├── Models/ # API data models
│ ├── Services/ # ApiClient, TokenStore (DPAPI)
│ └── Views/ # XAML pages and dialogs
│
└── docs/
├── generate_pdf.py # Technical reference PDF generator (ReportLab)
├── MedPharm_ERP_Documentation.pdf # Technical reference (Volume I)
├── generate_service_manual.py # Service manual PDF generator (ReportLab)
├── MedPharm_ERP_Service_Manual.pdf # Service manual (Volume II)
├── generate_developer_manual.py # Developer manual PDF generator (ReportLab)
└── MedPharm_ERP_Developer_Manual.pdf # Developer manual (Volume III)
- Python 3.10+
- Operating System: Ubuntu/Debian, Fedora/RHEL, macOS, Arch Linux, or Windows (WSL)
- Display server required for the Qt desktop application (X11 or Wayland)
| Package | Version | Purpose |
|---|---|---|
| PyQt6 | >= 6.6.0 | Desktop GUI framework |
| Flask | >= 3.0.0 | Web portal + Cloud API |
| flask-cors | >= 4.0.0 | CORS support for REST API |
| SQLAlchemy | >= 2.0.0 | ORM and database management |
| Werkzeug | >= 3.0.0 | Password hashing & WSGI |
| matplotlib | >= 3.8.0 | Analytics charts |
| numpy | >= 1.26.0 | Numerical support for charts |
| gunicorn | >= 21.2.0 | Production WSGI server |
| ReportLab | >= 4.0 | PDF documentation generation |
| Platform | Language | Min Version | Key Dependencies |
|---|---|---|---|
| Android | Kotlin | API 26+ | Retrofit, OkHttp, Coroutines, EncryptedSharedPreferences |
| iOS | Swift | iOS 16+ | SwiftUI, async/await, Keychain Services |
| macOS | Swift | macOS 13+ | SwiftUI, NavigationSplitView |
| Windows | C# | .NET 8 | WPF, Newtonsoft.Json, CommunityToolkit.Mvvm, DPAPI |
The install script handles OS detection, Python version checking, virtual environment creation, dependency installation, database initialization, and integration tests.
git clone https://github.com/stillwell/MedPharm.git
cd MedPharm
chmod +x install.sh
./install.shThe installer will:
- Detect your operating system and install system-level dependencies
- Verify Python 3.10+ is available
- Create a virtual environment in
venv/ - Install all Python packages including flask-cors for cloud API support
- Initialize the SQLite database and seed it with sample data
- Seed expanded reference data (symptoms, conditions, additional medications)
- Run integration tests to verify everything works
- Preserve the tracked launcher scripts (
start_web.sh,start_desktop.sh,start_cloud.sh,generate_docs.sh) — fallback templates with full GPL headers are only written when a launcher is missing from the working tree
For server deployments, the API and patient web portal can run as systemd services that start on boot, restart on failure, and run under a dedicated medpharm system user. The native systemd setup uses the same /opt/medpharm install path and the same medpharm user as the published Docker images, so the security model is identical whether you ship via Docker, Kubernetes, or native processes.
# Migrate this checkout to /opt/medpharm, create the medpharm user, build a
# venv, and install + start medpharm-api.service + medpharm-web.service.
sudo ./install-services.sh install
# Subcommands
sudo ./install-services.sh status # systemctl status for both units
sudo ./install-services.sh logs --follow # tail journald for both
sudo ./install-services.sh restart # restart both
sudo ./install-services.sh start --api-only # selective start
sudo ./install-services.sh update-units # re-render after editing the templates
sudo ./install-services.sh uninstall # stop, disable, remove unit files
sudo ./install-services.sh uninstall --purge # also remove /opt/medpharm + the user
# (the DB is archived to /var/backups/medpharm/ first)
sudo ./install-services.sh --help # full flag referenceWhat gets installed. Two unit files at /etc/systemd/system/:
medpharm-api.service— gunicorn binding:8080, runningrun_cloud:app. OrderedAfter=network-online.target.medpharm-web.service— gunicorn binding:5000, runningrun_web:app. OrderedAfter=medpharm-api.serviceso first-paint requests see a populated schema.
Both units enable systemd sandboxing (NoNewPrivileges, ProtectSystem=strict, ProtectHome=read-only with explicit ReadWritePaths, MemoryDenyWriteExecute, RestrictNamespaces, SystemCallFilter=@system-service) so a compromise of the API or web portal can't pivot to the rest of the host. The unit templates live at systemd/medpharm-api.service.template and systemd/medpharm-web.service.template — read them to see exactly what is enabled, and edit + update-units if you need to relax anything for your environment.
Operator overrides go in /etc/medpharm/medpharm.env (system-wide) or ${INSTALL_DIR}/.env (per-install). Any of MEDPHARM_JWT_SECRET, MEDPHARM_FIELD_KEY, MEDPHARM_TLS_MODE, MEDPHARM_CORS_ORIGINS, etc. — see the unit templates for the full list of Environment= defaults.
Useful flags on install:
| Flag | Effect |
|---|---|
--user=NAME |
Service user (default medpharm) |
--no-create-user |
Don't create the user; assume it already exists |
--prefix=PATH |
Install dir (default /opt/medpharm) |
--api-port=N, --web-port=N, --workers=N |
Override defaults |
--api-only, --web-only |
Install only one service |
--from-current (default) |
rsync this checkout into the prefix |
--clone-fresh |
git clone from the upstream repo into the prefix instead |
--no-start |
Install + enable but don't start (staged rollouts) |
--force |
Overwrite an existing prefix (the existing tree is archived to ${PREFIX}.bak.<timestamp> first) |
--user-mode |
Install as systemctl --user units (no sudo, no boot start unless you also loginctl enable-linger) |
Choosing a deployment mode. All three published deployment paths converge on /opt/medpharm + the medpharm user (UID 1000, no login shell):
| Mode | Best for | Service supervision |
|---|---|---|
install-services.sh install |
Bare-metal / VM hosts that already have systemd | systemd unit, journald, systemctl restart |
docker compose -f docker-compose.hub.yml up -d |
Single-host containerised deploy | Docker daemon |
k8s/medpharm-k8s.sh deploy |
Multi-node / cluster deploys | kubelet, with the deployment's runAsNonRoot: true, dropped capabilities, seccompProfile: RuntimeDefault |
If you also want automatic source-tree updates on a recurring schedule, layer ./update.sh --install-schedule=daily on top — that timer runs as the operator who installed it (not the medpharm service user) so it can git pull and trigger a systemctl restart medpharm-api medpharm-web as needed.
The shipped seed data covers ~80 demo medications. To populate a realistic catalogue from public US government sources, two loaders compose:
# Phase 1: FDA NDC Directory + NIH DSLD — names, codes, manufacturer, dose form,
# route, DEA schedule for ~360k Rx + OTC + dietary-supplement products. ~5 min.
python3 database/load_fda_data.py all
# Phase 3: DailyMed SPL labels — indications, contraindications, side effects,
# warnings, dosage extracted from the HL7 V3 SPL XML the FDA publishes.
# Incremental enrichment via the NLM REST API:
python3 database/load_dailymed_spl.py fetch --top 500
# OR offline parse of an FDA bulk SPL extract:
python3 database/load_dailymed_spl.py parse --from-dir /path/to/extracted/splsBoth loaders are idempotent, resumable, streaming (never load all rows in memory), and non-destructive — they preserve the original hand-curated seed data (rows with data_source='seed') and only update rows they previously inserted.
The 1.7.6-E schema adds three indexes (data_source, lower(brand_name), lower(generic_name)), and search_medications / /medications/search are now paginated so the patient-facing UIs continue to perform well at 300k+ rows.
Full operator guide with subcommand tables, performance notes, and a "how much should I load" sizing matrix: docs/MEDICATIONS_DATABASE.md.
Once installed, the working tree can keep itself current with the upstream master branch on GitHub. Every update path backs up the SQLite database to data/backups/medpharm-YYYYMMDD-HHMMSS.db before touching the working tree, so a botched deploy can always be rolled back to the prior commit + the matching snapshot.
./update.sh # Interactive: check, prompt, backup, fast-forward
./update.sh --check-only # Just report whether new commits exist
./update.sh --auto # Fully unattended: silent if up-to-date,
# one stdout line if it pulled, stderr on failure
./update.sh --install-schedule=daily # Recurring auto-update (systemd timer or cron)
./update.sh --show-schedule # What will run, and when
./update.sh --uninstall-schedule # Remove the recurring job
./update.sh --help # Full flag referenceSchedule mechanics. --install-schedule[=PERIOD] prefers a systemd --user timer (sandboxed via ProtectSystem=strict / ProtectHome=read-only / ReadWritePaths={SCRIPT_DIR}, persistent across reboots, catches up on missed runs after wake) and falls back to a crontab entry when systemd-user isn't available (containers, headless boxes, BSDs). PERIOD accepts hourly, daily, weekly (default), monthly, or a literal OnCalendar=... (systemd) / cron=... (cron) expression. Both flavours stamp the install with a recognisable marker so --uninstall-schedule removes exactly the entry this script created and never touches anything else in your crontab.
Safety guarantees.
- A directory-based PID lock at
.update.lock.d/keeps a manual run and a scheduled run from colliding on the git index. Stale locks (PID gone) are stolen automatically. --autorefuses to update a dirty working tree (no unsupervised auto-stash); it logs the dirty files and exits non-zero so cron / journalctl surface the problem.- DB backup uses
sqlite3 .backup(online, consistent) when available; falls back tocpof the file plus any WAL/SHM sidecars. - The DB path is auto-detected in this priority order:
$MEDPHARM_DB_PATHenv var →medpharm_erp.db(project root, therun_cloud.pydefault) →data/medpharm_erp.db(Docker volume default) →data/medpharm.db(legacy).
After any update, restart any running MedPharm services (./start_cloud.sh, ./start_web.sh, ./start_desktop.sh, the Docker stack) to pick up the new code.
The repository ships with uninstall.sh, which reverses every artifact install.sh creates. Tracked source files (launcher scripts, .env.example, docs, Dockerfiles) are never touched.
./uninstall.sh # Interactive — remove venv, DB, log, __pycache__
./uninstall.sh --force # Non-interactive (for CI / scripting)
./uninstall.sh --keep-data # Preserve data/medpharm.db
./uninstall.sh --docker --docker-volumes # Tear down API-only container + persistent volume
./uninstall.sh --all --force # Scorched earth: containers, volumes, images, env, no prompts
./uninstall.sh --help # Full flag referenceDocker teardown is opt-in — nothing Docker-related runs unless you pass --docker, --docker-server, --docker-volumes, --docker-images, or --all. See docs/INSTALLATION.md for the full flag matrix.
Manifests live under k8s/ as a kustomize base with cloud-specific overlays. Use the wrapper script for the shortest path:
./k8s/medpharm-k8s.sh install --hostname=erp.example.com # auto-detects GKE/EKS/generic
./k8s/medpharm-k8s.sh status
./k8s/medpharm-k8s.sh deploy --tag=1.7.6 # rolling upgrade
./k8s/medpharm-k8s.sh backup pre-upgrade.db # SQLite hot backup
./k8s/medpharm-k8s.sh --help # full command referenceOr go through kubectl directly:
kubectl create namespace medpharm
kubectl -n medpharm create secret generic medpharm-secrets \
--from-literal=MEDPHARM_JWT_SECRET="$(openssl rand -base64 48)" \
--from-literal=MEDPHARM_SECRET_KEY="$(openssl rand -base64 48)"
kubectl apply -k k8s/overlays/gcp # or aws / genericFull walkthrough (GKE ManagedCertificate, EKS ALB Controller, ingress-nginx + cert-manager) is in docs/KUBERNETES.md.
To reinstall after uninstalling:
./install.sh --freshgit clone https://github.com/stillwell/MedPharm.git
cd MedPharm
# Create and activate virtual environment
python3 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
pip install -r requirements-cloud.txt
pip install reportlab
# Initialize the database
python3 -c "
import sys, os
sys.path.insert(0, os.getcwd())
from database.db_manager import DatabaseManager
from database.seed_data import seed_database
from database.seed_expanded import seed_expanded_data
db = DatabaseManager('medpharm_erp.db')
db.init_db()
seed_database(db)
seed_expanded_data(db)
print('Database initialized with sample data.')
"Both Docker images run on Ubuntu Server 24.04 LTS and are published to Docker Hub under the enlightec namespace. You can either pull pre-built images (fastest, recommended) or build from source using the included Dockerfiles.
No local build required. Pull the images and start a container in under a minute.
One-line install (uses the included installer):
./install.sh --docker # API only (port 8080)
./install.sh --docker-server # Full stack: Nginx + API + Patient Portal (port 80)
./install.sh --docker --docker-server # Both at once (API on 8080 + Full stack on 80)Both --docker and --docker-server can be combined in a single invocation. When combined, the full-stack container's direct API host port is auto-remapped from 8080 to 8081 to avoid clashing with the API-only container that already owns 8080. The full-stack Nginx entry point remains on port 80, and the direct Patient Portal port remains on 5000.
Interactive quick-launch helper:
./start_docker_hub.sh # Menu — choose API only, full stack, pull, or stop
./start_docker_hub.sh api # Start API-only container
./start_docker_hub.sh server # Start full stack
./start_docker_hub.sh pull # Pull both images without starting
./start_docker_hub.sh stop # Stop all MedPharm containersManual docker compose (API only) — uses docker-compose.hub.yml:
docker compose -f docker-compose.hub.yml pull
docker compose -f docker-compose.hub.yml up -d
curl -k https://localhost:8080/api/v1/health # TLS on (self-signed by default)Manual docker compose (Full stack) — uses server/docker-compose.hub.yml:
cd server
cp .env.example .env # Edit secrets before production
docker compose -f docker-compose.hub.yml pull
docker compose -f docker-compose.hub.yml up -d
curl -k https://localhost/api/v1/health # TLS via Nginx (self-signed by default)Pull without docker compose:
docker pull enlightec/medpharm-server:latest # Full stack
docker pull enlightec/medpharm-api:latest # API only
# Pin to a specific release
docker pull enlightec/medpharm-api:1.7.6Run directly with docker run:
# API only (TLS on port 8080 — self-signed cert auto-generated)
docker run -d --name medpharm-api \
-p 8080:8080 \
-v medpharm-data:/data \
-v medpharm-tls:/etc/ssl/medpharm \
-e MEDPHARM_JWT_SECRET=your-secret-here \
-e MEDPHARM_SECRET_KEY=your-other-secret \
enlightec/medpharm-api:latest
# Full stack (Nginx TLS on 443, HTTP on 80 redirects to 443)
docker run -d --name medpharm-server \
-p 80:80 -p 443:443 -p 8080:8080 -p 5000:5000 \
-v medpharm-data:/data \
-v medpharm-logs:/var/log/medpharm \
-v medpharm-tls:/etc/ssl/medpharm \
-e MEDPHARM_JWT_SECRET=your-secret-here \
-e MEDPHARM_SECRET_KEY=your-other-secret \
enlightec/medpharm-server:latestTLS by default — both images terminate TLS out of the box. On first boot they auto-generate a self-signed cert into the
medpharm-tlsvolume (or/etc/ssl/medpharmon a host bind-mount). For production, drop a CA-issuedfullchain.pem+privkey.peminto that volume and setMEDPHARM_TLS_MODE=require. To fall back to plaintext (never in production), setMEDPHARM_TLS_MODE=disable.
If you want to build the images locally (e.g., to make source modifications):
Full Server Stack (Nginx + Cloud API + Web Portal):
cd server
cp .env.example .env # Edit secrets before production use
docker compose up -d # Build from server/Dockerfile and start
docker compose logs -f # View logsAPI Only (lightweight, no Nginx):
docker compose up -d # Build from root Dockerfile, starts API on port 8080Services available (either option):
| Endpoint | Description |
|---|---|
https://localhost/api/v1/health |
REST API via Nginx TLS (full stack only) |
https://localhost/portal/ |
Web Portal via Nginx TLS (full stack only) |
http://localhost/ |
301 → https://localhost/ (full stack only) |
https://localhost:8080/ |
API direct access (gunicorn TLS) |
http://localhost:5000/ |
Web Portal direct access — plaintext, localhost-only (full stack only) |
You can run the API-only image and the full-stack image on the same host in a single installer invocation:
./install.sh --docker --docker-serverBoth sets of flags after the same ./install.sh command are parsed together. The installer auto-remaps the full-stack container's direct API host port from 8080 → 8081 so it does not collide with the API-only container that owns 8080.
Resulting endpoints when both deployments are active:
| Endpoint | Container | Description |
|---|---|---|
https://localhost:8080/api/v1 |
enlightec/medpharm-api |
Standalone REST API (gunicorn TLS) |
https://localhost/ |
enlightec/medpharm-server |
Full-stack Nginx TLS entry point |
https://localhost/api/v1/health |
enlightec/medpharm-server |
Full-stack API via Nginx TLS |
https://localhost/portal/ |
enlightec/medpharm-server |
Patient Portal via Nginx TLS |
http://localhost:8081/ |
enlightec/medpharm-server |
Full-stack API direct — plaintext (remapped from 8080) |
http://localhost:5000/ |
enlightec/medpharm-server |
Patient Portal direct — plaintext |
Stop both deployments:
docker compose -f docker-compose.hub.yml down
docker compose -f server/docker-compose.hub.yml downDocker images are automatically built and pushed to Docker Hub on every tagged release. The pipeline:
- Validates source code — Syntax checks all Python modules, tests database initialization, verifies API health check and web portal login page
- Builds & pushes two images to Docker Hub:
enlightec/medpharm-server:<version>— Full stack (Nginx + API + Web Portal)enlightec/medpharm-api:<version>— API only
To trigger a release:
git tag v1.7.6
git push origin v1.7.6 # Triggers the pipelineRequired GitHub Secrets (set in Settings > Secrets and variables > Actions):
| Secret | Description |
|---|---|
DOCKERHUB_USERNAME |
Docker Hub username (e.g., enlightec) |
DOCKERHUB_TOKEN |
Docker Hub access token (create one here) |
Android:
cd android
./gradlew assembleDebugiOS / macOS (on a Mac):
Open ios/MedPharm/MedPharm.xcodeproj or macos/MedPharm/MedPharm.xcodeproj in Xcode and build.
iOS / macOS from Linux or Windows:
Apple's toolchain is macOS-only, so xcodebuild cannot run natively on Linux. Three options cover engineers without a Mac:
# Path 1a — GitHub Actions (.github/workflows/{ios,macos}-build.yml)
gh auth login
./ios/build_via_actions.sh # → MedPharm-iOS-Simulator.app.zip + unsigned device archive
./macos/build_via_actions.sh # → MedPharm-macOS.app.zip (unsigned)
# Path 1b — Cirrus CI fallback (.cirrus.yml). Free macOS-on-M1 minutes for
# public OSS, independent of GitHub billing. Install the Cirrus CI GitHub App
# once at https://github.com/marketplace/cirrus-ci, then:
./ios/build_via_cirrus.sh # same artifacts as 1a
./macos/build_via_cirrus.sh
# Path 2 — Offline compile-check via Swift on Linux (no network, no Mac)
./ios/swift_lint.sh # `swift build` of the Foundation-only subset
./macos/swift_lint.sh # (Models/, QRConfigParser) — instant feedbackFull details, signing notes, and limitations: ios/README.md § Build from Linux, macos/README.md § Build from Linux.
Windows:
cd windows/MedPharm
dotnet build
dotnet runMedPharm ERP publishes two official container images to Docker Hub under the enlightec namespace. Both images are built from the Dockerfiles in this repository and are tagged on every versioned release (v*.*.*).
| Image | Docker Hub | Contents | Base | Exposed Ports |
|---|---|---|---|---|
enlightec/medpharm-server:latest |
hub.docker.com/r/enlightec/medpharm-server | Nginx + Cloud REST API + Patient Web Portal (Supervisor-managed) | Ubuntu 24.04 LTS | 80, 8080, 5000 |
enlightec/medpharm-api:latest |
hub.docker.com/r/enlightec/medpharm-api | Cloud REST API only (Gunicorn) | Ubuntu 24.04 LTS | 8080 |
All images are available at https://hub.docker.com/u/enlightec and can be browsed at https://hub.docker.com/repositories/enlightec.
| Tag | Description |
|---|---|
latest |
Most recent release |
1.7.6, 1.7.5, 1.7.4, 1.7.3, 1.7.2, 1.6.0, 1.1.1, 1.1.0, 1.0.0 |
Pinned semantic version tags (published from v*.*.* git tags) |
docker pull enlightec/medpharm-server:latest
docker pull enlightec/medpharm-api:latest
# Pin to a specific release
docker pull enlightec/medpharm-api:1.7.6The fastest way to run MedPharm without any local Python setup:
git clone https://github.com/stillwell/MedPharm.git
cd MedPharm
# Option 1 — interactive menu
./start_docker_hub.sh
# Option 2 — direct flags
./start_docker_hub.sh api # API only on port 8080
./start_docker_hub.sh server # Full stack on port 80
# Option 3 — installer
./install.sh --docker # API only
./install.sh --docker-server # Full stack
./install.sh --docker --docker-server # Both (API on 8080 + Full stack on 80)
./install.sh --docker --tag=1.7.6 # Pin to a specific releaseThe repository ships two compose files that reference the Docker Hub images (no build step):
| File | Purpose |
|---|---|
docker-compose.hub.yml |
API-only deployment (enlightec/medpharm-api) |
server/docker-compose.hub.yml |
Full stack deployment (enlightec/medpharm-server) |
# API only
docker compose -f docker-compose.hub.yml up -d
# Full stack
cd server && docker compose -f docker-compose.hub.yml up -dBoth images use named Docker volumes so your data survives container restarts and re-pulls:
| Volume | Mount Point | Purpose |
|---|---|---|
medpharm-data |
/data |
SQLite database (medpharm_erp.db) |
medpharm-logs |
/var/log/medpharm |
Nginx + Supervisor logs (full stack only) |
Docker images are automatically rebuilt and published to Docker Hub by the GitHub Actions workflow on every tagged release. See the CI/CD Pipeline section for details.
Skip the Python build entirely and run the official images directly:
./start_docker_hub.sh server # Full stack at https://localhost (HTTP on :80 redirects to :443)
./start_docker_hub.sh api # API only at https://localhost:8080See Docker Hub Images for more options.
./start_cloud.shOr manually:
source venv/bin/activate
python3 run_cloud.pyThe API server starts at https://localhost:8080/api/v1 (self-signed cert auto-generated into ./data/tls/ on first run). All Android, iOS, macOS, and Windows clients connect to this endpoint. Set MEDPHARM_TLS_MODE=disable to force plaintext http://localhost:8080 for local dev without certs.
./start_web.shOr manually:
source venv/bin/activate
python3 run_web.pyOpen your browser to http://localhost:5000 and log in with one of the patient accounts listed in Default Credentials. Behind the full server stack this is served as https://localhost/portal/ via the Nginx TLS reverse proxy.
./start_desktop.shOr manually:
source venv/bin/activate
python3 run_qt.pyA login dialog will appear. Use one of the staff credentials listed in Default Credentials.
Pass the port as an argument:
./start_web.sh 8080The clients (Android, iOS, macOS, Windows) all expect the API to be reachable at a public URL. When the server lives behind a home firewall, double-NAT, carrier-grade NAT, or any environment where you cannot get a routable IP, the bundled ngrok integration punches a TLS tunnel from ngrok's edge network to the local API container so the clients can connect from anywhere.
The integration touches three layers:
- Server-side install / launcher —
install.sh --ngrokprovisions the ngrok agent (apt repo on Debian/Ubuntu, Homebrew on macOS, yum repo on RHEL/Fedora, static binary fallback).start_ngrok.shboots the tunnel againstlocalhost:8080and writes the public URL intodata/. - Docker Hub variant —
docker-compose.hub.ymlships an opt-inngrokprofile that runs the officialngrok/ngrok:latestcontainer alongside the API../start_docker_hub.sh ngrokbrings both up. - Client-side onboarding — Android and iOS Login screens have a Scan QR button that auto-fills the Server URL from the QR code generated by the server. macOS and Windows have a Paste from clipboard button that does the same from text.
Sign up at https://dashboard.ngrok.com (the free tier is sufficient for development; paid tiers add reserved domains). The token is captured once and persisted forever — every launcher script sources it automatically after that. Three equivalent ways to provide it:
A. Browser-based capture (recommended). The installer opens your default browser at the ngrok dashboard, prompts for the token with input hidden (no echo, no scrollback leak), and saves it.
./install.sh --ngrok-login # capture only
./install.sh --docker --ngrok --ngrok-login # full firewalled-server setupThe flow is:
- The installer attempts to open https://dashboard.ngrok.com/get-started/your-authtoken via
xdg-open(Linux),open(macOS),sensible-browser, theBROWSERenv var, orpowershell.exe Start-Process(WSL). If none of those work, it prints the URL for you to visit manually. - You log in (or sign up) and copy the token shown in the gray code box.
- You return to the terminal, press
<Enter>, and paste the token at the prompt. Input is hidden viaread -s. - The installer calls
ngrok config add-authtoken(writes~/.config/ngrok/ngrok.yml) and writes~/.config/medpharm/ngrok.envwithchmod 0600so future invocations ofstart_ngrok.shandstart_docker_hub.sh ngrokpick the token up automatically.
B. Inline. Skip the browser entirely if you already have the token on the clipboard:
./install.sh --ngrok --ngrok-authtoken=2abc...XYZThis also writes both ~/.config/ngrok/ngrok.yml and ~/.config/medpharm/ngrok.env.
C. Environment variable. Useful in CI:
NGROK_AUTHTOKEN=2abc...XYZ ./start_ngrok.sh
NGROK_AUTHTOKEN=2abc...XYZ ./start_docker_hub.sh ngrokThe order of precedence at run time is:
- Explicit
--authtoken=/--ngrok-authtoken=flag NGROK_AUTHTOKENalready exported in the caller's environment~/.config/medpharm/ngrok.env(auto-sourced)~/.config/ngrok/ngrok.yml(used by the ngrok agent itself)
To rotate the token (e.g. after regenerating it on the dashboard), just re-run ./install.sh --ngrok-login — the new value overwrites both files.
./start_ngrok.sh # tunnel localhost:8080, defaults to US edge
./start_ngrok.sh --port=80 # tunnel a different port (e.g. full-stack Nginx)
./start_ngrok.sh --region=eu # us | eu | ap | au | sa | jp | in
./start_ngrok.sh --domain=med.example.ngrok-free.app
# pin a reserved hostname (paid tiers)
./start_ngrok.sh --stop # tear down a running tunnelOn success the script prints the public URL and writes three artifacts under data/:
| File | Purpose |
|---|---|
data/ngrok_public_url.txt |
Plain text — https://<random>.ngrok-free.app |
data/ngrok_client_config.json |
{"api_base_url": "<url>/api/v1", ...} — drop-in for clients |
data/ngrok_qr.png |
QR code of the API base URL — scan from Android/iOS Login screens |
The ngrok inspector dashboard (live request log + replay) is always available locally at http://127.0.0.1:4040.
QR generation depends on
qrencode(apt install qrencode/brew install qrencode) or theqrcodePython package (already pulled into the venv by the source install). If neither is present the URL and JSON files are still written; the QR step is skipped.
NGROK_AUTHTOKEN=2abc...XYZ ./start_docker_hub.sh ngrokThis brings up enlightec/medpharm-api plus the ngrok compose service, polls the in-container ngrok inspector (http://127.0.0.1:4040/api/tunnels) for the public URL, then writes the same data/ngrok_public_url.txt / data/ngrok_client_config.json / data/ngrok_qr.png artifacts. The interactive menu (./start_docker_hub.sh with no arguments) also has a 5) Start ngrok tunnel option.
To stop both containers:
docker compose -f docker-compose.hub.yml --profile ngrok down
# or simply: ./start_docker_hub.sh stop| Client | How to apply the URL |
|---|---|
| Android | Login screen → tap 📷 Scan QR → point the camera at data/ngrok_qr.png displayed on the server's screen. The Server URL field auto-fills and the value is persisted. |
| iOS | Login screen → tap Scan QR → align the QR code in the camera viewfinder. Same auto-fill behaviour. |
| macOS | Copy the contents of data/ngrok_client_config.json (or just the URL line). Login window → Paste. |
| Windows | Same as macOS — Login window → Paste from clipboard. |
| Qt desktop (clinical staff) | Connects to a local SQLite DB and does not consume the REST API directly, so no client-side change is required. |
The QRConfigParser in each client accepts any of three payloads:
- The bare API base URL —
https://abc123.ngrok-free.app/api/v1 - The full client-config JSON —
{"api_base_url":"https://abc123.ngrok-free.app/api/v1", ...} - A bare ngrok host —
https://abc123.ngrok-free.app(the/api/v1suffix is appended automatically)
- Free ngrok URLs rotate every time the agent restarts. For a stable URL across reboots, either reserve a paid ngrok domain (
--domain=...) or move to a real DNS A-record + a reverse proxy. - Free-tier tunnels also expire after a few hours of idle time without an auth token;
--ngrok-authtoken=removes that limit. - The ngrok inspector at
http://127.0.0.1:4040exposes full request/response bodies for any traffic crossing the tunnel — bind it only to loopback (the compose file does this automatically) and never expose port 4040 to the public Internet. - ngrok's edge network is not part of your HIPAA Business Associate Agreement (BAA) unless you separately negotiate one with ngrok, Inc. For PHI-bearing production traffic, prefer a managed reverse proxy / dedicated VPN over ngrok.
The REST API provides JWT-authenticated endpoints for all client platforms:
- Authentication:
POST /api/v1/auth/login/patient,POST /api/v1/auth/login/staff,POST /api/v1/auth/register - Patient Endpoints:
/api/v1/patient/dashboard,/prescriptions,/billing,/records,/appointments,/profile,/notifications - Insurance Claims:
POST /api/v1/patient/insurance/claimsto submit,GETto list, staff can process via/staff/insurance/claims/<id>/process - Reference Data:
/api/v1/reference/symptoms,/reference/conditions,/medications/search - Staff Endpoints:
/api/v1/staff/dashboard,/patients,/appointments,/prescriptions,/analytics/*
All endpoints require a Bearer <token> Authorization header except /health, /auth/*.
-
Login — Authenticate with your staff credentials. Access is role-based (Doctor, Psychiatrist, Pharmacist, Admin).
-
Dashboard — After login, the dashboard shows key performance indicators (active patients, pending prescriptions, today's appointments, monthly revenue), today's schedule, and recent activity. Data refreshes automatically every 60 seconds.
-
Patient Management — The left panel lists all patients with a search bar. Select a patient to view their full profile in the right panel, which is organized into 7 tabs: Demographics, Vitals, Allergies, Diagnoses, Prescriptions, Billing, and Records. Click "Add Patient" to register a new patient.
-
Prescriptions — View all prescriptions or create new ones. The prescription dialog lets you select a patient, add multiple medication lines, and set dosage/frequency/duration/quantity for each. When you add medications, the system automatically checks for drug-drug interactions and displays warnings with severity levels. Saving a prescription auto-generates a priced invoice.
-
Medication Database — Browse and search the full catalog of 75+ medications. Filter by drug class or schedule. Select any medication to view detailed information including NDC code, manufacturer, dosage forms, pricing, and contraindications.
-
Appointments — A calendar widget on the left lets you pick a date. Appointments for that date appear on the right. Create new appointments or update their status through the workflow: Scheduled → Checked In → In Progress → Completed.
-
Medical Records — Select a patient and filter records by type (lab results, imaging, clinical notes, etc.).
-
Billing — View all invoices with status indicators (Pending, Partial, Paid, Overdue). Record payments against invoices. Submit and process insurance claims with approval/denial workflow. Summary cards show total billed, collected, and outstanding amounts.
-
Symptoms & Conditions — Browse the medical reference database. Search symptoms by name or body system with emergency indicators. Search conditions by name, ICD-10 code, or category. Select any entry to view detailed information including associated conditions, typical medications, and prevalence data.
-
Analytics — Four chart panels powered by matplotlib: monthly revenue trends, patient age/gender demographics, top prescribed medications, and provider workload distribution.
-
Register — New patients verify their identity using 4 factors: first name, last name, date of birth, and last 4 digits of SSN. These must match an existing patient record in the system. Once verified, create a username and password.
-
Login — Log in with your portal credentials.
-
Dashboard — View a summary of active prescriptions, upcoming appointments, and recent invoices at a glance.
-
Prescriptions — Browse active, past, and all prescriptions. View full details including medications, dosages, refill counts, and costs. Request refills for eligible medications directly from the detail page.
-
Billing — View all invoices and their payment status. Click "Pay" on any invoice with an outstanding balance to submit a payment via credit card, debit card, or bank account (ACH).
-
Records — Access your medical records filtered by type.
-
Appointments — View upcoming and past appointments with date, time, provider, and status.
-
Medications — See your current medications with full details: dosage, frequency, drug class, manufacturer, and special instructions.
-
Profile — Update your phone number, email, and address. View insurance information and manage allergy records.
-
User Guide — Click your name in the top-right corner and select User Guide from the dropdown to open an in-portal walkthrough covering every page, the notification bell, refill requests, online payment, secure messaging, privacy/HIPAA controls, and a troubleshooting checklist. The same guide is also reachable directly at
/help.
MedPharm ERP ships with three complementary PDF manuals, each generated by a self-contained ReportLab script under docs/. Volume I describes what the software is; Volume II describes what it does under an operator's care; Volume III describes how it was built and how to extend it. They are designed to be read together.
| Volume | File | Generator | Audience |
|---|---|---|---|
| I — Technical Reference | docs/MedPharm_ERP_Documentation.pdf |
docs/generate_pdf.py |
Architects, integrators, external auditors |
| II — Service Manual | docs/MedPharm_ERP_Service_Manual.pdf |
docs/generate_service_manual.py |
System Operators, Security Officers, on-call responders |
| III — Developer Manual | docs/MedPharm_ERP_Developer_Manual.pdf |
docs/generate_developer_manual.py |
Software engineers, maintainers, contributors |
All three PDFs are produced as PDF 1.5, linearized for Fast Web View. They render reliably in Adobe Acrobat Reader, Apple Preview, GitHub's in-browser PDF viewer (pdf.js), Chrome/Edge/Firefox, and on iOS / Android tablets. Each generator script invokes Ghostscript (gs) at the end of its build to perform the linearization pass — if Ghostscript is not on PATH, the unoptimised ReportLab output is left in place and a banner is omitted from the build log; the file is still valid, only not web-streamable.
Note: The 1.7.5 release of Volume III (Developer Manual) emitted a PDF whose cover-page navy backdrop bled into every body page, rendering long stretches of text as dark-blue text on a dark-blue field, and inserted a blank page before each part divider. Both defects were corrected in 1.7.6; if you have an older locally-built copy, regenerate it with
python3 docs/generate_developer_manual.pyto pick up the fix.
The pre-generated PDF is located at docs/MedPharm_ERP_Documentation.pdf. Regenerate it with:
./generate_docs.sh
# or manually:
source venv/bin/activate && python3 docs/generate_pdf.pyContents:
- System architecture overview with component diagrams
- Complete database schema — all 23 SQLAlchemy models and relationships
- Multi-platform client architecture (Android, iOS, macOS, Windows)
- Cloud REST API endpoint reference with authentication details
- Insurance claims workflow documentation
- Drug interaction checking algorithm walkthrough
- Flask web portal route reference
- Qt desktop application module documentation
- Security control matrix (JWT auth, HMAC-SHA256, DPAPI, Keychain, password hashing, audit logging)
- Deployment and configuration guide
- Links to external documentation (SQLAlchemy, Flask, PyQt6, Retrofit, SwiftUI, WPF)
An operations-focused companion to the technical reference, written for the people responsible for keeping a MedPharm deployment healthy in production. Where Volume I describes the system's anatomy (models, routes, code), the service manual describes its life under an operator's care: commissioning, daily routine, security operations, data stewardship, upgrades, fault diagnosis, and disaster recovery.
The manual is approximately 200 pages, organised into nine parts and thirty-three chapters plus six appendices. Each chapter opens with context, proceeds through procedure, and closes with a verification step so that work can be confirmed complete rather than merely attempted. Four signal words — Note, Caution, Danger, and Legend — are used consistently throughout with precise meanings defined in the front matter.
Structure:
| Part | Title | Chapters |
|---|---|---|
| I | Orientation | Platform overview, operator roles, service topology |
| II | Commissioning | Pre-install survey, sizing, commissioning procedures, post-commissioning validation |
| III | Daily Operations | Daily routine, startup/shutdown, user administration, monitoring, log management |
| IV | Security Operations | Security handbook, TLS lifecycle, secret rotation, HIPAA controls, incident response |
| V | Data Stewardship | Database administration, backup, restore / point-in-time recovery, retention |
| VI | Maintenance and Change | Maintenance calendar, patching, dependency hygiene, change management |
| VII | Fault Diagnosis | Troubleshooting methodology, server / client / database / network symptom directories |
| VIII | Business Continuity | Failure modes and RTO/RPO, DR runbooks, BCP |
| IX | Appendices | Command reference, env var reference, port/protocol reference, directory layout, glossary, revision history |
Regenerate the PDF:
source venv/bin/activate
python3 docs/generate_service_manual.pyThe generator is deliberately self-contained — it reads no project files at build time, so it compiles cleanly in sterile build environments (CI, container builds, documentation-only forks) and does not drift when subordinate modules are refactored. The output is written to docs/MedPharm_ERP_Service_Manual.pdf.
Note: The service manual is written in the second person, addressing the System Operator directly. It assumes familiarity with a Linux shell, the UNIX file-permission model, and the ability to read simple SQL. No formal DBA credentials are required.
The engineering counterpart to the previous two volumes. Where Volume I describes what the software is and Volume II describes how to operate it, Volume III describes how it was built and how to extend, debug, and evolve it. Audience: software engineers joining the project, returning maintainers, external auditors, and contributors preparing pull requests.
The manual runs to ~100 pages organised into eight parts and thirty-four chapters plus six appendices, with embedded flowcharts, syntax-highlighted source-code samples, hyperlinks, and support email addresses. Code samples are reproduced inline so the manual does not drift when subordinate modules are refactored.
Structure:
| Part | Title | Chapters |
|---|---|---|
| I | Foundations | Project vision, technology stack, architecture, repository layout |
| II | The Data Model | DB design philosophy, core / clinical / financial / HIPAA / messaging models |
| III | The Backend | DatabaseManager, field encryption, REST API, JWT, CSRF/lockout/MFA, Flask portal |
| IV | The Clients | Qt desktop, Android, iOS, macOS, Windows |
| V | Cross-Cutting Concerns | Audit, TLS, Docker, Kubernetes |
| VI | Development Workflow | Dev environment, testing, branching, releases, debugging |
| VII | Extending MedPharm | Recipes for new model / endpoint / module / client platform |
| VIII | Appendices | File tour, SQL schema, endpoint catalogue, error codes, glossary, support |
Regenerate the PDF:
source venv/bin/activate
python3 docs/generate_developer_manual.pyThe output is written to docs/MedPharm_ERP_Developer_Manual.pdf and opens cleanly in Adobe Acrobat Reader, Apple Preview, or any modern browser PDF viewer. Hyperlinks in the manual (mailto: and https://) are clickable.
Tip: A new engineer should expect to spend three to five working days reading Volume III end-to-end while opening the real source files alongside. After the first pass, the table of contents becomes the index. Part VII (Extending MedPharm) is the right starting point if you are about to land a substantial change.
MedPharm ERP is built and documented for deployment by U.S. HIPAA-covered entities and their business associates. The technical safeguards in code (45 CFR § 164.312) are paired with administrative, physical, and organisational documentation under docs/. The hub is docs/HIPAA_COMPLIANCE.md, which maps every Security Rule standard to its implementation or its policy doc and ships a deployment checklist.
| Document | What it covers | HIPAA reference |
|---|---|---|
HIPAA_COMPLIANCE.md |
Master control map + deployment checklist | 45 CFR § 164.302 et seq. |
NOTICE_OF_PRIVACY_PRACTICES.md |
Patient-facing notice template | § 164.520 |
RISK_ANALYSIS_TEMPLATE.md |
Annual risk analysis & risk-management plan | § 164.308(a)(1)(ii)(A)–(B) |
CONTINGENCY_PLAN.md |
Backup, disaster recovery, emergency-mode operation | § 164.308(a)(7) |
SANCTIONS_POLICY.md |
Workforce sanctions framework with violation categories A–D | § 164.308(a)(1)(ii)(C) |
WORKFORCE_TRAINING.md |
Onboarding + annual refresh + incident-driven training | § 164.308(a)(5) |
MINIMUM_NECESSARY.md |
RBAC + minimum-necessary policy | § 164.502(b) / § 164.514(d) |
PATIENT_RIGHTS.md |
Access, amendment, accounting, restriction, complaints — front-desk playbook | §§ 164.522–528 |
DATA_RETENTION_POLICY.md |
Retention windows, destruction methods, legal-hold workflow | § 164.316(b)(2), § 164.530(j) |
BREACH_NOTIFICATION.md |
Five-stage incident workflow + notification timelines | §§ 164.400–414 |
BAA_TEMPLATE.md |
Business Associate Agreement template | § 164.504(e) |
The implementation lives under security/. Each module names the regulatory section it implements in its docstring:
| Module | Standard | What it does |
|---|---|---|
security/audit.py |
§ 164.312(b), (c)(1) | Hash-chained audit_log; verify_audit_chain() recomputes the chain to detect tampering. |
security/phi.py |
§ 164.312(b) | @log_phi_access decorator stamps phi_access_log for every PHI read; powers § 164.528 accounting. |
security/encryption.py |
§ 164.312(a)(2)(iv), (e)(2)(ii) | Fernet FieldCipher for marked PHI columns; refuses to boot in production without cryptography and MEDPHARM_FIELD_KEY. |
security/passwords.py |
§ 164.312(d) | Complexity, history, rotation. PBKDF2-HMAC-SHA256 via Werkzeug. |
security/totp.py |
§ 164.312(d) | TOTP MFA. |
security/lockout.py |
§ 164.308(a)(5)(ii)(C) | Failed-login monitoring & temporary lockout. |
security/sessions.py |
§ 164.312(a)(2)(iii) | Idle timeout + absolute lifetime cap; HSTS, CSP, SameSite=Lax, security headers. |
security/csrf.py |
§ 164.312(c)(2) | CSRF tokens on every state-changing portal route. |
security/emergency.py |
§ 164.312(a)(2)(ii) | Break-glass emergency-access grant with mandatory justification + audit entry. |
security/deidentify.py |
§ 164.514(b)(2) | Safe Harbor 18-identifier removal + free-text redaction for analytics export. |
security/rate_limit.py |
§ 164.308(a)(1)(ii)(B), defence-in-depth | Sliding-window per-IP rate limiter for unauthenticated and hot endpoints. |
security/log_redaction.py |
Defence-in-depth around (b) | Logging filter that scrubs SSN, phone, email, IP, URL, dates, MRN, ages > 89, and JWTs from log records. |
Reminder. Code is one of three pillars. Without the policy and process documents above (and the workforce training and signed BAAs they reference), MedPharm is not HIPAA-compliant in itself — it is a HIPAA-aligned platform that a covered entity can use to be compliant.
| Role | Username | Password |
|---|---|---|
| Doctor | dr.carter | doctor123 |
| Doctor | dr.chen | doctor123 |
| Psychiatrist | dr.brooks | doctor123 |
| Pharmacist | pharm.davis | pharm123 |
| Admin | admin | admin123 |
| Patient | Username | Password |
|---|---|---|
| John Smith | jsmith_portal | patient123 |
| Maria Johnson | mjohnson_portal | patient123 |
| Emily Williams | ewilliams_portal | patient123 |
| Sarah Davis | sdavis_portal | patient123 |
| Linda Martinez | lmartinez_portal | patient123 |
Note: These are demo credentials for development and testing. Change all passwords before any production deployment.
The cloud API server and Docker deployment can be configured via environment variables:
| Variable | Default | Description |
|---|---|---|
MEDPHARM_DB_PATH |
medpharm_erp.db |
Path to SQLite database |
MEDPHARM_SECRET_KEY |
Auto-generated | Flask secret key |
MEDPHARM_JWT_SECRET |
Dev default | JWT signing secret (change in production) |
MEDPHARM_TOKEN_EXPIRY |
86400 |
Access token lifetime in seconds (24h) |
MEDPHARM_REFRESH_EXPIRY |
604800 |
Refresh token lifetime in seconds (7 days) |
MEDPHARM_CORS_ORIGINS |
* |
Allowed CORS origins |
MEDPHARM_HOST |
0.0.0.0 |
Server bind host |
MEDPHARM_PORT |
8080 |
Server bind port |
MEDPHARM_DEBUG |
false |
Enable debug mode |
MEDPHARM_HTTP_PORT |
80 |
Nginx listen port (Docker full stack) |
MEDPHARM_API_PORT |
8080 |
Gunicorn API port (Docker full stack) |
MEDPHARM_WEB_PORT |
5000 |
Gunicorn Web Portal port (Docker full stack) |
MEDPHARM_WORKERS |
4 |
Gunicorn worker processes |
MEDPHARM_THREADS |
2 |
Gunicorn threads per worker |
MEDPHARM_HTTPS_PORT |
443 |
Nginx TLS listen port (Docker full stack) |
MEDPHARM_TLS_MODE |
auto |
auto | require | disable — see TLS section below |
MEDPHARM_TLS_DIR |
/etc/ssl/medpharm |
Directory holding fullchain.pem + privkey.pem |
MEDPHARM_TLS_HOSTNAME |
localhost |
CN / SAN for self-signed cert generation |
MEDPHARM_TLS_DAYS |
825 |
Validity (days) for auto-generated self-signed certs |
Every install path — source Python, docker-compose, docker run, Kubernetes — terminates TLS by default. This satisfies the HIPAA transmission-security rule (§ 164.312(e)(1)) and removes the failure mode where a new deployment silently serves PHI over plain HTTP.
How certs are provisioned
| Install path | Cert location | Auto-generated? |
|---|---|---|
API-only Docker (enlightec/medpharm-api) |
medpharm-tls volume → /etc/ssl/medpharm |
Yes (gunicorn entrypoint) |
Server stack Docker (enlightec/medpharm-server) |
medpharm-tls volume → /etc/ssl/medpharm |
Yes (nginx + gunicorn) |
Source Python (./start_cloud.sh) |
./data/tls/ |
Yes (server/nginx/generate-cert.sh) |
| Kubernetes | Secret named medpharm-tls in the medpharm namespace |
Yes, via ./k8s/medpharm-k8s.sh install |
Modes (MEDPHARM_TLS_MODE):
auto(default): if no cert is found atMEDPHARM_TLS_DIR, a self-signed RSA-4096 cert is generated on first boot. If one is already present (you mounted a CA-issued cert), it is used as-is.require: the container fails to start unless a cert is mounted. Use this in production to guarantee no fallback path.disable: plaintext HTTP. Local dev only.
Rotating the self-signed cert
# Docker
docker compose -f docker-compose.hub.yml exec api rm /etc/ssl/medpharm/*.pem
docker compose -f docker-compose.hub.yml restart api
# Kubernetes
./k8s/medpharm-k8s.sh rotate-tlsUsing a CA-issued cert (production)
Drop fullchain.pem and privkey.pem into the medpharm-tls volume (or the K8s medpharm-tls Secret) and set MEDPHARM_TLS_MODE=require. Never ship production with a self-signed cert — browsers and mobile clients will refuse the connection, and training users to click past warnings is itself a compliance failure.
Clients & self-signed certs (dev only)
- Android (
android/app/src/main/res/xml/network_security_config.xml) — thelocalhost/10.0.2.2domain-config trusts user-installed CAs in addition to the system store. Export the server'sfullchain.pemand install it on the device as a user CA to dismiss the self-signed warning. - iOS / macOS — add the self-signed cert to the iOS Simulator trust store (drag into the running simulator) or the macOS keychain and mark it as trusted.
URLSessionotherwise refuses the connection. - Windows (.NET) —
HttpClientuses the Windows certificate store. Importfullchain.peminto Trusted Root Certification Authorities for the current user.
This project is licensed under the GNU General Public License v3.0.
Copyright (C) 2026 Enlightec Ltd. Author: Robert Andrew Stillwell Email: Andrew.Stillwell@enlightec.com
See the LICENSE file for the full license text.
MedPharm ERP — Built by Enlightec Ltd.
Creativity in Productivity
