Skip to main content

Fulfillment Architecture

This document describes how orders are allocated, picked, and delivered in Switchyard.

Overview

Switchyard uses a hybrid fulfillment model:
SourceWhen UsedFlow
RFC InventoryItems in stock at warehouseinventory_itemspick_listbagtoterobot
Retail SweepItems not in stocksweep_items → driver shops → intake → bagtoterobot
The allocation engine automatically decides which source to use based on inventory availability and economics.

Staff and Roles

The staff table manages employees who can be pickers, drivers, or both:
FieldDescription
is_pickerCan pick orders from RFC inventory
is_driverCan perform sweeps at retailers
user_idOptional link to admin user account
Staff members can have both roles, but cannot perform both simultaneously. Each pick_list is assigned to a picker, and each sweep is assigned to a driver.

Order Allocation

When an order is placed, the allocation engine decides how each item will be fulfilled:

Allocation Logic

  1. Check RFC Inventory (FEFO/FIFO order)
    • If sufficient stock exists → allocate to pick_list
    • Set fulfillment_source = 'inventory'
  2. Fallback to Sweep
    • If insufficient/no inventory → allocate to sweep
    • Set fulfillment_source = 'sweep'
    • Select best retailer based on price

Database Function: allocate_order_item

-- Allocate an order item to inventory or sweep
SELECT * FROM allocate_order_item(
    p_order_item_id := 'order-item-uuid',
    p_sellable_product_id := 'sellable-uuid',
    p_quantity := 2
);
-- Returns: allocation_type, available_quantity, inventory_item_id

Inventory Purchasing Flow

The system can automatically source inventory from retailers, considering bulk vs individual pricing:

Retailer Selection Function

-- Get best retailer for a sellable product (considers bulk pricing)
SELECT * FROM get_best_retailer('sellable-product-uuid');
-- Returns: sellable_id, store_name, unit_price, units_per_purchase, price_per_unit, source_type

Sweep Types

TypePurposeAllocation Table
orderFulfill customer orderssweep_order_allocations
inventoryRestock RFC inventorysweep_inventory_allocations

Routes and Sweeps

Routes

Routes group multiple sweeps for a single driver trip:
FieldDescription
route_dateDate of the route
driver_idStaff member (driver role)
statusscheduled, in_progress, completed

Sweeps

Each sweep is a shopping trip to one retailer:
FieldDescription
route_idParent route (optional)
store_idRetailer store
sweep_type’order’ or ‘inventory’
driver_idStaff member (driver role)

Sweep Items

Items on the sweep manifest include retailer aisle location:
FieldDescription
sellable_product_idWhat to buy
quantityHow many to buy
store_location_textAisle location from retailer_mappings
store_aisleParsed aisle number
unit_priceExpected price
actual_pricePrice driver paid

Pick Lists

RFC picking assignments for warehouse pickers:
FieldDescription
order_idOrder being picked
picker_idStaff member (picker role)
statuspending, in_progress, completed

Pick List Items

Each item includes the specific inventory item to pick (FEFO/FIFO):
FieldDescription
sellable_product_idWhat to pick
inventory_item_idSpecific inventory item (FEFO/FIFO)
quantityHow many to pick
bag_item_idCreated when scanned into bag

Bags, Totes, and Robot Delivery

Assembly Flow

  1. Picking Path: Picker scans items into bags from inventory_items
  2. Intake Path: Intake staff scans sweep items into bags
  3. Bags: Temperature-separated (ambient, chilled, frozen)
  4. Totes: One order → multiple totes → multiple bags each
  5. Robots: One robot per tote (1:1 relationship)

Entity Relationships

EntityContainsRelationship
OrderTotesOne-to-many
ToteBagsOne-to-many
BagBag ItemsOne-to-many
Bag ItemOrder ItemFulfills

Temperature Zones

Bag TypeContents
ambientRoom temperature items
chilledRefrigerated items
frozenFrozen items

FEFO/FIFO Picking

When selecting inventory items to pick:
-- Pick oldest expiring first, then oldest received for non-perishables
SELECT * FROM pick_inventory_fefo_fifo(
    p_sellable_product_id := 'sellable-uuid',
    p_quantity := 2
);
-- Returns: inventory_item_id, available_quantity, expiration_date, received_at, location_id
Always use the FEFO/FIFO functions to select inventory. Never pick items randomly - this ensures proper rotation and reduces waste.

System Interaction Summary

SystemReads FromWrites To
Scrapers-scraped_products, retailer_mappings, retailer_pricing
Admin DashboardAll tablessellable_products, variant_groups, inventory_items, orders, sweeps, staff
Zebra Scanners (Picking)sellable_products, inventory_items, pick_listspick_list_items, bag_items, bags
Zebra Scanners (Intake)sweeps, sweep_itemsbag_items, bags, inventory_items
Store API (Frontend App)sellable_products, variant_groups, carts, promotions, locationscarts, cart_line_items, orders, order_items, payment_sessions
Driver Approutes, sweeps, sweep_items, retailer_mappingssweep_items (status, picked_quantity, actual_price)
Robotstotes, orderstote status updates
Allocation Engineorders, inventory_items, retailer_mappings, sweepsorder_items, sweep_items, pick_list_items, sweep_order_allocations
Inventory Engineinventory_items, retailer_pricing, bulk_sku_relationshipssweeps (type=inventory), sweep_items, sweep_inventory_allocations
Payment Providerspayment_sessionspayments, captures, refunds