> **Current deployment (2026-10-12):** Use the repository README for setup. The application now runs as a supervised Python ASGI process with PostgreSQL and a static React build. PostgreSQL stores shared expiring sessions, OTPs, links, cache and rate limits; ReportLab generates PDFs. Docker, Redis, Celery and WeasyPrint are removed. The architecture and deployment examples below describe the earlier proposal.
# Munal LMS — Technical Reference & Architecture Specification

> **Version**: 1.0  
> **Date**: 2026-09-20  
> **Author**: Solution Architecture / Lead Engineering  
> **Audience**: Development Team, DevOps, QA

> **Implementation status:** This is a historical architecture proposal. Its package lists, examples, and diagrams include planned components that may not exist in the application. Check [`Stack-Reference.md`](Stack-Reference.md) and `pyproject.toml` for the current audited stack; in particular, the Celery, TOTP, S3, Excel, and SMS sections are proposals unless explicitly confirmed by current code. Frontend routing is now implemented with TanStack Router and a persistent role-aware shell.

---

## 1. Project Overview

Munal LMS is a **Laboratory Management System** designed for a diagnostic lab. It covers the full lifecycle: patient registration → appointment booking → visit/order creation → sample collection → test execution → report generation → billing → notification delivery. The system is **single-tenant** (one organization) with support for multiple branches/units under the same lab.

### 1.1 Key Modules (from ERD)

| Module | Core Entities | Purpose |
|---|---|---|
| **Access Control** | `USER`, `ROLE_PERMISSION`, `PERMISSION`, `USER_PERMISSION_OVERRIDE` | RBAC with per-user overrides |
| **Lab Management** | `LAB` | Lab profile & branch/unit settings |
| **Test Catalog** | `TEST`, `TEST_CATEGORY`, `TEST_PARAMETER`, `TEST_PARAMETER_RANGE`, `TEST_PACKAGE`, `TEST_PACKAGE_ITEM` | Tests, parameters with gender/age-based ranges, packages |
| **Patient Management** | `PATIENT` | Demographics, contact, portal credentials |
| **Appointments** | `APPOINTMENT`, `APPOINTMENT_SLOT` | Slot-based scheduling with capacity management |
| **Visit Orders** | `VISIT_ORDER`, `ORDER_TEST`, `ORDER_PACKAGE` | Order lifecycle with referral tracking |
| **Sample Collection** | `SAMPLE` | Sample tracking with chain of custody |
| **Reports** | `REPORT` | Versioned reports with verification workflow |
| **Billing** | `BILL`, `DISCOUNT_RULE`, `PAYMENT` | 1:1 bill per order, discount rules, payment tracking |
| **Referrals** | `REFERRAL`, `COMMISSION_ENTRY` | Doctor/Hospital/Agent referral commissions |
| **Notifications** | `NOTIFICATION` | Multi-channel (Email, Portal, SMS) delivery |

### 1.2 Client-Facing Scope (Revised)

> [!IMPORTANT]
> **No dedicated client portal.** Clients (patients) receive a **link via SMS/Email** with:
> - **Report download** (PDF)
> - **Appointment viewer** (view-only)
> 
> Client identity = **mobile number** (used as username). Authentication via OTP or token-based link.

---

## 2. Architecture Decision Records (ADRs)

### 2.1 Frontend — React + Vite (NOT Next.js)

| Criteria | Next.js | React + Vite | Verdict |
|---|---|---|---|
| **SSR/SEO** | ✅ Built-in | ❌ Not built-in | **Not needed** — Admin panel is behind auth; client page is link-based (no SEO) |
| **Bundle Size** | Heavier (framework overhead) | Leaner | ✅ Vite wins |
| **Dev Speed / HMR** | Fast | **Blazing fast** (~10x cold start) | ✅ Vite wins |
| **API Routes** | Built-in | N/A (we have FastAPI) | **Redundant** — FastAPI handles all API |
| **Complexity** | Higher (SSR hydration, server components, middleware) | Simpler SPA mental model | ✅ Vite wins for team velocity |
| **Deployment** | Needs Node.js server or Vercel | Static files → any CDN/Nginx | ✅ Vite wins (cheaper, simpler) |
| **File-based routing** | Built-in | Via library (TanStack Router) | Neutral |

**Decision: React 18+ with Vite**

**Rationale:**
- This is an **internal admin-heavy SPA** — SSR adds complexity with zero benefit
- The client-facing link page is a single lightweight route within the same app (or a standalone micro-app)
- Vite's sub-second HMR dramatically improves developer experience
- Static SPA deployment is simpler and cheaper (Nginx, S3+CloudFront, any CDN)
- FastAPI already provides the complete API layer — Next.js API routes would be redundant

---

### 2.2 Backend — FastAPI (NOT NestJS)

