vital-core / src /lib /sampling.js
vital-core sync
Deploy snapshot for Hugging Face Spaces
50841a0
Raw
History Blame Contribute Delete
2.65 kB
import crypto from 'node:crypto';
/**
* Per-engine weekly sampling. Each engine has a target coverage rate —
* the fraction of a week's unique pages it should run on (axe 100%,
* Alfa 30%, Lighthouse 10%, etc.). Rates are set in one place,
* config/targets.yml under `sampling:`.
*
* The scan is incremental: pages are scanned in batches across the week,
* so we cannot pick "30% of this week's pages" up front — we don't know
* the full set until the week ends. Instead each page decides, at scan
* time, whether it is in a given engine's sample, using a stable hash of
* (pageId + engine + week). Because the hash is uniform over pageIds,
* about `rate` of pages fall in the sample, and the decision is:
* - reproducible: re-running a week reproduces the same sample;
* - stable within a week: a page stays in (or out of) an engine's
* sample no matter which nightly run reaches it;
* - independent per engine: Alfa's 30% is not correlated with
* Lighthouse's 10%.
*/
/**
* Normalize a configured rate to a fraction in [0,1]. Accepts a fraction
* (0.3), a percentage (30), or a percent string ("30%"). Missing/invalid
* rates default to 0 (engine off) so a typo never silently runs an
* expensive engine everywhere.
*/
export function normalizeRate(raw) {
if (raw == null) return 0;
let n = typeof raw === 'string' ? parseFloat(raw.replace('%', '')) : Number(raw);
if (!Number.isFinite(n) || n < 0) return 0;
if (n > 1) n = n / 100; // a value above 1 is read as a percentage
return Math.min(1, n);
}
/**
* Deterministic membership test: is this page in `engine`'s sample for
* `week`, given a coverage `rate` (fraction or percentage)?
*
* rate >= 1 -> always; rate <= 0 -> never. Otherwise compare a uniform
* hash of (pageId|engine|week) in [0,1) against the rate.
*/
export function shouldRun(engine, pageId, week, rate) {
const r = normalizeRate(rate);
if (r >= 1) return true;
if (r <= 0) return false;
return hashUnit(`${pageId}|${engine}|${week}`) < r;
}
/** Map a string to a uniform value in [0,1) via the first 52 bits of SHA-256. */
function hashUnit(s) {
const hex = crypto.createHash('sha256').update(s).digest('hex').slice(0, 13); // 52 bits
return parseInt(hex, 16) / 2 ** 52;
}
/**
* Resolve the effective sampling rates for a target: defaults merged with
* any per-target overrides. Engines absent from the map default to 0
* (off), except via `fallback` which callers can use for engines that
* should run unless explicitly disabled.
*/
export function ratesFor(config, target) {
return { ...(config.sampling ?? {}), ...(target.sampling ?? {}) };
}