# Phase 1 — Medicine Library

**Goal**: A two-level catalog of medicines. The **Medicine** level represents the generic substance you take (what regimens reference). The **MedicineProduct** level represents a specific purchasable item from a brand/manufacturer (what you buy and track prices for). Cabinet inventory tracks current quantity at the Medicine level, with an optional link back to which product it came from.

**Depends on**: Phase 0 (auth, households, shared types)

---

## Deliverables

1. `Medicine` and `MedicineProduct` MongoDB schemas with full CRUD APIs
2. Full-text search with category/form filters
3. Cascade delete protection (cannot delete medicine with linked products)
4. Medicine library web UI (search, filter, add, edit, delete -- both levels)

---

## Data Model

### Medicine Schema (Generic Level)

The generic substance -- what you take. Regimens and cabinet items reference this.

```typescript
// packages/shared/src/types/medicine.ts
export interface Medicine {
  id: string;
  householdId: string;
  name: string; // Display name, e.g., "Metformin" or "Vitamin D3"
  form: MedicineForm;
  strength: number; // e.g., 500
  strengthUnit: StrengthUnit; // e.g., 'mg' (weight/count only)
  category: MedicineCategory;
  notes?: string;
  tags: string[];
  createdBy: string;
  createdAt: Date;
  updatedAt: Date;
}

export enum MedicineForm {
  TABLET = 'tablet',
  CAPSULE = 'capsule',
  LIQUID = 'liquid',
  CREAM = 'cream',
  INJECTION = 'injection',
  INHALER = 'inhaler',
  PATCH = 'patch',
  DROPS = 'drops',
  POWDER = 'powder',
  SUPPOSITORY = 'suppository',
  OTHER = 'other',
}

export enum StrengthUnit {
  MG = 'mg',
  MCG = 'mcg',
  G = 'g',
  ML = 'ml',
  IU = 'IU',
  PERCENT = '%',
  OTHER = 'other',
}

export enum MedicineCategory {
  PRESCRIPTION = 'prescription',
  OTC = 'otc',
  SUPPLEMENT = 'supplement',
  OTHER = 'other',
}
```

### MedicineProduct Schema (Purchasable Level)

A specific brand/package you can buy. References a Medicine. Used for price tracking and ordering. Vial products can include concentration data.

```typescript
// packages/shared/src/types/medicine.ts
export interface MedicineProduct {
  id: string;
  householdId: string;
  medicineId: string; // Reference to Medicine
  medicineName: string; // Denormalized for display
  brand: string; // e.g., "CVS Health", "Kirkland"
  manufacturer?: string;
  packageSize: number; // e.g., 90 (pills per bottle)
  packageUnit: DosageUnit; // e.g., 'pill', 'ml', 'vial'
  concentration?: number; // For vials only, e.g., 100
  concentrationUnit?: ConcentrationUnit; // For vials only, e.g., 'units/mL'
  imageUrl?: string;
  notes?: string;
  source: MedicineProductSource;
  createdBy: string;
  createdAt: Date;
  updatedAt: Date;
}

export enum MedicineProductSource {
  MANUAL = 'manual',
  IMPORT = 'import',
}

export enum ConcentrationUnit {
  MG_PER_ML = 'mg/mL',
  MCG_PER_ML = 'mcg/mL',
  UNITS_PER_ML = 'units/mL',
}
```

### Relationship Diagram

```
Medicine (generic)              MedicineProduct (purchasable)
┌─────────────────────┐         ┌──────────────────────────────┐
│ Metformin 500mg tab │◄────────│ CVS Metformin 500mg, 90ct   │
│                     │◄────────│ Kirkland Metformin 500mg, 60ct│
└─────────────────────┘         └──────────────────────────────┘
        ▲                               ▲
        │                               │
   Referenced by:                  Referenced by:
   - Regimens (Phase 3)           - PriceRecords (Phase 4)
   - CabinetItems (Phase 2)      - CabinetItems (Phase 2, optional)
```

### MongoDB Indexes

```javascript
// Medicine
{ householdId: 1, name: 'text', tags: 'text' }
{ householdId: 1, category: 1 }
{ householdId: 1, name: 1, strength: 1, strengthUnit: 1, form: 1 } // dedup

// MedicineProduct
{ householdId: 1, medicineId: 1 }
{ householdId: 1, brand: 'text' }
```

---

## API Endpoints

### MedicinesModule (Generic Level)

