Docs/Skills and code/skill script-cohort-analysis

.claude/skills/script-cohort-analysis/scripts/raterFeatures.js

Code46 lines2.2 KBSkill: script-cohort-analysis

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.