Daily Bluesky bot for AT Mot. Invites players and congratulates yesterday's solvers.
0

Configure Feed

Select the types of activity you want to include in your feed.

at trunk 1.8 kB View raw
1import { SOLVER_SAMPLE_CAP, type Lang, type ResultRecord, type YesterdayCounts } from './config.js'; 2import { dailyPlayerCount, dailyResultBacklinks, backlinkUri } from './constellation.js'; 3import { getRecordByUri } from './identity.js'; 4 5/** 6 * Yesterday's { players, solvers, sampled } for one language. `players` is the 7 * cheap distinct-DID count; `solvers` is the number of *distinct* DIDs whose 8 * sampled record (≤ SOLVER_SAMPLE_CAP) shows a win. 9 * 10 * Both numbers are deduped by DID so they are commensurable, and `solvers` is 11 * clamped to `players` so the published copy can never say more people solved 12 * than played. `sampled` is true whenever the sample is incomplete — the page 13 * was truncated, or we resolved fewer distinct players than Constellation 14 * counted (a dropped read or index skew) — so compose hedges instead of 15 * publishing an untrustworthy exact "X solved, Y didn't". 16 */ 17export async function yesterdayCounts(lang: Lang, yesterdayN: number): Promise<YesterdayCounts> { 18 const [players, { records, truncated }] = await Promise.all([ 19 dailyPlayerCount(lang, yesterdayN), 20 dailyResultBacklinks(lang, yesterdayN, SOLVER_SAMPLE_CAP), 21 ]); 22 23 const seenDids = new Set<string>(); 24 const solverDids = new Set<string>(); 25 await Promise.all( 26 records.map(async (bl) => { 27 const rec = await getRecordByUri<ResultRecord>(backlinkUri(bl)); 28 if (!rec || rec.lang !== lang || rec.puzzleNumber !== yesterdayN) return; 29 seenDids.add(bl.did); 30 if (rec.solved) solverDids.add(bl.did); 31 }), 32 ); 33 34 let solvers = solverDids.size; 35 if (players != null) solvers = Math.min(solvers, players); 36 37 const sampled = truncated || players == null || (players > 0 && seenDids.size < players); 38 39 return { players, solvers, sampled }; 40}