| Method | Path                  | Description                       | Auth   |
| ------ | --------------------- | --------------------------------- | ------ |
| GET    | `/medicines`          | List/search medicines (paginated) | member |
| GET    | `/medicines/:id`      | Get single medicine               | member |
| POST   | `/medicines`          | Create medicine                   | member |
| PATCH  | `/medicines/:id`      | Update medicine                   | member |
| DELETE | `/medicines/:id`      | Soft-delete medicine              | admin  |
| POST   | `/medicines/import`   | Bulk import from CSV/JSON         | admin  |

### MedicineProductsModule (Purchasable Level)

| Method | Path                                    | Description                                | Auth   |
| ------ | --------------------------------------- | ------------------------------------------ | ------ |
| GET    | `/medicines/:medicineId/products`       | List products for a medicine               | member |
| GET    | `/medicine-products/:id`                | Get single product                         | member |
| POST   | `/medicines/:medicineId/products`       | Create product under a medicine            | member |
| PATCH  | `/medicine-products/:id`                | Update product                             | member |
| DELETE | `/medicine-products/:id`                | Soft-delete product                        | admin  |

### Query Parameters for GET `/medicines`

```
?q=metformin                  # Full-text search
&category=prescription        # Filter by category
&form=tablet                  # Filter by form
&tags=daily,morning           # Filter by tags (AND)
&cursor=abc123                # Cursor-based pagination
&limit=20                     # Page size (max 100)
&sort=name|-updatedAt         # Sort field, prefix - for desc
```

### Response Shape

```typescript
interface PaginatedResponse<T> {
  data: T[];
  pagination: {
    cursor: string | null; // null = last page
    hasMore: boolean;
    total: number;
  };
}
```

---

## Tasks

### 1.1 -- Shared Types & Validation

- [x] Add Medicine types to `packages/shared/src/types/medicine.ts`
- [x] Add MedicineProduct types (same file)
- [x] Add enums to `packages/shared/src/enums/medicine.enums.ts`
- [x] Create Zod schemas:
  - `CreateMedicineSchema`, `UpdateMedicineSchema`, `MedicineQuerySchema`
  - `CreateMedicineProductSchema`, `UpdateMedicineProductSchema`

### 1.2 -- Medicine Mongoose Schema & Repository

- [x] `packages/api/src/modules/medicines/medicines.repository.ts`
- [x] `MedicinesRepository` with:
  - `findByHousehold(householdId, query)` -- supports text search, filters, cursor pagination
  - `findById(id, householdId)`
  - `findDuplicate(householdId, name, strength, strengthUnit, form, excludeId?)`
  - `create(data)`
  - `update(id, householdId, data)`
  - `softDelete(id, householdId)`

### 1.3 -- MedicineProduct Mongoose Schema & Repository

- [x] `packages/api/src/modules/medicine-products/medicine-products.repository.ts`
- [x] `MedicineProductsRepository` with:
  - `findByMedicine(householdId, medicineId, query)` -- paginated
  - `findById(id, householdId)`
  - `countByMedicineId(medicineId)` -- for cascade delete check
  - `create(data)`
  - `update(id, householdId, data)`
  - `softDelete(id, householdId)`

### 1.4 -- Services & Routes

- [x] `MedicinesService` with business logic:
  - Dedup check on create (same name + strength + strengthUnit + form within household)
  - Cascade: when deleting a medicine, check for linked products and block if any exist
- [x] `MedicineProductsService` with business logic:
  - Validate medicineId exists on create
  - Denormalize medicineName
- [x] Route plugins for both modules, registered via `fp()`

### 1.5 -- Web UI: Medicine Library

- [x] `/medicines` page:
  - Search bar with text search
  - Category and form filter dropdowns
  - Medicine list view
  - Each medicine card shows: name, strength + unit, form, category badge
  - Click a medicine to see its detail page with products
- [x] Add/Edit medicine form
- [x] Add/Edit product form (nested under a medicine detail page)
  - Brand, manufacturer, package size, concentration (vials only)
- [x] Delete with confirmation (blocked if products exist)

---

## Acceptance Criteria

- [x] Can create, read, update, delete medicines (generic level) via API
- [x] Can create, read, update, delete medicine products (purchasable level) via API
- [x] Medicine products are correctly linked to their parent medicine
- [x] Full-text search returns relevant results
- [x] Category and form filters work on the medicines list
- [x] Cascade delete check prevents deleting medicines with linked products
- [x] Web UI shows two-level hierarchy: medicines with nested products
- [x] All queries are scoped to `householdId`
- [x] Dedup check prevents creating duplicate medicines (same name + strength + unit + form)
- [x] Vial products support concentration + concentration unit fields
---

## Estimated Effort

Medium. Two related CRUD modules with search. The two-level structure adds a bit of complexity over a flat model but keeps the domain clean.
