S3 outputs

Static artifacts are published to records.abstractplay.com (S3 + CloudFront). DynamoDB exports land in abstractplay-db-dump daily (dumpdb); batch jobs read the latest completed export.

Records bucket layout

Key pattern Producer Description
ALL.json records Array of all APGameRecord objects
_summary.json summarize Full site-wide analytics monolith — see Summarize
_summary-site.json summarize Tier 0 site overview (lazy-load bootstrap)
_summary-players.json summarize Tier 1 per-player bulk stats
_summary-ratings.json summarize Tier 2 ratings bulk (Glicko-enriched)
player/{playerId}-summary.json player-summary-worker Per-player summary slice (~few KB)
_summary-player-manifest.json player-summary-fanout Fan-out metadata after enqueue
_manifest.json records-manifest S3 object listing + summaryFiles (v2)
meta/{metaGame}.json records Game records filtered by meta game name
player/{playerId}.json records Game records for one player
event/{eventId}.json records Game records for a tournament or org event
ttm/{playerId}.json records-ttm Array of inter-move durations (milliseconds)
mvtimes.json records-move-times Move activity counts by meta game and time window
recommendations/cooccur.json records-cooccur PMI co-occurrence matrix for game recommendations
tournament-summary.json tournament-data Per-player tournament aggregate stats
player/tournaments/{playerId}.json tournament-data Individual tournament results for one player

Game record format

Each record in ALL.json, meta/, player/, and event/ files conforms to the APGameRecord schema documented in Recranks. Records are produced by calling GameFactory(metaGame, state).genRecord(...) in records.ts.

Key header fields used downstream:

_summary.json and tier files

The monolith (_summary.json) is typed as StatSummary in src/types/stats/StatSummary.ts. Tier types: StatSummaryTiers.ts.

Key Type Role
_summary.json StatSummary Full superset; backward compatible download / batch consumers
_summary-site.json StatSummarySite Site stats, geo, histograms (site keys), metaStats, plays, topPlayers
_summary-players.json StatSummaryPlayers players.* + histograms.players / playerTimeouts
_summary-ratings.json StatSummaryRatings ratings.* including Glicko aggregates
player/{userId}-summary.json PlayerSummarySlice One user's Tier 1/2 subset

All JSON objects use Content-Type: application/json. Each tier/slice includes generated (ISO timestamp).

Monolith top-level fields

Field Meaning
numGames, numPlayers Totals from ALL.json
oldestRec, newestRec Date range of completed games
timeoutRate Fraction of games with clock timeout or abandonment
abandonedRate Fraction of games closed by abandonment only
playContext Casual vs tournament/org-event game counts
pieRates Pie invocation rates for supported meta games
playerCountMix Player-count distribution for multi-player metas
ratings ELO/Glicko/Trueskill aggregates (highest, avg, weighted, glickoByGame, glickoSite, glickoMeta)
topPlayers Top-rated player/game pairs
plays, players Game and player activity rankings
histograms Play-count distributions, first-timers, returning players, weekly active movers, timeout/abandonment rates
metaStats Per-game two-player stats (length, first-player win rate, draw rate)
geoStats Registered user counts by country (from live USERS table)
activeGeoStats Players who completed a game in the past 30 days, by profile country
rivalries Two-player pair frequencies (≥50 shared games); anonymized unless both players opted in (players array when named)
seasonality Move-time activity by UTC day/hour (from mvtimes.json; last 365 days)
hoursPer { mean, median, n, byWeek } — winsorized (p2–p98) hours per move site-wide

Full field documentation: Summarize.

mvtimes.json

Produced by records-move-times. Object with:

Key Meaning
raw1w, raw1m, raw6m, raw1y Move counts by meta game in each rolling window
players1w, … Distinct active players by meta game per window
playersSum1w, … Cumulative unique-player scores by meta game
seasonality Site-wide move activity bins (last 365 days): { movesByDow, playersByDow, movesByHour, windowDays }
weeklyActiveMovers { originMs, byWeek } — distinct players with ≥1 move per seven-day bucket (same origin as completion histograms; move data ~1y)

summarize copies seasonality and aligns weeklyActiveMovers into _summary.json (histograms.activeMovers).