| Criteria | FastAPI (Python) | NestJS (Node.js/TS) | Verdict |
|---|---|---|---|
| **Performance** | ~15,000 req/s (async) | ~12,000 req/s | Both excellent |
| **Type Safety** | Pydantic v2 (runtime validation) | TypeScript (compile-time) | Both strong |
| **Auto API Docs** | ✅ Swagger + ReDoc built-in | Swagger via decorator | ✅ FastAPI wins |
| **Async Support** | Native `async/await` | Native | Neutral |
| **ORM/Migration** | SQLAlchemy 2.0 + **Alembic** | TypeORM/Prisma + own migration | ✅ FastAPI aligns with Alembic |
| **Data Science / PDF** | Python ecosystem (ReportLab, pandas, numpy) | Limited | ✅ FastAPI wins for lab reports |
| **Learning Curve** | Low-Moderate | High (decorators, DI, modules) | ✅ FastAPI wins |
| **Ecosystem for Lab/Medical** | Rich (HL7, FHIR libraries) | Limited | ✅ FastAPI wins |
| **Boilerplate** | Minimal | Significant (modules, providers, controllers) | ✅ FastAPI wins |

**Decision: FastAPI**

**Rationale:**
- **Alembic alignment** — You've already decided on Alembic; it's native to the Python ecosystem
- **Report generation** — Python excels at PDF generation (ReportLab, WeasyPrint), data processing, and statistical computations needed for lab parameter analysis
- **Auto-generated OpenAPI docs** — Zero-effort Swagger UI for frontend devs
- **Rapid development** — Less boilerplate than NestJS, faster iteration
- **Async-first** — Excellent for I/O-bound operations (DB, Redis, external APIs)

---

### 2.3 Database — PostgreSQL 16+

**Why PostgreSQL:**
- ACID compliance for financial transactions (billing, payments)
- JSONB for flexible test parameter metadata
- Row-Level Security (RLS) for multi-tenant data isolation
- Full-text search for patient/test lookup
- Excellent indexing (B-tree, GIN, GiST) for performance
- Mature ecosystem with SQLAlchemy

---

### 2.4 Cache & Session — Redis 7+

**Use Cases:**
| Use Case | TTL | Key Pattern |
|---|---|---|
| Session/Auth tokens | 24h | `session:{user_id}` |
| Rate limiting (OTP) | 60s | `ratelimit:otp:{phone}` |
| OTP codes | 5min | `otp:{phone}` |
| Appointment slot locks | 10min | `slot_lock:{slot_id}` |
| Test catalog cache | 1h | `lab:{lab_id}:tests` |
| Report access tokens | 72h | `report_token:{token}` |
| Dashboard analytics | 15min | `dashboard:{lab_id}:{metric}` |

---

### 2.5 Monitoring & Logging

> [!NOTE]
> **Prometheus is for metrics, not logging.** You need both.

| Concern | Tool | Cost | Purpose |
|---|---|---|---|
| **Structured Logging** | **Structlog** | Free/OSS | JSON-structured application logs |
| **Log Aggregation** | **Loki** (Grafana) | Free/OSS | Centralized log storage & querying |
| **Metrics** | **Prometheus** | Free/OSS | Time-series metrics (request latency, error rates, DB pool) |
| **Dashboards** | **Grafana** | Free/OSS | Visualization for both logs (Loki) and metrics (Prometheus) |
| **APM/Tracing** | **OpenTelemetry** | Free/OSS | Distributed tracing across services |
| **Error Tracking** | **Sentry** | Free tier (5K events/mo) | Exception tracking with stack traces |

**Logging Stack: Structlog → Loki → Grafana** (all free, self-hosted)  
**Metrics Stack: Prometheus → Grafana** (all free, self-hosted)

---

## 3. Complete Tech Stack

### 3.1 Frontend Stack

```
React 18+  →  Vite 6  →  TypeScript 5.x
```

| Category | Package | Version | Why This |
|---|---|---|---|
| **Build Tool** | `vite` | ^6.x | Sub-second HMR, ESBuild-powered |
| **Language** | `typescript` | ^5.x | Type safety, better DX |
| **Routing** | `@tanstack/react-router` | ^1.x | Type-safe routing, file-based option, best-in-class |
| **State (Server)** | `@tanstack/react-query` | ^5.x | Server state, caching, auto-refetch, mutations |
| **State (Client)** | `zustand` | ^5.x | Lightweight client state (UI state, auth) |
| **Forms** | `react-hook-form` | ^7.x | Performant forms (no re-renders) |
| **Validation** | `zod` | ^3.x | Schema validation (shared with API types) |
| **HTTP Client** | `ky` | ^1.x | Lightweight, modern fetch wrapper (or `axios` ^1.x) |
| **UI Components** | `@shadcn/ui` | latest | Accessible, composable, copy-paste components |
| **Styling** | `tailwindcss` | ^4.x | Utility-first CSS (shadcn dependency) |
| **Data Tables** | `@tanstack/react-table` | ^8.x | Headless, powerful data tables |
| **Charts** | `recharts` | ^2.x | Composable charts for dashboards |
| **Date/Time** | `date-fns` | ^4.x | Lightweight date utilities |
| **PDF Viewer** | `@react-pdf-viewer/core` | ^3.x | In-browser PDF report preview |
| **Notifications/Toast** | `sonner` | ^2.x | Beautiful, accessible toast notifications |
| **Icons** | `lucide-react` | latest | Consistent, beautiful icon set |
| **Internationalization** | `i18next` + `react-i18next` | latest | Multi-language support (Nepali, English) |

