Grand Diomande Research · Full HTML Reader

Milkmen Delivery - Shared Agent CRM Architecture Plan

-- Many-to-many: agents can share territories CREATE TABLE territory_agents ( territory_id uuid REFERENCES territories(id), agent_id uuid REFERENCES agents(id), role text NOT NULL CHECK (role IN ('seeder', 'closer', 'both')), visit_order integer NOT NULL, -- 1 = visits first, 2 = visits second active boolean DEFAULT true, PRIMARY KEY (territory_id, agent_id) ); ```

Agents That Account for Themselves architecture technical paper candidate score 58 .md

Full Public Reader

Milkmen Delivery - Shared Agent CRM Architecture Plan

Version: 1.0
Date: February 2025
Scope: Rebuild CRM for two-agent (Mohamed + Carson) shared territory system

---

1. Vision & Goals

### The New System
A CRM where two sales agents work the same territories sequentially:
1. Carson (seeder) visits a location first
2. System auto-schedules Mohamed (closer) to visit N days later
3. Both agents see pre-planned calendars built ahead of time
4. One unified dashboard (not separate per-agent views)

### Core Principles
- Shared Territories - Same locations, different visit sequences
- Sequential Workflows - Agent 1 → wait → Agent 2, automatically
- Pre-Planned Schedules - Know your week before it starts
- Unified View - One dashboard, filter by agent

---

2. Data Model

2.1 Updated Tables

`territories` (New - Replaces agent_territories for shared use)

sql
CREATE TABLE territories (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  name text NOT NULL,                    -- "SoHo", "Lower Manhattan"
  region text NOT NULL,                   -- "NYC", "LA"
  boundaries jsonb,                       -- GeoJSON polygon
  center_lat numeric,
  center_lng numeric,
  active boolean DEFAULT true,
  created_at timestamptz DEFAULT now(),
  updated_at timestamptz DEFAULT now()
);

-- Many-to-many: agents can share territories
CREATE TABLE territory_agents (
  territory_id uuid REFERENCES territories(id),
  agent_id uuid REFERENCES agents(id),
  role text NOT NULL CHECK (role IN ('seeder', 'closer', 'both')),
  visit_order integer NOT NULL,           -- 1 = visits first, 2 = visits second
  active boolean DEFAULT true,
  PRIMARY KEY (territory_id, agent_id)
);

`visit_sequences` (New - Core to Sequential Workflow)

sql
CREATE TABLE visit_sequences (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  location_id uuid REFERENCES locations(id) ON DELETE CASCADE,
  territory_id uuid REFERENCES territories(id),

  -- Sequence definition
  current_step integer NOT NULL DEFAULT 1,  -- Which step we're on (1, 2, 3...)
  total_steps integer NOT NULL DEFAULT 2,   -- Total planned steps
  status text CHECK (status IN ('pending', 'in_progress', 'completed', 'paused')),

  -- Timing
  started_at timestamptz,
  completed_at timestamptz,

  created_at timestamptz DEFAULT now(),
  updated_at timestamptz DEFAULT now(),

  UNIQUE(location_id)  -- One active sequence per location
);

CREATE TABLE visit_steps (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  sequence_id uuid REFERENCES visit_sequences(id) ON DELETE CASCADE,
  step_number integer NOT NULL,             -- 1, 2, 3...

  -- Assignment
  agent_id uuid REFERENCES agents(id),
  agent_role text,                          -- 'seeder', 'closer'

  -- Scheduling
  scheduled_date date,
  scheduled_time_window text,               -- 'morning', 'afternoon', 'any'
  days_after_previous integer DEFAULT 7,    -- Auto-schedule N days after step N-1

  -- Execution
  status text CHECK (status IN ('pending', 'scheduled', 'completed', 'skipped', 'rescheduled')),
  completed_at timestamptz,
  completed_by_agent_id uuid REFERENCES agents(id),

  -- Outcome (links to visit_outcomes for details)
  outcome_id uuid REFERENCES visit_outcomes(id),
  outcome_summary text,                     -- Quick reference

  created_at timestamptz DEFAULT now(),
  updated_at timestamptz DEFAULT now(),

  UNIQUE(sequence_id, step_number)
);

`agent_schedules` (New - Pre-Planned Calendars)

sql
CREATE TABLE agent_schedules (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  agent_id uuid REFERENCES agents(id) ON DELETE CASCADE,
  date date NOT NULL,

  -- Daily capacity
  max_visits integer DEFAULT 12,
  scheduled_visits integer DEFAULT 0,

  -- Time blocks
  available_from time DEFAULT '09:00',
  available_until time DEFAULT '17:00',

  -- Status
  status text CHECK (status IN ('open', 'full', 'blocked', 'off')),
  notes text,

  created_at timestamptz DEFAULT now(),
  updated_at timestamptz DEFAULT now(),

  UNIQUE(agent_id, date)
);

-- Pre-generated schedule slots
CREATE TABLE schedule_slots (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  schedule_id uuid REFERENCES agent_schedules(id) ON DELETE CASCADE,
  visit_step_id uuid REFERENCES visit_steps(id),

  -- Slot details
  slot_order integer NOT NULL,
  estimated_time time,
  estimated_duration_minutes integer DEFAULT 30,

  -- Location quick reference
  location_id uuid REFERENCES locations(id),
  location_title text,
  location_address text,

  -- Status
  status text CHECK (status IN ('planned', 'confirmed', 'completed', 'cancelled', 'no_show')),
  completed_at timestamptz,

  created_at timestamptz DEFAULT now(),

  UNIQUE(schedule_id, slot_order)
);

`locations` - Add Fields

sql
ALTER TABLE locations ADD COLUMN IF NOT EXISTS
  current_sequence_id uuid REFERENCES visit_sequences(id),
  next_scheduled_visit_id uuid REFERENCES visit_steps(id),
  sequence_status text;  -- Quick lookup: 'seeding', 'awaiting_closer', 'in_closing', 'completed'

2.2 Supabase Functions

sql
-- Auto-schedule next visit after completion
CREATE OR REPLACE FUNCTION auto_schedule_next_visit()
RETURNS TRIGGER AS $$
DECLARE
  next_step visit_steps%ROWTYPE;
  next_agent_id uuid;
  schedule_date date;
BEGIN
  -- Only run when step is completed
  IF NEW.status = 'completed' AND OLD.status != 'completed' THEN
    -- Find next step in sequence
    SELECT * INTO next_step
    FROM visit_steps
    WHERE sequence_id = NEW.sequence_id
      AND step_number = NEW.step_number + 1;

    IF FOUND THEN
      -- Calculate schedule date
      schedule_date := COALESCE(NEW.completed_at::date, CURRENT_DATE) + next_step.days_after_previous;

      -- Update next step
      UPDATE visit_steps
      SET scheduled_date = schedule_date,
          status = 'scheduled'
      WHERE id = next_step.id;

      -- Create schedule slot for next agent
      -- (Additional logic for slot creation)
    END IF;
  END IF;

  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trigger_auto_schedule_next
  AFTER UPDATE ON visit_steps
  FOR EACH ROW EXECUTE FUNCTION auto_schedule_next_visit();

---

3. Feature Architecture

3.1 Feature Directory Structure

src/features/
├── territories/
│   ├── components/
│   │   ├── TerritoryList.tsx
│   │   ├── TerritoryMap.tsx
│   │   └── TerritoryAgentAssignment.tsx
│   ├── hooks/
│   │   ├── useTerritories.ts
│   │   └── useTerritoryLocations.ts
│   └── index.ts
│
├── scheduling/
│   ├── components/
│   │   ├── AgentCalendar.tsx
│   │   ├── WeekView.tsx
│   │   ├── DaySchedule.tsx
│   │   └── ScheduleSlotCard.tsx
│   ├── hooks/
│   │   ├── useAgentSchedule.ts
│   │   ├── useScheduleBuilder.ts
│   │   └── useAutoScheduler.ts
│   └── services/
│       └── schedule-generator.ts
│
├── visits/
│   ├── components/
│   │   ├── VisitSequenceCard.tsx
│   │   ├── VisitStepProgress.tsx
│   │   ├── VisitOutcomeForm.tsx
│   │   └── SequentialVisitTimeline.tsx
│   ├── hooks/
│   │   ├── useVisitSequence.ts
│   │   ├── useVisitStep.ts
│   │   └── useCompleteVisit.ts
│   └── index.ts
│
├── dashboard/
│   ├── components/
│   │   ├── UnifiedDashboard.tsx
│   │   ├── AgentSwitcher.tsx
│   │   ├── TodaySchedule.tsx
│   │   ├── UpcomingVisits.tsx
│   │   └── TeamMetrics.tsx
│   ├── hooks/
│   │   └── useDashboardData.ts
│   └── index.ts
│
└── shared/
    ├── components/
    │   ├── LocationCard.tsx
    │   ├── AgentBadge.tsx
    │   └── StatusBadge.tsx
    └── hooks/
        └── useLocations.ts

3.2 Core Components

Unified Dashboard

tsx
// src/features/dashboard/components/UnifiedDashboard.tsx
interface UnifiedDashboardProps {
  defaultAgent?: string;  // Pre-select agent
}

export function UnifiedDashboard({ defaultAgent }: UnifiedDashboardProps) {
  const [selectedAgent, setSelectedAgent] = useState<string | 'all'>(defaultAgent || 'all');
  const { agents } = useAgents();
  const { schedule, isLoading } = useAgentSchedule(selectedAgent);

  return (
    <MainLayout>
      {/* Agent Switcher - Top Bar */}
      <AgentSwitcher
        agents={agents}
        selected={selectedAgent}
        onSelect={setSelectedAgent}
      />

      {/* Today's Schedule */}
      <TodaySchedule
        agent={selectedAgent}
        schedule={schedule?.today}
      />

      {/* Week Overview */}
      <WeekView
        agent={selectedAgent}
        schedule={schedule?.week}
      />

      {/* Pending Actions */}
      <PendingActions agent={selectedAgent} />

      {/* Team Metrics (when 'all' selected) */}
      {selectedAgent === 'all' && <TeamMetrics />}
    </MainLayout>
  );
}

Agent Calendar with Pre-Planned Visits

tsx
// src/features/scheduling/components/AgentCalendar.tsx
interface AgentCalendarProps {
  agentId: string;
  weekStart: Date;
}

export function AgentCalendar({ agentId, weekStart }: AgentCalendarProps) {
  const { schedule, regenerate, addSlot, removeSlot } = useAgentSchedule(agentId, weekStart);

  return (
    <div className="grid grid-cols-7 gap-2">
      {schedule.days.map(day => (
        <DayColumn
          key={day.date}
          date={day.date}
          slots={day.slots}
          capacity={day.maxVisits}
          onSlotClick={(slot) => openSlotDetails(slot)}
          onAddClick={() => addSlot(day.date)}
        />
      ))}
    </div>
  );
}

Visit Sequence Progress

tsx
// src/features/visits/components/VisitSequenceCard.tsx
interface VisitSequenceCardProps {
  locationId: string;
}

export function VisitSequenceCard({ locationId }: VisitSequenceCardProps) {
  const { sequence, steps, currentStep } = useVisitSequence(locationId);

  return (
    <Card>
      <CardHeader>
        <CardTitle>{sequence.location.title}</CardTitle>
        <Badge>{sequence.status}</Badge>
      </CardHeader>
      <CardContent>
        {/* Visual timeline showing all steps */}
        <div className="flex items-center gap-2">
          {steps.map((step, i) => (
            <React.Fragment key={step.id}>
              <StepIndicator
                step={step}
                isCurrent={i === currentStep - 1}
              />
              {i < steps.length - 1 && (
                <Arrow days={steps[i + 1].days_after_previous} />
              )}
            </React.Fragment>
          ))}
        </div>

        {/* Current step details */}
        <CurrentStepDetails step={steps[currentStep - 1]} />
      </CardContent>
    </Card>
  );
}

---

4. User Flows

4.1 Daily Flow - Agent Perspective

1. MORNING - Check Today's Schedule
   ├── Open dashboard → See pre-planned visits
   ├── Review each location's context (previous visits, notes)
   └── Confirm/adjust schedule if needed

2. DURING DAY - Execute Visits
   ├── Navigate to location (Google Maps integration)
   ├── Complete visit → Log outcome
   ├── System auto-schedules partner's follow-up
   └── See updated metrics

3. END OF DAY - Review
   ├── See completion stats
   ├── Review tomorrow's schedule
   └── Flag any issues for attention

4.2 Sequential Visit Flow

LOCATION: "Blue Bottle - SoHo"

Step 1: Carson (Seeder) - Day 1
├── Scheduled: Monday 10:00 AM
├── Visit: Introduce product, leave samples
├── Outcome: "Left samples, interested, speak to owner Friday"
└── Auto-trigger: Schedule Mohamed for Day 1 + 5 days = Saturday

Step 2: Mohamed (Closer) - Day 6 (Auto-Scheduled)
├── Scheduled: Saturday 11:00 AM (auto-generated)
├── Context: Sees Carson's notes, outcome
├── Visit: Follow-up with owner
└── Outcome: "Closed! First order: 10 boxes"

Step 3: (Optional) Carson - Day 13
├── If needed: Check-in visit
└── Or: Mark sequence complete

4.3 Schedule Generation Flow

WEEKLY SCHEDULE GENERATION

Input:
├── Territory: "SoHo"
├── Agents: Carson (seeder), Mohamed (closer)
├── Week: Feb 10-14, 2025
└── Settings: 12 visits/day max, 7-day follow-up gap

Process:
1. Get all locations needing seeding (no recent visit)
2. Get all locations awaiting closing (seeded, due for follow-up)
3. Prioritize by: urgency, clustering (nearby locations)
4. Distribute across week:
   - Carson: Seed new locations Mon-Wed
   - Mohamed: Close follow-ups Thu-Fri
   - Both: Flexible time blocks

Output:
├── Carson's Schedule: [Mon: 10 visits, Tue: 12, Wed: 11] (seeding)
├── Mohamed's Schedule: [Thu: 8, Fri: 9] (closing follow-ups)
└── Auto-scheduled follow-ups for next week

---

5. Implementation Phases

### Phase 1: Foundation (Week 1-2)
Goal: Core data model + basic scheduling

#### Tasks:
1. Create new database tables
- `territories`, `territory_agents`
- `visit_sequences`, `visit_steps`
- `agent_schedules`, `schedule_slots`

2. Build core hooks
- `useTerritories()`
- `useVisitSequence(locationId)`
- `useAgentSchedule(agentId, dateRange)`

3. Create territory setup UI
- List/create territories
- Assign agents with roles (seeder/closer)

Deliverables:
- [ ] Database migrations created and applied
- [ ] Territory management page working
- [ ] Basic scheduling hooks functional

---

### Phase 2: Sequential Visits (Week 3-4)
Goal: Two-step visit workflow working

#### Tasks:
1. Visit sequence management
- Create sequence when first visiting location
- Track step progression
- Auto-schedule next step

2. Visit completion flow
- Complete visit → record outcome
- Trigger auto-scheduling
- Update location status

3. Supabase functions
- `auto_schedule_next_visit()` trigger
- `generate_weekly_schedule()` function

Deliverables:
- [ ] Visit sequence cards showing progress
- [ ] Auto-scheduling working on visit completion
- [ ] Location cards show sequence status

---

### Phase 3: Calendar & Scheduling (Week 5-6)
Goal: Pre-planned calendars for each agent

#### Tasks:
1. Calendar views
- Week view with daily slots
- Day view with detailed schedule
- Drag-drop rescheduling

2. Schedule generation
- Batch generate weekly schedules
- Intelligent slot allocation
- Route optimization (clustering)

3. Schedule management
- Add/remove slots
- Reschedule visits
- Block off time

Deliverables:
- [ ] Agent calendar views
- [ ] "Generate Week" button working
- [ ] Drag-drop rescheduling

---

### Phase 4: Unified Dashboard (Week 7-8)
Goal: Single dashboard for both agents

#### Tasks:
1. Dashboard layout
- Agent switcher (dropdown)
- Today's schedule panel
- Upcoming visits widget
- Team metrics (both agents)

2. Filtering & views
- Filter by agent, territory, status
- Map view with assigned routes
- List view with sorting

3. Mobile optimization
- Touch-friendly schedule
- Quick visit logging
- GPS-based check-in

Deliverables:
- [ ] Unified dashboard deployed
- [ ] Agent switching working
- [ ] Mobile-responsive design

---

### Phase 5: Polish & Migration (Week 9-10)
Goal: Production-ready, old data migrated

#### Tasks:
1. Data migration
- Import existing locations
- Create initial sequences
- Backfill visit history

2. Testing & fixes
- End-to-end testing
- Performance optimization
- Edge case handling

3. Documentation & training
- User guide
- Video walkthrough
- API documentation

Deliverables:
- [ ] All existing data migrated
- [ ] No critical bugs
- [ ] Documentation complete

---

6. Technical Decisions

Frontend State Management

TanStack Query (keep)
├── Server state (locations, schedules, agents)
├── Caching with invalidation
└── Optimistic updates for visit completion

Zustand (for local state)
├── UI state (selected agent, filters)
├── Draft schedules before saving
└── Undo/redo for schedule changes

Realtime Updates

Supabase Realtime (existing)
├── Subscribe to schedule_slots changes
├── Live updates when partner completes visit
└── Notifications for auto-scheduled visits

Route Optimization

Single implementation: src/services/route-optimization/
├── Cluster nearby locations
├── Minimize travel between slots
└── Respect time windows

---

7. API Design

New Endpoints (Supabase RPC)

sql
-- Generate weekly schedule for agent
CREATE FUNCTION generate_agent_week(
  p_agent_id uuid,
  p_week_start date,
  p_territory_ids uuid[]
) RETURNS jsonb;

-- Complete a visit step with auto-scheduling
CREATE FUNCTION complete_visit_step(
  p_step_id uuid,
  p_outcome_data jsonb
) RETURNS jsonb;

-- Get agent's schedule for date range
CREATE FUNCTION get_agent_schedule(
  p_agent_id uuid,
  p_start_date date,
  p_end_date date
) RETURNS jsonb;

-- Get unified dashboard data
CREATE FUNCTION get_dashboard_data(
  p_agent_id uuid DEFAULT NULL,  -- NULL = all agents
  p_date date DEFAULT CURRENT_DATE
) RETURNS jsonb;

---

8. Migration Strategy

From Old to New

1. Keep old system running during transition
2. Create new tables alongside existing
3. Build new features on new tables
4. Migrate data incrementally
5. Switch over when ready
6. Archive old code

Data Migration Script

sql
-- Create sequences for locations with visit history
INSERT INTO visit_sequences (location_id, territory_id, current_step, total_steps, status)
SELECT
  l.id,
  t.id,
  CASE
    WHEN l.third_visit IS NOT NULL THEN 3
    WHEN l.second_visit IS NOT NULL THEN 2
    WHEN l.first_visit IS NOT NULL THEN 1
    ELSE 1
  END,
  2,  -- Default to 2-step sequences
  CASE
    WHEN l.is_partner OR l.stage = 'closed_won' THEN 'completed'
    ELSE 'in_progress'
  END
FROM locations l
JOIN territories t ON ST_Contains(t.boundaries, ST_Point(l.longitude, l.latitude));

---

9. Success Metrics

### Key Performance Indicators
| Metric | Target | How to Measure |
|--------|--------|----------------|
| Schedule Adherence | >85
| Follow-up Rate | 100
| Time to Schedule | <2 min | Time to generate weekly schedule |
| Dashboard Load Time | <2s | Page load metrics |
| Mobile Completion Rate | >90

---

10. Risks & Mitigations

RiskImpactMitigation
Over-engineeringDelayed launchStart minimal, iterate
Data migration issuesLost historyComprehensive backup, incremental migration
Schedule conflictsFrustrated usersConflict detection, easy rescheduling
Auto-schedule errorsWrong datesManual override, clear notifications
Performance at scaleSlow UIPagination, lazy loading, indexing

---

Appendix: Quick Reference

### Territory Roles
- Seeder (Carson): First visit, introduce product, leave samples
- Closer (Mohamed): Follow-up visit, close the deal

### Visit Sequence Statuses
- `pending` - Not started
- `in_progress` - At least one step completed
- `completed` - All steps done or closed
- `paused` - On hold (seasonal, etc.)

### Schedule Slot Statuses
- `planned` - Generated but not confirmed
- `confirmed` - Agent accepted
- `completed` - Visit done
- `cancelled` - Removed from schedule
- `no_show` - Agent didn't visit

### Default Timing
- Steps apart: 7 days (configurable)
- Visits per day: 12 max (configurable)
- Working hours: 9 AM - 5 PM (configurable)

Promotion Decision

Promote into a technical note or architecture paper with implementation anchors.

Source Anchor

milkmendelivery/ARCHITECTURE_PLAN.md

Detected Structure

Method · References · Math · Code Anchors · Architecture