recommendations/cooccur.json

PMI-normalized co-occurrence neighbors per meta-game for the hybrid recommender. Includes optional stars boost (includeStarredBoost). Full schema and algorithm: Recommendation co-occurrence.

Private ops bucket (private-ops-153672715141-us-east-1-an)

Not served via CloudFront. IAM-restricted.

Key pattern Producer Description
recommendations/analytics/_state.json records-rec-analytics Watermark and dedupe state
recommendations/analytics/daily/YYYY-MM-DD.json records-rec-analytics UTC daily impression rollups
recommendations/analytics/summary.json records-rec-analytics Latest window + rolling 7d/30d
recommendations/analytics/report/YYYY-MM-DD.md records-rec-analytics Human/agent-readable report
gamemove-layout/analytics/_state.json layout-feedback-analytics Watermark and dedupe state
gamemove-layout/analytics/daily/YYYY-MM-DD.json layout-feedback-analytics UTC daily layout feedback rollups (includes note text)
gamemove-layout/analytics/summary.json layout-feedback-analytics Latest window + rolling 7d/30d + recentNotes
gamemove-layout/analytics/report/YYYY-MM-DD.md layout-feedback-analytics Human-readable layout feedback report
stats/rivalries.json summarize All qualifying rivalry pairs with user IDs and display names (not anonymized; min 5 shared games)

See Recommendation analytics and Summarize.

Dump bucket layout

Exports from dumpdb appear under:

AWSDynamoDB/{export-uid}/manifest-summary.json
AWSDynamoDB/{export-uid}/data/*.ion.gz

Batch functions locate the newest manifest-summary.json, extract the UID, and read all data/*.gz files for that export.

_manifest.json (v2)

Typed in src/utils/recordsManifest.ts. The records bucket manifest is no longer a bare S3 Contents array.

{
  version: 2,
  generated: string,          // ISO timestamp when manifest was built
  summaryFiles: {
    monolith:  { key, lastModified?, size? },
    site:      { key, lastModified?, size? },
    players:   { key, lastModified?, size? },
    ratings:   { key, lastModified?, size? },
    playerSummaryPattern: "player/{userId}-summary.json",
    playerManifest: { key: "_summary-player-manifest.json", ... }
  },
  objects: _Object[]            // full bucket listing (same data as legacy root array)
}

Backward compatibility: legacy consumers expecting a root array should use Array.isArray(data) ? data : data.objects.

records-manifest runs at 04:00 and 07:30 UTC (late pass is after summarize at 06:00 and player-summary-fanout at 06:15). The handler logs a warning if any required _summary*.json tier key is missing from the listing.

CloudFront and caching

Distribution: EM4FVU08T5188https://records.abstractplay.com

CloudFront does not run blanket /* invalidations (removed to avoid quota/cost issues). Freshness relies on S3 object headers set by crons.

S3 cache headers (all records-bucket JSON)

Object type Cache-Control Content-Type
Daily batch JSON (ALL.json, meta/*, player/*, _summary*.json, etc.) public, max-age=0, must-revalidate application/json
_manifest.json no-cache application/json

After each daily cron overwrite, the next CDN/browser request revalidates with S3 (If-None-Match); changed objects return a new body without invalidation.

Implemented in src/utils/recordsJson.ts (putRecordsJson).

Gzip compression

CloudFront compresses responses only when the origin returns a compressible Content-Type (e.g. application/json). Objects previously uploaded as application/octet-stream were not compressed.

  1. Crons — set Content-Type: application/json on upload (above).
  2. CloudFront — enable Compress objects automatically on the default cache behavior.

One-time enable (prod credentials):

npm run enable-records-cdn-compress
# preview: npm run enable-records-cdn-compress -- --dry-run

Or in the AWS Console: CloudFront → distribution EM4FVU08T5188Behaviors → Edit default → Compress objects automatically: Yes.

Verify after deploy + CF propagation:

curl.exe -sI -H "Accept-Encoding: gzip" https://records.abstractplay.com/_summary-site.json

Expect Content-Encoding: gzip and Content-Length much smaller than the uncompressed object.

Clients should use _manifest.json summaryFiles or tier URLs rather than hard-coding cache assumptions.

Related