### 3.2 Backend Stack

```
FastAPI  →  Python 3.12+  →  Uvicorn (ASGI)
```

| Category | Package | Version | Why This |
|---|---|---|---|
| **Framework** | `fastapi` | ^0.115+ | Async, auto-docs, Pydantic integration |
| **ASGI Server** | `uvicorn[standard]` | latest | Production ASGI server |
| **Process Manager** | `gunicorn` | latest | Multi-worker process management (with uvicorn workers) |
| **ORM** | `sqlalchemy[asyncio]` | ^2.0 | Async ORM, mature, type-annotated |
| **Migrations** | `alembic` | latest | Schema migrations with auto-generate |
| **Validation** | `pydantic` | ^2.x | Data validation, settings, serialization |
| **Auth - JWT** | `python-jose[cryptography]` | latest | JWT token creation/verification |
| **Auth - Password** | `passlib[bcrypt]` | latest | Password hashing |
| **Auth - OTP** | `pyotp` | latest | TOTP/HOTP for client OTP |
| **Redis Client** | `redis[hiredis]` | latest | Async Redis with hiredis for performance |
| **Background Tasks** | `celery` | ^5.x | Distributed task queue (reports, notifications) |
| **Task Broker** | Redis (reuse) | — | Celery broker (alternatively RabbitMQ) |
| **PDF Generation** | `weasyprint` | latest | HTML → PDF report generation |
| **PDF Templates** | `jinja2` | latest | HTML template engine for reports |
| **Excel Export** | `openpyxl` | latest | Excel exports for lab data |
| **SMS Gateway** | `httpx` | latest | Async HTTP for SMS API calls (Sparrow, Aakash) |
| **Email** | `fastapi-mail` | latest | Async email sending |
| **File Storage** | `boto3` | latest | S3-compatible storage (MinIO or AWS S3) |
| **Image Processing** | `pillow` | latest | Logo uploads, image handling |
| **CORS** | Built-in (`CORSMiddleware`) | — | Cross-origin request handling |
| **Rate Limiting** | `slowapi` | latest | API rate limiting |
| **Logging** | `structlog` | latest | Structured JSON logging |
| **Error Tracking** | `sentry-sdk[fastapi]` | latest | Exception monitoring |
| **Testing** | `pytest` + `pytest-asyncio` + `httpx` | latest | Async test suite |
| **Code Quality** | `ruff` | latest | Linting + formatting (replaces black, isort, flake8) |
| **Type Checking** | `mypy` | latest | Static type analysis |

### 3.3 Infrastructure Stack

| Category | Tool | Purpose |
|---|---|---|
| **Database** | PostgreSQL 16+ | Primary relational store |
| **Cache/Queue** | Redis 7+ | Caching, sessions, Celery broker, OTP store |
| **Object Storage** | MinIO (self-hosted) or AWS S3 | Report PDFs, lab logos, file uploads |
| **Reverse Proxy** | Nginx | SSL termination, static files, load balancing |
| **Containerization** | Docker + Docker Compose | Development & deployment |
| **CI/CD** | GitHub Actions | Automated testing, linting, deployment |
| **Monitoring** | Prometheus + Grafana | Metrics & dashboards |
| **Log Aggregation** | Loki + Grafana | Centralized logging |
| **Error Tracking** | Sentry (free tier) | Real-time error monitoring |

---

## 4. System Architecture Diagram

```mermaid
graph TB
    subgraph Client Layer
        A["Admin SPA<br/>(React + Vite)"]
        B["Client Link Page<br/>(Report/Appointment)"]
    end

    subgraph API Gateway
        C["Nginx<br/>(Reverse Proxy + SSL)"]
    end

    subgraph Application Layer
        D["FastAPI<br/>(Uvicorn + Gunicorn)"]
        E["Celery Workers<br/>(Background Tasks)"]
    end

    subgraph Data Layer
        F[("PostgreSQL 16<br/>(Primary DB)")]
        G[("Redis 7<br/>(Cache/Broker)")]
        H["MinIO / S3<br/>(File Storage)"]
    end

    subgraph Observability
        I["Prometheus"]
        J["Loki"]
        K["Grafana"]
        L["Sentry"]
    end

    subgraph External Services
        M["SMS Gateway<br/>(Sparrow/Aakash SMS)"]
        N["Email Service<br/>(SMTP/SES)"]
    end

    A -->|HTTPS| C
    B -->|HTTPS| C
    C -->|Proxy Pass| D
    D -->|SQLAlchemy Async| F
    D -->|Cache/Session| G
    D -->|File Upload| H
    D -->|Enqueue Tasks| G
    E -->|Process Tasks| G
    E -->|Generate Reports| H
    E -->|Send SMS| M
    E -->|Send Email| N
    E -->|DB Read/Write| F
    D -->|Metrics| I
    D -->|Logs| J
    I --> K
    J --> K
    D -->|Errors| L
    E -->|Errors| L
```

---

## 5. Project Folder Structure

### 5.1 Backend (`/backend`)

