Skip to main content

Database Schema Documentation

Complete documentation of the Sprout platform database schemas.

Overview

The Sprout platform uses PostgreSQL with Prisma ORM. There are two main database schemas:

  • sprout_backend - Main application database
  • sprout_etl - ETL service database (includes additional tables for geographic data)

sprout_backend Schema

Core Models

SkyboxInventory

Stores available inventory from Skybox platform.

model SkyboxInventory {
id Int @id @default(autoincrement())
skybox_inventory_id Int @unique
skybox_event_id Int
listing_price Decimal? @db.Decimal(10, 2)
event_date DateTime?
tags String?
metadata Json
created_at DateTime @default(now())
last_update DateTime @updatedAt
deleted_at DateTime?
}

Key Fields:

  • skybox_inventory_id - Unique Skybox inventory identifier
  • skybox_event_id - Associated Skybox event ID
  • listing_price - Current listing price
  • metadata - Additional inventory data (JSON)

SkyboxSoldInventory

Stores sold inventory records from Skybox.

Structure: Similar to SkyboxInventory, tracks sold items.

AssignedSoldInventory

Tracks purchase assignments and order confirmations.

model AssignedSoldInventory {
id Int @id @default(autoincrement())
skyboxInventoryId Int
totalOrderCost Decimal
orderConfirmationNumber String
additionalOrderNumbers String?
multi_pack Int?
website String
ticketsReplaced Boolean
userId String
account String
password String
created_at DateTime @default(now())
last_update DateTime @updatedAt
notes String?
manuallyScheduled Boolean @default(false)
integration String
}

Key Fields:

  • orderConfirmationNumber - Order confirmation from purchase
  • integration - Which integration was used (TickOps, etc.)
  • manuallyScheduled - Whether purchase was manually triggered

Event Models

TicketmasterEvent

Stores events from Ticketmaster Discovery API.

model TicketmasterEvent {
id Int @id @default(autoincrement())
event_id String
name String?
event_url String?
event_date String?
venue String?
created_at DateTime @default(now())

@@index([event_id])
}

SkyboxResult

Stores matched Skybox events with Ticketmaster events.

model SkyboxResult {
id Int @id @default(autoincrement())
ticketmaster_event_id String
skybox_event_id Int
skybox_event_name String
skybox_event_url String
skybox_event_date String
skybox_venue String
created_at DateTime @default(now())
scrape_results ScrapeResult[]
tickops_results TickopsResult[]

@@index([skybox_event_id])
@@index([ticketmaster_event_id])
}

Relationships:

  • scrape_results - Scraped data for this event
  • tickops_results - TickOps API results

CombinedResult

Combines Ticketmaster and Skybox event data with pricing information.

model CombinedResult {
id Int @id @default(autoincrement())
ticketmaster_event_id String
skybox_event_id Int
skybox_event_name String
skybox_event_url String
skybox_event_date String
skybox_venue String
ticketmaster_event_name String
ticketmaster_event_url String
ticketmaster_venue String
ticketType String @default("")
missingReason String?
tickopsTickets Json
vividTickets Json
final_data Json
processed Boolean @default(false)
last_monitored_at DateTime?
next_monitored_at DateTime?
latest_remaining_tickets Int?
monitoredListings MonitoredListing[]
eventRunItems EventRunItem[]
purchases Purchase[]

@@index([skybox_event_id])
@@index([ticketmaster_event_id])
}

Key Fields:

  • tickopsTickets - Ticket data from TickOps API
  • vividTickets - Ticket data from VividSeats
  • processed - Whether event has been processed
  • last_monitored_at - Last price monitoring check

Pipeline Models

EventRun

Tracks ETL pipeline execution runs.

model EventRun {
id Int @id @default(autoincrement())
status String? // 'running', 'completed', 'failed'
metadata Json?
created_at DateTime @default(now())
finished_at DateTime?
updated_at DateTime @updatedAt
created_purchases Boolean @default(false)

runItems EventRunItem[]
}

EventRunItem

Individual items processed in an event run.

model EventRunItem {
id Int @id @default(autoincrement())
eventRunId Int
combinedResultId Int
skybox_event_id Int
metadata Json?
created_at DateTime @default(now())
updated_at DateTime @updatedAt

eventRun EventRun @relation(fields: [eventRunId], references: [id])
combinedResult CombinedResult @relation(fields: [combinedResultId], references: [id])

@@index([eventRunId])
@@index([combinedResultId])
@@index([skybox_event_id])
}

Monitoring Models

MonitoredListing

Tracks inventory items being monitored for price changes.

model MonitoredListing {
id Int @id @default(autoincrement())
skybox_inventory_id Int
skybox_event_id Int
combined_result_id Int?
is_monitoring_active Boolean @default(true)
is_monitoring_enabled Boolean @default(true)
monitoring_disabled_reason String?
monitoring_disabled_at DateTime?
last_price_check_at DateTime?
last_our_price_at_check Decimal?
last_market_price_compared Decimal?
last_check_status PriceCheckStatus?
notes String?

created_at DateTime @default(now())
updated_at DateTime @updatedAt

combinedResult CombinedResult? @relation(fields: [combined_result_id], references: [id])
priceHistory MonitoredListingPriceHistory[]

@@index([skybox_inventory_id])
@@index([skybox_event_id])
@@index([combined_result_id])
@@index([is_monitoring_active])
@@index([is_monitoring_enabled])
@@index([last_check_status])
}

MonitoredListingPriceHistory

Historical price check records.

model MonitoredListingPriceHistory {
id Int @id @default(autoincrement())
monitored_listing_id Int
check_timestamp DateTime
our_price_at_check Decimal
market_price_compared Decimal?
check_status PriceCheckStatus
details Json?

monitoredListing MonitoredListing @relation(fields: [monitored_listing_id], references: [id], onDelete: Cascade)

@@index([monitored_listing_id])
@@index([check_timestamp])
@@index([check_status])
}

