Spreadsheet-UX op een echte relationele engine
Deze repository wordt als momentopname gepubliceerd: een clone bevat de volledige inhoud, maar geen commitgeschiedenis.
import type { ControlDb } from '../control/db.js';
import { newId } from '../engine/ids.js';
/**
* Reading and writing the landing page's evidence.
*
* Nothing here computes anything. What the reward *is* lives in `reward()`
* below and in the window constants beside it, and that is deliberately the
* only place it is defined: the moments are replayed from these rows on every
* fit rather than accumulated in a column, so changing the definition of
* durable use re-scores the whole history instead of corrupting it.
*/
/**
* How long an account is watched before its impression is allowed to vote.
*
* A fixed window from *signup*, not from today, so an account from January
* cannot out-earn one from June simply by having existed longer. It is also
* what makes an impression mature: nothing is scored until its window has
* closed, because an impression from last Tuesday is not a zero, it is a
* question that has not been answered yet, and feeding it in as a zero would
* mark down whatever the page is currently showing.
*/
export const OBSERVATION_WINDOW_DAYS = 28;
/**
* Active days past this add nothing.
*
* The cap and the log together are what keep one enthusiast from deciding the
* voice of the site. Against a few dozen observations an uncapped reward is a
* single-sample experiment wearing a covariance matrix.
*/
export const ACTIVE_DAY_CAP = 20;
/** Durable use, on [0, 1]. */
export function reward(activeDays: number): number {
const capped = Math.max(0, Math.min(activeDays, ACTIVE_DAY_CAP));
return Math.log1p(capped) / Math.log1p(ACTIVE_DAY_CAP);
}
export interface ScoredImpression {
id: string;
items: string[];
createdAt: string;
converted: boolean;
activeDays: number;
reward: number;
}
interface ImpressionRow {
id: string;
items: string;
created_at: string;
account_id: string | null;
active_days: number | null;
}
/** Serve a page. The row exists from here on; whether it counts is `markEngaged`. */
export function recordImpression(control: ControlDb, items: string[]): string {
const id = newId('imp');
control.sqlite
.prepare('INSERT INTO voice_impression (id, items) VALUES (?, ?)')
.run(id, JSON.stringify(items));
return id;
}
/**
* A person looked at it.
*
* Idempotent, and it never moves an existing timestamp — a reload posting the
* beat twice must not restate when the page was seen.
*/
export function markEngaged(control: ControlDb, id: string): void {
control.sqlite
.prepare("UPDATE voice_impression SET engaged_at = datetime('now') WHERE id = ? AND engaged_at IS NULL")
.run(id);
}
/**
* Attach a signup to the page that produced it.
*
* Refuses to overwrite: an impression id is quoted back by the browser, so the
* only thing stopping the same id being replayed against a second account is
* this clause. Worst case someone donates their own signup to a page they liked
* — which is a rounding error, and cheaper than a token exchange for it.
*/
export function attachAccount(control: ControlDb, id: string, accountId: string): void {
control.sqlite
.prepare('UPDATE voice_impression SET account_id = ? WHERE id = ? AND account_id IS NULL')
.run(accountId, id);
}
/** This account did something today. Cheap, idempotent, once per account per day. */
export function recordActiveDay(control: ControlDb, accountId: string): void {
control.sqlite
.prepare("INSERT OR IGNORE INTO account_day (account_id, day) VALUES (?, date('now'))")
.run(accountId);
}
/**
* Every impression whose answer is in, oldest first.
*
* Oldest first because the bleed is applied per observation: replaying in the
* order the evidence arrived is what makes the replay identical to an online
* accumulator that had been running the whole time.
*
* `limit` takes the most recent N and still replays them oldest-first, which is
* how the report shows a recent window beside the all-time one. A traffic
* source changing under you reads as those two diverging.
*/
export function scoredImpressions(control: ControlDb, limit?: number): ScoredImpression[] {
const cutoff = `-${OBSERVATION_WINDOW_DAYS} days`;
const rows = control.sqlite
.prepare<[string, string, number], ImpressionRow>(
`SELECT i.id, i.items, i.created_at, i.account_id,
(SELECT COUNT(*) FROM account_day d
WHERE d.account_id = i.account_id
AND d.day >= date(a.created_at)
AND d.day < date(a.created_at, ?)) AS active_days
FROM voice_impression i
LEFT JOIN account a ON a.id = i.account_id
WHERE i.engaged_at IS NOT NULL
-- Mature from the account's clock when it converted, from the page's
-- when it did not: a signup two days ago has not had its 28 days yet
-- even if the page it came from was served a month before that.
AND COALESCE(a.created_at, i.created_at) <= datetime('now', ?)
ORDER BY i.created_at DESC
LIMIT ?`,
)
.all(`+${OBSERVATION_WINDOW_DAYS} days`, cutoff, limit && limit > 0 ? limit : -1);
return rows
.reverse()
.map((row) => {
const activeDays = row.account_id ? (row.active_days ?? 0) : 0;
return {
id: row.id,
items: parseItems(row.items),
createdAt: row.created_at,
converted: row.account_id !== null,
activeDays,
reward: row.account_id ? reward(activeDays) : 0,
};
});
}
/** Impressions seen by a person but not yet old enough to say anything. */
export function pendingCount(control: ControlDb): number {
const row = control.sqlite
.prepare<[string], { n: number }>(
`SELECT COUNT(*) AS n
FROM voice_impression i
LEFT JOIN account a ON a.id = i.account_id
WHERE i.engaged_at IS NOT NULL
AND COALESCE(a.created_at, i.created_at) > datetime('now', ?)`,
)
.get(`-${OBSERVATION_WINDOW_DAYS} days`);
return row?.n ?? 0;
}
/**
* A stored `items` array that will not parse is a corrupted row, not a reason
* to take the landing page down. It drops out of the fit as an empty page.
*/
function parseItems(json: string): string[] {
try {
const parsed: unknown = JSON.parse(json);
return Array.isArray(parsed) ? parsed.filter((v): v is string => typeof v === 'string') : [];
} catch {
return [];
}
}