Overview
src/services/dealsService.ts is the central coordinator of the two-layer hybrid filter pipeline. It exposes two public functions — one for read-only queries and one used by the cron job that also records results into the deduplication log.
Exported functions
fetchDeals
America/Bogota timezone), it is returned immediately without hitting any external API. Otherwise the full pipeline runs via runPipeline().
fetchAndMarkDeals
markAsNotified(). Used exclusively by the cron job so that games broadcast today are suppressed in future runs within the deduplication window.
Returns the same PipelineResult union it receives from runPipeline() so the cron can handle each status appropriately.
Return type — PipelineResult
Discriminant field of the union.
Present only when
status === 'ok'. The curated list of deals ready for broadcast.Present only when
status === 'ai_error'. A short description of why the AI layer failed.Pipeline lock — pipelineRunning
A module-level boolean flag prevents concurrent pipeline executions:
runPipeline() is called while a run is already in progress (e.g., a /deals command arrives during a cron execution), the second caller immediately returns the current snapshot if it is fresh, or { status: 'no_deals' } otherwise. This prevents:
- Duplicate calls to the CheapShark API and GPT
- Race conditions on the JSON data files
Pipeline implementation — _runPipelineImpl
applyHardFilters so that module remains pure (no I/O). If candidates is empty, returns { status: 'no_deals' } immediately.
Step 3 — Hash candidates for cache
{ status: 'ai_error', reason } is returned.
Step 5 — Reconstruct FilteredDeal list
When the AI layer fails but a fresh same-day snapshot exists, the pipeline returns
{ status: 'ok', deals: snapshot.deals } as a fallback. A snapshot from a previous day is not used as a fallback because prices and deal URLs may have already changed.