adjust "top" window

This commit is contained in:
Devin Zuczek
2026-08-05 15:49:59 -04:00
parent bc96a6245b
commit f6561f1ec9
4 changed files with 28 additions and 20 deletions
+15 -9
View File
@@ -352,23 +352,29 @@ async function publicInventions(db: D1Database, featuredOnly = false): Promise<S
return results.map((r) => JSON.parse(r.data) as SavedInvention)
}
/** Midnight UTC today, as the ISO timestamp `acquired_at` is compared against. */
function startOfUtcDay(): string {
return `${new Date().toISOString().slice(0, 10)}T00:00:00.000Z`
/** Length of the "today" window — a trailing day, not the calendar one. */
const TOP_TODAY_WINDOW_MS = 24 * 60 * 60 * 1000
/** 24 hours ago, as the ISO timestamp `acquired_at` is compared against. */
function startOfWindow(): string {
return new Date(Date.now() - TOP_TODAY_WINDOW_MS).toISOString()
}
/**
* The "top today" feed — the inventions other players picked up TODAY, most first.
* The "top today" feed — the inventions other players picked up in the last 24 hours,
* most first.
*
* Ranked from the acquisitions the `econ` worker records in `inventory_invention` at
* purchase time, grouped by invention, rather than from the lifetime counters on the
* invention itself: those never reset, so "top today" used to mean "top ever" and the
* shelf only changed when something overtook a total built up over months.
*
* "Today" is the UTC day, matching the timestamps econ writes. The day therefore rolls
* over at 00:00 UTC wherever the player is, and the feed IS EMPTY until the first
* acquisition of that day — nothing stands in for it, the same way the featured feed
* serves nothing while nothing is curated.
* "Today" is a TRAILING 24 hours, not the calendar UTC day, so the feed doesn't empty
* itself at midnight UTC and slowly refill through the small hours — it always covers a
* full day's worth of activity. It is still genuinely a window: an invention nobody has
* picked up since yesterday falls off, and the feed IS EMPTY when nothing at all was
* acquired in a day. Nothing stands in for it, the same way the featured feed serves
* nothing while nothing is curated.
*
* An acquired invention that has since been unpublished or hidden drops out: this is a
* public feed, so it is filtered like every other one. Paginated via skip/take AFTER
@@ -379,7 +385,7 @@ export async function getTopInventions(
skip: number,
take: number
): Promise<SavedInvention[]> {
const counts = await getInventionAcquisitionCounts(db, startOfUtcDay())
const counts = await getInventionAcquisitionCounts(db, startOfWindow())
if (counts.length === 0) return []
// getInventionsByIds answers in the order it is asked, so the ranking survives the
+6 -5
View File
@@ -622,7 +622,7 @@ export const avatarRoutes = new Hono<App>({ strict: false })
}
)
// The "top today" invention feed — the inventions most acquired since 00:00 UTC,
// The "top today" invention feed — the inventions most acquired in the last 24 hours,
// counted from the purchase rows the `econ` worker writes. A real day window, so an
// empty list is a quiet day rather than a bug. Paginated via skip/take (take defaults
// to 50, as the client asks for). Bare array.
@@ -632,10 +632,11 @@ export const avatarRoutes = new Hono<App>({ strict: false })
tags: ['Inventions'],
summary: 'The “top today” feed',
description:
'Published inventions ranked by how many players acquired them TODAY (since ' +
'00:00 UTC), counted from the purchase records — free grants included, one per ' +
'player per invention. Genuinely a day window: empty until the days first ' +
'acquisition, and it resets at midnight UTC.',
'Published inventions ranked by how many players acquired them in the last 24 ' +
'hours, counted from the purchase records — free grants included, one per ' +
'player per invention. Genuinely a window: an invention nobody has picked up ' +
'since yesterday falls off, and a day with no acquisitions at all serves an ' +
'empty list. It trails the clock rather than resetting at midnight.',
parameters: pageParams(50),
responses: { 200: json(InventionDto.array(), 'The top inventions') },
}),
+6 -5
View File
@@ -1184,22 +1184,23 @@ describe('public endpoints', () => {
.run()
}
// Today's acquisitions, which is what "top today" now counts: 201 picked up by three
// Recent acquisitions, which is what "top today" now counts: 201 picked up by three
// players, 203 by one. 204/205 are acquired too — an unpublished and a hidden
// invention can still be owned — and must not surface in a public feed.
for (const accountId of [7001, 7002, 7003]) await grantInvention(env.DB, accountId, 201)
await grantInvention(env.DB, 7001, 203)
await grantInvention(env.DB, 7001, 204)
await grantInvention(env.DB, 7002, 205)
// 202 was acquired, but not today — the window is the current UTC day, so it is out.
// 202 was acquired 25 hours ago, just past the trailing 24-hour window, so it is out
// the feed really does forget, rather than accumulating every acquisition ever.
await env.DB.prepare(
'INSERT INTO inventory_invention (account_id, invention_id, acquired_at) VALUES (?1, ?2, ?3)'
)
.bind(7004, 202, '2020-01-01T00:00:00.000Z')
.bind(7004, 202, new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString())
.run()
// Top: most acquisitions today first. Download counts no longer rank anything — 202
// has the biggest of them and is absent entirely.
// Top: most acquisitions in the window first. Download counts no longer rank anything —
// 202 has the biggest of them and is absent entirely.
const top = await ids(await exports.default.fetch(`${ORIGIN}/api/inventions/v1/toptoday`))
expect(top).toEqual([201, 203])
@@ -65,7 +65,7 @@ export async function ownsInvention(
/**
* How many times each invention was acquired at or after `since`, most-acquired first
* (ties broken by newest invention, so paging is stable). Backs the `api` worker's "top
* today" feed, which passes the start of the current UTC day.
* today" feed, which passes 24 hours ago.
*
* `acquired_at` holds `toISOString()` output, which is fixed-width UTC, so a lexical
* `>=` on the string is a chronological comparison — no date parsing in SQL.