feat(linkedin-studio): RE-R3a — persist relevance score on the store record + rank the morning brief on it [skip-docs]
R3 slice 1 (research-deepening). Stop discarding the relevance judgment the
trend-spotter already computes: persist a 4-field TrendScore {mode, dimensions,
composite, priority} on TrendRecord (schema v2->v3, additive lossless migrate),
computed by the existing score.ts composite()+band() (one owner, no new arithmetic),
threaded item->store; then rankForBrief sorts each bucket composite-first (sentinel
-1 for unscored) and renderBrief surfaces "· <priority> (<mode>)" per body entry
(briefSummary shows the band only). First-sight only; mode-blind ranking with the mode
shown so the operator can disambiguate instruments.
- score.ts: TrendScore + requiredDimensions(mode) (ordered) + scoreEnvelope (composes
composite+band; throws on bad dim by contract)
- types.ts: SCHEMA_VERSION 2->3; TrendRecord.score?
- store.ts: TrendInput.score?; addTrend persists first-sight (duplicate keeps it);
migrate comment v1->v2->v3 (logic unchanged, JSON.stringify preserves the field)
- item.ts: TrendItem.score?; normalizeItem validates (non-array score/dimensions + the
mode's five dims in [1,10]) -> structured error never throw, carries validated dims;
itemToInput -> scoreEnvelope (no throw on the capture path; direct call throws by contract)
- brief.ts: composite-primary comparator; band+mode render; exact ranking: descriptor
- cli.ts: capture persists score via itemToInput (doc-only); add/score paths unchanged
- agents/trend-spotter.md Step 4.5: capture batch carries the Step-2 dimensions
- gate: TRENDS_TESTS_FLOOR 104->146; new unconditional Section 16j; ASSERT floor 94->99
Tests: trends 146/146 (RED two-phase: logic-RED store/brief/cli; stub-first then
assertion-RED score/item). Gate green (Passed 114 / Failed 0; 113 checks >= 99).
Hook suite 139/139 untouched. Counts 27/19/29 unchanged. No new source file/agent/command.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmHCQjJHUyWwxGAVVjNLgp
This commit is contained in:
parent
4d3b9f4711
commit
e169c78710
14 changed files with 829 additions and 40 deletions
|
|
@ -1,7 +1,15 @@
|
|||
import { describe, test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { KORTFORM_WEIGHTS, LONG_FORM_WEIGHTS, composite, band, triage } from "../src/score.js";
|
||||
import {
|
||||
KORTFORM_WEIGHTS,
|
||||
LONG_FORM_WEIGHTS,
|
||||
composite,
|
||||
band,
|
||||
triage,
|
||||
requiredDimensions,
|
||||
scoreEnvelope,
|
||||
} from "../src/score.js";
|
||||
|
||||
const r1 = (x: number) => Math.round(x * 10) / 10;
|
||||
const sum = (o: Record<string, number>) => Object.values(o).reduce((a, b) => a + b, 0);
|
||||
|
|
@ -142,4 +150,42 @@ describe("trends scorer (RE-R1 / B2)", () => {
|
|||
assert.deepEqual(dropped, []);
|
||||
});
|
||||
});
|
||||
|
||||
describe("requiredDimensions (RE-R3a / SC1)", () => {
|
||||
test("kortform -> the five keys in SSOT weight order", () => {
|
||||
assert.deepEqual(requiredDimensions("kortform"), ["pillar", "audience", "timing", "angle", "authority"]);
|
||||
});
|
||||
|
||||
test("long-form -> the five keys in SSOT weight order", () => {
|
||||
assert.deepEqual(requiredDimensions("long-form"), ["pillar", "depth", "angle", "authority", "currency"]);
|
||||
});
|
||||
|
||||
test("order is pinned to the SSOT weight literals (a silent reorder fails)", () => {
|
||||
assert.deepEqual(requiredDimensions("kortform"), Object.keys(KORTFORM_WEIGHTS));
|
||||
assert.deepEqual(requiredDimensions("long-form"), Object.keys(LONG_FORM_WEIGHTS));
|
||||
});
|
||||
});
|
||||
|
||||
describe("scoreEnvelope (RE-R3a / SC1)", () => {
|
||||
test("composes composite()+band() — composite/priority equal the existing functions (one owner)", () => {
|
||||
const dims = { pillar: 8, audience: 7, timing: 9, angle: 6, authority: 5 };
|
||||
const env = scoreEnvelope("kortform", dims);
|
||||
assert.equal(env.mode, "kortform");
|
||||
assert.deepEqual(env.dimensions, dims);
|
||||
assert.equal(env.composite, composite(dims, "kortform"));
|
||||
assert.equal(env.priority, band(composite(dims, "kortform")).priority);
|
||||
});
|
||||
|
||||
test("long-form envelope composes the long-form composite/band", () => {
|
||||
const dims = { pillar: 9, depth: 8, angle: 7, authority: 6, currency: 5 };
|
||||
const env = scoreEnvelope("long-form", dims);
|
||||
assert.equal(env.composite, composite(dims, "long-form"));
|
||||
assert.equal(env.priority, band(composite(dims, "long-form")).priority);
|
||||
});
|
||||
|
||||
test("a bad dimension makes scoreEnvelope throw (via composite — defense-in-depth contract)", () => {
|
||||
const dims = { pillar: 8, audience: 7, timing: 99, angle: 6, authority: 5 };
|
||||
assert.throws(() => scoreEnvelope("kortform", dims));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue