Protocol Raw System Architecture¶
Layer 2: Component Registry¶
Version: 2.0 Status: Production Reference Last Updated: February 2026 Owner: Protocol Raw Operations Note: Section 1 auto-generated from live database introspection on 2026-02-21
1. Database Schema (raw_ops)¶
All operational data lives in the raw_ops schema in Supabase PostgreSQL.
1.1 Core Customer & Order Tables¶
customers¶
Purpose: Customer master data synced from Shopify
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
shopify_customer_id |
text | YES | Shopify Customer ID |
email |
citext | YES | |
first_name |
text | YES | First Name |
last_name |
text | YES | Last Name |
phone |
text | YES | Phone |
address_json |
jsonb | YES | Address JSON |
marketing_opt_in |
boolean | YES | Marketing Opt In (default: false) |
created_at |
timestamptz | NO | Record created (default: now()) |
address1 |
text | YES | Address1 |
address2 |
text | YES | Address2 |
city |
text | YES | City |
region |
text | YES | Region |
postcode |
text | YES | Postcode |
country_code |
text | YES | Country Code |
portal_first_visited_at |
timestamptz | YES | Portal First Visited At |
persona |
text | YES | Persona |
acquisition_diet |
text | YES | Acquisition Diet |
Indexes:
- PRIMARY KEY on (id)
- customers_email_key UNIQUE on (email)
- customers_shopify_customer_id_key UNIQUE on (shopify_customer_id)
- idx_customers_acquisition_diet on (acquisition_diet)
- idx_customers_portal_first_visited on (portal_first_visited_at)
orders¶
Purpose: Order headers from Shopify webhooks
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
shopify_order_id |
text | YES | Shopify Order ID |
customer_id |
uuid | YES | FK to customers |
subtotal_ex_vat |
numeric | NO | Subtotal Ex Vat (default: 0) |
vat |
numeric | NO | Vat (default: 0) |
total_inc_vat |
numeric | NO | Total Inc Vat (default: 0) |
currency |
text | NO | Currency (default: 'GBP') |
status |
order_status | NO | Status (default: 'PENDING') |
is_subscription |
boolean | NO | Is Subscription (default: false) |
utm |
jsonb | YES | Utm |
ordered_at |
timestamptz | NO | Ordered At (default: now()) |
created_at |
timestamptz | NO | Record created (default: now()) |
order_number_label |
text | YES | Order Number Label |
created_at_shopify |
timestamptz | YES | Created At Shopify |
updated_at_shopify |
timestamptz | YES | Updated At Shopify |
fulfillment_status |
text | YES | Fulfillment Status |
subtotal_gbp |
numeric | YES | Subtotal GBP |
tax_gbp |
numeric | YES | Tax GBP |
discount_gbp |
numeric | YES | Discount GBP |
total_gbp |
numeric | YES | Total GBP |
ship_name |
text | YES | Ship Name |
ship_phone |
text | YES | Ship Phone |
ship_address1 |
text | YES | Ship Address1 |
ship_address2 |
text | YES | Ship Address2 |
ship_city |
text | YES | Ship City |
ship_postcode |
text | YES | Ship Postcode |
ship_country |
text | YES | Ship Country |
notes_json |
jsonb | YES | Notes JSON |
tags |
ARRAY | YES | Tags |
utm_json |
jsonb | YES | Utm JSON |
export_state |
text | YES | Export State |
exported_at |
timestamptz | YES | Exported At |
external_order_ref |
text | YES | External Order Ref |
acked_at |
timestamptz | YES | Acked At |
manifest_version |
text | YES | Manifest Version (default: 'v1') |
referral_slug |
text | YES | Referral Slug |
referral_id |
uuid | YES | FK to referrals |
discount_codes |
jsonb | YES | Discount Codes |
referral_processed_at |
timestamptz | YES | Referral Processed At |
credit_checked_at |
timestamptz | YES | Credit Checked At |
credit_applied_pence |
integer | YES | Credit Applied Pence (default: 0) |
Indexes:
- PRIMARY KEY on (id)
- idx_orders_allocation_pending on (status, created_at) WHERE (status = 'PAID'::raw_ops.order_status)
- idx_orders_created_at_desc on (created_at)
- idx_orders_credit_pending on (created_at) WHERE (credit_checked_at IS NULL)
- idx_orders_customer_recent on (customer_id, ordered_at)
- idx_orders_export_eligibility on (status, export_state, ordered_at) WHERE ((status = 'PAID'::raw_ops.order_status) AND (export_state IS NULL))
- idx_orders_export_state on (status, export_state)
- idx_orders_paid_unallocated on (created_at) WHERE (status = 'PAID'::raw_ops.order_status)
- idx_orders_referral_id on (referral_id) WHERE (referral_id IS NOT NULL)
- orders_ordered_at_idx on (ordered_at)
- orders_ordered_at_idx1 on (ordered_at)
- orders_shopify_order_id_key UNIQUE on (shopify_order_id)
- uq_orders_shopify_id UNIQUE on (shopify_order_id)
order_items¶
Purpose: Line items per order
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
order_id |
uuid | NO | FK to orders |
product_id |
uuid | YES | FK to products |
qty |
integer | NO | Qty |
unit_price_ex_vat |
numeric | NO | Unit Price Ex Vat |
shopify_line_item_id |
text | YES | Shopify Line Item ID |
shopify_product_id |
text | YES | Shopify Product ID |
sku |
text | YES | SKU |
product_name |
text | YES | Product Name |
quantity |
integer | YES | Quantity |
unit_price_gbp |
numeric | YES | Unit Price GBP |
fulfilled_qty |
integer | NO | Fulfilled Qty (default: 0) |
pet_number |
integer | YES | Pet Number |
pet_name |
text | YES | Pet Name |
daily_grams |
integer | YES | Daily Grams |
life_stage |
text | YES | Life Stage |
Indexes:
- PRIMARY KEY on (id)
- idx_order_items_allocation_lookup on (order_id, id)
- idx_order_items_order_id on (order_id)
- idx_order_items_order_lookup on (order_id, id, product_id)
- idx_order_items_pet on (order_id, pet_number) WHERE (pet_number IS NOT NULL)
- idx_order_items_product on (product_id) WHERE (product_id IS NOT NULL)
- order_items_shopify_line_item_id_key UNIQUE on (shopify_line_item_id)
order_notes¶
Purpose: Internal notes attached to orders
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
created_at |
timestamptz | YES | Record created (default: now()) |
order_id |
uuid | NO | FK to orders |
author |
text | NO | Author |
note |
text | NO | Note |
note_type |
text | YES | Note Type (default: 'general') |
support_ticket_id |
uuid | YES | FK to support_tickets |
refund_id |
uuid | YES | FK to refunds |
Indexes:
- PRIMARY KEY on (id)
- idx_order_notes_created on (created_at)
- idx_order_notes_order on (order_id)
products¶
Purpose: Product catalog
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
sku |
text | NO | SKU |
name |
text | NO | Name |
kg |
numeric | NO | kg |
is_active |
boolean | NO | Is Active (default: true) |
created_at |
timestamptz | NO | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- products_sku_key UNIQUE on (sku)
formulations¶
Purpose: Recipe/formulation definitions
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
version |
text | NO | Version |
name |
text | NO | Name |
description |
text | YES | Description |
effective_from |
date | NO | Effective From |
effective_until |
date | YES | Effective Until |
shopify_page_url |
text | NO | Shopify Page URL |
lab_certificate_pdf_url |
text | YES | Lab Certificate PDF URL |
lab_name |
text | YES | Lab Name |
certificate_reference |
text | YES | Certificate Reference |
analysis_date |
date | YES | Analysis Date |
fediaf_verified |
boolean | YES | Fediaf Verified (default: true) |
created_at |
timestamptz | YES | Record created (default: now()) |
updated_at |
timestamptz | YES | Last modified (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- formulations_version_key UNIQUE on (version)
- idx_formulations_current on (effective_until) WHERE (effective_until IS NULL)
- idx_formulations_effective on (effective_from, effective_until)
feeding_plans¶
Purpose: Customer feeding plan configurations
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
customer_id |
uuid | NO | FK to customers |
dog_name |
text | YES | Dog Name |
dog_weight_kg |
numeric | NO | Dog Weight kg |
life_stage |
text | NO | Life Stage (default: 'adult') |
activity_level |
text | NO | Activity Level (default: 'moderate') |
is_neutered |
boolean | NO | Is Neutered (default: true) |
body_condition |
text | YES | Body Condition (default: 'ideal') |
daily_grams |
integer | NO | Daily Grams |
daily_kcal |
integer | NO | Daily Kcal |
box_size |
text | NO | Box Size (default: '12kg') |
created_at |
timestamptz | NO | Record created (default: now()) |
updated_at |
timestamptz | NO | Last modified (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_feeding_plans_customer on (customer_id)
1.2 Batch & Inventory Tables¶
batches¶
Purpose: Production batches with QA status
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
batch_code |
text | NO | Batch Code |
product_id |
uuid | YES | FK to products |
produced_at |
date | NO | Produced At |
status |
batch_status | NO | Status (default: 'QA_HOLD') |
notes |
text | YES | Notes |
created_at |
timestamptz | NO | Record created (default: now()) |
kg_produced |
numeric | YES | kg Produced |
total_cost_gbp |
numeric | YES | Total Cost GBP |
cogs_per_kg_gbp |
numeric | YES | Cogs Per kg GBP |
proof_url |
text | YES | Proof URL |
production_date |
date | YES | Production Date |
expiry_date |
date | YES | Expiry Date |
storage_temp_min |
numeric | YES | Storage Temp Min |
storage_temp_max |
numeric | YES | Storage Temp Max |
packaging_type |
varchar(50) | YES | Packaging Type |
qty_on_hand |
numeric | YES | Qty On Hand (default: 0) |
qty_reserved |
numeric | YES | Qty Reserved (default: 0) |
qr_image_url |
text | YES | Qr Image URL |
proof_insert_pdf_url |
text | YES | Proof Insert PDF URL |
proof_published_at |
timestamptz | YES | Proof Published At |
inserts_printed_at |
timestamptz | YES | Inserts Printed At |
inserts_shipped_to_3pl_at |
timestamptz | YES | Inserts Shipped To 3pl At |
proof_insert_generated_at |
timestamptz | YES | Proof Insert Generated At |
qa_hold_started_at |
timestamptz | YES | QA Hold Started At |
released_at |
timestamptz | YES | Released At |
formulation_id |
uuid | YES | FK to formulations |
label_pdf_url |
text | YES | Label PDF URL |
qty_soft_reserved |
integer | YES | Qty Soft Reserved (default: 0) |
last_reservation_at |
timestamptz | YES | Last Reservation At |
public_batch_id |
text | YES | Public Batch ID |
Indexes:
- PRIMARY KEY on (id)
- batches_batch_code_key UNIQUE on (batch_code)
- batches_batch_code_unique UNIQUE on (batch_code)
- batches_public_batch_id_key UNIQUE on (public_batch_id)
- batches_status_idx on (status)
- batches_status_idx1 on (status)
- idx_batches_availability on (product_id, status, qty_on_hand, qty_reserved) WHERE (status = 'RELEASED'::raw_ops.batch_status)
- idx_batches_batch_code on (batch_code)
- idx_batches_batch_code_released on (batch_code) WHERE (status = 'RELEASED'::raw_ops.batch_status)
- idx_batches_fefo on (expiry_date, status) WHERE (status = 'RELEASED'::raw_ops.batch_status)
- idx_batches_fefo_lookup on (product_id, expiry_date, produced_at) WHERE (status = 'RELEASED'::raw_ops.batch_status)
- idx_batches_formulation_id on (formulation_id)
- idx_batches_proof_insert_pdf_url on (proof_insert_pdf_url)
- idx_batches_proof_insert_workflow on (status, proof_url, proof_insert_pdf_url) WHERE (status = 'RELEASED'::raw_ops.batch_status)
- idx_batches_public_id on (public_batch_id)
- idx_batches_qa_hold on (id) WHERE (status = 'QA_HOLD'::raw_ops.batch_status)
- idx_batches_released_available on (status) WHERE (status = 'RELEASED'::raw_ops.batch_status)
- idx_batches_status on (status) WHERE (status = 'RELEASED'::raw_ops.batch_status)
- idx_batches_status_released on (status, proof_published_at) WHERE (status = 'RELEASED'::raw_ops.batch_status)
batch_status_history¶
Purpose: Audit trail of batch status changes
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
batch_id |
uuid | NO | FK to batches |
old_status |
text | YES | Old Status |
new_status |
text | NO | New Status |
triggered_by |
text | YES | Triggered By |
lab_result_id |
uuid | YES | FK to lab_results |
created_at |
timestamptz | YES | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_batch_status_history_batch_id on (batch_id)
- idx_batch_status_history_created_at on (created_at)
inventory¶
Purpose: Stock levels by batch and location
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
batch_id |
uuid | NO | FK to batches |
location |
text | NO | Location |
kg_available |
numeric | NO | kg Available |
updated_at |
timestamptz | NO | Last modified (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- inventory_batch_id_location_key UNIQUE on (batch_id, location)
inventory_movements¶
Purpose: Complete audit trail of inventory changes
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
sku |
text | NO | SKU |
movement_type |
text | NO | Movement Type |
qty_change |
integer | NO | Qty Change |
qty_before |
integer | NO | Qty Before |
qty_after |
integer | NO | Qty After |
reference_id |
uuid | YES | Reference ID |
reference_type |
text | YES | Reference Type |
notes |
text | YES | Notes |
created_at |
timestamptz | NO | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_inventory_movements_created_at on (created_at)
- idx_inventory_movements_sku on (sku)
inventory_on_hand¶
Purpose: Current on-hand inventory snapshot
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
sku |
text | NO | SKU |
qty_available |
integer | NO | Qty Available (default: 0) |
location |
text | YES | Location (default: '3PL-PRIMARY') |
updated_at |
timestamptz | NO | Last modified (default: now()) |
created_at |
timestamptz | NO | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- inventory_on_hand_sku_key UNIQUE on (sku)
inventory_planning_config¶
Purpose: Inventory planning parameters
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
config_key |
text | NO | Config Key |
config_value |
numeric | NO | Config Value |
description |
text | YES | Description |
updated_at |
timestamptz | YES | Last modified (default: now()) |
updated_by |
text | YES | Updated By |
Indexes:
- PRIMARY KEY on (id)
- inventory_planning_config_config_key_key UNIQUE on (config_key)
allocations¶
Purpose: Links order items to batches (FEFO)
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: uuid_generate_v4()) |
order_item_id |
uuid | NO | FK to order_items |
batch_id |
uuid | NO | FK to batches |
qty_allocated |
numeric | NO | Qty Allocated |
created_at |
timestamptz | NO | Record created (default: now()) |
is_partial |
boolean | YES | Is Partial (default: false) |
allocation_group_id |
uuid | YES | Allocation Group ID |
remaining_qty |
integer | YES | Remaining Qty (default: 0) |
Indexes:
- PRIMARY KEY on (id)
- idx_allocations_batch on (batch_id)
- idx_allocations_batch_lookup on (order_item_id, batch_id)
- idx_allocations_batch_tracking on (batch_id, qty_allocated)
- idx_allocations_group on (allocation_group_id) WHERE (allocation_group_id IS NOT NULL)
- idx_allocations_item_lookup on (order_item_id)
- idx_allocations_order_item on (order_item_id)
- idx_allocations_order_item_id on (order_item_id)
allocation_queue¶
Purpose: Pending allocation requests
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
order_id |
uuid | NO | FK to orders |
order_item_id |
uuid | YES | FK to order_items |
priority |
integer | YES | Priority (default: 100) |
state |
text | NO | State (default: 'pending') |
attempts |
integer | YES | Attempts (default: 0) |
max_attempts |
integer | YES | Max Attempts (default: 5) |
last_error |
text | YES | Last Error |
created_at |
timestamptz | NO | Record created (default: now()) |
started_at |
timestamptz | YES | Started At |
completed_at |
timestamptz | YES | Completed At |
Indexes:
- PRIMARY KEY on (id)
- idx_allocation_queue_order on (order_id)
- idx_allocation_queue_pending on (priority, created_at) WHERE (state = 'pending'::text)
- idx_allocation_queue_state on (state, created_at)
allocation_alert_config¶
Purpose: Alert thresholds for allocation monitoring
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
alert_type |
text | NO | Alert Type |
threshold_value |
numeric | NO | Threshold Value |
enabled |
boolean | YES | Enabled (default: true) |
description |
text | YES | Description |
created_at |
timestamptz | YES | Record created (default: now()) |
updated_at |
timestamptz | YES | Last modified (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- allocation_alert_config_alert_type_key UNIQUE on (alert_type)
allocation_alert_history¶
Purpose: History of allocation alerts triggered
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
alert_type |
text | NO | Alert Type |
severity |
text | NO | Severity |
message |
text | NO | Message |
details |
jsonb | YES | Details |
resolved_at |
timestamptz | YES | Resolved At |
created_at |
timestamptz | YES | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_alert_history_created on (created_at)
- idx_alert_history_unresolved on (alert_type, created_at) WHERE (resolved_at IS NULL)
lab_results¶
Purpose: Parsed laboratory test results
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
batch_id |
uuid | NO | FK to batches |
lab_name |
text | NO | Lab Name |
report_pdf_url |
text | NO | Report PDF URL |
salmonella_absent |
boolean | YES | Salmonella Absent |
enterobacteriaceae_cfu_per_g |
integer | YES | Enterobacteriaceae Cfu Per G |
outcome |
lab_outcome | NO | Outcome |
collected_at |
date | YES | Collected At |
reported_at |
timestamptz | NO | Reported At (default: now()) |
raw_email_metadata |
jsonb | YES | Raw Email Metadata |
confidence_score |
numeric | YES | Confidence Score |
pdf_hash |
text | YES | PDF Hash |
parsed_by |
text | YES | Parsed By (default: 'openai') |
listeria_absent |
boolean | YES | Listeria Absent |
Indexes:
- PRIMARY KEY on (id)
- idx_lab_results_batch_id on (batch_id)
- idx_lab_results_batch_id_reported on (batch_id, reported_at)
- lab_results_batch_id_key UNIQUE on (batch_id)
- lab_results_outcome_idx on (outcome)
- lab_results_outcome_idx1 on (outcome)
- unique_batch_result UNIQUE on (batch_id, lab_name, reported_at)
- ux_lab_results_batch_id UNIQUE on (batch_id)
lab_pdf_patterns¶
Purpose: Patterns for parsing lab PDF reports
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
lab_name |
text | NO | Lab Name |
batch_code_regex |
text | NO | Batch Code Regex |
salmonella_pattern |
text | NO | Salmonella Pattern |
listeria_pattern |
text | YES | Listeria Pattern |
enterobacteriaceae_pattern |
text | NO | Enterobacteriaceae Pattern |
notes |
text | YES | Notes |
created_at |
timestamptz | YES | Record created (default: now()) |
updated_at |
timestamptz | YES | Last modified (default: now()) |
Indexes:
- PRIMARY KEY on (id)
1.3 Fulfillment & Dispatch Tables¶
shipments¶
Purpose: Dispatch and tracking data
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
order_id |
uuid | NO | FK to orders |
batch_id |
uuid | YES | FK to batches |
courier |
text | YES | Courier |
tracking_no |
text | YES | Tracking No |
status |
shipment_status | NO | Status (default: 'CREATED') |
dispatched_at |
timestamptz | YES | Dispatched At |
delivered_at |
timestamptz | YES | Delivered At |
address_json |
jsonb | YES | Address JSON |
created_at |
timestamptz | NO | Record created (default: now()) |
courier_cost_gbp |
numeric | YES | Courier Cost GBP |
packaging_cost_gbp |
numeric | YES | Packaging Cost GBP |
coolant_cost_gbp |
numeric | YES | Coolant Cost GBP |
service |
text | YES | Service |
dispatch_temp_recorded |
numeric | YES | Dispatch Temp Recorded |
coolant_units_used |
smallint | YES | Coolant Units Used |
courier_temp_compliance |
varchar(20) | YES | Courier Temp Compliance (default: 'PENDING') |
Indexes:
- PRIMARY KEY on (id)
- idx_shipments_batch_id on (batch_id)
- idx_shipments_created_at on (created_at)
- idx_shipments_dispatched_at_desc on (dispatched_at)
- idx_shipments_order_id on (order_id)
- idx_shipments_status_active on (status) WHERE (status <> ALL (ARRAY['DELIVERED'::raw_ops.shipment_status, 'RETURNED'::raw_ops.shipment_status]))
- idx_shipments_temp_audit on (courier_temp_compliance, created_at) WHERE (dispatch_temp_recorded IS NOT NULL)
- idx_shipments_tracking_no on (tracking_no)
- idx_shipments_tracking_no_unique UNIQUE on (tracking_no) WHERE (tracking_no IS NOT NULL)
- shipments_status_dispatched_at_idx on (status, dispatched_at)
- shipments_status_dispatched_at_idx1 on (status, dispatched_at)
courier_events¶
Purpose: Courier tracking event log
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
shipment_id |
uuid | NO | FK to shipments |
event_code |
text | NO | Event Code |
event_text |
text | YES | Event Text |
event_time |
timestamptz | NO | Event Time |
raw_payload |
jsonb | YES | Raw Payload |
event_type |
varchar(50) | YES | Event Type |
requires_customer_notification |
boolean | YES | Requires Customer Notification (default: false) |
customer_notified_at |
timestamptz | YES | Customer Notified At |
notification_channel |
varchar(20) | YES | Notification Channel |
created_at |
timestamptz | YES | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- courier_events_event_time_idx on (event_time)
- courier_events_event_time_idx1 on (event_time)
- idx_courier_events_created_at on (created_at)
- idx_courier_events_notification_pending on (requires_customer_notification, customer_notified_at) WHERE (requires_customer_notification = true)
- idx_courier_events_shipment_id on (shipment_id)
- idx_courier_events_shipment_recent on (shipment_id, event_time)
- idx_courier_events_shipment_time on (shipment_id, event_time)
courier_event_mapping¶
Purpose: Mapping of courier event codes to statuses
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
courier_name |
varchar(50) | NO | Courier Name |
event_code |
text | NO | Event Code |
event_type |
varchar(50) | NO | Event Type |
requires_customer_notification |
boolean | YES | Requires Customer Notification (default: false) |
notification_template_id |
varchar(100) | YES | Notification Template ID |
customer_message_short |
text | YES | Customer Message Short |
internal_priority |
varchar(20) | YES | Internal Priority |
created_at |
timestamptz | YES | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- courier_event_mapping_courier_name_event_code_key UNIQUE on (courier_name, event_code)
- idx_courier_event_mapping_code on (courier_name, event_code)
- idx_courier_mapping_lookup on (courier_name, event_code)
packing_instructions¶
Purpose: PCM requirements per order
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
bigint | NO | Primary key (default: nextval('raw_ops.packing_instructions_id_seq') |
order_id |
uuid | YES | FK to orders |
shopify_order_id |
text | YES | Shopify Order ID |
box_size |
varchar(10) | NO | Box Size |
insulation_type |
varchar(50) | NO | Insulation Type (default: 'Wool') |
pcm_mass_kg |
numeric | NO | PCM Mass kg |
pcm_packs_count |
smallint | NO | PCM Packs Count |
ambient_forecast_max |
numeric | YES | Ambient Forecast Max |
transit_hours_expected |
smallint | YES | Transit Hours Expected |
forecast_retrieved_at |
timestamptz | YES | Forecast Retrieved At (default: now()) |
risk_level |
varchar(20) | NO | Risk Level |
logger_required |
boolean | YES | Logger Required (default: false) |
special_notes |
text | YES | Special Notes |
created_at |
timestamptz | YES | Record created (default: now()) |
calculated_by |
varchar(50) | YES | Calculated By (default: 'automated') |
calculation_version |
varchar(10) | YES | Calculation Version (default: '1.0') |
batch_id |
uuid | YES | FK to batches |
Indexes:
- PRIMARY KEY on (id)
- idx_packing_instructions_batch_id on (batch_id)
- idx_packing_instructions_calc_version on (calculation_version)
- idx_packing_instructions_created_at on (created_at)
- idx_packing_instructions_order_id on (order_id)
- idx_packing_instructions_shopify_order_id on (shopify_order_id)
- unique_order_instruction UNIQUE on (order_id)
pack_quality_checks¶
Purpose: Quality check records during packing
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
bigint | NO | Primary key (default: nextval('raw_ops.pack_quality_checks_id_seq') |
order_id |
uuid | YES | FK to orders |
packing_instruction_id |
bigint | YES | FK to packing_instructions |
shopify_order_id |
text | YES | Shopify Order ID |
expected_total_weight_kg |
numeric | NO | Expected Total Weight kg |
actual_weight_kg |
numeric | NO | Actual Weight kg |
weight_variance_kg |
numeric | YES | Weight Variance kg |
weight_variance_pct |
numeric | YES | Weight Variance Pct |
pack_started_at |
timestamptz | YES | Pack Started At |
pack_completed_at |
timestamptz | YES | Pack Completed At |
pack_time_seconds |
integer | YES | Pack Time Seconds |
logger_activated |
boolean | YES | Logger Activated (default: false) |
void_fill_complete |
boolean | YES | Void Fill Complete (default: false) |
visual_check_pass |
boolean | YES | Visual Check Pass (default: false) |
packed_by |
varchar(100) | NO | Packed By |
packed_at |
timestamptz | YES | Packed At (default: now()) |
photo_url |
text | YES | Photo URL |
notes |
text | YES | Notes |
created_at |
timestamptz | YES | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_pack_quality_checks_order_id on (order_id)
- idx_pack_quality_checks_packed_at on (packed_at)
- idx_pack_quality_checks_packed_by on (packed_by)
export_outbox¶
Purpose: Orders queued for export to fulfillment
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
batch_id |
text | NO | Batch ID |
order_ids |
ARRAY | NO | Order Ids |
csv_data |
jsonb | NO | Csv Data |
status |
text | NO | Status (default: 'pending') |
attempts |
integer | NO | Attempts (default: 0) |
last_attempt_at |
timestamptz | YES | Last Attempt At |
error_message |
text | YES | Error Message |
created_at |
timestamptz | NO | Record created (default: now()) |
sent_at |
timestamptz | YES | Sent At |
confirmed_at |
timestamptz | YES | Confirmed At |
Indexes:
- PRIMARY KEY on (id)
- idx_outbox_failed on (status, attempts, last_attempt_at) WHERE (status = 'failed'::text)
- idx_outbox_pending on (status, created_at) WHERE (status = 'pending'::text)
- idx_outbox_retry on (status, attempts) WHERE ((status = 'failed'::text) AND (attempts < 3))
dispatch_import_queue¶
Purpose: Incoming dispatch data from 3PL
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
external_order_id |
text | NO | External Order ID |
dispatch_data |
jsonb | NO | Dispatch Data |
processing_status |
text | NO | Processing Status (default: 'pending') |
created_at |
timestamptz | NO | Record created (default: now()) |
processed_at |
timestamptz | YES | Processed At |
error_message |
text | YES | Error Message |
Indexes:
- PRIMARY KEY on (id)
- idx_dispatch_queue_order on (external_order_id)
- idx_dispatch_queue_status on (processing_status, created_at) WHERE (processing_status = ANY (ARRAY['pending'::text, 'failed'::text]))
processed_3pl_files¶
Purpose: Track processed 3PL import files
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
file_name |
text | NO | File Name |
file_hash |
text | NO | File Hash |
processed_at |
timestamptz | NO | Processed At (default: now()) |
record_count |
integer | NO | Record Count |
source |
text | NO | Source (default: 'manual') |
Indexes:
- PRIMARY KEY on (id)
- idx_processed_3pl_files_hash on (file_hash)
- processed_3pl_files_file_hash_key UNIQUE on (file_hash)
temperature_audits¶
Purpose: Temperature monitoring audit records
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
shipment_id |
uuid | NO | FK to shipments |
logger_core_csv_url |
text | YES | Logger Core Csv URL |
logger_corner_csv_url |
text | YES | Logger Corner Csv URL |
arrival_core_celsius |
numeric | YES | Arrival Core Celsius |
arrival_corner_celsius |
numeric | YES | Arrival Corner Celsius |
pass_class |
text | YES | Pass Class |
created_at |
timestamptz | NO | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
temperature_logger_data¶
Purpose: Raw temperature logger readings
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
bigint | NO | Primary key (default: nextval('raw_ops.temperature_logger_data_id_seq') |
order_id |
uuid | YES | FK to orders |
shipment_id |
uuid | YES | FK to shipments |
logger_id |
text | NO | Logger ID |
logger_type |
text | YES | Logger Type (default: 'USB') |
readings |
jsonb | NO | Readings |
min_temp_c |
numeric | YES | Min Temp C |
max_temp_c |
numeric | YES | Max Temp C |
avg_temp_c |
numeric | YES | Avg Temp C |
time_above_5c_minutes |
integer | YES | Time Above 5c Minutes |
time_above_10c_minutes |
integer | YES | Time Above 10c Minutes |
trip_start_at |
timestamptz | YES | Trip Start At |
trip_end_at |
timestamptz | YES | Trip End At |
trip_duration_hours |
numeric | YES | Trip Duration Hours |
cold_chain_intact |
boolean | YES | Cold Chain Intact |
breach_detected |
boolean | YES | Breach Detected (default: false) |
breach_details |
text | YES | Breach Details |
data_uploaded_at |
timestamptz | YES | Data Uploaded At (default: now()) |
data_source |
text | YES | Data Source |
raw_file_url |
text | YES | Raw File URL |
created_at |
timestamptz | YES | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_temp_logger_cold_chain_intact on (cold_chain_intact)
- idx_temp_logger_logger_id on (logger_id)
- idx_temp_logger_order_id on (order_id)
- idx_temp_logger_shipment_id on (shipment_id)
- idx_temp_logger_uploaded_at on (data_uploaded_at)
high_risk_postcodes¶
Purpose: Postcodes requiring extra cold chain protection
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
bigint | NO | Primary key (default: nextval('raw_ops.high_risk_postcodes_id_seq') |
postcode_prefix |
text | NO | Postcode Prefix |
region_name |
text | NO | Region Name |
typical_transit_hours |
integer | NO | Typical Transit Hours |
courier_service_level |
text | YES | Courier Service Level |
delivery_policy |
text | NO | Delivery Policy |
checkout_message |
text | NO | Checkout Message |
is_active |
boolean | YES | Is Active (default: true) |
notes |
text | YES | Notes |
created_at |
timestamptz | YES | Record created (default: now()) |
updated_at |
timestamptz | YES | Last modified (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- high_risk_postcodes_postcode_prefix_key UNIQUE on (postcode_prefix)
- idx_high_risk_postcodes_active on (is_active) WHERE (is_active = true)
- idx_high_risk_postcodes_prefix on (postcode_prefix)
allowed_delivery_areas¶
Purpose: Delivery coverage area definitions
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
bigint | NO | Primary key (default: nextval('raw_ops.allowed_delivery_areas_id_seq') |
postcode_prefix |
text | NO | Postcode Prefix |
area_name |
text | NO | Area Name |
phase |
text | NO | Phase |
enabled_from_date |
date | YES | Enabled From Date |
typical_transit_hours |
integer | NO | Typical Transit Hours (default: 48) |
is_active |
boolean | YES | Is Active (default: true) |
notes |
text | YES | Notes |
created_at |
timestamptz | YES | Record created (default: now()) |
updated_at |
timestamptz | YES | Last modified (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_allowed_delivery_areas_active on (is_active) WHERE (is_active = true)
- idx_allowed_delivery_areas_prefix on (postcode_prefix)
1.4 Support & Customer Service Tables¶
support_tickets¶
Purpose: Customer support requests
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
customer_id |
uuid | YES | FK to customers |
customer_email |
text | NO | Customer Email |
source |
text | YES | Source (default: 'email') |
subject |
text | YES | Subject |
body |
text | YES | Body |
attachments |
jsonb | YES | Attachments |
ai_category |
text | YES | AI Category |
ai_confidence |
numeric | YES | AI Confidence |
ai_draft_response |
text | YES | AI Draft Response |
ai_recommended_action |
text | YES | AI Recommended Action |
ai_escalation_reason |
text | YES | AI Escalation Reason |
status |
text | YES | Status (default: 'pending') |
final_response |
text | YES | Final Response |
resolution_action |
text | YES | Resolution Action |
resolved_by |
text | YES | Resolved By |
related_batch_code |
text | YES | Related Batch Code |
related_order_id |
uuid | YES | Related Order ID |
created_at |
timestamptz | YES | Record created (default: now()) |
ai_processed_at |
timestamptz | YES | AI Processed At |
human_reviewed_at |
timestamptz | YES | Human Reviewed At |
resolved_at |
timestamptz | YES | Resolved At |
response_sent_at |
timestamptz | YES | Response Sent At |
approved_by |
text | YES | Approved By |
approved_at |
timestamptz | YES | Approved At |
response_channel |
text | YES | Response Channel |
edit_reason |
text | YES | Edit Reason |
original_draft |
text | YES | Original Draft |
crisp_session_id |
text | YES | Crisp Session ID |
is_pre_purchase |
boolean | YES | Is Pre Purchase (default: true) |
assigned_to |
text | YES | Assigned To |
response_method |
text | YES | Response Method |
human_edited |
boolean | YES | Human Edited (default: false) |
edit_distance |
integer | YES | Edit Distance |
actions_taken |
jsonb | YES | Actions Taken (default: '[]') |
replacement_order_id |
text | YES | Replacement Order ID |
first_viewed_at |
timestamptz | YES | First Viewed At |
internal_notes |
jsonb | YES | Internal Notes (default: '[]') |
outbound_reason |
text | YES | Outbound Reason |
is_outbound |
boolean | YES | Is Outbound (default: false) |
reply_count |
integer | YES | Reply Count (default: 0) |
last_customer_reply_at |
timestamptz | YES | Last Customer Reply At |
resolution_type |
text | YES | Resolution Type |
resolution_category |
text | YES | Resolution Category |
resolution_summary |
text | YES | Resolution Summary |
draft_usage |
text | YES | Draft Usage |
last_response_at |
timestamptz | YES | Last Response At |
email_message_id |
text | YES | Email Message ID |
email_references |
text | YES | Email References |
csat_survey_scheduled_for |
timestamptz | YES | Csat Survey Scheduled For |
csat_survey_sent_at |
timestamptz | YES | Csat Survey Sent At |
csat_survey_token |
text | YES | Csat Survey Token |
Indexes:
- PRIMARY KEY on (id)
- idx_support_tickets_csat_ready on (csat_survey_scheduled_for) WHERE ((csat_survey_scheduled_for IS NOT NULL) AND (csat_survey_sent_at IS NULL))
- idx_support_tickets_customer on (customer_id)
- idx_support_tickets_outbound on (is_outbound, status) WHERE (is_outbound = true)
- idx_support_tickets_resolution_type on (resolution_type) WHERE (resolution_type IS NOT NULL)
- idx_support_tickets_status_open on (status) WHERE (status <> ALL (ARRAY['resolved'::text, 'closed'::text]))
- idx_tickets_ai_category on (ai_category)
- idx_tickets_batch on (related_batch_code) WHERE (related_batch_code IS NOT NULL)
- idx_tickets_batch_recent on (related_batch_code, created_at) WHERE (ai_category = 'quality'::text)
- idx_tickets_created on (created_at)
- idx_tickets_crisp_session on (crisp_session_id) WHERE (crisp_session_id IS NOT NULL)
- idx_tickets_customer on (customer_id)
- idx_tickets_pending_queue on (status, created_at) WHERE (status = 'pending'::text)
- idx_tickets_priority_queue on (status, ai_escalation_reason, ai_confidence, created_at)
- idx_tickets_status on (status)
- idx_tickets_status_created on (status, created_at)
ticket_messages¶
Purpose: Full conversation history per ticket
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
ticket_id |
uuid | NO | FK to support_tickets |
direction |
text | NO | Direction |
sender |
text | NO | Sender |
subject |
text | YES | Subject |
body |
text | NO | Body |
delivery_id |
text | YES | Delivery ID |
created_at |
timestamptz | YES | Record created (default: now()) |
email_message_id |
text | YES | Email Message ID |
Indexes:
- PRIMARY KEY on (id)
- idx_ticket_messages_created_at on (created_at)
- idx_ticket_messages_ticket_id on (ticket_id)
ticket_notes¶
Purpose: Internal agent notes on tickets
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
ticket_id |
uuid | NO | FK to support_tickets |
note_type |
text | NO | Note Type (default: 'internal') |
content |
text | NO | Content |
created_by |
text | YES | Created By |
created_at |
timestamptz | YES | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_ticket_notes_ticket_id on (ticket_id)
cs_agent_decisions¶
Purpose: AI classification and draft tracking
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
ticket_id |
uuid | YES | FK to support_tickets |
category |
text | YES | Category |
confidence_score |
numeric | YES | Confidence Score |
reasoning |
text | YES | Reasoning |
draft_response |
text | YES | Draft Response |
proposed_actions |
jsonb | YES | Proposed Actions (default: '[]') |
escalation_reason |
text | YES | Escalation Reason |
processed_at |
timestamptz | YES | Processed At (default: now()) |
review_status |
text | YES | Review Status (default: 'pending') |
reviewed_at |
timestamptz | YES | Reviewed At |
reviewed_by |
text | YES | Reviewed By |
rejection_reason |
text | YES | Rejection Reason |
rejection_feedback |
text | YES | Rejection Feedback |
created_at |
timestamptz | YES | Record created (default: now()) |
source |
text | YES | Source (default: 'email') |
dedupe_key |
text | YES | Dedupe Key |
execution_duration_ms |
integer | YES | Execution Duration ms |
model_used |
text | YES | Model Used |
policy_gate_triggered |
boolean | YES | Policy Gate Triggered (default: false) |
policy_gate_code |
text | YES | Policy Gate Code |
Indexes:
- PRIMARY KEY on (id)
- cs_agent_decisions_dedupe_key_unique UNIQUE on (dedupe_key)
- idx_cs_agent_decisions_pending on (review_status) WHERE (review_status = 'pending'::text)
- idx_cs_agent_decisions_review_status on (review_status) WHERE (review_status = 'pending'::text)
- idx_cs_agent_decisions_source on (source)
- idx_cs_agent_decisions_ticket on (ticket_id)
chat_sessions¶
Purpose: Live chat session metadata
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
visitor_id |
text | NO | Visitor ID |
visitor_email |
text | YES | Visitor Email |
customer_id |
uuid | YES | FK to customers |
started_at |
timestamptz | YES | Started At (default: now()) |
last_message_at |
timestamptz | YES | Last Message At (default: now()) |
ended_at |
timestamptz | YES | Ended At |
entry_page |
text | YES | Entry Page |
referrer |
text | YES | Referrer |
device_type |
text | YES | Device Type |
status |
text | YES | Status (default: 'active') |
escalated_at |
timestamptz | YES | Escalated At |
escalated_reason |
text | YES | Escalated Reason |
support_ticket_id |
uuid | YES | FK to support_tickets |
message_count |
integer | YES | Message Count (default: 0) |
ai_message_count |
integer | YES | AI Message Count (default: 0) |
created_at |
timestamptz | YES | Record created (default: now()) |
updated_at |
timestamptz | YES | Last modified (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_chat_sessions_status on (status)
- idx_chat_sessions_visitor on (visitor_id)
chat_messages¶
Purpose: Live chat message history
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
session_id |
uuid | NO | FK to chat_sessions |
role |
text | NO | Role |
content |
text | NO | Content |
ai_confidence |
numeric | YES | AI Confidence |
ai_model |
text | YES | AI Model |
tokens_used |
integer | YES | Tokens Used |
response_time_ms |
integer | YES | Response Time ms |
escalation_trigger |
boolean | YES | Escalation Trigger (default: false) |
edited |
boolean | YES | Edited (default: false) |
created_at |
timestamptz | YES | Record created (default: now()) |
user_rating |
text | YES | User Rating |
user_rating_at |
timestamptz | YES | User Rating At |
user_rating_feedback |
text | YES | User Rating Feedback |
Indexes:
- PRIMARY KEY on (id)
- idx_chat_messages_rated on (user_rating, created_at) WHERE (user_rating IS NOT NULL)
- idx_chat_messages_session on (session_id, created_at)
escalation_queue¶
Purpose: Tickets requiring human escalation
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
created_at |
timestamptz | YES | Record created (default: now()) |
ticket_id |
uuid | NO | FK to support_tickets |
reason |
text | NO | Reason |
urgency |
text | NO | Urgency |
agent_recommendation |
text | YES | Agent Recommendation |
context_summary |
text | YES | Context Summary |
queued_at |
timestamptz | NO | Queued At |
position |
integer | YES | Position |
processed_at |
timestamptz | YES | Processed At |
holding_response_sent |
boolean | YES | Holding Response Sent (default: false) |
holding_response_sent_at |
timestamptz | YES | Holding Response Sent At |
Indexes:
- PRIMARY KEY on (id)
- idx_escalation_queue_pending on (queued_at) WHERE (processed_at IS NULL)
- idx_escalation_queue_urgency on (urgency, queued_at) WHERE (processed_at IS NULL)
csat_responses¶
Purpose: Customer satisfaction survey responses
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
ticket_id |
uuid | NO | FK to support_tickets |
customer_id |
uuid | NO | FK to customers |
rating |
integer | NO | Rating |
comment |
text | YES | Comment |
submitted_at |
timestamptz | YES | Submitted At (default: now()) |
submitted_from |
text | YES | Submitted From (default: 'email_link') |
Indexes:
- PRIMARY KEY on (id)
- csat_responses_ticket_id_key UNIQUE on (ticket_id)
- idx_csat_responses_rating on (rating)
- idx_csat_responses_submitted_at on (submitted_at)
customer_health_scores¶
Purpose: Computed customer health metrics
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
customer_id |
uuid | NO | FK to customers |
health_score |
integer | NO | Health Score |
risk_tier |
text | NO | Risk Tier |
previous_score |
integer | YES | Previous Score |
previous_tier |
text | YES | Previous Tier |
score_delta |
integer | YES | Score Delta |
tier_changed |
boolean | YES | Tier Changed |
delivery_score |
integer | NO | Delivery Score |
engagement_score |
integer | NO | Engagement Score |
support_score |
integer | NO | Support Score |
payment_score |
integer | NO | Payment Score |
tenure_score |
integer | NO | Tenure Score |
skip_score |
integer | NO | Skip Score |
delivery_issues_count |
integer | YES | Delivery Issues Count (default: 0) |
skipped_boxes_count |
integer | YES | Skipped Boxes Count (default: 0) |
support_tickets_count |
integer | YES | Support Tickets Count (default: 0) |
portal_logins_count |
integer | YES | Portal Logins Count (default: 0) |
proof_scans_count |
integer | YES | Proof Scans Count (default: 0) |
email_opens_count |
integer | YES | Email Opens Count (default: 0) |
payment_failures_count |
integer | YES | Payment Failures Count (default: 0) |
subscription_age_days |
integer | YES | Subscription Age Days |
total_orders_delivered |
integer | YES | Total Orders Delivered |
lifetime_value_pence |
integer | YES | Lifetime Value Pence |
calculated_at |
timestamptz | NO | Calculated At (default: now()) |
calculation_version |
text | NO | Calculation Version (default: '1.0') |
created_at |
timestamptz | NO | Record created (default: now()) |
updated_at |
timestamptz | NO | Last modified (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_health_scores_at_risk on (customer_id, health_score) WHERE (risk_tier = ANY (ARRAY['at_risk'::text, 'critical'::text]))
- idx_health_scores_calculated on (calculated_at)
- idx_health_scores_customer on (customer_id)
- idx_health_scores_score on (health_score)
- idx_health_scores_tier on (risk_tier)
- idx_health_scores_tier_changed on (customer_id) WHERE (tier_changed = true)
- unique_customer_health UNIQUE on (customer_id)
customer_interventions¶
Purpose: Proactive customer intervention records
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
customer_id |
uuid | NO | FK to customers |
intervention_type |
text | NO | Intervention Type |
trigger_component |
text | NO | Trigger Component |
trigger_value |
integer | NO | Trigger Value |
created_at |
timestamptz | NO | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_customer_interventions_lookup on (customer_id, intervention_type, created_at)
customer_engagement_events¶
Purpose: Customer engagement activity tracking
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
customer_id |
uuid | NO | FK to customers |
event_type |
text | NO | Event Type |
event_source |
text | NO | Event Source (default: 'system') |
event_metadata |
jsonb | YES | Event Metadata (default: '{}') |
created_at |
timestamptz | NO | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_engagement_created on (created_at)
- idx_engagement_customer on (customer_id)
- idx_engagement_customer_created on (customer_id, created_at)
- idx_engagement_type on (event_type)
testimonials¶
Purpose: Customer testimonials and reviews
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
customer_id |
uuid | NO | FK to customers |
source_ticket_id |
uuid | YES | FK to support_tickets |
source_type |
text | NO | Source Type (default: 'email_reply') |
original_text |
text | NO | Original Text |
approved_quote |
text | YES | Approved Quote |
display_city |
text | YES | Display City |
status |
text | NO | Status (default: 'flagged') |
flagged_at |
timestamptz | YES | Flagged At (default: now()) |
flagged_by |
text | YES | Flagged By |
request_sent_at |
timestamptz | YES | Request Sent At |
request_email_delivery_id |
text | YES | Request Email Delivery ID |
approved_at |
timestamptz | YES | Approved At |
approval_method |
text | YES | Approval Method |
is_featured |
boolean | YES | Is Featured (default: false) |
display_order |
integer | YES | Display Order |
created_at |
timestamptz | YES | Record created (default: now()) |
updated_at |
timestamptz | YES | Last modified (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_testimonials_customer on (customer_id)
- idx_testimonials_featured on (is_featured) WHERE ((status = 'approved'::text) AND (is_featured = true))
- idx_testimonials_status on (status)
1.5 AI & Knowledge Base Tables¶
ai_knowledge_sections¶
Purpose: Knowledge base sections for AI agent
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
section_key |
text | NO | Section Key |
version |
integer | NO | Version (default: 1) |
title |
text | NO | Title |
content |
text | NO | Content |
is_active |
boolean | NO | Is Active (default: false) |
created_at |
timestamptz | NO | Record created (default: now()) |
created_by |
text | YES | Created By (default: 'system') |
notes |
text | YES | Notes |
embedding |
vector | YES | Embedding |
Indexes:
- PRIMARY KEY on (id)
- idx_ai_knowledge_active on (section_key) WHERE (is_active = true)
- idx_kb_sections_embedding on (embedding)
- unique_section_version UNIQUE on (section_key, version)
ai_prompt_templates¶
Purpose: Reusable AI prompt templates
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
channel |
text | NO | Channel |
section_key |
text | NO | Section Key |
section_order |
integer | NO | Section Order |
is_included |
boolean | NO | Is Included (default: true) |
wrapper_prefix |
text | YES | Wrapper Prefix |
wrapper_suffix |
text | YES | Wrapper Suffix |
created_at |
timestamptz | NO | Record created (default: now()) |
updated_at |
timestamptz | NO | Last modified (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_ai_templates_channel on (channel, section_order) WHERE (is_included = true)
- unique_channel_section UNIQUE on (channel, section_key)
ai_prompt_logs¶
Purpose: Log of AI prompt executions
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
channel |
text | NO | Channel |
assembled_at |
timestamptz | NO | Assembled At (default: now()) |
section_versions |
jsonb | NO | Section Versions |
prompt_hash |
text | NO | Prompt Hash |
token_estimate |
integer | YES | Token Estimate |
Indexes:
- PRIMARY KEY on (id)
- idx_ai_logs_recent on (channel, assembled_at)
agents¶
Purpose: Registered AI agent definitions
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
name |
text | NO | Name |
email |
text | NO | |
role |
text | NO | Role (default: 'agent') |
is_active |
boolean | NO | Is Active (default: true) |
created_at |
timestamptz | NO | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- agents_email_key UNIQUE on (email)
- idx_agents_active on (is_active) WHERE (is_active = true)
agent_executions¶
Purpose: AI agent execution runs
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
created_at |
timestamptz | YES | Record created (default: now()) |
ticket_id |
uuid | YES | FK to support_tickets |
trigger_source |
text | NO | Trigger Source |
model_used |
text | YES | Model Used (default: 'claude-sonnet-4-20250514') |
started_at |
timestamptz | NO | Started At |
completed_at |
timestamptz | YES | Completed At |
duration_ms |
integer | YES | Duration ms |
input_tokens |
integer | YES | Input Tokens |
output_tokens |
integer | YES | Output Tokens |
total_tokens |
integer | YES | Total Tokens |
outcome |
text | YES | Outcome |
confidence_score |
numeric | YES | Confidence Score |
tools_used |
ARRAY | YES | Tools Used |
safety_filter_triggered |
boolean | YES | Safety Filter Triggered (default: false) |
safety_filter_violations |
ARRAY | YES | Safety Filter Violations |
error_message |
text | YES | Error Message |
error_type |
text | YES | Error Type |
dedupe_key |
text | YES | Dedupe Key |
Indexes:
- PRIMARY KEY on (id)
- agent_executions_dedupe_key_key UNIQUE on (dedupe_key)
- idx_agent_executions_created on (created_at)
- idx_agent_executions_dedupe_key on (dedupe_key) WHERE (dedupe_key IS NOT NULL)
- idx_agent_executions_escalated_recent on (created_at) WHERE (outcome = 'escalated'::text)
- idx_agent_executions_outcome on (outcome)
- idx_agent_executions_safety on (safety_filter_triggered) WHERE (safety_filter_triggered = true)
- idx_agent_executions_ticket on (ticket_id)
agent_decisions¶
Purpose: AI agent decision records
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
created_at |
timestamptz | YES | Record created (default: now()) |
execution_id |
uuid | NO | FK to agent_executions |
ticket_id |
uuid | YES | FK to support_tickets |
decision_type |
text | NO | Decision Type |
decision_rationale |
text | YES | Decision Rationale |
confidence_score |
numeric | YES | Confidence Score |
escalation_reason |
text | YES | Escalation Reason |
agent_recommendation |
text | YES | Agent Recommendation |
human_reviewed_at |
timestamptz | YES | Human Reviewed At |
human_reviewer |
text | YES | Human Reviewer |
human_agreed |
boolean | YES | Human Agreed |
human_feedback |
text | YES | Human Feedback |
Indexes:
- PRIMARY KEY on (id)
- agent_decisions_execution_id_unique UNIQUE on (execution_id)
- idx_agent_decisions_confidence on (confidence_score)
- idx_agent_decisions_execution on (execution_id)
- idx_agent_decisions_reviewed on (human_reviewed_at) WHERE (human_reviewed_at IS NOT NULL)
- idx_agent_decisions_type on (decision_type)
agent_tool_calls¶
Purpose: AI agent tool call audit log
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
created_at |
timestamptz | YES | Record created (default: now()) |
execution_id |
uuid | NO | FK to agent_executions |
tool_name |
text | NO | Tool Name |
tool_input |
jsonb | NO | Tool Input |
tool_output |
jsonb | YES | Tool Output |
started_at |
timestamptz | NO | Started At |
completed_at |
timestamptz | YES | Completed At |
duration_ms |
integer | YES | Duration ms |
success |
boolean | YES | Success |
error_message |
text | YES | Error Message |
Indexes:
- PRIMARY KEY on (id)
- idx_agent_tool_calls_created on (created_at)
- idx_agent_tool_calls_execution on (execution_id)
- idx_agent_tool_calls_tool on (tool_name)
1.6 Subscription & Billing Tables¶
subscriptions¶
Purpose: Local cache of Seal subscription state
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
customer_id |
uuid | NO | FK to customers |
shopify_subscription_id |
text | NO | Shopify Subscription ID |
seal_subscription_id |
text | YES | Seal Subscription ID |
status |
text | NO | Status (default: 'active') |
frequency_weeks |
integer | NO | Frequency Weeks (default: 4) |
price_gbp |
numeric | NO | Price GBP |
next_billing_date |
date | YES | Next Billing Date |
created_at |
timestamptz | NO | Record created (default: now()) |
updated_at |
timestamptz | NO | Last modified (default: now()) |
paused_at |
timestamptz | YES | Paused At |
cancelled_at |
timestamptz | YES | Cancelled At |
box_size |
text | YES | Box Size (default: '12kg') |
shipping_address |
text | YES | Shipping Address |
email |
text | YES | |
card_brand |
text | YES | Card Brand |
card_last_digits |
text | YES | Card Last Digits |
card_expiry_month |
integer | YES | Card Expiry Month |
card_expiry_year |
integer | YES | Card Expiry Year |
payment_status |
text | YES | Payment Status (default: 'healthy') |
failed_payment_count |
integer | YES | Failed Payment Count (default: 0) |
last_failed_at |
timestamptz | YES | Last Failed At |
dunning_started_at |
timestamptz | YES | Dunning Started At |
last_synced_at |
timestamptz | YES | Last Synced At (default: now()) |
seal_payload |
jsonb | YES | Seal Payload |
reminder_7day_sent_at |
timestamptz | YES | Reminder 7day Sent At |
reminder_48hour_sent_at |
timestamptz | YES | Reminder 48hour Sent At |
dunning_email_day0_sent |
boolean | YES | Dunning Email Day0 Sent (default: false) |
dunning_email_day3_sent |
boolean | YES | Dunning Email Day3 Sent (default: false) |
dunning_email_day7_sent |
boolean | YES | Dunning Email Day7 Sent (default: false) |
dunning_email_day10_sent |
boolean | YES | Dunning Email Day10 Sent (default: false) |
last_error_message |
text | YES | Last Error Message |
pause_reason |
text | YES | Pause Reason |
seal_internal_id |
text | YES | Seal Internal ID |
shopify_customer_id |
bigint | YES | Shopify Customer ID |
delivery_interval |
text | YES | Delivery Interval |
billing_interval |
text | YES | Billing Interval |
variant_id |
bigint | YES | Variant ID |
currency |
text | YES | Currency (default: 'GBP') |
subscription_created_at |
timestamptz | YES | Subscription Created At |
Indexes:
- PRIMARY KEY on (id)
- idx_subscriptions_card_expiry on (card_expiry_year, card_expiry_month) WHERE (status = 'active'::text)
- idx_subscriptions_customer on (customer_id)
- idx_subscriptions_dunning on (dunning_started_at) WHERE (dunning_started_at IS NOT NULL)
- idx_subscriptions_dunning_active on (dunning_started_at, last_failed_at) WHERE ((payment_status = ANY (ARRAY['failing'::text, 'failed'::text])) AND (status = 'ACTIVE'::text))
- idx_subscriptions_payment_status on (payment_status) WHERE (payment_status <> 'healthy'::text)
- idx_subscriptions_shopify on (shopify_subscription_id)
- idx_subscriptions_status on (status)
- subscriptions_shopify_subscription_id_key UNIQUE on (shopify_subscription_id)
subscription_events¶
Purpose: Immutable log of all subscription changes
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
subscription_id |
uuid | YES | FK to subscriptions |
seal_subscription_id |
text | YES | Seal Subscription ID |
customer_id |
uuid | YES | FK to customers |
event_type |
text | NO | Event Type |
event_source |
text | NO | Event Source |
old_status |
text | YES | Old Status |
new_status |
text | YES | New Status |
payload |
jsonb | YES | Payload |
event_at |
timestamptz | NO | Event At |
created_at |
timestamptz | YES | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_sub_events_at on (event_at)
- idx_sub_events_customer on (customer_id)
- idx_sub_events_sub on (subscription_id)
- idx_sub_events_type on (event_type)
- idx_subscription_events_created on (created_at)
- idx_subscription_events_customer_id on (customer_id)
- idx_subscription_events_customer_recent on (customer_id, created_at)
- idx_subscription_events_seal_id on (seal_subscription_id)
- idx_subscription_events_subscription_id on (subscription_id)
- idx_subscription_events_type on (event_type)
- idx_subscription_events_type_created on (event_type, created_at)
subscription_actions¶
Purpose: Subscription modification actions
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
customer_id |
uuid | NO | FK to customers |
subscription_id |
uuid | YES | FK to subscriptions |
action_type |
text | NO | Action Type |
action_data |
jsonb | YES | Action Data (default: '{}') |
source |
text | NO | Source (default: 'portal') |
created_at |
timestamptz | NO | Record created (default: now()) |
seal_synced |
boolean | YES | Seal Synced (default: false) |
email_sent |
boolean | YES | Email Sent (default: false) |
Indexes:
- PRIMARY KEY on (id)
- idx_subscription_actions_created on (created_at)
- idx_subscription_actions_customer on (customer_id)
- idx_subscription_actions_subscription on (subscription_id)
- idx_subscription_actions_type on (action_type)
subscription_config¶
Purpose: Subscription configuration parameters
| Column | Type | Nullable | Description |
|---|---|---|---|
config_key |
text | NO | Config Key |
config_value |
text | NO | Config Value |
description |
text | YES | Description |
updated_at |
timestamptz | YES | Last modified (default: now()) |
updated_by |
text | YES | Updated By |
Indexes:
- PRIMARY KEY on (config_key)
billing_attempts¶
Purpose: Payment billing attempt records
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
subscription_id |
uuid | YES | FK to subscriptions |
seal_subscription_id |
text | NO | Seal Subscription ID |
seal_billing_attempt_id |
text | NO | Seal Billing Attempt ID |
customer_id |
uuid | YES | FK to customers |
scheduled_at |
timestamptz | NO | Scheduled At |
attempted_at |
timestamptz | YES | Attempted At |
completed_at |
timestamptz | YES | Completed At |
status |
text | NO | Status (default: 'scheduled') |
shopify_order_id |
text | YES | Shopify Order ID |
order_id |
uuid | YES | FK to orders |
error_code |
text | YES | Error Code |
error_message |
text | YES | Error Message |
retry_count |
integer | YES | Retry Count (default: 0) |
requires_authentication |
boolean | YES | Requires Authentication (default: false) |
authentication_url |
text | YES | Authentication URL |
amount_pence |
integer | YES | Amount Pence |
currency |
text | YES | Currency (default: 'GBP') |
is_retry |
boolean | YES | Is Retry (default: false) |
original_attempt_id |
uuid | YES | FK to billing_attempts |
triggered_manually |
boolean | YES | Triggered Manually (default: false) |
seal_payload |
jsonb | YES | Seal Payload |
created_at |
timestamptz | YES | Record created (default: now()) |
updated_at |
timestamptz | YES | Last modified (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- billing_attempts_seal_billing_attempt_id_key UNIQUE on (seal_billing_attempt_id)
- idx_billing_attempts_customer_id on (customer_id)
- idx_billing_attempts_customer_recent on (customer_id, attempted_at)
- idx_billing_attempts_failed on (subscription_id, status) WHERE (status = 'failed'::text)
- idx_billing_attempts_scheduled on (scheduled_at)
- idx_billing_attempts_seal_id on (seal_billing_attempt_id)
- idx_billing_attempts_seal_sub on (seal_subscription_id)
- idx_billing_attempts_status on (status)
- idx_billing_attempts_sub on (subscription_id)
- idx_billing_attempts_subscription_id on (subscription_id)
payment_events¶
Purpose: Payment lifecycle events
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
customer_id |
uuid | NO | FK to customers |
order_id |
uuid | YES | FK to orders |
subscription_id |
uuid | YES | Subscription ID |
event_type |
text | NO | Event Type |
failure_reason |
text | YES | Failure Reason |
attempt_number |
integer | YES | Attempt Number (default: 1) |
amount_pence |
integer | YES | Amount Pence |
shopify_event_id |
text | YES | Shopify Event ID |
event_metadata |
jsonb | YES | Event Metadata (default: '{}') |
created_at |
timestamptz | NO | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_payment_events_created on (created_at)
- idx_payment_events_customer on (customer_id)
- idx_payment_events_customer_created on (customer_id, created_at)
- idx_payment_events_type on (event_type)
seal_webhook_inbox¶
Purpose: Raw incoming Seal webhooks
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
webhook_topic |
text | NO | Webhook Topic |
seal_subscription_id |
text | YES | Seal Subscription ID |
payload |
jsonb | NO | Payload |
headers |
jsonb | YES | Headers |
hmac_signature |
text | YES | Hmac Signature |
hmac_valid |
boolean | YES | Hmac Valid |
processing_status |
text | YES | Processing Status (default: 'pending') |
processing_attempts |
integer | YES | Processing Attempts (default: 0) |
last_error |
text | YES | Last Error |
error_category |
text | YES | Error Category |
processed_at |
timestamptz | YES | Processed At |
processing_duration_ms |
integer | YES | Processing Duration ms |
idempotency_key |
text | YES | Idempotency Key |
received_at |
timestamptz | YES | Received At (default: now()) |
created_at |
timestamptz | YES | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_seal_webhook_inbox_recent on (created_at)
- idx_seal_webhook_inbox_retry_queue on (received_at) WHERE (processing_status = ANY (ARRAY['pending'::text, 'failed'::text]))
- idx_seal_webhook_inbox_status on (processing_status)
- idx_seal_webhook_inbox_subscription on (seal_subscription_id)
- idx_seal_webhook_inbox_topic on (webhook_topic)
- idx_seal_webhook_received on (received_at)
- idx_seal_webhook_status on (processing_status)
- idx_seal_webhook_sub_id on (seal_subscription_id)
- idx_seal_webhook_topic on (webhook_topic)
- seal_webhook_inbox_idempotency_key_key UNIQUE on (idempotency_key)
1.7 Customer Credits & Referrals¶
customer_credits¶
Purpose: Customer credit balances
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
customer_id |
uuid | NO | FK to customers |
amount_pence |
integer | NO | Amount Pence |
remaining_pence |
integer | NO | Remaining Pence |
source_type |
text | NO | Source Type |
source_id |
uuid | YES | Source ID |
source_description |
text | YES | Source Description |
status |
text | NO | Status (default: 'available') |
expires_at |
timestamptz | NO | Expires At |
expiry_warning_sent_at |
timestamptz | YES | Expiry Warning Sent At |
created_at |
timestamptz | NO | Record created (default: now()) |
updated_at |
timestamptz | NO | Last modified (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_credits_expiry_warning on (expires_at, expiry_warning_sent_at) WHERE ((status = 'active'::text) AND (remaining_pence > 0))
- idx_customer_credits_customer_available on (customer_id, created_at) WHERE (status = 'available'::text)
- idx_customer_credits_customer_id on (customer_id)
- idx_customer_credits_expires_at on (expires_at) WHERE (status = 'available'::text)
- idx_customer_credits_expiry_warning on (expires_at) WHERE ((status = 'available'::text) AND (expiry_warning_sent_at IS NULL))
- idx_customer_credits_source on (source_type, source_id)
- idx_customer_credits_status on (status)
customer_credit_events¶
Purpose: Credit balance change events
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
event_type |
text | NO | Event Type |
credit_id |
uuid | YES | FK to customer_credits |
customer_id |
uuid | YES | FK to customers |
order_id |
uuid | YES | FK to orders |
application_id |
uuid | YES | FK to customer_credit_applications |
payload |
jsonb | NO | Payload (default: '{}') |
created_at |
timestamptz | NO | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_credit_events_credit_id on (credit_id)
- idx_credit_events_customer_id on (customer_id)
- idx_credit_events_order_id on (order_id)
- idx_credit_events_type on (event_type)
customer_credit_applications¶
Purpose: Credit application to orders
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
credit_id |
uuid | NO | FK to customer_credits |
order_id |
uuid | NO | FK to orders |
customer_id |
uuid | NO | FK to customers |
amount_applied_pence |
integer | NO | Amount Applied Pence |
status |
text | NO | Status (default: 'applied') |
reversed_at |
timestamptz | YES | Reversed At |
reversed_reason |
text | YES | Reversed Reason |
applied_at |
timestamptz | NO | Applied At (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- customer_credit_applications_credit_id_order_id_key UNIQUE on (credit_id, order_id)
- idx_credit_applications_credit_id on (credit_id)
- idx_credit_applications_customer_id on (customer_id)
- idx_credit_applications_order_id on (order_id)
referral_codes¶
Purpose: Referral code definitions
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
customer_id |
uuid | NO | FK to customers |
slug |
text | NO | Slug |
display_name |
text | YES | Display Name |
total_referrals |
integer | YES | Total Referrals (default: 0) |
successful_referrals |
integer | YES | Successful Referrals (default: 0) |
total_rewards_earned_pence |
integer | YES | Total Rewards Earned Pence (default: 0) |
is_active |
boolean | YES | Is Active (default: true) |
paused_at |
timestamptz | YES | Paused At |
paused_reason |
text | YES | Paused Reason |
created_at |
timestamptz | YES | Record created (default: now()) |
updated_at |
timestamptz | YES | Last modified (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_referral_codes_active on (is_active) WHERE (is_active = true)
- idx_referral_codes_customer_id on (customer_id)
- idx_referral_codes_slug on (slug)
- referral_codes_slug_key UNIQUE on (slug)
- unique_customer_referral_code UNIQUE on (customer_id)
referrals¶
Purpose: Referral relationship records
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
referral_code_id |
uuid | NO | FK to referral_codes |
referrer_customer_id |
uuid | NO | FK to customers |
referee_customer_id |
uuid | YES | FK to customers |
referee_email |
text | NO | Referee Email |
referee_order_id |
uuid | YES | FK to orders |
referee_first_delivery_at |
timestamptz | YES | Referee First Delivery At |
status |
text | NO | Status (default: 'pending') |
fraud_flags |
jsonb | YES | Fraud Flags (default: '[]') |
fraud_checked_at |
timestamptz | YES | Fraud Checked At |
referrer_reward_id |
uuid | YES | FK to referral_rewards |
created_at |
timestamptz | YES | Record created (default: now()) |
confirmed_at |
timestamptz | YES | Confirmed At |
rewarded_at |
timestamptz | YES | Rewarded At |
updated_at |
timestamptz | YES | Last modified (default: now()) |
credit_id |
uuid | YES | FK to customer_credits |
Indexes:
- PRIMARY KEY on (id)
- idx_referrals_credit_id on (credit_id) WHERE (credit_id IS NOT NULL)
- idx_referrals_pending on (status, created_at) WHERE (status = 'pending'::text)
- idx_referrals_referee_customer_id on (referee_customer_id)
- idx_referrals_referee_email on (referee_email)
- idx_referrals_referral_code_id on (referral_code_id)
- idx_referrals_referrer_customer_id on (referrer_customer_id)
- idx_referrals_status on (status)
- unique_referee_email UNIQUE on (referee_email)
referral_events¶
Purpose: Referral lifecycle events
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
event_type |
text | NO | Event Type |
referral_id |
uuid | YES | FK to referrals |
referral_code_id |
uuid | YES | FK to referral_codes |
reward_id |
uuid | YES | FK to referral_rewards |
order_id |
uuid | YES | FK to orders |
customer_id |
uuid | YES | FK to customers |
payload |
jsonb | NO | Payload (default: '{}') |
created_at |
timestamptz | YES | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_referral_events_created on (created_at)
- idx_referral_events_order_id on (order_id) WHERE (order_id IS NOT NULL)
- idx_referral_events_referral_id on (referral_id) WHERE (referral_id IS NOT NULL)
- idx_referral_events_type on (event_type, created_at)
referral_rewards¶
Purpose: Referral reward issuance records
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
customer_id |
uuid | NO | FK to customers |
referral_id |
uuid | NO | FK to referrals |
discount_code |
text | NO | Discount Code |
amount_pence |
integer | NO | Amount Pence (default: 1500) |
is_used |
boolean | YES | Is Used (default: false) |
used_at |
timestamptz | YES | Used At |
used_order_id |
uuid | YES | FK to orders |
expires_at |
timestamptz | NO | Expires At |
is_expired |
boolean | YES | Is Expired (default: false) |
shopify_discount_id |
text | YES | Shopify Discount ID |
shopify_synced_at |
timestamptz | YES | Shopify Synced At |
created_at |
timestamptz | YES | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_referral_rewards_customer_id on (customer_id)
- idx_referral_rewards_discount_code on (discount_code)
- idx_referral_rewards_expiring on (expires_at) WHERE ((is_used = false) AND (is_expired = false))
- idx_referral_rewards_referral_id on (referral_id)
- idx_referral_rewards_unused on (customer_id, is_used, is_expired) WHERE ((is_used = false) AND (is_expired = false))
- referral_rewards_discount_code_key UNIQUE on (discount_code)
refunds¶
Purpose: Refund records and processing status
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
created_at |
timestamptz | YES | Record created (default: now()) |
order_id |
uuid | YES | FK to orders |
customer_id |
uuid | YES | FK to customers |
support_ticket_id |
uuid | YES | FK to support_tickets |
shopify_refund_id |
text | YES | Shopify Refund ID |
amount_gbp |
numeric | NO | Amount GBP |
refund_type |
text | NO | Refund Type |
reason |
text | NO | Reason |
processed_by |
text | YES | Processed By |
processed_at |
timestamptz | YES | Processed At |
status |
text | YES | Status (default: 'pending') |
internal_note |
text | YES | Internal Note |
customer_notified |
boolean | YES | Customer Notified (default: false) |
shopify_order_id |
text | YES | Shopify Order ID |
shopify_synced |
boolean | YES | Shopify Synced (default: false) |
shopify_synced_at |
timestamptz | YES | Shopify Synced At |
shopify_error |
text | YES | Shopify Error |
notes |
text | YES | Notes |
Indexes:
- PRIMARY KEY on (id)
- idx_refunds_created_at on (created_at)
- idx_refunds_customer on (customer_id)
- idx_refunds_customer_id on (customer_id)
- idx_refunds_order on (order_id)
- idx_refunds_order_id on (order_id)
- idx_refunds_status on (status) WHERE (status = 'pending'::text)
- idx_refunds_ticket on (support_ticket_id)
replacement_requests¶
Purpose: Product replacement request records
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
created_at |
timestamptz | YES | Record created (default: now()) |
ticket_id |
uuid | NO | FK to support_tickets |
customer_id |
uuid | NO | FK to customers |
original_order_id |
uuid | NO | FK to orders |
reason |
text | NO | Reason |
agent_notes |
text | YES | Agent Notes |
status |
text | YES | Status (default: 'pending') |
reviewed_at |
timestamptz | YES | Reviewed At |
reviewed_by |
text | YES | Reviewed By |
review_notes |
text | YES | Review Notes |
replacement_order_id |
uuid | YES | FK to orders |
fulfilled_at |
timestamptz | YES | Fulfilled At |
Indexes:
- PRIMARY KEY on (id)
- idx_replacement_requests_customer on (customer_id)
- idx_replacement_requests_pending on (created_at) WHERE (status = 'pending'::text)
- idx_replacement_requests_status on (status)
1.8 Calculator & Portal Tables¶
calculator_tokens¶
Purpose: Calculator session tokens
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
token |
uuid | NO | Token (default: gen_random_uuid()) |
box_size |
text | NO | Box Size |
daily_grams |
integer | NO | Daily Grams |
delivery_weeks |
integer | NO | Delivery Weeks |
status |
text | NO | Status (default: 'pending') |
email |
text | YES | |
address_line_1 |
text | YES | Address Line 1 |
shopify_discount_code |
text | YES | Shopify Discount Code |
shopify_price_rule_id |
text | YES | Shopify Price Rule ID |
created_at |
timestamptz | NO | Record created (default: now()) |
validated_at |
timestamptz | YES | Validated At |
used_at |
timestamptz | YES | Used At |
expires_at |
timestamptz | NO | Expires At (default: (now() + '30 days') |
user_agent |
text | YES | User Agent |
ip_address |
inet | YES | Ip Address |
Indexes:
- PRIMARY KEY on (id)
- calculator_tokens_token_key UNIQUE on (token)
- idx_calculator_tokens_status on (status, created_at) WHERE (status = ANY (ARRAY['pending'::text, 'validated'::text]))
- idx_calculator_tokens_token on (token) WHERE (status <> 'used'::text)
calculator_pets¶
Purpose: Pet profiles from calculator
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
discount_id |
uuid | NO | FK to calculator_discounts |
pet_number |
integer | NO | Pet Number |
pet_name |
text | YES | Pet Name |
life_stage |
text | NO | Life Stage |
weight_kg |
numeric | NO | Weight kg |
age_range |
text | YES | Age Range |
neutered |
boolean | YES | Neutered |
activity_level |
text | NO | Activity Level |
body_condition |
text | YES | Body Condition |
calculated_rer |
numeric | NO | Calculated Rer |
calculated_mer |
numeric | NO | Calculated Mer |
daily_grams |
integer | NO | Daily Grams |
monthly_price_gbp |
integer | NO | Monthly Price GBP |
created_at |
timestamptz | YES | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_calculator_pets_discount on (discount_id)
- unique_pet_per_discount UNIQUE on (discount_id, pet_number)
calculator_discounts¶
Purpose: Calculator discount definitions
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
token |
text | NO | Token |
box_size |
text | NO | Box Size |
daily_grams |
integer | NO | Daily Grams |
delivery_weeks |
integer | NO | Delivery Weeks |
regular_price |
integer | NO | Regular Price |
discounted_price |
integer | NO | Discounted Price |
used |
boolean | YES | Used (default: false) |
used_at |
timestamptz | YES | Used At |
email |
text | YES | |
address_line_1 |
text | YES | Address Line 1 |
discount_code |
text | YES | Discount Code |
fraud_flag |
boolean | YES | Fraud Flag (default: false) |
fraud_reason |
text | YES | Fraud Reason |
created_at |
timestamptz | YES | Record created (default: now()) |
expires_at |
timestamptz | YES | Expires At (default: (now() + '72:00:00') |
postcode |
text | YES | Postcode |
address |
text | YES | Address |
pet_count |
integer | YES | Pet Count (default: 1) |
household_total_gbp |
integer | YES | Household Total GBP |
household_daily_grams |
integer | YES | Household Daily Grams |
dog_name |
text | YES | Dog Name |
abandonment_sent_at |
timestamptz | YES | Abandonment Sent At |
calculated_mer |
integer | YES | Calculated Mer |
previous_diet |
text | YES | Previous Diet |
Indexes:
- PRIMARY KEY on (id)
- calculator_discounts_token_key UNIQUE on (token)
- idx_calculator_discounts_abandonment_queue on (created_at) WHERE ((used = false) AND (abandonment_sent_at IS NULL) AND (email IS NOT NULL) AND (discount_code IS NOT NULL))
- idx_calculator_discounts_address on (address_line_1)
- idx_calculator_discounts_email on (email)
- idx_calculator_discounts_expires on (expires_at)
- idx_calculator_discounts_postcode on (postcode)
- idx_calculator_discounts_token on (token)
- idx_calculator_discounts_used on (used)
calculator_discount_usage¶
Purpose: Calculator discount usage tracking
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
email |
text | NO | |
address_line_1 |
text | NO | Address Line 1 |
token_id |
uuid | YES | FK to calculator_tokens |
used_at |
timestamptz | NO | Used At (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- calculator_discount_usage_email_address_line_1_key UNIQUE on (email, address_line_1)
- idx_calculator_discount_usage_lookup on (email, address_line_1)
portal_sessions¶
Purpose: Customer/ops portal session tracking
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
customer_id |
uuid | NO | FK to customers |
token |
text | NO | Token |
created_at |
timestamptz | NO | Record created (default: now()) |
expires_at |
timestamptz | NO | Expires At (default: (now() + '7 days') |
last_accessed_at |
timestamptz | YES | Last Accessed At |
ip_address |
text | YES | Ip Address |
user_agent |
text | YES | User Agent |
Indexes:
- PRIMARY KEY on (id)
- idx_portal_sessions_customer on (customer_id)
- idx_portal_sessions_expires on (expires_at)
- idx_portal_sessions_token on (token)
- portal_sessions_token_key UNIQUE on (token)
1.9 Email & Communications Tables¶
outbox¶
Purpose: Email/notification outbox queue
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
created_at |
timestamptz | NO | Record created (default: now()) |
target |
text | NO | Target |
event_type |
text | NO | Event Type |
payload_json |
jsonb | NO | Payload JSON |
state |
text | NO | State (default: 'pending') |
attempts |
integer | NO | Attempts (default: 0) |
first_sent_at |
timestamptz | YES | First Sent At |
last_sent_at |
timestamptz | YES | Last Sent At |
error_message |
text | YES | Error Message |
manifest_version |
text | NO | Manifest Version (default: 'v1') |
hash |
text | YES | Hash |
kind |
text | YES | Kind |
updated_at |
timestamptz | NO | Last modified (default: now()) |
idempotency_key |
text | YES | Idempotency Key |
Indexes:
- PRIMARY KEY on (id)
- idx_outbox_state_created on (state, created_at) WHERE (state = 'pending'::text)
- ix_outbox_state_created_at on (state, created_at)
- ix_outbox_target_state on (target, state)
- outbox_idempotency_key_key UNIQUE on (idempotency_key)
email_events¶
Purpose: Email delivery event tracking
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
customer_id |
uuid | NO | FK to customers |
subscription_id |
uuid | NO | FK to subscriptions |
event_type |
text | NO | Event Type |
billing_date |
date | NO | Billing Date |
sent_at |
timestamptz | YES | Sent At (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- email_events_subscription_id_event_type_billing_date_key UNIQUE on (subscription_id, event_type, billing_date)
- idx_email_events_lookup on (subscription_id, event_type, billing_date)
lifecycle_events¶
Purpose: Customer lifecycle event log
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
event_name |
text | NO | Event Name |
idempotency_key |
text | NO | Idempotency Key |
customer_id |
uuid | NO | FK to customers |
customer_email |
text | NO | Customer Email |
event_data |
jsonb | NO | Event Data (default: '{}') |
status |
text | NO | Status (default: 'pending') |
attempts |
integer | NO | Attempts (default: 0) |
max_attempts |
integer | NO | Max Attempts (default: 5) |
created_at |
timestamptz | NO | Record created (default: now()) |
scheduled_for |
timestamptz | NO | Scheduled For (default: now()) |
next_retry_at |
timestamptz | YES | Next Retry At |
processed_at |
timestamptz | YES | Processed At |
customerio_delivery_id |
text | YES | Customerio Delivery ID |
last_error |
text | YES | Last Error |
error_history |
jsonb | YES | Error History (default: '[]') |
source_table |
text | YES | Source Table |
source_id |
uuid | YES | Source ID |
triggered_by |
text | YES | Triggered By (default: 'system') |
Indexes:
- PRIMARY KEY on (id)
- idx_lifecycle_events_customer on (customer_id, created_at)
- idx_lifecycle_events_name_status on (event_name, status, created_at)
- idx_lifecycle_events_pending on (scheduled_for, created_at) WHERE (status = 'pending'::text)
- idx_lifecycle_events_retry on (next_retry_at) WHERE ((status = 'failed'::text) AND (attempts < max_attempts))
- idx_lifecycle_events_source on (source_table, source_id) WHERE (source_id IS NOT NULL)
- lifecycle_events_idempotency_key_key UNIQUE on (idempotency_key)
1.10 Advertising & Marketing Tables¶
ad_accounts¶
Purpose: Advertising platform account connections
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
bigint | NO | Primary key (default: nextval('raw_ops.ad_accounts_id_seq') |
platform |
text | NO | Platform |
account_id |
text | NO | Account ID |
account_name |
text | YES | Account Name |
is_active |
boolean | YES | Is Active (default: true) |
api_credentials_ref |
text | YES | API Credentials Ref |
last_sync_at |
timestamptz | YES | Last Sync At |
last_sync_status |
text | YES | Last Sync Status |
created_at |
timestamptz | YES | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- ad_accounts_platform_account_id_key UNIQUE on (platform, account_id)
ad_campaigns¶
Purpose: Ad campaign performance data
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
bigint | NO | Primary key (default: nextval('raw_ops.ad_campaigns_id_seq') |
platform |
text | NO | Platform |
account_id |
text | NO | Account ID |
account_name |
text | YES | Account Name |
campaign_id |
text | NO | Campaign ID |
campaign_name |
text | NO | Campaign Name |
adset_id |
text | YES | Adset ID |
adset_name |
text | YES | Adset Name |
date |
date | NO | Date |
spend_local |
numeric | YES | Spend Local |
currency_code |
text | YES | Currency Code (default: 'GBP') |
spend_gbp |
numeric | NO | Spend GBP |
impressions |
integer | YES | Impressions (default: 0) |
clicks |
integer | YES | Clicks (default: 0) |
conversions |
integer | YES | Conversions (default: 0) |
attribution_window |
text | YES | Attribution Window (default: '7d_click_1d_view') |
imported_at |
timestamptz | YES | Imported At (default: now()) |
updated_at |
timestamptz | YES | Last modified (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- ad_campaigns_platform_account_id_campaign_id_date_key UNIQUE on (platform, account_id, campaign_id, date)
- idx_ad_campaigns_date on (date)
- idx_ad_campaigns_platform on (platform, date)
1.11 System & Ops Tables¶
webhook_inbox¶
Purpose: All incoming webhooks with processing status
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
received_at |
timestamptz | NO | Received At (default: now()) |
topic |
text | NO | Topic |
shopify_order_id |
bigint | YES | Shopify Order ID |
shopify_order_number |
text | YES | Shopify Order Number |
raw_headers |
jsonb | NO | Raw Headers |
raw_payload |
jsonb | NO | Raw Payload |
hmac_verified |
boolean | YES | Hmac Verified (default: false) |
processing_status |
text | NO | Processing Status (default: 'pending') |
processed_at |
timestamptz | YES | Processed At |
processing_time_ms |
integer | YES | Processing Time ms |
processing_attempts |
integer | NO | Processing Attempts (default: 0) |
last_error |
text | YES | Last Error |
last_error_at |
timestamptz | YES | Last Error At |
error_category |
text | YES | Error Category |
created_at |
timestamptz | NO | Record created (default: now()) |
updated_at |
timestamptz | NO | Last modified (default: now()) |
processing_duration_ms |
integer | YES | Processing Duration ms |
Indexes:
- PRIMARY KEY on (id)
- idx_inbox_pending on (received_at) WHERE (processing_status = ANY (ARRAY['pending'::text, 'failed'::text, 'retrying'::text]))
- idx_inbox_shopify_order on (shopify_order_id)
- idx_inbox_status on (processing_status, received_at)
- idx_webhook_inbox_retry_queue on (received_at) WHERE (processing_status = ANY (ARRAY['pending'::text, 'failed'::text, 'retrying'::text]))
- idx_webhook_inbox_shopify_order on (shopify_order_id) WHERE (shopify_order_id IS NOT NULL)
- idx_webhook_inbox_status on (processing_status, created_at)
dead_letter_queue¶
Purpose: Failed messages for manual review
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: uuid_generate_v4()) |
source |
text | NO | Source |
topic |
text | NO | Topic |
payload |
jsonb | NO | Payload |
error_message |
text | YES | Error Message |
retries |
integer | NO | Retries (default: 0) |
created_at |
timestamptz | NO | Record created (default: now()) |
resolved |
boolean | NO | Resolved (default: false) |
resolved_at |
timestamptz | YES | Resolved At |
Indexes:
- PRIMARY KEY on (id)
ops_events¶
Purpose: Operational event log (audit trail)
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
entity_type |
text | NO | Entity Type |
entity_id |
uuid | NO | Entity ID |
kind |
text | NO | Kind |
message |
text | NO | Message |
created_at |
timestamptz | NO | Record created (default: now()) |
meta |
jsonb | YES | Meta |
slack_notified_at |
timestamptz | YES | Slack Notified At |
processed_at |
timestamptz | YES | Processed At |
customer_event_type |
text | YES | Customer Event Type |
event_payload |
jsonb | YES | Event Payload |
source_system |
text | YES | Source System |
source_id |
uuid | YES | Source ID |
processing_time_ms |
integer | YES | Processing Time ms |
Indexes:
- PRIMARY KEY on (id)
- idx_ops_events_allocations on (kind, created_at) WHERE (kind = ANY (ARRAY['allocation_success'::text, 'allocation_warning'::text, 'allocation_partial'::text]))
- idx_ops_events_cold_chain on (kind, created_at) WHERE (kind = ANY (ARRAY['TEMPERATURE_LOGGED'::text, 'TEMPERATURE_ALERT'::text, 'PACKAGING_ASSIGNED'::text]))
- idx_ops_events_courier_unprocessed on (created_at) WHERE ((processed_at IS NULL) AND (kind = 'CUSTOMER_EVENT'::text) AND (customer_event_type = 'courier_exception'::text))
- idx_ops_events_customer_polling on (kind, processed_at, created_at) WHERE (kind = 'CUSTOMER_EVENT'::text)
- idx_ops_events_kind_created on (kind, created_at)
- idx_ops_events_slack_notified on (slack_notified_at) WHERE ((entity_type = 'SHIPMENT'::text) AND (kind = 'EXCEPTION'::text))
- idx_ops_events_source on (source_system, source_id)
monitoring_runs¶
Purpose: Monitor execution history
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
check_name |
text | NO | Check Name |
run_at |
timestamptz | NO | Run At (default: now()) |
duration_ms |
integer | YES | Duration ms |
status |
text | NO | Status (default: 'running') |
result_summary |
text | YES | Result Summary |
result_json |
jsonb | YES | Result JSON |
alert_triggered |
boolean | YES | Alert Triggered (default: false) |
error_message |
text | YES | Error Message |
Indexes:
- PRIMARY KEY on (id)
- idx_monitoring_runs_check_run on (check_name, run_at)
- idx_monitoring_runs_errors on (status, run_at) WHERE (status = 'error'::text)
alert_log¶
Purpose: Alert delivery log
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
sent_at |
timestamptz | NO | Sent At (default: now()) |
channel |
text | NO | Channel |
severity |
text | NO | Severity |
title |
text | NO | Title |
source_check |
text | YES | Source Check |
monitoring_run_id |
uuid | YES | FK to monitoring_runs |
payload_json |
jsonb | NO | Payload JSON |
delivery_status |
text | YES | Delivery Status (default: 'pending') |
delivery_response |
text | YES | Delivery Response |
retry_count |
integer | YES | Retry Count (default: 0) |
idempotency_key |
text | YES | Idempotency Key |
Indexes:
- PRIMARY KEY on (id)
- idx_alert_log_channel_sent on (channel, sent_at)
- idx_alert_log_failed on (delivery_status, sent_at) WHERE (delivery_status = 'failed'::text)
- idx_alert_log_idempotency on (idempotency_key, sent_at) WHERE (idempotency_key IS NOT NULL)
address_changes¶
Purpose: Track address updates for sync
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
created_at |
timestamptz | YES | Record created (default: now()) |
customer_id |
uuid | NO | FK to customers |
changed_by |
text | NO | Changed By |
agent_email |
text | YES | Agent Email |
old_address |
jsonb | NO | Old Address |
new_address |
jsonb | NO | New Address |
support_ticket_id |
uuid | YES | FK to support_tickets |
source |
text | NO | Source |
shopify_synced |
boolean | YES | Shopify Synced (default: false) |
shopify_synced_at |
timestamptz | YES | Shopify Synced At |
seal_synced |
boolean | YES | Seal Synced (default: false) |
seal_synced_at |
timestamptz | YES | Seal Synced At |
reason |
text | YES | Reason |
Indexes:
- PRIMARY KEY on (id)
- idx_address_changes_created on (created_at)
- idx_address_changes_customer on (customer_id)
config¶
Purpose: Application configuration key-value store
| Column | Type | Nullable | Description |
|---|---|---|---|
key |
text | NO | Key |
value_numeric |
numeric | YES | Value Numeric |
value_text |
text | YES | Value Text |
description |
text | YES | Description |
updated_at |
timestamptz | YES | Last modified (default: now()) |
Indexes:
- PRIMARY KEY on (key)
system_config¶
Purpose: System-level configuration
| Column | Type | Nullable | Description |
|---|---|---|---|
key |
text | NO | Key |
value |
text | NO | Value |
created_at |
timestamptz | YES | Record created (default: now()) |
updated_at |
timestamptz | YES | Last modified (default: now()) |
Indexes:
- PRIMARY KEY on (key)
retry_config¶
Purpose: Retry policy configuration
| Column | Type | Nullable | Description |
|---|---|---|---|
error_category |
text | NO | Error Category |
max_attempts |
integer | NO | Max Attempts |
backoff_seconds |
ARRAY | NO | Backoff Seconds |
alert_on_attempt |
integer | YES | Alert On Attempt |
escalate_on_attempt |
integer | YES | Escalate On Attempt |
description |
text | YES | Description |
created_at |
timestamptz | NO | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (error_category)
cron_jobs_metadata¶
Purpose: Cron job execution metadata
| Column | Type | Nullable | Description |
|---|---|---|---|
jobname |
text | NO | Jobname |
description |
text | YES | Description |
schedule |
text | YES | Schedule |
created_at |
timestamptz | YES | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (jobname)
exceptions_queue¶
Purpose: Operational exceptions requiring attention
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
order_id |
uuid | NO | FK to orders |
shipment_id |
uuid | YES | FK to shipments |
issue_type |
text | NO | Issue Type |
issue_description |
text | NO | Issue Description |
raw_data |
jsonb | YES | Raw Data |
resolved |
boolean | NO | Resolved (default: false) |
resolved_at |
timestamptz | YES | Resolved At |
resolved_by |
text | YES | Resolved By |
created_at |
timestamptz | NO | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_exceptions_queue_unresolved on (resolved, created_at) WHERE (NOT resolved)
manual_override_audit¶
Purpose: Audit trail for manual overrides
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
batch_id |
uuid | NO | FK to batches |
batch_code |
text | NO | Batch Code |
override_type |
text | NO | Override Type |
old_status |
text | YES | Old Status |
new_status |
text | YES | New Status |
requester_name |
text | NO | Requester Name |
approver_name |
text | NO | Approver Name |
justification |
text | NO | Justification |
evidence_pdf_url |
text | NO | Evidence PDF URL |
created_at |
timestamptz | YES | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_manual_override_audit_batch_id on (batch_id)
- idx_manual_override_audit_created_at on (created_at)
currency_rates¶
Purpose: Currency exchange rates
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
bigint | NO | Primary key (default: nextval('raw_ops.currency_rates_id_seq') |
date |
date | NO | Date |
from_currency |
text | NO | From Currency |
to_currency |
text | YES | To Currency (default: 'GBP') |
rate |
numeric | NO | Rate |
source |
text | YES | Source (default: 'exchangerate-api') |
fetched_at |
timestamptz | YES | Fetched At (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- currency_rates_date_from_currency_to_currency_key UNIQUE on (date, from_currency, to_currency)
partner_contacts¶
Purpose: Partner/supplier contact directory
| Column | Type | Nullable | Description |
|---|---|---|---|
target |
text | NO | Target |
contact_name |
text | YES | Contact Name |
email |
text | NO | |
cc |
text | YES | Cc |
created_at |
timestamptz | NO | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (target)
invoice_parsing_jobs¶
Purpose: Invoice PDF parsing job tracking
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
status |
text | NO | Status (default: 'pending') |
invoice_pdf_url |
text | NO | Invoice PDF URL |
sender_email |
text | YES | Sender Email |
invoice_type |
text | YES | Invoice Type |
parsed_data |
jsonb | YES | Parsed Data |
confidence_score |
numeric | YES | Confidence Score |
last_error |
text | YES | Last Error |
created_at |
timestamptz | YES | Record created (default: now()) |
processed_at |
timestamptz | YES | Processed At |
processing_duration_ms |
integer | YES | Processing Duration ms |
Indexes:
- PRIMARY KEY on (id)
- idx_invoice_jobs_created on (created_at)
- idx_invoice_jobs_failed_review on (status, created_at) WHERE (status = 'failed_review'::text)
- idx_invoice_jobs_performance on (processed_at, processing_duration_ms) WHERE (processing_duration_ms IS NOT NULL)
- idx_invoice_jobs_status on (status)
- idx_invoice_jobs_type on (invoice_type)
proof_artifacts¶
Purpose: Proof portal generated assets
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
batch_id |
uuid | NO | FK to batches |
proof_url |
text | NO | Proof URL |
qr_image_url |
text | YES | Qr Image URL |
proof_insert_pdf_url |
text | YES | Proof Insert PDF URL |
proof_json_url |
text | YES | Proof JSON URL |
checksum |
text | YES | Checksum |
generated_at |
timestamptz | NO | Generated At (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_proof_artifacts_batch on (batch_id)
- proof_artifacts_batch_id_key UNIQUE on (batch_id)
proof_jobs¶
Purpose: Proof generation job tracking
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
batch_id |
uuid | NO | FK to batches |
job_key |
text | NO | Job Key |
state |
text | NO | State (default: 'queued') |
attempts |
integer | NO | Attempts (default: 0) |
last_error |
text | YES | Last Error |
created_at |
timestamptz | NO | Record created (default: now()) |
updated_at |
timestamptz | NO | Last modified (default: now()) |
processing_duration_ms |
integer | YES | Processing Duration ms |
Indexes:
- PRIMARY KEY on (id)
- idx_proof_jobs_batch_id on (batch_id, updated_at)
- idx_proof_jobs_state on (state, created_at)
- idx_proof_jobs_state_created on (state, created_at) WHERE (state = ANY (ARRAY['queued'::text, 'failed'::text]))
- proof_jobs_job_key_key UNIQUE on (job_key)
- ux_proof_jobs_jobkey UNIQUE on (job_key)
proof_page_events¶
Purpose: Proof page visitor analytics
| Column | Type | Nullable | Description |
|---|---|---|---|
id |
uuid | NO | Primary key (default: gen_random_uuid()) |
event_type |
text | NO | Event Type |
batch_code |
text | NO | Batch Code |
public_batch_id |
text | YES | Public Batch ID |
referrer |
text | YES | Referrer |
user_agent |
text | YES | User Agent |
device_type |
text | YES | Device Type |
share_method |
text | YES | Share Method |
created_at |
timestamptz | NO | Record created (default: now()) |
Indexes:
- PRIMARY KEY on (id)
- idx_proof_page_events_batch on (batch_code, created_at)
- idx_proof_page_events_type_created on (event_type, created_at)
2. Database Views¶
2.1 Monitoring Views¶
| View | Purpose | Source Table(s) |
|---|---|---|
v_webhook_health |
24-hour processing metrics | webhook_inbox |
v_retry_queue_depth |
Current retry queue status | webhook_inbox |
v_error_summary |
Error patterns by category | webhook_inbox |
v_outbox_health |
Email queue status | email_outbox |
v_lab_sla_tracking |
Lab turnaround metrics | batches, lab_results |
v_working_capital_metrics |
Cash tied in QA hold | batches, inventory |
v_daily_snapshot_health |
Snapshot function health | Multiple |
2.2 Operational Views¶
| View | Purpose | Source Table(s) |
|---|---|---|
v_support_queue |
Open tickets with context | support_tickets, customers, cs_agent_decisions |
v_customer_context |
Full customer view | customers, orders, subscriptions, shipments |
v_pack_queue |
Orders ready for packing | orders, packing_instructions, allocations |
v_released_batches_verified |
Batches with lab results | batches, lab_results |
v_inventory_status |
Current stock levels | inventory, batches |
2.3 Alert Views¶
| View | Purpose | Trigger Condition |
|---|---|---|
alert_retry_queue_backup |
Webhook queue alert | pending > 100 OR oldest > 30 min |
alert_slow_processing |
Performance alert | p95 > 5s OR max > 10s |
3. Database Functions¶
3.1 Core Processing Functions¶
| Function | Purpose | Called By |
|---|---|---|
process_order_webhook_txn() |
Process Shopify order | shopify-webhook-handler |
allocate_order_to_batches() |
FEFO allocation | Database trigger |
release_batch() |
Release batch from QA | SOP-01 automation |
execute_manual_inventory_adjustment() |
Manual stock adjustment | Ops Portal |
3.2 Monitoring Functions (v2)¶
| Function | Purpose | Schedule |
|---|---|---|
fn_check_order_ingestion_slo_v2() |
Order ingestion health | Every 2 min |
fn_check_outbox_health_v2() |
Email queue health | Every 2 min |
fn_check_stock_levels_v2() |
Low stock detection | Every 15 min |
fn_check_lab_sla_v2() |
Lab SLA tracking | Every 15 min |
fn_check_working_capital_v2() |
QA hold value | Every 6 hours |
fn_daily_snapshot_v2() |
Daily ops digest | Daily 09:00 |
fn_check_support_queue_v2() |
Support queue depth | Every 15 min |
3.3 Utility Functions¶
| Function | Purpose |
|---|---|
generate_public_batch_id() |
SHA-256 hash for batch |
get_daily_snapshot() |
Daily metrics |
get_low_stock_alerts() |
Stock below threshold |
get_current_inventory_for_reconciliation() |
Inventory display |
retry_failed_webhooks() |
Webhook retry logic |
4. Database Triggers¶
| Trigger | Table | Event | Function | Purpose |
|---|---|---|---|---|
trg_allocate_on_order |
orders | INSERT | allocate_order_to_batches() |
Auto-allocate new orders |
trg_allocate_pending_on_release |
batches | UPDATE (status → RELEASED) | allocate_pending_orders() |
Allocate waiting orders |
trigger_set_public_batch_id |
batches | INSERT | set_public_batch_id() |
Generate public batch ID |
trg_decrement_inventory_on_dispatch |
shipments | INSERT/UPDATE (status → DISPATCHED) | fn_decrement_inventory_on_dispatch() |
Reduce stock |
trg_log_inventory_movement |
inventory | UPDATE | log_inventory_movement() |
Audit trail |
5. Edge Functions¶
5.1 Ingestion Functions¶
shopify-webhook-handler¶
Purpose: Receive and process Shopify order webhooks
| Attribute | Value |
|---|---|
| Endpoint | POST /functions/v1/shopify-webhook-handler |
| Trigger | Shopify orders/create webhook |
| Auth | HMAC signature validation |
| SOP Reference | SOP-00 |
Processing Steps:
1. Validate Shopify HMAC signature
2. Store raw webhook in webhook_inbox (status: pending)
3. Call process_order_webhook_txn() function
4. Upsert customer data
5. Create order record
6. Create order items
7. Mark webhook as success/failed
Environment Variables:
- SHOPIFY_WEBHOOK_SECRET - HMAC validation key
seal-webhook-handler¶
Purpose: Receive and process Seal subscription webhooks
| Attribute | Value |
|---|---|
| Endpoint | POST /functions/v1/seal-webhook-handler |
| Trigger | Seal subscription events |
| Auth | HMAC signature validation |
| SOP Reference | SOP-SUB-00 |
Event Types Handled:
- subscription.created
- subscription.updated
- subscription.cancelled
- subscription.paused
- subscription.resumed
- billing_attempt.success
- billing_attempt.failed
5.2 Processing Functions¶
create-batch¶
Purpose: Create new production batch with all assets
| Attribute | Value |
|---|---|
| Endpoint | POST /functions/v1/create-batch |
| Trigger | Ops Portal |
| Auth | Service role |
| SOP Reference | SOP-01 |
Request Body:
Processing Steps: 1. Generate sequential batch code (PR-YYMMDD-NNN) 2. Generate public batch ID (SHA-256 hash) 3. Generate QR code image 4. Generate pouch label PDF 5. Create batch record 6. Create inventory record (QA_HOLD) 7. Send email to co-packer with label PDF
cs-agent-triage¶
Purpose: AI classification and draft response
| Attribute | Value |
|---|---|
| Endpoint | POST /functions/v1/cs-agent-triage |
| Trigger | Make.com (Gmail watch) |
| Auth | X-Internal-Secret header |
| SOP Reference | SOP-CS-01, SOP-CS-03 |
Request Body:
{
"email_body": "...",
"email_subject": "...",
"email_from": "...",
"message_id": "...",
"dedupe_key": "..."
}
Processing Steps: 1. Check dedupe_key for duplicate 2. Run preflight policy gates (health, refund, legal) 3. Load customer context 4. Search knowledge base (pgvector) 5. Call Claude API for classification 6. Generate draft response 7. Store in cs_agent_decisions 8. Return classification result
Config:
{
model: 'claude-sonnet-4-20250514',
maxIterations: 10,
maxTokens: 4096,
timeoutMs: 30000,
shadowMode: true,
confidenceThreshold: 0.70
}
send-support-email¶
Purpose: Send support response with threading
| Attribute | Value |
|---|---|
| Endpoint | POST /functions/v1/send-support-email |
| Trigger | Ops Portal, CS-03 automation |
| Auth | Service role |
| SOP Reference | SOP-CS-00 |
Processing Steps: 1. Fetch ticket email_message_id 2. Build In-Reply-To and References headers 3. Send via Customer.io Transactional API 4. Log to ticket_messages 5. Update ticket status
sync-address-change¶
Purpose: Sync address to Shopify and Seal
| Attribute | Value |
|---|---|
| Endpoint | POST /functions/v1/sync-address-change |
| Trigger | Database trigger on customers |
| Auth | Service role |
| SOP Reference | SOP-CS-00 |
5.3 Monitoring Functions¶
run-monitor¶
Purpose: Generic monitor runner
| Attribute | Value |
|---|---|
| Endpoint | POST /functions/v1/run-monitor |
| Trigger | pg_cron via pg_net |
| Auth | Anon key (internal) |
| SOP Reference | SOP-MON-01 |
Request Body:
Available Checks:
- order_ingestion_slo
- outbox_health
- stock_levels
- lab_sla
- daily_snapshot
- working_capital
- support_queue
ops-alerter¶
Purpose: Send formatted Slack alerts
| Attribute | Value |
|---|---|
| Endpoint | POST /functions/v1/ops-alerter |
| Trigger | Monitoring functions |
| Auth | None (internal) |
| SOP Reference | SOP-MON-01 |
Request Body:
{
"channel": "ops-alerts",
"severity": "warning",
"title": "Alert Title",
"message": "Description",
"fields": [{"label": "Field", "value": "Value", "inline": true}]
}
Channels:
- ops-alerts → #ops-alerts (warning)
- ops-urgent → #ops-urgent (critical)
- daily-ops → #daily-ops (info)
5.4 Operational Functions¶
calculate-pack-day-instructions¶
Purpose: Calculate PCM requirements
| Attribute | Value |
|---|---|
| Endpoint | POST /functions/v1/calculate-pack-day-instructions |
| Trigger | Ops Portal |
| Auth | Service role |
| SOP Reference | SOP-PACK-01 |
retry-failed-webhooks¶
Purpose: Retry failed webhook processing
| Attribute | Value |
|---|---|
| Endpoint | POST /functions/v1/retry-failed-webhooks |
| Trigger | pg_cron (every 5 min) |
| Auth | Service role |
| SOP Reference | SOP-00 |
6. pg_cron Jobs¶
| Job Name | Schedule | Purpose | SOP |
|---|---|---|---|
monitor-order-ingestion-slo |
*/2 * * * * |
Order ingestion health | SOP-MON-02 |
monitor-outbox-health |
*/2 * * * * |
Email queue health | SOP-MON-01 |
monitor-stock-levels |
*/15 * * * * |
Low stock alerts | SOP-MON-01 |
monitor-lab-sla |
*/15 * * * * |
Lab SLA tracking | SOP-MON-01 |
monitor-working-capital |
0 */6 * * * |
QA hold value | SOP-MON-01 |
monitor-daily-snapshot |
0 9 * * * |
Daily digest | SOP-03 |
monitor-support-queue |
*/15 * * * * |
Support queue depth | SOP-CS-03 |
retry-failed-webhooks |
*/5 * * * * |
Webhook retry | SOP-00 |
process-email-outbox |
* * * * * |
Send queued emails | SOP-05 |
order-export |
0 6 * * * |
Export orders | SOP-0X |
7. Make.com Scenarios¶
| Scenario | Trigger | Purpose | SOP |
|---|---|---|---|
| CS-01: Email Triage | Gmail inbox watch | Process inbound support emails | SOP-CS-01 |
| Address Update Sync | Supabase webhook | Sync address to Shopify + Seal | SOP-CS-00 |
| Lab Results Processing | Gmail (lab emails) | Parse PDF → OpenAI → update batch | SOP-01 |
| DPD Tracking Ingestion | DPD webhook | Store tracking events | SOP-02 |
| Courier Exception Handler | courier_events trigger | Customer notification | SOP-02 |
| Event Bridge | Database trigger | Deliver events to Customer.io | SOP-LC-01 |
Make.com Webhook URLs¶
| Scenario | URL |
|---|---|
| Address Update Sync | https://hook.eu2.make.com/yxpdv3l9gvy433tem2tmxds2ssek53t2 |
| CS-01 Email Triage | https://hook.eu2.make.com/[REDACTED] |
| DPD Tracking | https://hook.eu2.make.com/[REDACTED] |
8. Slack Channels¶
| Channel | Purpose | Severity | Sources |
|---|---|---|---|
#ops-alerts |
Standard operational alerts | Warning | All monitors |
#ops-urgent |
Critical issues | Critical | Stock, working capital, escalations |
#daily-ops |
Daily digest | Info | Daily snapshot |
9. Customer.io Templates¶
9.1 Transactional Templates¶
| Template ID | Trigger | Purpose |
|---|---|---|
order_confirmation_first |
First order created | Welcome + order receipt |
order_confirmation_repeat |
Repeat order created | Order receipt |
order_dispatched |
Order dispatched | Tracking info |
order_delivered |
Order delivered | Delivery confirmation + proof link |
subscription_paused |
Subscription paused | Pause confirmation |
subscription_resumed |
Subscription resumed | Resume confirmation |
subscription_cancelled |
Subscription cancelled | Cancellation confirmation |
delivery_skipped |
Delivery skipped | Skip confirmation |
delivery_rescheduled |
Delivery rescheduled | New date confirmation |
9.2 Lifecycle Templates¶
| Template ID | Timing | Purpose |
|---|---|---|
transition_guide |
+24 hours | How to introduce raw |
transition_week_1 |
+7 days | Week 1 check-in |
transition_week_2 |
+14 days | Week 2 check-in |
delivery_reminder_7day |
-7 days | Upcoming delivery |
delivery_lock_48h |
-48 hours | Last chance to modify |
winback_30day |
+30 days after cancel | Win-back offer |
winback_60day |
+60 days after cancel | Stronger offer |
9.3 Operational Templates¶
| Template ID | Trigger | Purpose |
|---|---|---|
delivery_exception |
Courier exception | Proactive delay notice |
payment_failed_day0 |
Payment fails | Update card prompt |
payment_failed_day3 |
+3 days | Reminder |
payment_failed_day7 |
+7 days | Urgent reminder |
card_expiring_30day |
-30 days | Card expiry warning |
card_expiring_7day |
-7 days | Urgent card update |
10. Related Documentation¶
| Document | Purpose |
|---|---|
| Layer 1: System Map | Visual architecture and data flows |
| Layer 3: Integration Specifications | API specs for external systems |
| Layer 4: SOP Cross-Reference | Master SOP index |
Protocol Raw — Verified safe, batch by batch.