```
backend/
├── alembic/                          # Database migrations
│   ├── versions/                     # Migration scripts
│   ├── env.py
│   └── alembic.ini
├── app/
│   ├── __init__.py
│   ├── main.py                       # FastAPI app factory
│   ├── config.py                     # Settings (Pydantic BaseSettings)
│   ├── database.py                   # Async engine, session factory
│   │
│   ├── core/                         # Cross-cutting concerns
│   │   ├── security.py               # JWT, password hashing, OTP
│   │   ├── permissions.py            # RBAC permission checker
│   │   ├── dependencies.py           # FastAPI dependency injection
│   │   ├── exceptions.py             # Custom exception classes
│   │   ├── middleware.py             # CORS, logging, rate-limit middleware
│   │   └── pagination.py            # Cursor/offset pagination utilities
│   │
│   ├── models/                       # SQLAlchemy ORM models (1 file per entity)
│   │   ├── __init__.py               # Re-export all models
│   │   ├── base.py                   # Declarative base, mixins (TimestampMixin, SoftDeleteMixin)
│   │   ├── user.py                   # USER, ROLE_PERMISSION, PERMISSION, USER_PERMISSION_OVERRIDE
│   │   ├── lab.py                    # LAB
│   │   ├── patient.py                # PATIENT
│   │   ├── test.py                   # TEST, TEST_CATEGORY, TEST_PARAMETER, TEST_PARAMETER_RANGE
│   │   ├── package.py                # TEST_PACKAGE, TEST_PACKAGE_ITEM
│   │   ├── appointment.py            # APPOINTMENT, APPOINTMENT_SLOT
│   │   ├── order.py                  # VISIT_ORDER, ORDER_TEST, ORDER_PACKAGE
│   │   ├── sample.py                 # SAMPLE
│   │   ├── report.py                 # REPORT
│   │   ├── billing.py                # BILL, DISCOUNT_RULE, PAYMENT
│   │   ├── referral.py               # REFERRAL, COMMISSION_ENTRY
│   │   └── notification.py           # NOTIFICATION
│   │
│   ├── schemas/                      # Pydantic request/response schemas
│   │   ├── user.py
│   │   ├── patient.py
│   │   ├── test.py
│   │   ├── order.py
│   │   ├── report.py
│   │   ├── billing.py
│   │   └── ...                       # Mirror of models/
│   │
│   ├── api/                          # API route handlers
│   │   ├── __init__.py
│   │   ├── router.py                 # Master router aggregation
│   │   ├── v1/                       # API version 1
│   │   │   ├── auth.py               # Login, OTP, token refresh
│   │   │   ├── users.py              # User CRUD, role assignment
│   │   │   ├── labs.py               # Lab profile management
│   │   │   ├── patients.py           # Patient CRUD, search
│   │   │   ├── tests.py              # Test & parameter management
│   │   │   ├── packages.py           # Package CRUD
│   │   │   ├── appointments.py       # Appointment scheduling
│   │   │   ├── orders.py             # Visit order lifecycle
│   │   │   ├── samples.py            # Sample collection
│   │   │   ├── reports.py            # Report generation, verification
│   │   │   ├── billing.py            # Bill/payment management
│   │   │   ├── referrals.py          # Referral & commission tracking
│   │   │   ├── notifications.py      # Notification management
│   │   │   ├── dashboard.py          # Analytics endpoints
│   │   │   └── client.py             # Client-facing: report download, appointment view
│   │   └── deps.py                   # Shared endpoint dependencies
│   │
│   ├── services/                     # Business logic layer
│   │   ├── auth_service.py
│   │   ├── patient_service.py
│   │   ├── order_service.py
│   │   ├── report_service.py         # Report generation + PDF
│   │   ├── billing_service.py        # Bill calculation, discount logic
│   │   ├── notification_service.py   # SMS/Email dispatch
│   │   ├── commission_service.py     # Referral commission calculation
│   │   └── dashboard_service.py      # Analytics aggregation
│   │
│   ├── repositories/                 # Data access layer (DB queries)
│   │   ├── base.py                   # Generic CRUD repository
│   │   ├── patient_repo.py
│   │   ├── order_repo.py
│   │   ├── report_repo.py
│   │   └── ...
│   │
│   ├── tasks/                        # Celery background tasks
│   │   ├── __init__.py               # Celery app configuration
│   │   ├── report_tasks.py           # PDF generation
│   │   ├── notification_tasks.py     # SMS/Email sending
│   │   └── analytics_tasks.py        # Periodic dashboard computation
│   │
│   ├── templates/                    # Jinja2 templates
│   │   ├── reports/                  # Report PDF templates
│   │   │   ├── base_report.html
│   │   │   ├── hematology.html
│   │   │   └── biochemistry.html
│   │   └── emails/                   # Email templates
│   │       ├── otp.html
│   │       └── report_ready.html
│   │
│   └── utils/                        # Shared utilities
│       ├── code_generator.py         # Patient code, order number generation
│       ├── date_utils.py
│       ├── file_utils.py             # S3/MinIO upload helpers
│       └── phone_utils.py            # Phone number validation/formatting
│
├── tests/
│   ├── conftest.py                   # Fixtures, test DB setup
│   ├── test_auth.py
│   ├── test_patients.py
│   ├── test_orders.py
│   └── ...
│
├── pyproject.toml                    # Project deps, tool config (ruff, mypy, pytest)
├── Dockerfile
├── docker-compose.yml
└── .env.example
```