PriceCheckStatus Enum:

  • COMPETITIVE - Price is competitive
  • UNCOMPETITIVE - Price is not competitive
  • NO_COMPETITORS_IN_SECTION - No competitors found
  • MARKET_DATA_UNAVAILABLE_API - Market data unavailable
  • OUR_DATA_MISSING - Our inventory data missing
  • MARKET_DATA_FETCH_FAILED - Failed to fetch market data
  • PROCESSING_ERROR - Error during processing
  • EVENT_SOLD_OUT - Event is sold out
  • PARTIAL_DATA_AVAILABLE - Only partial data available

User Models

User

Application users.

model User {
id Int @id @default(autoincrement())
email String @unique
firstName String?
lastName String?
picture String?
is_admin Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

Purchase Models

Purchase

Tracks purchase attempts and results.

model Purchase {
id Int @id @default(autoincrement())
combinedResultId Int @unique
status String // 'SUCCESS', 'FAILED'
purchasePrice Decimal @db.Decimal(10, 2)
competitorPrice Decimal @db.Decimal(10, 2)
errorMessage String?
metadata Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
combinedResult CombinedResult @relation(fields: [combinedResultId], references: [id])

@@index([combinedResultId])
}

sprout_etl Schema

The ETL schema includes all models from sprout_backend plus additional models:

Geographic Models

Geo

Geographic locations for event discovery.

model Geo {
id Int @id @default(autoincrement())
latitude Float
longitude Float
location Unsupported("public.geography(Point,4326)")? // PostGIS Point geography
city String?
state String?
country String? @default("US")
radius Float // Search radius in miles/km
last_checked_at DateTime?
event_count Int @default(0)
page_depth Int @default(0)
active Boolean @default(true)
frequency Int @default(6) // Hours between checks
duplicate_count Int @default(0)
created_at DateTime @default(now())
updated_at DateTime @updatedAt

ticketmasterEvents TicketmasterEvent[]

@@index([latitude, longitude])
@@index([active, last_checked_at])
@@index([frequency])
}

Key Features:

  • Uses PostGIS for geographic queries
  • Tracks event discovery by location
  • Configurable check frequency

Venue

Venue information with geographic data.

model Venue {
id Int @id @default(autoincrement())
ticketmaster_id String? @unique
name String
address String?
city String?
state String?
country String? @default("US")
postal_code String?
latitude Float?
longitude Float?
location Unsupported("public.geography(Point,4326)")? // PostGIS Point geography
timezone String?
capacity Int?
venue_type String? // Arena, Stadium, Theater, etc.
metadata Json?
created_at DateTime @default(now())
updated_at DateTime @updatedAt

ticketmasterEvents TicketmasterEvent[]

@@index([ticketmaster_id])
@@index([city, state])
@@index([latitude, longitude])
@@index([venue_type])
}

Additional Models

TicketmasterQuotaHistory

Tracks Ticketmaster API quota usage.

model TicketmasterQuotaHistory {
id Int @id @default(autoincrement())
hour_timestamp DateTime @unique
daily_used Int
daily_limit Int
hourly_used Int
hourly_limit Int
is_quota_exhausted Boolean
created_at DateTime @default(now())
updated_at DateTime @updatedAt

@@index([hour_timestamp, created_at])
}

TicketmasterRequestLog

Logs all Ticketmaster API requests for monitoring.

model TicketmasterRequestLog {
id Int @id @default(autoincrement())
sync_run_id String
request_url String
request_params Json
response_status Int
response_time_ms Int
total_elements Int?
total_pages Int?
page_number Int
page_size Int
events_returned Int
events_saved Int
events_duplicate Int
error_message String?
response_data Json?
created_at DateTime @default(now())

@@index([sync_run_id, created_at])
@@index([created_at])
@@index([response_status])
}

Strategy

Trading strategies for purchase decisions.

model Strategy {
id Int @id @default(autoincrement())
name String @unique
description String?
parameters Json?
active Boolean @default(true)
priority Int @default(0)
created_at DateTime @default(now())
updated_at DateTime @updatedAt

@@index([active, priority])
}

Relationships

Event Matching Flow

TicketmasterEvent

SkyboxResult (matches Ticketmaster to Skybox)

CombinedResult (combines all data sources)

MonitoredListing (tracks price monitoring)

Purchase (purchase attempts)

Pipeline Flow

EventRun

EventRunItem (one per event processed)

CombinedResult (references the combined event data)

Indexes

Key indexes for performance:

  • Event IDs: event_id, skybox_event_id, ticketmaster_event_id
  • Monitoring: is_monitoring_active, last_check_status
  • Geographic: latitude, longitude (for PostGIS queries)
  • Timestamps: created_at, last_checked_at (for time-based queries)

Migrations

Migrations are managed via Prisma:

# Create new migration
doppler run -- npx prisma migrate dev --name migration_name

# Apply migrations
doppler run -- npx prisma migrate deploy

# Check migration status
doppler run -- npx prisma migrate status

Data Access Patterns

Common Queries

Find events by location:

const events = await prisma.ticketmasterEvent.findMany({
where: {
geo: {
active: true,
last_checked_at: { lt: new Date() }
}
},
include: { geo: true }
});

Get monitored listings:

const listings = await prisma.monitoredListing.findMany({
where: {
is_monitoring_active: true,
combinedResult: {
processed: false
}
},
include: {
combinedResult: true,
priceHistory: {
orderBy: { check_timestamp: 'desc' },
take: 10
}
}
});

Next Steps