mirror of
https://github.com/tiennm99/vngeoguessr.git
synced 2026-10-13 02:14:54 +00:00
Four scripts each carried a copy of the .env parser, and one copy lacked the unquoting the others had, so vercel env pull output broke it. They share scripts/lib/env.mjs now. The hand-edited region configuration moves out of the boundary builder into scripts/lib/region-config.mjs, where it no longer shares a name with the generated tree the sibling scripts import. scripts/lib/assign-districts.mjs, which decides which district a panorama is credited to, gets its first tests, against synthetic polygons so they pin geometry rules rather than data. A data:refresh command runs the whole pipeline, with its Mapillary cost stated where it is invoked.
64 lines
2.3 KiB
JavaScript
64 lines
2.3 KiB
JavaScript
// Print the daily play statistics /api/guess records in Redis.
|
|
//
|
|
// Usage: node scripts/stats.mjs [days]
|
|
// days how many most-recent days to show (default 14)
|
|
//
|
|
// Needs the Upstash pair (UPSTASH_REDIS_REST_URL/_TOKEN or KV_REST_API_URL/
|
|
// _TOKEN) in .env or the environment. Read-only: a handful of HGETALL and
|
|
// PFCOUNT commands, nothing written.
|
|
//
|
|
// Columns: rounds and distinct players that day, rounds per player, and the
|
|
// share of rounds that scored zero at each level the player could pick. The
|
|
// last line counts distinct players across the whole window against the sum of
|
|
// the daily counts: the difference is player-days from someone who had
|
|
// already played on another day in the window, the cheapest available
|
|
// measure of whether anyone comes back.
|
|
|
|
import { statsDays, readDay, distinctPlayersAcross } from '../src/lib/stats.js';
|
|
import { applyEnvFile } from './lib/env.mjs';
|
|
|
|
applyEnvFile();
|
|
|
|
const LEVELS = ['country', 'province', 'district', 'daily'];
|
|
const windowDays = Math.max(1, Number(process.argv[2]) || 14);
|
|
|
|
const days = (await statsDays()).slice(-windowDays);
|
|
if (days.length === 0) {
|
|
console.log('No statistics recorded yet.');
|
|
process.exit(0);
|
|
}
|
|
|
|
const pct = (part, whole) => (whole ? `${Math.round((part / whole) * 100)}%` : '-');
|
|
const header = ['day', 'rounds', 'players', 'r/p', ...LEVELS.map((level) => `${level} 0-share`)];
|
|
const rows = [header];
|
|
let totalRounds = 0;
|
|
let sumPlayers = 0;
|
|
|
|
for (const day of days) {
|
|
const stats = await readDay(day);
|
|
totalRounds += stats.rounds;
|
|
sumPlayers += stats.players;
|
|
rows.push([
|
|
stats.day,
|
|
String(stats.rounds),
|
|
String(stats.players),
|
|
stats.players ? (stats.rounds / stats.players).toFixed(1) : '-',
|
|
...LEVELS.map((level) => {
|
|
const bucket = stats.byLevel[level];
|
|
return bucket ? `${pct(bucket.zero, bucket.rounds)} of ${bucket.rounds}` : '-';
|
|
}),
|
|
]);
|
|
}
|
|
|
|
const widths = header.map((_, i) => Math.max(...rows.map((row) => row[i].length)));
|
|
for (const row of rows) {
|
|
console.log(row.map((cell, i) => cell.padEnd(widths[i])).join(' '));
|
|
}
|
|
|
|
const distinct = await distinctPlayersAcross(days);
|
|
console.log('');
|
|
console.log(`${days.length} days, ${totalRounds} rounds, ${distinct} distinct players.`);
|
|
console.log(
|
|
`${sumPlayers - distinct} returning player-days (${pct(sumPlayers - distinct, sumPlayers)} of ${sumPlayers} player-days).`
|
|
);
|