### 5.2 Frontend (`/frontend`)

```
frontend/
├── public/
│   └── favicon.svg
├── src/
│   ├── main.tsx                      # App entry point
│   ├── App.tsx                       # Root component, providers
│   ├── routeTree.gen.ts              # Auto-generated routes (TanStack)
│   │
│   ├── config/
│   │   ├── api.ts                    # API base URL, interceptors
│   │   ├── constants.ts              # App-wide constants
│   │   └── env.ts                    # Environment variable validation (zod)
│   │
│   ├── routes/                       # File-based routes (TanStack Router)
│   │   ├── __root.tsx                # Root layout
│   │   ├── _auth/                    # Auth-required layout group
│   │   │   ├── dashboard.tsx
│   │   │   ├── patients/
│   │   │   │   ├── index.tsx         # Patient list
│   │   │   │   └── $patientId.tsx    # Patient detail
│   │   │   ├── appointments/
│   │   │   ├── orders/
│   │   │   ├── reports/
│   │   │   ├── billing/
│   │   │   ├── lab-settings/
│   │   │   └── user-management/
│   │   ├── login.tsx
│   │   └── client/                   # Client-facing link pages
│   │       ├── report.$token.tsx     # Report download page
│   │       └── appointments.$token.tsx
│   │
│   ├── components/                   # Reusable UI components
│   │   ├── ui/                       # shadcn/ui primitives
│   │   ├── layout/                   # Sidebar, Header, PageWrapper
│   │   ├── data-table/               # Generic data table component
│   │   ├── forms/                    # Shared form components
│   │   └── charts/                   # Dashboard chart components
│   │
│   ├── hooks/                        # Custom React hooks
│   │   ├── useAuth.ts
│   │   ├── usePermission.ts
│   │   ├── useDebounce.ts
│   │   └── usePagination.ts
│   │
│   ├── services/                     # API service layer (TanStack Query)
│   │   ├── api-client.ts             # Configured HTTP client (ky/axios)
│   │   ├── auth.api.ts
│   │   ├── patients.api.ts
│   │   ├── orders.api.ts
│   │   ├── reports.api.ts
│   │   └── ...
│   │
│   ├── stores/                       # Zustand stores (client state only)
│   │   ├── auth.store.ts
│   │   ├── ui.store.ts               # Sidebar state, theme, etc.
│   │   └── lab.store.ts              # Active lab context
│   │
│   ├── lib/                          # Utility functions
│   │   ├── utils.ts                  # cn() helper, formatters
│   │   ├── validators.ts             # Zod schemas
│   │   └── permissions.ts            # Permission constants & helpers
│   │
│   ├── types/                        # TypeScript type definitions
│   │   ├── api.types.ts              # API response types
│   │   ├── models.ts                 # Domain model types
│   │   └── enums.ts                  # Role, Status enums
│   │
│   └── styles/
│       └── globals.css               # Tailwind directives, custom CSS
│
├── index.html
├── vite.config.ts
├── tailwind.config.ts
├── tsconfig.json
├── package.json
├── Dockerfile
└── .env.example
```

---

## 6. Authentication & Authorization Architecture

### 6.1 Staff Authentication (Admin Panel)

```
Login Flow:
  Phone/Email + Password → POST /api/v1/auth/login
    → Validate credentials (bcrypt)
    → Generate JWT (access: 30min, refresh: 7d)
    → Store refresh token in Redis
    → Return { access_token, refresh_token, user, permissions[] }
```

- **Access Token**: Short-lived JWT (30 min), sent as `Authorization: Bearer <token>`
- **Refresh Token**: Long-lived (7 days), stored in Redis, used to rotate access tokens
- **RBAC**: Permissions loaded from `ROLE_PERMISSION` + `USER_PERMISSION_OVERRIDE` on login

### 6.2 Client Authentication (Link-based)

```
Client Access Flow:
  Report Ready → Generate unique token → Store in Redis (72h TTL)
    → Send SMS/Email with link: https://app.munal.com/client/report/{token}
    → Client opens link → Validate token from Redis
    → Show report download + appointment list (read-only)
```

- **No password, no login form** — Token-based access via link
- Token is one-time-use or time-bound (72 hours)
- Client identity is the mobile number (stored in `PATIENT.phone`)

### 6.3 Permission Model

```python
# Example permissions (from ERD PERMISSION table)
PERMISSIONS = {
    "manage_catalog": "Create/edit tests, packages, categories",
    "manage_patients": "CRUD patients",
    "manage_orders": "Create/edit visit orders",
    "collect_samples": "Mark samples as collected",
    "verify_reports": "Verify/release lab reports",
    "manage_billing": "Create/edit bills, process payments",
    "view_dashboard": "Access analytics dashboard",
    "manage_users": "CRUD staff users, assign roles",
    "manage_lab_settings": "Edit lab profile, appointment slots",
    "manage_referrals": "CRUD referrals, view commissions",
}
```

---

## 7. API Design Standards

### 7.1 Versioned REST API

```
Base URL: /api/v1/

# Resource naming conventions:
GET    /api/v1/patients                 # List (paginated)
POST   /api/v1/patients                 # Create
GET    /api/v1/patients/{id}            # Retrieve
PUT    /api/v1/patients/{id}            # Update
DELETE /api/v1/patients/{id}            # Soft delete

# Nested resources:
GET    /api/v1/orders/{id}/tests        # Tests in an order
POST   /api/v1/orders/{id}/samples      # Collect sample for order

# Actions (non-CRUD):
POST   /api/v1/reports/{id}/verify      # Verify a report
POST   /api/v1/reports/{id}/release     # Release to patient
POST   /api/v1/bills/{id}/pay           # Record payment
```

### 7.2 Standard Response Envelope

```json
// Success (single item)
{
  "success": true,
  "data": { ... },
  "message": "Patient created successfully"
}

// Success (paginated list)
{
  "success": true,
  "data": [ ... ],
  "pagination": {
    "page": 1,
    "page_size": 20,
    "total_items": 156,
    "total_pages": 8
  }
}

// Error
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Phone number is required",
    "details": [
      { "field": "phone", "message": "This field is required" }
    ]
  }
}
```

### 7.3 Error Codes

| HTTP Status | Error Code | Usage |
|---|---|---|
| 400 | `VALIDATION_ERROR` | Invalid input data |
| 401 | `UNAUTHORIZED` | Missing or invalid token |
| 403 | `FORBIDDEN` | Insufficient permissions |
| 404 | `NOT_FOUND` | Resource not found |
| 409 | `CONFLICT` | Duplicate entry, state conflict |
| 422 | `UNPROCESSABLE_ENTITY` | Business logic violation |
| 429 | `RATE_LIMITED` | Too many requests |
| 500 | `INTERNAL_ERROR` | Server error |

---

## 8. Database Standards & Conventions

### 8.1 Naming Conventions

| Element | Convention | Example |
|---|---|---|
| Tables | `snake_case`, singular | `visit_order`, `test_parameter` |
| Columns | `snake_case` | `created_at`, `lab_id` |
| Primary Keys | `id` (UUID v7 or BIGINT) | `id` |
| Foreign Keys | `{referenced_table}_id` | `patient_id`, `lab_id` |
| Indexes | `ix_{table}_{column}` | `ix_patient_phone` |
| Unique Constraints | `uq_{table}_{column}` | `uq_patient_lab_phone` |
| Enums | `snake_case` type name | `order_status`, `user_role` |

### 8.2 Common Mixins (SQLAlchemy)

```python
class TimestampMixin:
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now()
    )
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
    )

class SoftDeleteMixin:
    deleted_at: Mapped[datetime | None] = mapped_column(
        DateTime(timezone=True), nullable=True, default=None
    )
    
    @hybrid_property
    def is_deleted(self) -> bool:
        return self.deleted_at is not None

class LabScopedMixin:
    """Branch-scoped: entity belongs to a specific lab branch/unit."""
    lab_id: Mapped[uuid.UUID] = mapped_column(
        ForeignKey("lab.id"), index=True, nullable=False
    )
```

### 8.3 Single-Tenant, Multi-Branch Strategy

- **Single organization** — One deployment, one database, one set of users
- **`lab_id` = Branch/Unit** — The `LAB` table represents branches or units within the organization (e.g., Main Lab, Branch Lab, Collection Center). The `lab_id` FK on entities scopes data to a specific branch.
- **Shared user pool** — Staff users may operate across branches (configurable per role)
- **Branch-scoped queries** — When a user is working within a branch context, queries filter by `lab_id`

```python
# Example: Branch-scoped repository
class BaseRepository:
    async def list(self, lab_id: UUID | None = None, **filters):
        query = select(self.model)
        if lab_id:  # Filter by branch when context is set
            query = query.where(self.model.lab_id == lab_id)
        # Apply additional filters...
        return await self.session.execute(query)
```

### 8.4 Migration Workflow (Alembic)

```bash
# Generate migration after model changes
alembic revision --autogenerate -m "add_appointment_notes_column"

# Apply migrations
alembic upgrade head

# Rollback last migration
alembic downgrade -1

# View migration history
alembic history
```

> [!WARNING]
> **Migration Rules:**
> - Never edit a migration that has been applied to staging/production
> - Always review auto-generated migrations before applying
> - Include data migrations in separate files from schema migrations
> - Test rollback (`downgrade`) for every migration

---

## 9. Background Task Architecture (Celery)

### 9.1 Task Categories

| Task | Queue | Priority | Trigger |
|---|---|---|---|
| **PDF Report Generation** | `reports` | HIGH | Order tests completed |
| **SMS Notification** | `notifications` | HIGH | Report released, appointment reminder |
| **Email Notification** | `notifications` | MEDIUM | Report ready, password reset |
| **Commission Calculation** | `billing` | LOW | Order finalized |
| **Dashboard Analytics** | `analytics` | LOW | Periodic (every 15 min) |
| **Stale Slot Cleanup** | `maintenance` | LOW | Periodic (hourly) |

### 9.2 Celery Configuration

