215 lines
6 KiB
Markdown
215 lines
6 KiB
Markdown
|
|
# Phase 1 — Medicine Library
|
||
|
|
|
||
|
|
**Goal**: A searchable catalog of medicines with dosage and form information. Medicines are the atomic building blocks for regimens, cabinet inventory, and refill tracking.
|
||
|
|
|
||
|
|
**Depends on**: Phase 0 (auth, households, shared types)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Deliverables
|
||
|
|
|
||
|
|
1. `Medicine` MongoDB schema and full CRUD API
|
||
|
|
2. Full-text search with filters
|
||
|
|
3. Barcode lookup (future: integration with drug database APIs)
|
||
|
|
4. Bulk import (CSV/JSON)
|
||
|
|
5. Medicine library web UI (search, add, edit)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Data Model
|
||
|
|
|
||
|
|
### Medicine Schema
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// packages/shared/src/types/medicine.ts
|
||
|
|
export interface Medicine {
|
||
|
|
id: string;
|
||
|
|
householdId: string;
|
||
|
|
name: string;
|
||
|
|
genericName?: string;
|
||
|
|
brand?: string;
|
||
|
|
barcode?: string;
|
||
|
|
form: MedicineForm;
|
||
|
|
strength: number;
|
||
|
|
strengthUnit: StrengthUnit;
|
||
|
|
category: MedicineCategory;
|
||
|
|
activeIngredient?: string;
|
||
|
|
manufacturer?: string;
|
||
|
|
notes?: string;
|
||
|
|
imageUrl?: string;
|
||
|
|
tags: string[];
|
||
|
|
source: MedicineSource;
|
||
|
|
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',
|
||
|
|
VITAMIN = 'vitamin',
|
||
|
|
HERBAL = 'herbal',
|
||
|
|
OTHER = 'other',
|
||
|
|
}
|
||
|
|
|
||
|
|
export enum MedicineSource {
|
||
|
|
MANUAL = 'manual',
|
||
|
|
BARCODE_LOOKUP = 'barcode_lookup',
|
||
|
|
IMPORT = 'import',
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### MongoDB Indexes
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
// Text index for search
|
||
|
|
{ name: 'text', genericName: 'text', brand: 'text', activeIngredient: 'text', tags: 'text' }
|
||
|
|
|
||
|
|
// Compound indexes
|
||
|
|
{ householdId: 1, category: 1 }
|
||
|
|
{ householdId: 1, barcode: 1 } // unique within household
|
||
|
|
{ householdId: 1, name: 1, strength: 1, form: 1 } // near-unique for dedup
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## API Endpoints
|
||
|
|
|
||
|
|
### MedicinesModule
|
||
|
|
|
||
|
|
| 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 |
|
||
|
|
| GET | `/medicines/barcode/:code` | Lookup by barcode | member |
|
||
|
|
| POST | `/medicines/import` | Bulk import from CSV/JSON | 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
|
||
|
|
|
||
|
|
- Add all types above to `packages/shared/src/types/medicine.ts`
|
||
|
|
- Add enums to `packages/shared/src/enums/`
|
||
|
|
- Create Zod schemas:
|
||
|
|
- `CreateMedicineSchema` — validates create payload
|
||
|
|
- `UpdateMedicineSchema` — partial, validates update payload
|
||
|
|
- `MedicineQuerySchema` — validates query params
|
||
|
|
|
||
|
|
### 1.2 — Mongoose Schema & Repository
|
||
|
|
|
||
|
|
- `packages/api/src/modules/medicines/medicines.repository.ts`
|
||
|
|
- `MedicinesRepository` with:
|
||
|
|
- `findByHousehold(householdId, query)` — supports text search, filters, cursor pagination
|
||
|
|
- `findByBarcode(householdId, barcode)`
|
||
|
|
- `findById(id, householdId)`
|
||
|
|
- `create(data)`
|
||
|
|
- `update(id, householdId, data)`
|
||
|
|
- `softDelete(id, householdId)`
|
||
|
|
- `bulkCreate(items[])`
|
||
|
|
|
||
|
|
### 1.3 — Service & Routes
|
||
|
|
|
||
|
|
- `MedicinesService` with business logic (dedup check on create, validation)
|
||
|
|
- `MedicinesRoutes` with Fastify route plugin registering all endpoints
|
||
|
|
- Register Awilix dependencies via `fp()` plugin
|
||
|
|
|
||
|
|
### 1.4 — Barcode Lookup
|
||
|
|
|
||
|
|
- `BarcodeLookupService`:
|
||
|
|
- First check local DB for matching barcode
|
||
|
|
- Placeholder for external drug database API integration (manual entry fallback)
|
||
|
|
- Cache results in local DB with `source: 'barcode_lookup'`
|
||
|
|
|
||
|
|
### 1.5 — Import Endpoint
|
||
|
|
|
||
|
|
- `POST /medicines/import` accepts multipart CSV or JSON file
|
||
|
|
- Validate each row against `CreateMedicineSchema`
|
||
|
|
- Return summary: `{ imported: N, skipped: M, errors: [...] }`
|
||
|
|
- CSV column mapping: `name, genericName, brand, barcode, form, strength, strengthUnit, category, activeIngredient, manufacturer`
|
||
|
|
|
||
|
|
### 1.6 — Web UI: Medicine Library
|
||
|
|
|
||
|
|
- `/medicines` page:
|
||
|
|
- Search bar with debounced full-text search
|
||
|
|
- Category and form filter dropdowns
|
||
|
|
- Tag filter chips
|
||
|
|
- Medicine grid/list view (toggle)
|
||
|
|
- Each medicine card shows: name, strength + unit, form, brand, category badge
|
||
|
|
- Add/Edit medicine modal:
|
||
|
|
- Form fields for all medicine properties
|
||
|
|
- Barcode field with "Lookup" button
|
||
|
|
- Import dialog: file upload with preview and error display
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Acceptance Criteria
|
||
|
|
|
||
|
|
- [ ] Can create, read, update, delete medicines via API
|
||
|
|
- [ ] Full-text search returns relevant results
|
||
|
|
- [ ] Barcode lookup checks local DB first
|
||
|
|
- [ ] Bulk import processes a CSV with 100+ medicines
|
||
|
|
- [ ] Web UI allows searching, filtering, adding, and editing medicines
|
||
|
|
- [ ] All medicine queries are scoped to `householdId`
|
||
|
|
- [ ] Dedup check prevents creating duplicate medicines (same name + strength + form)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Estimated Effort
|
||
|
|
|
||
|
|
Medium. Straightforward CRUD with search, following the same patterns established in Phase 0.
|