Skip to main content
All shared types are defined in src/types/index.ts and imported across the pipeline services, filters, notifier, and bot commands.

Deal

Raw deal object returned by the CheapShark API. All numeric values arrive as strings and are parsed downstream.
export interface Deal {
  title: string;
  metacriticScore: string;
  steamRatingText: string;
  steamRatingPercent: string;
  salePrice: string;
  normalPrice: string;
  savings: string;
  steamAppID: string;
  dealID: string;
  thumb: string;
}
title
string
Display name of the game as returned by CheapShark.
metacriticScore
string
Metacritic review score as a numeric string. Empty string if no score exists.
steamRatingText
string
Steam user review category label, e.g. "Very Positive", "Overwhelmingly Positive", "Mixed".
steamRatingPercent
string
Percentage of positive Steam user reviews as a numeric string.
salePrice
string
Current discounted price in USD.
normalPrice
string
Original price in USD before the sale.
savings
string
Discount percentage as a decimal string (e.g. "66.661110"). Rounded to an integer and stored in FilteredDeal.savingsPercent.
steamAppID
string
Steam application ID. Used as the canonical identifier throughout the entire pipeline.
dealID
string
CheapShark deal hash. Used to construct the redirect URL: https://www.cheapshark.com/redirect?dealID={dealID}.
thumb
string
URL of the Steam capsule thumbnail image.

FilteredDeal

A deal that has passed both filter layers and is ready to be stored, cached, and sent to users. All numeric fields have been parsed from strings. Prices and scores come from the original CheapShark data — not from GPT.
export interface FilteredDeal {
  title: string;
  steamAppID: string;
  salePrice: string;
  normalPrice: string;
  savingsPercent: number;
  metacriticScore: number;
  steamRatingText: string;
  dealUrl: string;
  reason: string;
}
title
string
Display name of the game.
steamAppID
string
Steam application ID.
salePrice
string
Current sale price in USD.
normalPrice
string
Original price in USD.
savingsPercent
number
Discount percentage as a rounded integer (e.g. 66). Parsed from Deal.savings.
metacriticScore
number
Metacritic score as an integer. 0 if not available.
steamRatingText
string
Steam review category label.
dealUrl
string
CheapShark redirect URL constructed from dealID. Sends the user to the active Steam deal page.
reason
string
Brief curation reason provided by GPT, in Spanish. This is the only field sourced from the AI model. Truncated to config.ai.maxReasonLength (120 characters) even if GPT exceeds the 12-word prompt limit.

NotifiedGame

Minimal deduplication record written to data/notified_games.json after each successful broadcast. Only the fields strictly necessary for deduplication are stored.
export interface NotifiedGame {
  steamAppID: string;
  notifiedAt: string;
}
steamAppID
string
Steam application ID of the notified game.
notifiedAt
string
ISO 8601 date-time string recording when the game was last notified. Used to enforce the deduplication window (DEDUP_DAYS, default 7 days).
title is intentionally not stored in NotifiedGame. The title is not needed for deduplication — only the steamAppID and timestamp are required to determine whether a game has been recently notified.

AISelection

Structured output from GPT representing the curation decision. GPT selects which candidate IDs pass and provides a brief reason for each.
export interface AISelection {
  selectedIds: string[];
  reasons: Record<string, string>;
}
selectedIds
string[]
Array of steamAppID strings chosen by GPT. Only IDs present in the input candidate set are accepted.
reasons
Record<string, string>
Map of steamAppID to a brief curation reason in Spanish. Keys correspond to entries in selectedIds.

AIFilterResult

Union type returned by filterDealsWithAI. Distinguishes a successful selection from a parse or network failure.
export type AIFilterResult =
  | { status: 'ok'; selection: AISelection }
  | { status: 'error'; reason: string };
status
'ok' | 'error'
'ok' — GPT responded with valid JSON and the selection was parsed successfully.'error' — GPT returned invalid JSON, the request timed out, or another failure occurred. The pipeline falls back to PipelineResult { status: 'ai_error' }.
selection
AISelection
Present when status is 'ok'. Contains the selected IDs and reasons.
reason
string
Present when status is 'error'. Human-readable description of the failure.

PipelineResult

Union type returned by fetchDeals (the top-level pipeline orchestrator) and propagated to bot command handlers. Distinguishes three distinct outcomes.
export type PipelineResult =
  | { status: 'ok'; deals: FilteredDeal[] }
  | { status: 'no_deals' }
  | { status: 'ai_error'; reason: string };
status
'ok' | 'no_deals' | 'ai_error'
'ok' — pipeline completed successfully and at least one deal is available.'no_deals' — pipeline completed but no deals survived both filter layers.'ai_error' — the AI filter failed. The bot replies with a user-facing error message and does not show deals.
deals
FilteredDeal[]
Present when status is 'ok'. The final curated list of deals ready to be formatted and sent.
reason
string
Present when status is 'ai_error'. Propagated from AIFilterResult.reason.

DailySnapshot

Persisted cache record written to data/snapshot.json after each successful pipeline run. The /deals command reads from this snapshot directly rather than re-running the pipeline on every request.
export interface DailySnapshot {
  deals: FilteredDeal[];
  candidatesHash: string;
  createdAt: string;
}
deals
FilteredDeal[]
The final curated deal list from the most recent successful pipeline run.
candidatesHash
string
SHA hash of the rule-filtered candidate set. If the same candidates are present on the next run, the pipeline skips the GPT call and reuses the existing snapshot.
createdAt
string
ISO 8601 date-time string recording when the snapshot was created. Used to determine whether the snapshot is still valid for the current day.

Build docs developers (and LLMs) love