```python
# tasks/__init__.py
from celery import Celery
from celery.schedules import crontab

celery_app = Celery("munal_lms")
celery_app.config_from_object({
    "broker_url": "redis://localhost:6379/1",
    "result_backend": "redis://localhost:6379/2",
    "task_serializer": "json",
    "task_track_started": True,
    "task_time_limit": 300,  # 5 min hard limit
    "task_soft_time_limit": 240,  # 4 min soft limit
    "worker_prefetch_multiplier": 1,  # Fair scheduling
    "task_routes": {
        "app.tasks.report_tasks.*": {"queue": "reports"},
        "app.tasks.notification_tasks.*": {"queue": "notifications"},
        "app.tasks.analytics_tasks.*": {"queue": "analytics"},
    },
})

# Periodic tasks (Celery Beat)
celery_app.conf.beat_schedule = {
    "refresh-dashboard-analytics": {
        "task": "app.tasks.analytics_tasks.compute_dashboard",
        "schedule": crontab(minute="*/15"),
    },
    "cleanup-expired-slot-locks": {
        "task": "app.tasks.maintenance_tasks.cleanup_slot_locks",
        "schedule": crontab(minute="0", hour="*"),
    },
    "send-appointment-reminders": {
        "task": "app.tasks.notification_tasks.send_daily_reminders",
        "schedule": crontab(minute="0", hour="7"),  # 7 AM daily
    },
}
```

---

## 10. Security Checklist

| Concern | Implementation |
|---|---|
| **SQL Injection** | SQLAlchemy parameterized queries (never raw SQL) |
| **XSS** | React auto-escapes; CSP headers via Nginx |
| **CSRF** | SPA architecture (JWT, no cookies) — CSRF N/A |
| **Rate Limiting** | `slowapi` — 5 OTP/min, 100 API/min per user |
| **CORS** | Whitelist frontend origins only |
| **Password Storage** | bcrypt via `passlib` |
| **JWT Security** | RS256 or HS256 with strong secret, short expiry |
| **File Upload** | Validate MIME type, max size (10MB), S3 virus scan |
| **Input Validation** | Pydantic schemas on every endpoint |
| **Secrets Management** | `.env` files, never committed; use Docker secrets in prod |
| **HTTPS** | Nginx SSL termination with Let's Encrypt |
| **Audit Logging** | Log all state-changing operations with user ID |
| **Data Isolation** | `lab_id` branch-scoping where applicable |

---

## 11. Development Workflow & Conventions

### 11.1 Git Branching Strategy

```
main          ← Production-ready code
  └── develop ← Integration branch
       ├── feature/MUN-101-patient-crud
       ├── feature/MUN-102-appointment-booking
       ├── bugfix/MUN-103-report-date-fix
       └── hotfix/MUN-104-billing-crash
```

- **Branch naming**: `{type}/MUN-{ticket}-{short-description}`
- **Commit format**: `feat(patients): add search by phone number` (Conventional Commits)
- **PR rules**: Min 1 approval, passing CI, no merge conflicts

### 11.2 Environment Setup

```bash
# Backend
cd backend
python -m venv .venv
source .venv/bin/activate        # Linux/Mac
.venv\Scripts\activate           # Windows
pip install -e ".[dev]"
cp .env.example .env
alembic upgrade head
uvicorn app.main:app --reload --port 8000

# Frontend
cd frontend
npm install
cp .env.example .env
npm run dev                      # → http://localhost:5173

# Full stack (Docker Compose)
docker compose up -d             # PostgreSQL, Redis, MinIO
cd backend && uvicorn app.main:app --reload
cd frontend && npm run dev
```

### 11.3 Code Quality Gates (CI)

```yaml
# .github/workflows/ci.yml (simplified)
Backend:
  - ruff check .                # Lint
  - ruff format --check .       # Format
  - mypy app/                   # Type check
  - pytest --cov=app            # Tests (min 80% coverage)
  - alembic check               # Migration integrity

Frontend:
  - npm run lint                # ESLint
  - npm run type-check          # TypeScript
  - npm run test                # Vitest
  - npm run build               # Build verification
```

---

## 12. Deployment Architecture

### 12.1 Docker Compose (Development & Staging)

```yaml
# docker-compose.yml
services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: munal_lms
      POSTGRES_USER: munal
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  redis:
    image: redis:7-alpine
    command: redis-server --requirepass ${REDIS_PASSWORD}
    ports:
      - "6379:6379"

  minio:
    image: minio/minio:latest
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: ${MINIO_USER}
      MINIO_ROOT_PASSWORD: ${MINIO_PASSWORD}
    volumes:
      - minio_data:/data
    ports:
      - "9000:9000"
      - "9001:9001"

  backend:
    build: ./backend
    command: uvicorn app.main:app --host 0.0.0.0 --port 8000
    env_file: ./backend/.env
    depends_on: [postgres, redis, minio]
    ports:
      - "8000:8000"

  celery-worker:
    build: ./backend
    command: celery -A app.tasks worker -l info -Q reports,notifications,billing,analytics
    env_file: ./backend/.env
    depends_on: [postgres, redis]

  celery-beat:
    build: ./backend
    command: celery -A app.tasks beat -l info
    env_file: ./backend/.env
    depends_on: [redis]

  frontend:
    build: ./frontend
    ports:
      - "3000:80"

volumes:
  pgdata:
  minio_data:
```

