S10 (a1): public ledger profiles v1

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kev
2026-07-11 19:32:22 -04:00
parent aaafc3e0f2
commit b20145c215
19 changed files with 1226 additions and 6 deletions
+11 -5
View File
@@ -386,6 +386,11 @@ async function countRowsForDate(gameDate, opts = {}) {
* 30-day aggregate over the PUBLIC model record (user_id NULL): hit rate,
* beat-the-close rate, pending count. Percentages are null below
* MIN_AGG_SAMPLE — the UI must show "record building" instead.
*
* A1 Session 10 — `opts.userId` swaps the public `.is('user_id', null)`
* scoping for `.eq('user_id', uid)`: the SAME aggregate (same window, same
* n≥20 gate) over one user's own ledger, powering public profiles. The
* public default is untouched.
*/
async function getModelAggregate(opts = {}) {
const empty = {
@@ -400,8 +405,9 @@ async function getModelAggregate(opts = {}) {
const since = new Date(nowMs - AGG_WINDOW_DAYS * 24 * 3600 * 1000).toISOString().slice(0, 10);
let settledQ = sb.from('ledger_entries')
.select('outcome, clv_result, player_key, grade')
.is('user_id', null)
.select('outcome, clv_result, player_key, grade');
settledQ = opts.userId ? settledQ.eq('user_id', opts.userId) : settledQ.is('user_id', null);
settledQ = settledQ
.not('outcome', 'is', null)
.gte('game_date', since)
.limit(AGG_FETCH_LIMIT);
@@ -412,9 +418,9 @@ async function getModelAggregate(opts = {}) {
if (error) return { ...empty, error: error.message };
let pendingQ = sb.from('ledger_entries')
.select('id', { count: 'exact', head: true })
.is('user_id', null)
.is('outcome', null);
.select('id', { count: 'exact', head: true });
pendingQ = opts.userId ? pendingQ.eq('user_id', opts.userId) : pendingQ.is('user_id', null);
pendingQ = pendingQ.is('outcome', null);
if (opts.sport) pendingQ = pendingQ.eq('sport', String(opts.sport).toLowerCase());
if (opts.playerKey) pendingQ = pendingQ.eq('player_key', opts.playerKey);
if (opts.team) pendingQ = pendingQ.eq('team', opts.team);