Snapshot cache
After a successful pipeline run, the result is persisted todata/snapshot.json.
Data structure
types/index.ts
Snapshot freshness
A snapshot is considered fresh if itscreatedAt date matches todayβs date in the America/Bogota timezone.
snapshotCache.ts
The timezone is hard-coded to
America/Bogota because the cron schedule is defined in that timezone. Without this, a server running in UTC could compare dates against the wrong calendar day and incorrectly serve a yesterdayβs snapshot as fresh.Startup cleanup
At process startup,clearStaleSnapshot() is called to delete any snapshot that is not from today. This prevents the bot from serving outdated deals if it was restarted after being offline for one or more days.
snapshotCache.ts
Candidate hash caching
Every time the pipeline runs, the Layer 1 candidate set is hashed before calling GPT. If the hash matches the one stored in the snapshot, the existing selection is reused and GPT is not called.The hash function
snapshotCache.ts
Fields included in the hash
The hash covers both the fields GPT uses for its decision and the fields that determine the deal visible to the user:| Field | Why itβs included |
|---|---|
steamAppID | Game identity |
title | Sent to GPT for recognition |
metacriticScore | Sent to GPT for recognition |
steamRatingText | Sent to GPT for recognition |
salePrice | User-visible data; a price change should invalidate the cache |
normalPrice | User-visible data; affects displayed discount |
savings | User-visible data |
dealID | Changes when CheapShark rotates the deal link |
When is GPT called?
| Scenario | GPT called? | Reason |
|---|---|---|
| First run of the day, no snapshot | Yes | No hash to compare against |
| Candidates changed since last snapshot | Yes | Hash mismatch |
/deals requested, fresh snapshot exists | No | Snapshot served directly |
| Cron fires, same candidates as last run | No | Hash matched |
| GPT call fails, fresh snapshot exists | No | Snapshot used as fallback |
| GPT call fails, no fresh snapshot | β | ai_error returned; no broadcast |
Deduplication
The deduplication system prevents the same game from being recommended to users more than once within a rolling window.Data structure
types/index.ts
data/notified_games.json.
How it works
Load notified IDs
Before Layer 1 runs,
getNotifiedIds() reads notified_games.json and returns a Set<string> of steamAppID values whose notifiedAt is within the last DEDUP_DAYS days.Inject into rules filter
The
notifiedIds set is passed to applyHardFilters(). Any deal whose steamAppID is in the set is rejected immediately. This keeps rulesFilter.ts free of I/O.Deduplication window
The lookback window is configured withDEDUP_DAYS (default: 7). A game broadcast on Monday will not appear again until the following Tuesday at the earliest.
deduplication.ts
Deduplication applies only to the cron broadcast path (
fetchAndMarkDeals). When a user calls /deals, the pipeline may include games that were already broadcast, because fetchDeals never writes to notified_games.json.Atomic writes
Bothsnapshot.json and notified_games.json are written using write-file-atomic, which writes to a temp file and renames it. This prevents a partial write from corrupting the file if the process is interrupted.
Architecture
How the cache layer fits into the overall system and the pipeline lock.
Filter pipeline
The Layer 1 rules that produce the candidate set that is hashed.