### 12.2 Production Deployment

```
                    ┌─────────────────┐
    Internet  ────> │   Nginx / ALB   │
                    │  (SSL + Static) │
                    └────────┬────────┘
                             │
                ┌────────────┼────────────┐
                │            │            │
        ┌───────▼──┐  ┌─────▼─────┐  ┌───▼──────┐
        │ Frontend │  │  FastAPI   │  │  FastAPI  │
        │ (Static) │  │ Worker 1   │  │ Worker 2  │
        └──────────┘  └─────┬──────┘  └─────┬─────┘
                            │               │
                     ┌──────▼───────────────▼──────┐
                     │       PostgreSQL (Primary)    │
                     │       Redis (Cache/Queue)     │
                     │       MinIO (Object Store)    │
                     └───────────────────────────────┘
```

---

## 13. Key Environment Variables

```env
# .env.example (Backend)

# Application
APP_ENV=development                    # development | staging | production
APP_DEBUG=true
APP_SECRET_KEY=change-me-in-production
APP_ALLOWED_ORIGINS=http://localhost:5173

# Database
DATABASE_URL=postgresql+asyncpg://munal:password@localhost:5432/munal_lms

# Redis
REDIS_URL=redis://:password@localhost:6379/0

# JWT
JWT_SECRET_KEY=your-jwt-secret
JWT_ACCESS_TOKEN_EXPIRE_MINUTES=30
JWT_REFRESH_TOKEN_EXPIRE_DAYS=7

# File Storage (S3/MinIO)
S3_ENDPOINT_URL=http://localhost:9000
S3_ACCESS_KEY=minioadmin
S3_SECRET_KEY=minioadmin
S3_BUCKET_NAME=munal-lms

# SMS Gateway
SMS_API_URL=https://api.sparrowsms.com/v2/sms
SMS_API_TOKEN=your-sms-token
SMS_SENDER_ID=MunalLab

# Email
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=noreply@munal.com
SMTP_PASSWORD=app-password

# Celery
CELERY_BROKER_URL=redis://:password@localhost:6379/1

# Sentry
SENTRY_DSN=https://your-sentry-dsn

# Prometheus
PROMETHEUS_ENABLED=true
```

---

## 14. Recommended Development Timeline

| Phase | Duration | Deliverables |
|---|---|---|
| **Phase 1: Foundation** | 2 weeks | Project scaffolding, Docker setup, DB models, Alembic migrations, Auth (JWT + RBAC), basic CI/CD |
| **Phase 2: Core Modules** | 4 weeks | Patient CRUD, Test/Package catalog, Appointment scheduling, Visit orders |
| **Phase 3: Lab Workflow** | 3 weeks | Sample collection, Report generation (PDF), Verification workflow |
| **Phase 4: Financial** | 2 weeks | Billing, Discounts, Payments, Referral commissions |
| **Phase 5: Notifications** | 1 week | SMS/Email integration, Client link pages (report download, appointment viewer) |
| **Phase 6: Dashboard & Polish** | 2 weeks | Analytics dashboard, search optimization, UX polish |
| **Phase 7: Testing & Hardening** | 2 weeks | E2E testing, load testing, security audit, staging deployment |

> **Total Estimate: ~16 weeks** (4 months) with a 2-3 dev team

---

## 15. Quick Reference — Package Install Commands

### Backend Setup

```bash
# Create project with pyproject.toml
mkdir backend && cd backend
python -m venv .venv && source .venv/bin/activate

# Core
pip install fastapi uvicorn[standard] gunicorn

# Database
pip install sqlalchemy[asyncio] asyncpg alembic

# Validation & Security
pip install pydantic pydantic-settings python-jose[cryptography] passlib[bcrypt] pyotp

# Redis & Background Tasks
pip install redis[hiredis] celery

# PDF & Files
pip install weasyprint jinja2 openpyxl pillow boto3

# Communication
pip install fastapi-mail httpx

# Observability
pip install structlog sentry-sdk[fastapi] prometheus-fastapi-instrumentator

# API Utilities
pip install slowapi python-multipart

# Dev Tools
pip install ruff mypy pytest pytest-asyncio httpx
```

### Frontend Setup

```bash
# Scaffold with Vite
npx -y create-vite@latest ./ --template react-ts

# Core
npm i @tanstack/react-router @tanstack/react-query zustand

# Forms & Validation
npm i react-hook-form zod @hookform/resolvers

# HTTP
npm i ky   # or: npm i axios

# UI
npx shadcn@latest init
npm i @tanstack/react-table recharts lucide-react sonner

# i18n
npm i i18next react-i18next

# Date
npm i date-fns

# PDF
npm i @react-pdf-viewer/core @react-pdf-viewer/default-layout

# Dev
npm i -D @types/node vitest @testing-library/react
```

---

> [!TIP]
> **For new developers joining the project:** Start by reading this document end-to-end, then set up Docker Compose, run migrations, and explore the Swagger UI at `http://localhost:8000/docs`.
