Docs/Skills and code/skill script-cohort-analysis
.claude/skills/script-cohort-analysis/scripts/raterFeatures.js
What the file says about itself
Turn one rater JSON (rubric v3) into numeric features. Shared by analyze.js and perturb.js so calibration and cohort analysis measure exactly the same things.
// Turn one rater JSON (rubric v3) into numeric features. Shared by analyze.js and perturb.js
// so calibration and cohort analysis measure exactly the same things.
const { toSec, mean } = require('./lib');
const { DIMS, CHECKLIST, ITEM_IDS } = require('./checklist');
function raterFeatures(r, durationSec) {
const D = durationSec;
const secs = r.sections.map((s, i) => ({ ...s, idx: i, s: toSec(s.start), e: Math.min(toSec(s.end), D) }));
const len = (x) => Math.max(0, x.e - x.s);
const total = secs.reduce((a, x) => a + len(x), 0) || D;
const pos = Object.fromEntries(secs.map((x) => [x.id, x.idx]));
const body = secs.slice(1).filter((x) => !['cta', 'sponsor'].includes(x.function));
const dependent = body.filter((x) => (x.depends_on || []).some((id) => pos[id] !== undefined && pos[id] < x.idx));
const forward = secs.reduce((a, x) => a
+ (x.references_unintroduced || []).length
+ (x.depends_on || []).filter((id) => pos[id] !== undefined && pos[id] > x.idx).length, 0);
const weighted = (lo, hi) => {
let w = 0, s = 0;
for (const x of secs) {
const a = Math.max(x.s, lo), b = Math.min(x.e, hi);
if (b > a) { w += b - a; s += (b - a) * x.interest; }
}
return w ? s / w : null;
};
const first = weighted(0, D / 2), second = weighted(D / 2, D);
const f = {
ext_dependency_density: body.length ? dependent.length / body.length : 0,
ext_forward_refs: forward,
ext_filler_ratio: secs.filter((x) => ['filler', 'repetition'].includes(x.function)).reduce((a, x) => a + len(x), 0) / total,
ext_interest_mean: weighted(0, D),
ext_interest_trend: first !== null && second !== null ? second - first : 0,
ext_time_to_promise_sec: toSec(r.promise.timestamp),
ext_major_loops_per_10min: (r.loops || []).filter((l) => l.size === 'major').length / (D / 600),
hol_rater_percentile: r.holistic.percentile,
};
for (const id of ITEM_IDS) f[`item_${id}`] = r.checks[id].pass ? 1 : 0;
for (const d of DIMS) f[`chk_${d}`] = mean(CHECKLIST[d].map(([id]) => f[`item_${id}`]));
f.chk_composite = mean(DIMS.map((d) => f[`chk_${d}`]));
return f;
}
module.exports = { raterFeatures };
Shown as it is in the repository at build time (2026-11-03); the home directory appears as <home>. If this page and the file differ, the file is right. Back to the catalogue.