Production-grade multi-tenant B2B healthcare SaaS (FastAPI, React, Postgres RLS). Built as an architectural showcase for the LEO Framework.
0
stars
107
commits
Python
primary language
Aug 25, 2026
updated
A modular monolith for clinic operations: booking, CRM, ERP, omnichannel messaging, tasks, finance, patient PWA, and a vendor-side platform contour. Shipped as one repository, not a slide deck.
The application code in this repo was not hand-typed. It was written by a coding agent operating under LEO — a written constitution of roles, laws, and gates. I directed the process. The agent executed it. This is the debut run of that process, published with its roughness intact.
What this is · How it was built · Architecture · Quick start · Known limitations · License
MedCore is a shared-schema multi-tenant clinic OS (dental vertical first). Tenancy is Organization → Clinic, with a separate founder/vendor contour at /platform/*. It is not a single-clinic box install.
| Contour | Who | Where |
|---|---|---|
| Public / marketing | Incoming clinics | /, /signup |
| Clinic operations | Staff (RBAC) | /admin/* |
| Patient | Patients | /c/:clinicSlug/*, /app/* |
| Platform | Vendor / founder | /platform/* |
What is actually in the tree (verified against code, not a pitch):
src/application/booking_slot_advisory_lock.py). An if in application code is treated as a UX hint, not as protection.owner / manager / admin / doctor. CI diffs every router's require_permissions(...) against the matrix (tests/application/test_sec_rbac_router_permissions_inventory.py, snapshot documentation/rbac_router_permissions.txt)./health, /metrics (Prometheus), Grafana dashboards under deploy/.test_* functions (this tree), 800+ collected cases on the last full pytest --collect-only recorded for the LEO case study, plus Vitest and Playwright.Full product map: documentation/PRODUCT_OVERVIEW.md.
Demo seed, English chrome. Floor ops, staff chat, calendar, tasks — with a modal open where it helps.
| Chair schedule | New booking |
|---|---|
![]() | ![]() |
| Team chat | New group |
|---|---|
![]() | ![]() |

| Calendar | New event |
|---|---|
![]() | ![]() |
| Tasks | Patient card |
|---|---|
![]() | ![]() |
Regenerate: README_SCREENSHOTS=1 BASE_URL=http://127.0.0.1:3010 npx playwright test e2e/readme-screenshots.spec.ts from frontend/ (Compose UI on :3010). After a frontend layout change, rebuild that image or point BASE_URL at Vite. A short demo video can wait.
I came into software between intern and junior, in a hiring freeze that would not give production experience to anyone who did not already have it. Toy CRUD and LeetCode do not close that gap. This clinic OS was the challenge I set myself: ship something with real invariants — no double-booking, no tenant leak, no UUID in a manager-facing table — or admit I could not.
I did not have a senior team. I also did not have, as internalized skill, the knowledge that actually decides whether a system like this holds: concurrency, tenancy, RBAC drift, outbox semantics, lock lifetimes, adversarial review. That knowledge exists; it just was not in my hands yet. So I wrote it down as rules an agent would have to follow, and I sat in the seat of the person who routes, refuses, and publishes.
That rule system became LEO — Lead Engineering Orchestrator. MedCore is the first end-to-end product it shipped, when LEO was thinner than the 41-law / 22-role constitution it is now. Later client work made the laws sharper. This repository is the debut, not the polished flagship — published with the scars visible, because a cleaned-up demo would prove the wrong thing.
The claim is not "no-code" in the Bubble/Airtable sense. This is a real FastAPI / React / PostgreSQL codebase. The claim is about authorship and process:
@DEV in LEO) wrote it.If you want the process, not the clinic domain: github.com/alex-zaporozhan/leo. The longer origin note is documentation/ORIGIN.md.
src/api → src/application → src/domain → src/infrastructure
One deployable. Celery workers share the same codebase. That is a modular monolith, not microservices.
Platform (founder / vendor) /platform/*
└── Organization (SaaS tenant)
└── Clinic (operational tenant)
├── Staff — JWT realm admin, 49-code RBAC
└── Patient — JWT realm patient, PWA
Isolation today is application-layer (clinic_id / organization_id in services and repositories) plus cross-tenant negative tests. PostgreSQL RLS is used selectively (e.g. organization_entitlements), not as the sole control on every table (ADR-007). Shared-schema multi-tenant is the truth. Database-per-tenant is not what this repo is.
EDITION=box|basic in the code is a legacy SKU cut, not the product architecture. Default is the full platform.
| Layer | Choice |
|---|---|
| API | Python 3.11, FastAPI, Pydantic v2, Uvicorn |
| Domain / DB | SQLAlchemy 2 (async), asyncpg, Alembic, PostgreSQL 16 |
| Jobs / cache | Celery 5, Redis 7 |
| Frontend | React 18, TypeScript, Vite 6, Mantine 7, TanStack Query 5 |
| Quality | pytest, ruff / black / mypy, vitest, Playwright |
| Ship | Docker Compose; Jenkins → GHCR; GitHub Actions as PR gates; optional Docker Hub for a single VPS |
Integrations (payments, SMS, OAuth, captcha, OpenAI-compatible AI) are config-gated: no key, the module stays quiet. Demo seeds may use Russian payment/SMS providers; they are adapters, not the domain model.
.
├── src/ # FastAPI app — api / application / domain / infrastructure
├── frontend/ # Vite SPA — marketing, /admin, patient PWA, /platform
├── tests/ # pytest (API, services, security, outbox, tenancy)
├── alembic/ # schema migrations
├── deploy/ # Prometheus / Grafana
├── documentation/ # public docs (start here after this README)
├── docs/adr/ # architecture decision records
├── docker-compose.yml
├── LICENSE # PolyForm Shield 1.0.0
└── SECURITY.md
Internal engineering notes under docs/ are process history and passports. They are not the public front door. If a sentence in an older .md disagrees with the code, the code wins.
You need Docker, and — for the host-run path — Python 3.11 + Poetry and Node 18+.
cp .env.example .env # set SECRET_KEY and JWT_SECRET_KEY
docker compose up -d --build --wait
--wait needs Compose v2.20+ (waits until backend is healthy). Migrations are a one-shot; exit 0 is success.
/health, /docs (docs off in production)db and redis, not postgres.Seed demo users inside the backend container (no host Poetry required):
# Linux / macOS / Git Bash
bash scripts/seed_demo_compose.sh
# Windows PowerShell
powershell -ExecutionPolicy Bypass -File scripts/seed_demo_compose.ps1
Equivalent: docker compose exec -T backend python -m src.scripts.seed_rbac_baseline then … seed_multi_tenant_showcase. Host Poetry against localhost:5442 still works if you prefer. Optional heavier single-clinic seed: seed_presentation_showcase (do not mix with the multi-tenant showcase unless you intend to).
Demo logins (local seeds only — never reuse in a real deployment):
| Seed | Password | Sign-in | |
|---|---|---|---|
| Multi-tenant showcase | owner.kazan@showcase-mt.demo | ShowcaseMT2026! | /admin/login |
| Presentation clinic | admin@dentapro.demo | Presentation2026! | /admin/login |
Full tables: documentation/DEMO_CREDENTIALS.md.
docker compose up -d db redis
# create dental_booking and dental_booking_test once
docker exec dental_booking_postgres psql -U postgres -c "CREATE DATABASE dental_booking_test;"
cp .env.example .env
poetry install
poetry run alembic upgrade head
poetry run uvicorn src.main:app --reload --port 8000
cd frontend && npm install && npm run dev
Vite is typically 5175 and proxies /api to host uvicorn 8000, or Compose 8010 if 8000 is down. Details: documentation/GETTING_STARTED.md.
poetry run pytest tests/ -q
cd frontend && npm test
DATABASE_URL_TEST must point at dental_booking_test. Stop compose backend / celery if they share the same Postgres — the instance max_connections is a shared budget.
This is the intended reading order for someone who arrived from LEO or HN:
src/application/booking_slot_advisory_lock.py + tests/core/test_booking_slot_policy_lock.py — the double-booking class.src/application/rbac_matrix.py + tests/application/test_sec_rbac_router_permissions_inventory.py — permission drift is a CI failure.src/application/multitenancy.py + tests/api/test_tenant_isolation_admin_paths.py — tenant scope.src/api/v1/router.py — 95 include_router mounts, one monolith.docs/adr/ — decisions with numbers, including ADR-017 (why this is not MIT).Honesty is the point of publishing a debut.
en default). Landing /, signup, and owner-invite use marketing. Task drawers and many other admin bodies still have Russian literals. Patient PWA (/app, /c/:slug) shell, booking wizard, chat, profile, store, history, loyalty / forms / clinic feed / booking-success use the patient ns (English default). Template field labels and form names still come from the API. /sandbox and legal placeholders can still be Russian. Demo seed data (staff names, huddles, omni, catalog overlay, ±14-day EN window: denser calendar, Kanban, meetings, doctor-role login) is English after seed_multi_tenant_showcase / backfill_showcase_saas_extras. Re-running the showcase seed on a marked DB applies extras idempotently (RU and EN title prefixes), refreshes the English video layer, then the two-week window. Demo rows are not Alembic migrations. Locale is localStorage.ui.locale per origin (:3010 ≠ :5175). Collapsed 80px sidebar has no switcher — expand it first.EDITION=box|basic still exists as a compatibility SKU gate. Ignore it unless you are studying that cut.Gaps that are not hidden behind a badge belong in GitHub issues after the repo is public, not in a rewritten history.
| Doc | What |
|---|---|
documentation/PRODUCT_OVERVIEW.md | Product contours and modules |
documentation/ORIGIN.md | Why this repo exists, in full |
documentation/GETTING_STARTED.md | Clone → demo |
documentation/DEMO_CREDENTIALS.md | Seed logins (DEMO only) |
documentation/LICENSING.md | License in plain language |
CONTRIBUTING.md | PR / test / config contract |
SECURITY.md | Private vulnerability reports |
CI_CD.md | Jenkins / GHCR / Docker Hub |
Source-available, not OSI Open Source. SPDX: LicenseRef-PolyForm-Shield-1.0.0.
That is the same family of license as LEO, for the same reason: use it, do not resell the product. Full comparison: documentation/LICENSING.md. Commercial exceptions: LinkedIn.
Alexandr Zaporojan — AI-native systems work; five years Emergency ICU before this; the person who wrote LEO because the market would not hand over a team.
Open to Founding Engineer roles, AI-native full-stack seats, and AI-SDLC architecture contracts.
The interesting question is not whether an LLM can write a FastAPI handler. It is whether a written process can keep the tenth month of that work from quietly contradicting the first.
107 commits
Python
69.1%
TypeScript
30.0%
Production-grade multi-tenant B2B healthcare SaaS (FastAPI, React, Postgres RLS). Built as an architectural showcase for the LEO Framework.
0
stars
107
commits
Python
primary language
Aug 25, 2026
updated
A modular monolith for clinic operations: booking, CRM, ERP, omnichannel messaging, tasks, finance, patient PWA, and a vendor-side platform contour. Shipped as one repository, not a slide deck.
The application code in this repo was not hand-typed. It was written by a coding agent operating under LEO — a written constitution of roles, laws, and gates. I directed the process. The agent executed it. This is the debut run of that process, published with its roughness intact.
What this is · How it was built · Architecture · Quick start · Known limitations · License
MedCore is a shared-schema multi-tenant clinic OS (dental vertical first). Tenancy is Organization → Clinic, with a separate founder/vendor contour at /platform/*. It is not a single-clinic box install.
| Contour | Who | Where |
|---|---|---|
| Public / marketing | Incoming clinics | /, /signup |
| Clinic operations | Staff (RBAC) | /admin/* |
| Patient | Patients | /c/:clinicSlug/*, /app/* |
| Platform | Vendor / founder | /platform/* |
What is actually in the tree (verified against code, not a pitch):
src/application/booking_slot_advisory_lock.py). An if in application code is treated as a UX hint, not as protection.owner / manager / admin / doctor. CI diffs every router's require_permissions(...) against the matrix (tests/application/test_sec_rbac_router_permissions_inventory.py, snapshot documentation/rbac_router_permissions.txt)./health, /metrics (Prometheus), Grafana dashboards under deploy/.test_* functions (this tree), 800+ collected cases on the last full pytest --collect-only recorded for the LEO case study, plus Vitest and Playwright.Full product map: documentation/PRODUCT_OVERVIEW.md.
Demo seed, English chrome. Floor ops, staff chat, calendar, tasks — with a modal open where it helps.
| Chair schedule | New booking |
|---|---|
![]() | ![]() |
| Team chat | New group |
|---|---|
![]() | ![]() |

| Calendar | New event |
|---|---|
![]() | ![]() |
| Tasks | Patient card |
|---|---|
![]() | ![]() |
Regenerate: README_SCREENSHOTS=1 BASE_URL=http://127.0.0.1:3010 npx playwright test e2e/readme-screenshots.spec.ts from frontend/ (Compose UI on :3010). After a frontend layout change, rebuild that image or point BASE_URL at Vite. A short demo video can wait.
I came into software between intern and junior, in a hiring freeze that would not give production experience to anyone who did not already have it. Toy CRUD and LeetCode do not close that gap. This clinic OS was the challenge I set myself: ship something with real invariants — no double-booking, no tenant leak, no UUID in a manager-facing table — or admit I could not.
I did not have a senior team. I also did not have, as internalized skill, the knowledge that actually decides whether a system like this holds: concurrency, tenancy, RBAC drift, outbox semantics, lock lifetimes, adversarial review. That knowledge exists; it just was not in my hands yet. So I wrote it down as rules an agent would have to follow, and I sat in the seat of the person who routes, refuses, and publishes.
That rule system became LEO — Lead Engineering Orchestrator. MedCore is the first end-to-end product it shipped, when LEO was thinner than the 41-law / 22-role constitution it is now. Later client work made the laws sharper. This repository is the debut, not the polished flagship — published with the scars visible, because a cleaned-up demo would prove the wrong thing.
The claim is not "no-code" in the Bubble/Airtable sense. This is a real FastAPI / React / PostgreSQL codebase. The claim is about authorship and process:
@DEV in LEO) wrote it.If you want the process, not the clinic domain: github.com/alex-zaporozhan/leo. The longer origin note is documentation/ORIGIN.md.
src/api → src/application → src/domain → src/infrastructure
One deployable. Celery workers share the same codebase. That is a modular monolith, not microservices.
Platform (founder / vendor) /platform/*
└── Organization (SaaS tenant)
└── Clinic (operational tenant)
├── Staff — JWT realm admin, 49-code RBAC
└── Patient — JWT realm patient, PWA
Isolation today is application-layer (clinic_id / organization_id in services and repositories) plus cross-tenant negative tests. PostgreSQL RLS is used selectively (e.g. organization_entitlements), not as the sole control on every table (ADR-007). Shared-schema multi-tenant is the truth. Database-per-tenant is not what this repo is.
EDITION=box|basic in the code is a legacy SKU cut, not the product architecture. Default is the full platform.
| Layer | Choice |
|---|---|
| API | Python 3.11, FastAPI, Pydantic v2, Uvicorn |
| Domain / DB | SQLAlchemy 2 (async), asyncpg, Alembic, PostgreSQL 16 |
| Jobs / cache | Celery 5, Redis 7 |
| Frontend | React 18, TypeScript, Vite 6, Mantine 7, TanStack Query 5 |
| Quality | pytest, ruff / black / mypy, vitest, Playwright |
| Ship | Docker Compose; Jenkins → GHCR; GitHub Actions as PR gates; optional Docker Hub for a single VPS |
Integrations (payments, SMS, OAuth, captcha, OpenAI-compatible AI) are config-gated: no key, the module stays quiet. Demo seeds may use Russian payment/SMS providers; they are adapters, not the domain model.
.
├── src/ # FastAPI app — api / application / domain / infrastructure
├── frontend/ # Vite SPA — marketing, /admin, patient PWA, /platform
├── tests/ # pytest (API, services, security, outbox, tenancy)
├── alembic/ # schema migrations
├── deploy/ # Prometheus / Grafana
├── documentation/ # public docs (start here after this README)
├── docs/adr/ # architecture decision records
├── docker-compose.yml
├── LICENSE # PolyForm Shield 1.0.0
└── SECURITY.md
Internal engineering notes under docs/ are process history and passports. They are not the public front door. If a sentence in an older .md disagrees with the code, the code wins.
You need Docker, and — for the host-run path — Python 3.11 + Poetry and Node 18+.
cp .env.example .env # set SECRET_KEY and JWT_SECRET_KEY
docker compose up -d --build --wait
--wait needs Compose v2.20+ (waits until backend is healthy). Migrations are a one-shot; exit 0 is success.
/health, /docs (docs off in production)db and redis, not postgres.Seed demo users inside the backend container (no host Poetry required):
# Linux / macOS / Git Bash
bash scripts/seed_demo_compose.sh
# Windows PowerShell
powershell -ExecutionPolicy Bypass -File scripts/seed_demo_compose.ps1
Equivalent: docker compose exec -T backend python -m src.scripts.seed_rbac_baseline then … seed_multi_tenant_showcase. Host Poetry against localhost:5442 still works if you prefer. Optional heavier single-clinic seed: seed_presentation_showcase (do not mix with the multi-tenant showcase unless you intend to).
Demo logins (local seeds only — never reuse in a real deployment):
| Seed | Password | Sign-in | |
|---|---|---|---|
| Multi-tenant showcase | owner.kazan@showcase-mt.demo | ShowcaseMT2026! | /admin/login |
| Presentation clinic | admin@dentapro.demo | Presentation2026! | /admin/login |
Full tables: documentation/DEMO_CREDENTIALS.md.
docker compose up -d db redis
# create dental_booking and dental_booking_test once
docker exec dental_booking_postgres psql -U postgres -c "CREATE DATABASE dental_booking_test;"
cp .env.example .env
poetry install
poetry run alembic upgrade head
poetry run uvicorn src.main:app --reload --port 8000
cd frontend && npm install && npm run dev
Vite is typically 5175 and proxies /api to host uvicorn 8000, or Compose 8010 if 8000 is down. Details: documentation/GETTING_STARTED.md.
poetry run pytest tests/ -q
cd frontend && npm test
DATABASE_URL_TEST must point at dental_booking_test. Stop compose backend / celery if they share the same Postgres — the instance max_connections is a shared budget.
This is the intended reading order for someone who arrived from LEO or HN:
src/application/booking_slot_advisory_lock.py + tests/core/test_booking_slot_policy_lock.py — the double-booking class.src/application/rbac_matrix.py + tests/application/test_sec_rbac_router_permissions_inventory.py — permission drift is a CI failure.src/application/multitenancy.py + tests/api/test_tenant_isolation_admin_paths.py — tenant scope.src/api/v1/router.py — 95 include_router mounts, one monolith.docs/adr/ — decisions with numbers, including ADR-017 (why this is not MIT).Honesty is the point of publishing a debut.
en default). Landing /, signup, and owner-invite use marketing. Task drawers and many other admin bodies still have Russian literals. Patient PWA (/app, /c/:slug) shell, booking wizard, chat, profile, store, history, loyalty / forms / clinic feed / booking-success use the patient ns (English default). Template field labels and form names still come from the API. /sandbox and legal placeholders can still be Russian. Demo seed data (staff names, huddles, omni, catalog overlay, ±14-day EN window: denser calendar, Kanban, meetings, doctor-role login) is English after seed_multi_tenant_showcase / backfill_showcase_saas_extras. Re-running the showcase seed on a marked DB applies extras idempotently (RU and EN title prefixes), refreshes the English video layer, then the two-week window. Demo rows are not Alembic migrations. Locale is localStorage.ui.locale per origin (:3010 ≠ :5175). Collapsed 80px sidebar has no switcher — expand it first.EDITION=box|basic still exists as a compatibility SKU gate. Ignore it unless you are studying that cut.Gaps that are not hidden behind a badge belong in GitHub issues after the repo is public, not in a rewritten history.
| Doc | What |
|---|---|
documentation/PRODUCT_OVERVIEW.md | Product contours and modules |
documentation/ORIGIN.md | Why this repo exists, in full |
documentation/GETTING_STARTED.md | Clone → demo |
documentation/DEMO_CREDENTIALS.md | Seed logins (DEMO only) |
documentation/LICENSING.md | License in plain language |
CONTRIBUTING.md | PR / test / config contract |
SECURITY.md | Private vulnerability reports |
CI_CD.md | Jenkins / GHCR / Docker Hub |
Source-available, not OSI Open Source. SPDX: LicenseRef-PolyForm-Shield-1.0.0.
That is the same family of license as LEO, for the same reason: use it, do not resell the product. Full comparison: documentation/LICENSING.md. Commercial exceptions: LinkedIn.
Alexandr Zaporojan — AI-native systems work; five years Emergency ICU before this; the person who wrote LEO because the market would not hand over a team.
Open to Founding Engineer roles, AI-native full-stack seats, and AI-SDLC architecture contracts.
The interesting question is not whether an LLM can write a FastAPI handler. It is whether a written process can keep the tenth month of that work from quietly contradicting the first.
107 commits
Python
69.1%
TypeScript
30.0%