Phase 8: Visitor Management - Visitor/VisitorLog/AccessZone models - Check-in/out with badge generation - Zone occupancy tracking - Kiosk and management pages Phase 9: Messaging & Communication - Announcements with priority levels - Acknowledgement tracking - Shift notes for team handoffs - AnnouncementBanner component Phase 10: Compliance & Audit Trail - Immutable AuditLog model - Document versioning and approval workflow - Acknowledgement tracking for SOPs - CSV export for audit logs Phase 11: Accessibility & i18n - WCAG 2.1 AA compliance utilities - react-i18next with EN/ES translations - User preferences context (theme, font size, etc) - High contrast and reduced motion support Phase 12: Hardware Integration - QR code generation for batches/plants/visitors - Printable label system - Visitor badge printing Phase 13: Advanced Features - Environmental monitoring (sensors, readings, alerts) - Financial tracking (transactions, P&L reports) - AI/ML insights (yield predictions, anomaly detection)
458 lines
13 KiB
Markdown
458 lines
13 KiB
Markdown
# Facility Layout Designer & Plant Addressing System
|
||
|
||
## 🎯 Vision
|
||
|
||
A Figma-like visual layout editor for designing facility layouts with precise plant addressing. Every plant gets a unique, hierarchical address that maps to physical location:
|
||
|
||
```
|
||
PROPERTY → BUILDING → ROOM → SECTION → ROW → COLUMN → POSITION
|
||
```
|
||
|
||
Example: `MAIN.GROW1.VEG-A.LEFT.R3.C5.P1` = Main Property, Grow Building 1, Veg Room A, Left Section, Row 3, Column 5, Position 1
|
||
|
||
---
|
||
|
||
## 📐 Data Model
|
||
|
||
```prisma
|
||
// Hierarchical Location Structure
|
||
model Property {
|
||
id String @id @default(uuid())
|
||
name String
|
||
address String?
|
||
licenseNum String? // DCC License Number
|
||
buildings Building[]
|
||
createdAt DateTime @default(now())
|
||
}
|
||
|
||
model Building {
|
||
id String @id @default(uuid())
|
||
property Property @relation(fields: [propertyId], references: [id])
|
||
propertyId String
|
||
name String // "Grow Building 1", "Processing"
|
||
code String // Short code: "GROW1"
|
||
type String // CULTIVATION, PROCESSING, DRYING, STORAGE
|
||
rooms Room[]
|
||
layout Json? // Visual layout data (x, y, width, height)
|
||
createdAt DateTime @default(now())
|
||
}
|
||
|
||
model Room {
|
||
id String @id @default(uuid())
|
||
building Building @relation(fields: [buildingId], references: [id])
|
||
buildingId String
|
||
name String
|
||
code String // "VEG-A"
|
||
type String // VEG, FLOWER, CLONE, DRY, CURE, TRIM
|
||
sections Section[]
|
||
layout Json? // Position within building
|
||
createdAt DateTime @default(now())
|
||
}
|
||
|
||
model Section {
|
||
id String @id @default(uuid())
|
||
room Room @relation(fields: [roomId], references: [id])
|
||
roomId String
|
||
name String // "Left Side", "Center Aisle"
|
||
code String // "LEFT", "CENTER"
|
||
type String // FLOOR, TABLE, RACK, HANGER, TRAY
|
||
rows Row[]
|
||
layout Json? // Position within room, dimensions
|
||
createdAt DateTime @default(now())
|
||
}
|
||
|
||
model Row {
|
||
id String @id @default(uuid())
|
||
section Section @relation(fields: [sectionId], references: [id])
|
||
sectionId String
|
||
number Int // Row 1, 2, 3...
|
||
positions Position[]
|
||
layout Json? // Position within section
|
||
}
|
||
|
||
model Position {
|
||
id String @id @default(uuid())
|
||
row Row @relation(fields: [rowId], references: [id])
|
||
rowId String
|
||
column Int // Column number
|
||
slot Int @default(1) // For multi-plant positions
|
||
plant Plant? @relation(fields: [plantId], references: [id])
|
||
plantId String? @unique
|
||
status String @default("EMPTY") // EMPTY, OCCUPIED, RESERVED, DAMAGED
|
||
}
|
||
|
||
model Plant {
|
||
id String @id @default(uuid())
|
||
tagNumber String @unique // METRC tag
|
||
batch Batch @relation(fields: [batchId], references: [id])
|
||
batchId String
|
||
position Position?
|
||
address String // Full hierarchical address
|
||
status String // ACTIVE, HARVESTED, DESTROYED, TRANSFERRED
|
||
history PlantLocationHistory[]
|
||
createdAt DateTime @default(now())
|
||
}
|
||
|
||
model PlantLocationHistory {
|
||
id String @id @default(uuid())
|
||
plant Plant @relation(fields: [plantId], references: [id])
|
||
plantId String
|
||
fromAddress String?
|
||
toAddress String
|
||
movedBy String // User ID
|
||
movedAt DateTime @default(now())
|
||
reason String? // TRANSPLANT, REORGANIZE, HARVEST
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🎨 Layout Editor UI
|
||
|
||
### Canvas Features (Figma-like)
|
||
|
||
- **Drag & Drop** - Add rooms, sections, rows to canvas
|
||
- **Resize Handles** - Adjust dimensions
|
||
- **Grid Snap** - Align to grid
|
||
- **Zoom & Pan** - Navigate large layouts
|
||
- **Layers** - Show/hide different elements
|
||
- **Templates** - Pre-built room layouts
|
||
|
||
### Element Types
|
||
|
||
#### 1. Floor Plan Elements
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ BUILDING: Grow 1 │
|
||
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||
│ │ VEG ROOM A │ │ VEG ROOM B │ │
|
||
│ │ ┌───┬───┬───┐ │ │ │ │
|
||
│ │ │ T1│ T2│ T3│ │ │ │ │
|
||
│ │ ├───┼───┼───┤ │ │ │ │
|
||
│ │ │ T4│ T5│ T6│ │ │ │ │
|
||
│ │ └───┴───┴───┘ │ │ │ │
|
||
│ └─────────────────┘ └─────────────────┘ │
|
||
│ ┌──────────────────────────────────────┐ │
|
||
│ │ FLOWER ROOM A │ │
|
||
│ │ [====] [====] [====] [====] │ 4 ft aisles │
|
||
│ │ [====] [====] [====] [====] │ │
|
||
│ │ [====] [====] [====] [====] │ │
|
||
│ └──────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
#### 2. Grow Table/Bed (Grid-based)
|
||
|
||
```
|
||
┌────────────────────────────────────────┐
|
||
│ TABLE T1 - VEG-A.LEFT.T1 │
|
||
│ Capacity: 6 rows × 8 columns = 48 │
|
||
│ ┌──┬──┬──┬──┬──┬──┬──┬──┐ │
|
||
│ │🌱│🌱│🌱│🌱│🌱│🌱│🌱│🌱│ R1 │
|
||
│ ├──┼──┼──┼──┼──┼──┼──┼──┤ │
|
||
│ │🌱│🌱│🌱│🌱│ │ │🌱│🌱│ R2 │
|
||
│ ├──┼──┼──┼──┼──┼──┼──┼──┤ │
|
||
│ │🌱│🌱│🌱│🌱│🌱│🌱│🌱│🌱│ R3 │
|
||
│ ├──┼──┼──┼──┼──┼──┼──┼──┤ │
|
||
│ │ │ │ │ │ │ │ │ │ R4 (empty) │
|
||
│ └──┴──┴──┴──┴──┴──┴──┴──┘ │
|
||
│ C1 C2 C3 C4 C5 C6 C7 C8 │
|
||
└────────────────────────────────────────┘
|
||
```
|
||
|
||
#### 3. Drying Rack (Hanger-based)
|
||
|
||
```
|
||
┌────────────────────────────────────────┐
|
||
│ DRY RACK 1 - DRY.MAIN.RACK1 │
|
||
│ Type: Vertical Hangers │
|
||
│ │
|
||
│ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ │
|
||
│ │H1│ │H2│ │H3│ │H4│ │H5│ │H6│ Level 1│
|
||
│ └──┘ └──┘ └──┘ └──┘ └──┘ └──┘ │
|
||
│ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ │
|
||
│ │H1│ │H2│ │H3│ │H4│ │H5│ │H6│ Level 2│
|
||
│ └──┘ └──┘ └──┘ └──┘ └──┘ └──┘ │
|
||
│ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ │
|
||
│ │H1│ │H2│ │H3│ │H4│ │H5│ │ │ Level 3│
|
||
│ └──┘ └──┘ └──┘ └──┘ └──┘ └──┘ │
|
||
└────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 API Endpoints
|
||
|
||
### Layout Management
|
||
|
||
```
|
||
POST /api/layout/property
|
||
POST /api/layout/building
|
||
POST /api/layout/room
|
||
POST /api/layout/section
|
||
POST /api/layout/row
|
||
|
||
PUT /api/layout/:entityType/:id
|
||
DELETE /api/layout/:entityType/:id
|
||
|
||
GET /api/layout/property/:id/full # Full hierarchy
|
||
GET /api/layout/room/:id/positions # All positions with status
|
||
```
|
||
|
||
### Plant Placement
|
||
|
||
```
|
||
POST /api/plants
|
||
Body: { tagNumber, batchId, positionId }
|
||
|
||
POST /api/plants/:id/move
|
||
Body: { toPositionId, reason }
|
||
|
||
POST /api/plants/bulk-place
|
||
Body: { batchId, positionIds[] } # Place batch into positions
|
||
|
||
GET /api/plants/by-address/:address # Lookup by address string
|
||
```
|
||
|
||
### Position Status
|
||
|
||
```
|
||
GET /api/positions/available
|
||
Query: ?roomId=x&count=48 # Find 48 available positions
|
||
|
||
POST /api/positions/reserve
|
||
Body: { positionIds[], batchId, until }
|
||
|
||
GET /api/rooms/:id/occupancy
|
||
Returns: { total, occupied, empty, reserved }
|
||
```
|
||
|
||
---
|
||
|
||
## 🎯 METRC Integration Requirements
|
||
|
||
### Address Format for METRC
|
||
|
||
METRC requires precise location tracking. Our address format maps to:
|
||
|
||
| Our Field | METRC Field |
|
||
|-----------|-------------|
|
||
| Property | License Premises |
|
||
| Building | Area |
|
||
| Room | Room |
|
||
| Section + Row + Column | Location (free text) |
|
||
|
||
### Sync Strategy
|
||
|
||
```
|
||
1. Plant created → Sync to METRC with location
|
||
2. Plant moved → Update METRC location
|
||
3. Plant harvested → Report harvest location
|
||
4. Audit → Generate location report matching METRC
|
||
```
|
||
|
||
---
|
||
|
||
## 🖥️ Frontend Components
|
||
|
||
### 1. LayoutEditor (Canvas)
|
||
|
||
```tsx
|
||
interface LayoutEditorProps {
|
||
propertyId: string;
|
||
mode: 'view' | 'edit';
|
||
onSave?: (layout: Layout) => void;
|
||
}
|
||
|
||
// Uses react-konva or @tldraw/tldraw for canvas
|
||
<LayoutEditor
|
||
propertyId={propertyId}
|
||
mode="edit"
|
||
onSave={handleSave}
|
||
/>
|
||
```
|
||
|
||
### 2. RoomGrid (Position Grid)
|
||
|
||
```tsx
|
||
interface RoomGridProps {
|
||
roomId: string;
|
||
onPositionClick?: (position: Position) => void;
|
||
selectedBatch?: string; // Highlight batch plants
|
||
}
|
||
|
||
<RoomGrid
|
||
roomId={roomId}
|
||
selectedBatch={batchId}
|
||
onPositionClick={(pos) => showPlantDetails(pos.plant)}
|
||
/>
|
||
```
|
||
|
||
### 3. PlantPlacer (Batch Placement Tool)
|
||
|
||
```tsx
|
||
interface PlantPlacerProps {
|
||
batchId: string;
|
||
plantCount: number;
|
||
onComplete: (placements: Placement[]) => void;
|
||
}
|
||
|
||
// Select multiple positions to place plants
|
||
<PlantPlacer
|
||
batchId={batch.id}
|
||
plantCount={48}
|
||
onComplete={handlePlacement}
|
||
/>
|
||
```
|
||
|
||
### 4. AddressLookup (Quick Find)
|
||
|
||
```tsx
|
||
// Scan or type address to jump to location
|
||
<AddressLookup
|
||
onFound={(plant) => navigateToPlant(plant)}
|
||
/>
|
||
|
||
// Input: "MAIN.GROW1.VEG-A.LEFT.R3.C5"
|
||
// Output: Highlights position, shows plant details
|
||
```
|
||
|
||
---
|
||
|
||
## 📱 Mobile Flow
|
||
|
||
1. **Scan Plant Tag** → Shows current position + batch info
|
||
2. **Move Plant** → Scan new position QR → Confirm move
|
||
3. **Quick Place** → Scan batch → Scan position → Repeat
|
||
4. **Audit Walk** → Show expected vs actual by position
|
||
|
||
---
|
||
|
||
## 🗓️ Implementation Phases
|
||
|
||
### Phase 1: Data Model & API (1 week)
|
||
|
||
- [ ] Create Prisma models
|
||
- [ ] Migrate database
|
||
- [ ] Build CRUD endpoints
|
||
- [ ] Address generation logic
|
||
|
||
### Phase 2: Basic Layout Editor (2 weeks)
|
||
|
||
- [ ] Canvas with react-konva
|
||
- [ ] Add/edit rooms, sections
|
||
- [ ] Grid generator for positions
|
||
- [ ] Save/load layouts
|
||
|
||
### Phase 3: Plant Placement (1 week)
|
||
|
||
- [ ] Place plants in positions
|
||
- [ ] Move plants (with history)
|
||
- [ ] Bulk placement tool
|
||
- [ ] Position reservation
|
||
|
||
### Phase 4: Visualization (1 week)
|
||
|
||
- [ ] Room grid view
|
||
- [ ] Color coding (by batch, stage, health)
|
||
- [ ] Occupancy heatmaps
|
||
- [ ] Print floor plans
|
||
|
||
### Phase 5: METRC Sync (2 weeks)
|
||
|
||
- [ ] Location format mapping
|
||
- [ ] Sync on plant create/move
|
||
- [ ] Audit report generation
|
||
- [ ] Discrepancy detection
|
||
|
||
---
|
||
|
||
## 📦 Tech Stack
|
||
|
||
| Component | Technology |
|
||
|-----------|------------|
|
||
| Canvas Editor | `react-konva` or `@tldraw/tldraw` |
|
||
| Grid Display | Custom React + CSS Grid |
|
||
| Address Parser | Regex + validation |
|
||
| QR Codes | `qrcode.react` |
|
||
| Mobile Scan | `@zxing/browser` |
|
||
| State Mgmt | Zustand (for editor state) |
|
||
|
||
---
|
||
|
||
## 🎨 Example Layouts
|
||
|
||
### Vegetative Room - Tables
|
||
|
||
```json
|
||
{
|
||
"type": "ROOM",
|
||
"code": "VEG-A",
|
||
"layout": {
|
||
"width": 40, // feet
|
||
"height": 30,
|
||
"sections": [
|
||
{
|
||
"code": "LEFT",
|
||
"type": "TABLE",
|
||
"x": 0, "y": 0,
|
||
"rows": 6,
|
||
"columns": 8,
|
||
"spacing": 12 // inches between plants
|
||
},
|
||
{
|
||
"code": "RIGHT",
|
||
"type": "TABLE",
|
||
"x": 20, "y": 0,
|
||
"rows": 6,
|
||
"columns": 8
|
||
}
|
||
],
|
||
"aisles": [
|
||
{ "x": 18, "y": 0, "width": 4, "height": 30 }
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
### Drying Room - Racks
|
||
|
||
```json
|
||
{
|
||
"type": "ROOM",
|
||
"code": "DRY-A",
|
||
"layout": {
|
||
"width": 20,
|
||
"height": 30,
|
||
"sections": [
|
||
{
|
||
"code": "RACK1",
|
||
"type": "HANGER",
|
||
"x": 0, "y": 0,
|
||
"levels": 4,
|
||
"hangersPerLevel": 20
|
||
},
|
||
{
|
||
"code": "RACK2",
|
||
"type": "HANGER",
|
||
"x": 10, "y": 0,
|
||
"levels": 4,
|
||
"hangersPerLevel": 20
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## ✅ Deliverables
|
||
|
||
1. **Prisma schema** with full hierarchy
|
||
2. **API endpoints** for CRUD + plant placement
|
||
3. **Layout Editor** component (Figma-like)
|
||
4. **Room Grid Viewer** with plant status
|
||
5. **Mobile placement flow** with scanning
|
||
6. **METRC-compatible address format**
|
||
7. **Audit reports** by location
|